GR-SAKURA で遊んでみる(4) Hello World

素の GR-SAKURA では、デバッグに使えそうなのは LED×4 程度しかない。
さすがにこの状態でのデバッグはキツいものがあるので、まずは PC との間で非同期のシリアル通信ができるようにしたい。

一般的な方法としては、

  1. USB ケーブルを接続し、PC に対して GR-SAKURA をシリアルポートに見せかける。
  2. GR-SAKURA と PC の両方に XBee を接続し、無線経由でシリアル通信させる。
  3. GR-SAKURA に CMOS <-> RS-232C のレベル変換を行う治具を取り付ける

といったところだろうか。

1 の方法は、Arduiono と同じ方法である。USB ケーブル以外に H/W を準備しなくてもよいのは魅力的だが、単にデバッグするために CDC クラスのドライバを実装するのはちょっと大掛かりな気がする。

2 と 3 は、RX63N に内蔵されている周辺機能であるSCI(serial communication interface)を利用して通信する方法である。すなわち、SCI を UART として利用し、非同期通信を行う*1

2 の方法では

  +-----------+                               +----------+
  |           | (a) +------+     +------+ (c) |          |     
  | GR-SAKURA +-----+ XBee | (b) | XBee +-----+    PC    |
  |           |     +------+     +------+     |          |     
  +-----------+                               +----------+

  (a) RX-63N の TXD2 および RXD2 端子に接続
  (b) 無線
  (c) XBee と USB-シリアル変換器を介して PC の USB 端子に接続

という構成になる。(b) の無線区間XBee 同士が通信を担い、(a) と (c) の間は非同期のシリアル通信を行う。すなわち、GR-SAKURA からも PC からも、対向機器はあたかもシリアルで接続されているように見える。

この方法では、構成上

  • XBee モジュール×2
  • XBee を GR-SAKURA に接続するためのコネクタ
  • USB <-> シリアル変換器(XBee 接続用のソケットが付いたもの)*2

が必要になり、5,000 円前後の追加投資が必要になる。

予算に余裕があればこの方法でいきたいところだが、貧乏人の筆者には 5,000 円の出費はなかなか痛いところだ。というわけで、消去法により 3 の方針でいくことにした。変換回路を作る程度の部品なら家に部品余ってるし…

なお、2 の方法については以下の資料が詳しい。

http://data.designspark.info/uploads/knowledge-items/chi-gr-renesas-tut-in-jp/2012_10_22%20connect%20with%20GR-SAKURA%20and%20XBee.pdf

レベル変換用の治具の作成

3 の場合、レベル変換用の治具を自作する必要がある。ちょっと格好悪いが、変換回路をブレッドボード上に実装した。

手持ちの関係で、レベルコンバータは Analog Devices の ADM3202 を使用した。ADM3202 は、0.1 uF(実際には 47uF まで可)のコンデンサを 5 つ外付けすることでチャージポンプとして動作し、GR-SAKURA の出力である CMOS レベルを -2 倍に昇圧して PC に出力できる。

今回は GR-SAKURA の SCI0 を使用するので、ADM3202 の各入出力端子は以下のように接続した。

ADM3202 の端子 GR-SAKURA の端子 DSUB-9pin の端子
7(T2OUT) - -
8(R2IN) - -
9(R2OUT) - -
10(T2IN) - -
11(T1IN) P20(TXD0) -
12(R1OUT) P21(RXD0) -
13(R1IN) - 2(RxD)
14(T1OUT) - 3(TxD)
15(GND) GND 5(GND)
16(VCC) VCC -

今回は(無精をして) H/W フロー制御は使用しないものとして、ADM3202 の 7-10 は未接続のままとしている。

シリアルドライバの実装

レベル変換の治具もできたので、早速シリアルドライバを実装して動かしてみた(ドライバを含むソースコード一式はこちら)。

今回作成したドライバは、以下の関数を外部に公開する。

  • void sci0_init(unsigned int baud)
  • int sci0_raw_putc(int c)
  • int sci0_putc(int c)
  • int sci0_raw_getc(void)
  • int sci0_getc(void)

sci0_init() は、引数で指定されたボーレートで通信を行うための H/W の初期化処理を実行する。sci0_putc() と sci0_getc() の動作は、関数名から類推できるであろうから説明は割愛する。
sci0_raw_putc()/sci0_raw_getc() は、CR->CR+LF/CR->LF の変換をしない点を除いて sci0_putc()/sci0_getc() と同じである。これらの関数は、(XMODEM プロトコル等)バイナリでの通信用途を想定している。

初期化処理の実装

H/W マニュアルでは、初期化処理を

  1. SCR.TIE、RIE、TE、RE、TEIE ビットを "0" に設定
  2. I/O ポート機能を設定
  3. SCR.CKE[1:0] ビットを設定
  4. SIMR1.IICM ビットを "0" に設定、SPMR.CKPH、CKPOL ビットを "0" に設定
  5. SMR、SCMR、SEMR レジスタに送信/受信フォーマットを設定
  6. BRR レジスタに値を設定
  7. SCR.TE、RE ビットに "1" を設定、および SCR.TIE、RIE ビットを設定

の手順で実行するように指示している。しかし、初期化の段階で 7 の手順を踏むと、まだ送信するデータもないのに TXI 割り込み要求を生成しそうな気がするが...*3

SCR.TE、TIE への設定は、実際に送信/受信する段階になってからするべきと考え、sci0_init() に於いては 1 から 6 の手順までを実装した。一方で、受信は常時有効とするため、RE および RIE は sci0_init() の段階で 1 に設定している。

 59 void
 60 sci0_init(unsigned int baud)
 61 {
 62         uint16_t prcr_bak;
 63
 64         disable_interrupts();
 65
 66         /* enable writing to registers
 67            related to the low power consumption functions */
 68         prcr_bak = readw(PRCR_ADDR) & 0x00ff;
 69         writew(PRCR_ADDR, 0xa502 | prcr_bak);
 70
 71         /* cancel module stop state of SCI0 */
 72         writel(MSTPCRB_ADDR, readl(MSTPCRB_ADDR) & ~(0x80000000));
 73
 74         /* clear the SCR.TIE, RIE, TE, RE and TEIE bits to 0 */
 75         writeb(SCI0_SCR_ADDR, 0x00);
 76
 77         /* enable writing to PFS registers */
 78         writeb(PWPR_ADDR, readb(PWPR_ADDR) & ~0x80);
 79         writeb(PWPR_ADDR, readb(PWPR_ADDR) | 0x40);
 80
 81         /* use P20 and P21 as TXD0 and RXD0 respectively */
 82         writeb(P20PFS_ADDR, (readb(P20PFS_ADDR) & ~0x1f) | 0x0a);
 83         writeb(P21PFS_ADDR, (readb(P21PFS_ADDR) & ~0x1f) | 0x0a);
 84
 85         /* disable writing to PFS registers */
 86         writeb(PWPR_ADDR, readb(PWPR_ADDR) | 0x80);
 87         writeb(PWPR_ADDR, readb(PWPR_ADDR) & ~0x40);
 88
 89         /* restore PRCR.PRC1 */
 90         writew(PRCR_ADDR, 0xa500 | prcr_bak);
 91
 92         /* use P20 and P21 as TXD0 and RXD0 */
 93         writeb(PORT2_PMR_ADDR, readb(PORT2_PMR_ADDR) | 0x03);
 94
 95         /* set bits SCR.CKE[1:0] in SCR: use on-chip baud rate generator */
 96         writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) & ~0x03);
 97
 98         /* clear the SIMR1.IICM bit to 0 */
 99         writeb(SCI0_SIMR1_ADDR, readb(SCI0_SIMR1_ADDR) & ~0x01);
100
101         /* clear the SPMR.CKPH and CKPOL bits to 0 */
102         writeb(SCI0_SPMR_ADDR, readb(SCI0_SPMR_ADDR) & ~0xc0);
103
104         /* set the data transmission/reception format in SMR, SCMR and SEMR */
105         writeb(SCI0_SMR_ADDR, 0x00);
106         writeb(SCI0_SCMR_ADDR, 0xf2);
107         writeb(SCI0_SEMR_ADDR, 0x00);
108
109         /* set a value in BRR: configure baud rate */
110         writeb(SCI0_BRR_ADDR, PCLK / (32 * baud) - 1);
111
112         /* configure interrupt source priority */
113         writeb(IPR214_ADDR, 0x01);      /* RXI, TXI and TEI */
114         writeb(IPR114_ADDR, 0x01);      /* ERI */
115
116         /* enable interrupt request of TXI and RXI */
117         writeb(IER1A_ADDR, readb(IER1A_ADDR) | 0xc0);
118
119         /* enable interrupt request of TEI */
120         writeb(IER1B_ADDR, readb(IER1B_ADDR) | 0x01);
121
122         /* enable interrupt request of GROUP12 */
123         writeb(IER0E_ADDR, readb(IER0E_ADDR) | 0x04);
124
125         /* enable interrupt request of ERI */
126         writel(GEN12_ADDR, readl(GEN12_ADDR) | 0x01);
127
128         /* enable serial reception */
129         writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) | 0x50);
130
131         init_queue(&sci_txqueue);
132         init_queue(&sci_rxqueue);
133         sci_tx_busy = 0;
134         sci_rx_error = 0;
135
136         enable_interrupts();
137 }

なお、前々回の LED 点滅プログラム作成時に扱ったコンペアマッチタイマと同様に、リセット直後では SCI0 にクロックが供給されていないので、これらの初期化処理に先立ってクロック供給を開始している。

また、割り込みコントローラ(ICUb)を設定し、

  • TXI 割り込み
  • TEI 割り込み
  • RXI 割り込み
  • REI 割り込み

の割り込みを受け付けるようにしている。

送信処理の実装

今回は割り込みを使う方針で実装するものとし、外部から呼び出される sci0_putc() および sci0_raw_putc() では、ドライバが持っているバッファへのキューイングおよび SCR.TE および SCR.TIE への設定のみを行う。残りの送信処理は割り込みハンドラにより行う。

139 int
140 sci0_raw_putc(int c)
141 {
142         c &= 0xff;
143
144         disable_interrupts();
145
146         if (!sci_tx_busy) {
147                 /* enable serial transmission */
148                 writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) | 0xa0);
149         }
150
151         if (enqueue(&sci_txqueue, (uint8_t)c) == -1) {
152                 enable_interrupts();
153                 return -1;
154         }
155
156         sci_tx_busy = 1;
157
158         enable_interrupts();
159
160         return c;
161 }

送信処理に使用する割り込みは二種類ある。

  • TXI 割り込み: 以下のいずれかの方法により生成される。
    • SCR.TIE に 1 を設定後 SCR.TE に 1 を設定
    • SCR.TIE および SCR.TE の両方に対して、1 命令で同時に 1 を設定
  • TEI 割り込み: 送信完了時(具体的にはストップビット送出後)に生成される。

これらの割り込みを利用して、大雑把には以下のように実装した。

  1. sci0_putc() または sci0_raw_putc() にて引数で渡された送信データをキューイングする。
  2. SCR.TE および TIE に 1 を設定する。これにより SCI は TXI 割り込み要求を生成する。
  3. TXI 割り込みに対応するハンドラの sci0_txi_handler() にて、送信データを TDR レジスタに書き込む。TDR レジスタに書き込まれた内容は、自動的にシフトレジスタ*4に転送され、実際のデータ送出が開始される。
  4. ストップビット送出後、SCI は TEI 割り込み要求を生成する。TEI 割り込み要求に対応するハンドラの sci0_tei_handler() にて、一旦 SCI.TE および SCI.TEI に 0 を設定する。キュー内にまだ送信データがあれば、再度 SCI.TE および SCI.TEI に 1 を設定し、TXI 割り込み要求を生成させて 3 へ、そうでなければそのまま送信処理を終了する。

TXIの割り込みハンドラ:

226 void
227 sci0_txi_handler(void)
228 {
229         int c;
230
231         disable_interrupts();
232
233         if (!sci_tx_busy) {
234                 /* disable serial transmission */
235                 writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) & ~0xa0);
236                 goto out;
237         }
238
239         if ((c = dequeue(&sci_txqueue)) == -1)
240                 goto out;
241
242         /* write the transmit data to TDR */
243         writeb(SCI0_TDR_ADDR, (uint8_t)(c & 0xff));
244
245         if (sci_txqueue.wi == sci_txqueue.ri) {
246                 /* enable TEI interrupt */
247                 writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) | 0x04);
248         }
249
250 out:
251         enable_interrupts();
252 }

TEIの割り込みハンドラ:

254 void
255 sci0_tei_handler(void)
256 {
257         disable_interrupts();
258
259         if (!sci_tx_busy)
260                 goto out;       /* nothing to do */
261
262         /* disable TEI interrupt and serial transmission */
263         writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) & ~0xa4);
264
265         if (sci_txqueue.wi == sci_txqueue.ri)
266                 sci_tx_busy = 0;
267         else {
268                 /* enable serial transmission */
269                 writeb(SCI0_SCR_ADDR, readb(SCI0_SCR_ADDR) | 0xa0);
270         }
271
272 out:
273         enable_interrupts();
274 }

SCI による送信処理は CPU の動作速度に比べると大変遅いので、SCI が送信処理をしている間に sci0_putc()/sci0_raw_putc() を呼び出してしまうことがある。送信データをわざわざキューイングをしているのは、その対策である。

送信中に sci0_putc()/sci0_raw_putc() を呼び出した場合は、1 のキューイングのみを行い、SCR.TE および TIE への設定は行わないようにしている(すでに 1 で設定済なので)。後段の割り込みハンドラにてキューから取り出して送信する処理は、この場合に於いても同様である。

受信処理の実装

SCI はデータを受信すると RXI 割り込みを生成する。受信データも割り込みハンドラにて受け取り、ドライバが持つバッファにキューイングするように実装した。

216 void
217 sci0_rxi_handler(void)
218 {
219         disable_interrupts();
220
221         (void)enqueue(&sci_rxqueue, readb(SCI0_RDR_ADDR));
222
223         enable_interrupts();
224 }

sci0_getc()/sci0_raw_getc() は、キューに受信データが既に入っていればそれを返し、そうでなければキューに受信データが入るまでポーリングしてから受信データを返すように実装した。

175 int
176 sci0_raw_getc(void)
177 {
178         int c;
179
180         for (;;) {
181                 disable_interrupts();
182
183                 if (sci_rx_error) {
184                         /* reception error occured */
185                         c = -1;
186                         break;
187                 }
188
189                 if ((c = dequeue(&sci_rxqueue)) != -1) {
190                         /* successfully received */
191                         break;
192                 }
193
194                 enable_interrupts();
195         }
196
197         /* clear error */
198         sci_rx_error = 0;
199
200         enable_interrupts();
201
202         return c;
203 }
受信エラー処理の実装

SCI は、受信時に於いてフレーミングエラー、オーバラン、パリティエラーのいずれかを検出すると、ERI 割り込み要求を生成する。この場合は、RXI 割り込み要求は生成されない。なお、オーバランエラーの場合のみ、ERI 割り込みハンドラ内で RDR レジスタを読んでおく必要がある。

今回の実装では、ERI の割り込みハンドラを以下のように実装した。

276 void
277 sci0_eri_handler(void)
278 {
279         uint8_t ssr;
280
281         disable_interrupts();
282
283         ssr = readb(SCI0_SSR_ADDR);
284         if (ssr & 0x20) {
285                 /* overrun error has been occured */
286                 (void)readb(SCI0_RDR_ADDR);     /* discard it */
287         }
288         /* clear all error flags in SSR */
289         writeb(SCI0_SSR_ADDR, (ssr & ~0x38) | 0xc0);
290
291         /* confirm that error flags have actually been cleared */
292         while ((readb(SCI0_SSR_ADDR) & 0x38) != 0x00) ;
293
294         while ((readb(GRP12_ADDR) & 0x01) != 0x00) ;
295
296         sci_rx_error = 1;
297
298         enable_interrupts();
299 }

ERI 割り込みは、(TXI/TEI/RXI のような)エッジ割り込みではなくレベル割り込みなので、プログラムで明示的に割り込み要求をクリアしないと、延々と割り込みが入り続けることになる。

割り込み要求のクリアは、SSR.ORER、PER、FER ビットの内、エラーに該当するもの(ORER -> オーバラン、PER -> パリティエラー、FER -> フレーミングエラー)を 0 に設定することで行う。上記の実装ではエラーの原因を判定することなく 289 行目でまとめて 0 にしている。

Hello World

main 関数は

  1 #include "interrupts.h"
  2 #include "sci0_async.h"
  3
  4 int
  5 main(void)
  6 {
  7         const char *p = "Hello World!\n";
  8
  9         interrupt_init();
 10         sci0_init(9600);
 11
 12         while (*p)
 13                 sci0_putc(*p++);
 14
 15         while (1);
 16 }

で実装した。

make 後 GR-SAKURA の内蔵 FlashROM に hello.mot を書き込み、cu(1) で

  $ cu -s 9600 -l /dev/ttyUSB0

とした後、GR-SAKURA に電源を再投入すると、無事 "Hello World!" が出力された

*1:SCI には UART 以外に、クロック同期方式の通信も可能である(つまり SCI は USART としての機能を持つ)。加えて I2C マスタや SPI、更にはスマートカード I/F の機能も含まれている

*2:秋月などで入手可能

*3:実際 Renesas が提供しているサンプルコードに於いても、初期化処理で SCR.TE に 1 を設定するようなことはしていない

*4:このシフトレジスタは、プログラマからは見えない