GR-SAKURA で遊んでみる(4) Hello World
素の GR-SAKURA では、デバッグに使えそうなのは LED×4 程度しかない。
さすがにこの状態でのデバッグはキツいものがあるので、まずは PC との間で非同期のシリアル通信ができるようにしたい。
一般的な方法としては、
- USB ケーブルを接続し、PC に対して GR-SAKURA をシリアルポートに見せかける。
- GR-SAKURA と PC の両方に XBee を接続し、無線経由でシリアル通信させる。
- 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 からも、対向機器はあたかもシリアルで接続されているように見える。
この方法では、構成上
が必要になり、5,000 円前後の追加投資が必要になる。
予算に余裕があればこの方法でいきたいところだが、貧乏人の筆者には 5,000 円の出費はなかなか痛いところだ。というわけで、消去法により 3 の方針でいくことにした。変換回路を作る程度の部品なら家に部品余ってるし…
なお、2 の方法については以下の資料が詳しい。
レベル変換用の治具の作成
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 マニュアルでは、初期化処理を
- SCR.TIE、RIE、TE、RE、TEIE ビットを "0" に設定
- I/O ポート機能を設定
- SCR.CKE[1:0] ビットを設定
- SIMR1.IICM ビットを "0" に設定、SPMR.CKPH、CKPOL ビットを "0" に設定
- SMR、SCMR、SEMR レジスタに送信/受信フォーマットを設定
- BRR レジスタに値を設定
- 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 割り込み: 送信完了時(具体的にはストップビット送出後)に生成される。
これらの割り込みを利用して、大雑把には以下のように実装した。
- sci0_putc() または sci0_raw_putc() にて引数で渡された送信データをキューイングする。
- SCR.TE および TIE に 1 を設定する。これにより SCI は TXI 割り込み要求を生成する。
- TXI 割り込みに対応するハンドラの sci0_txi_handler() にて、送信データを TDR レジスタに書き込む。TDR レジスタに書き込まれた内容は、自動的にシフトレジスタ*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!" が出力された