GR-SAKURA で遊んでみる(6) 簡易ブートローダ
以前、「RX63N の内蔵 ROM への書き込み可能回数が僅か 1,000 回なので、気軽に書き込みができない」という趣旨の記事を書いた。ある程度の規模のソフトウェアになってくると、(特にデバッグ時の)ビルド回数は 1,000 を超えるだろうし、その都度 ROM の内容を書き換えていたら...
この問題は、普段のデバッグ時は F/W を RAM にダウンロードして実行する、という方法で回避するのが一般的である(当然、デバッグ完了時点では ROM に焼き込んで、リセット例外により実行されるようにしておく) 。
一応 HEW があれば、RAM へのダウンロードは可能だが、普段の環境が Windows 以外の場合、RAM 上でのデバッグのためにわざわざ Windows を起動するのは億劫な話である。
そこで、RX63N の内蔵 RAM にダウンロード可能なブートローダを作成してみた。当然、ブートローダ自身は ROM に書き込む必要はあるが、一度書き込んでおけば、後は何度でも F/W を RAM にダウンロードできる。そもそも「非 Windows 環境下で直にビルドした F/W を GR-SAKURA に書き込みたい」という需要があるかどうかは疑問だが、誰かの役に立つかも知れないのでここに置いておく。
なお、今回のブートローダは GR-SAKURA 上で動作させることを目的として作成したものだが、マルツパーツ館の MTK-RX63N でも動作することを確認している。
ビルド方法
ソースツリーを展開後、make(1) を実行する前に、config.h の以下の箇所を(必要に応じて)書き換える。
17 #define CONFIG_SCI_CH (0) 18 #define CONFIG_SCI_BAUD (9600)
17 行目は、ユーザとの対話に使用する SCI のチャネル番号の指定である。例えば XBee を使用したいのであれば、ここを 0 -> 2 に書き換える*1。その他、MTK-RX63N で動作させる場合も 0 -> 2 に書き換える。
18 行目は非同期通信におけるボーレートの指定で、例えば 19200 baud にしたい場合は、ここを 9600 -> 19200 に書き換える。
後は、RX 向けの GNU ツールチェインに PATH が通っている状態で make(1) を実行すればよい。現状では、BSD および GNU の make(1) でビルドが通ることを確認している。
ブートローダの機能
今回作成したブートローダは、上述の RAM 実行に際して必要となる最低限の機能のみを実装している。ブートローダを ROM に書き込んだ状態で電源を投入すると、
>
というプロンプトが表示されるので、以下のいずれかの動作を指定する。
F/W のダウンロード
書式
rx [-c]
説明
XMODEM プロトコルを使用して、シリアル(場合によっては無線)経由での F/W のダウンロードを行う。
-c オプション指定時は XMODEM/CRC によるダウンロードを、オプション未指定時は XMODEM/SUM によるダウンロードを試みる。ダウンロード可能な F/W の形式は S-record のみである。
F/W の実行
書式
goto <アドレス>
説明
指定アドレスからの実行を行う(つまり PC の値を指定アドレス値に書き換える)。例えば、上述の rx コマンドでダウンロードした F/W のエントリポイントのアドレスが 0x00002000 の場合は、
goto 0x00002000
とすれば、F/W の実行が開始される。
メモリのダンプ
書式
dump [-bwl] <アドレス> <サイズ>
説明
指定アドレスを先頭として、指定サイズ分のメモリ内容を表示する。オプションにて何 byte 単位で表示するかを指定する。
-b 指定時 -> 1 byte 単位 -w 指定時 -> 2 byte 単位 -l 指定時 -> 4 byte 単位
オプション未指定の場合は 1 byte 単位での表示となる。
例
> dump 0x00002000 100 00002000: 7f b8 fd 7f 02 00 00 02 fd 7f 0a 00 fc 01 fd 73 |...............s| 00002010: 0c 50 22 00 00 39 39 01 39 56 01 fb 1e fe 03 08 |.P"..99.9V......| 00002020: f8 19 03 a5 fb 1e 10 00 08 ec 12 7a f2 e3 12 fb |...........z....| 00002030: 1e a2 00 08 3c 10 0e fb 1e 32 00 08 f0 18 58 12 |....<....2....X.| 00002040: fc 33 22 21 fb 75 41 21 39 a5 00 fb 1e 28 00 08 |.3"!.uA!9....(..| 00002050: f8 19 00 07 fb 1e a6 00 08 3c 10 09 fb 1e 2a 00 |.........<....*.| 00002060: 08 7a 01 66 31 39 88 00 fb 1e 20 00 08 f8 12 11 |.z.f19.... .....| 00002070: 33 c2 22 fb 1e 24 00 08 3d 10 02 fb 1e 26 00 08 |3."..$..=....&..| 00002080: f8 19 00 04 fb 1e fe 03 08 f8 19 00 a5 fb 1e 2a |...............*| 00002090: c0 08 f0 18 f0 19 f0 1a f0 1e fb 1e 0a c0 08 f0 |................| 000020a0: 10 f0 11 f0 12 f0 16 fb 12 4c 22 00 00 66 02 fb |.........L"..f..| 000020b0: 32 50 22 00 00 43 13 7f 88 fb 1e 06 80 08 f8 19 |2P"..C..........| 000020c0: dc 05 fb 1e 02 80 08 3d 10 40 fb 1e 04 80 08 3d |.......=.@.....=| 000020d0: 10 00 fb 1e 04 73 08 3c 10 01 fb 1e 03 72 08 f0 |.....s.<.....r..| 000020e0: 14 7f a8 fb 1e 00 80 08 3d 10 01 2e 00 6e 13 fb |........=....n..| 000020f0: 2e 06 80 08 3d 20 10 fb 2e 02 80 08 3d 20 40 fb |....= ......= @.| > dump -w 0x00002000 100 00002000: b87f 7ffd 0002 0200 7ffd 000a 01fc 73fd 00002010: 500c 0022 3900 0139 5639 fb01 fe1e 0803 00002020: 19f8 a503 1efb 0010 ec08 7a12 e3f2 fb12 00002030: a21e 0800 103c fb0e 321e 0800 18f0 1258 00002040: 33fc 2122 75fb 2141 a539 fb00 281e 0800 00002050: 19f8 0700 1efb 00a6 3c08 0910 1efb 002a 00002060: 7a08 6601 3931 0088 1efb 0020 f808 1112 00002070: c233 fb22 241e 0800 103d fb02 261e 0800 00002080: 19f8 0400 1efb 03fe f808 0019 fba5 2a1e 00002090: 08c0 18f0 19f0 1af0 1ef0 1efb c00a f008 000020a0: f010 f011 f012 fb16 4c12 0022 6600 fb02 000020b0: 5032 0022 4300 7f13 fb88 061e 0880 19f8 000020c0: 05dc 1efb 8002 3d08 4010 1efb 8004 3d08 000020d0: 0010 1efb 7304 3c08 0110 1efb 7203 f008 000020e0: 7f14 fba8 001e 0880 103d 2e01 6e00 fb13 000020f0: 062e 0880 203d fb10 022e 0880 203d fb40 > dump -l 0x00002000 100 00002000: 7ffdb87f 02000002 000a7ffd 73fd01fc 00002010: 0022500c 01393900 fb015639 0803fe1e 00002020: a50319f8 00101efb 7a12ec08 fb12e3f2 00002030: 0800a21e fb0e103c 0800321e 125818f0 00002040: 212233fc 214175fb fb00a539 0800281e 00002050: 070019f8 00a61efb 09103c08 002a1efb 00002060: 66017a08 00883931 00201efb 1112f808 00002070: fb22c233 0800241e fb02103d 0800261e 00002080: 040019f8 03fe1efb 0019f808 2a1efba5 00002090: 18f008c0 1af019f0 1efb1ef0 f008c00a 000020a0: f011f010 fb16f012 00224c12 fb026600 000020b0: 00225032 7f134300 061efb88 19f80880 000020c0: 1efb05dc 3d088002 1efb4010 3d088004 000020d0: 1efb0010 3c087304 1efb0110 f0087203 000020e0: fba87f14 0880001e 2e01103d fb136e00 000020f0: 0880062e fb10203d 0880022e fb40203d
XMODEM についてのメモ
仕事でしばしばお世話になっている XMODEM の話題を(といっても、個人的な備忘録に過ぎないが)。
概要
XMODEM プロトコルは 1977 年に Ward Christensen 氏によってMODEM.ASM プログラムに実装されたプロトコルで、二つの計算機間に於けるバイナリデータの転送を目的としている。
非同期シリアル通信を用いた転送を想定しており、実際に当時は
+------+ +------+ | | シリアル +-------+ 電話回線 +-------+ シリアル | | | PC +==========+ MODEM +-----------| MODEM +==========+ PC | | | +-------+ +-------+ | | +------+ +------+
というネットワーク環境下で XMODEM を使い、電子掲示板との間でファイルを転送していたらしい*1。現在でも、シリアル経由でのちょっとした転送の際にお世話になることは多い。
その他、XMODEM には public domain で仕様が公開されていることや、実装が容易という利点がある。特に、シリアルドライバが実装済であれば、僅か 100 行程度のコードを追加するだけで転送ができるようになる手軽さは大きい。
一方で
- 128 bytes 毎に ACK/NAK をやりとりするので転送効率が悪い
- ファイル内容以外の情報(ファイル名やファイルサイズ)を渡せない*2
- 制御文字をクォートする機能がないので、ソフトウェアフロー制御と組み合わせて使用できない。
- 複数ファイルを一括して転送(バッチ転送)する機能がない。
という欠点もある。
しかし、例えば MCU の内蔵 ROM に書き込むファームウェアを転送する、といった用途であれば、
- 一般にファイルサイズは小さい。
- ファイルの送信側(ホスト PC)と受信側(ターゲット)の間はシリアルケーブルで直結という理想的な条件下で通信するので、転送過程でデータが化けることは殆どなく、従ってパケット再送は生じない。
- (受信側においては)一般にファイル名およびファイルサイズの情報は不要である。
という理由から、上記の欠点の幾つかは問題にならない。
XMODEM プロトコルには何種類かの派生プロトコルが存在するが、(個人的な主観では)以下の 2 種類が広く用いられているように思う。
- XMODEM/CRC
- XMODEM-1k
XMODEM プロトコルの仕様
以下、XMODEM プロトコルの概略について記述するが、XMODEM およびその派生プロトコルの作者らによる文書は、以下から入手できる。
XMODEM プロトコルに興味のある方は、一読されることを勧める。
その他、今回の記事の作成に際し、以下を参考にした。
通信手順
XMODEM プロトコルでは、一方がファイル送信側(以下単に送信側)、もう一方がファイル受信側(以下単に受信側)と役割を決めた上で、一対一の通信を行う。以下に通信手順の概略を示す。
1) 最初に受信側が NAK(0x15) の 1 byte を送信側に送ることで通信を開始する。
2) NAK を受けた送信側は、以下の構造を持つパケットを受信側に送る
フィールド | バイト長 | 説明 |
---|---|---|
SOH | 1 | 0x01 の固定値を設定する。 受信側はこの値を以てパケットの開始を認識する。 |
blk # | 1 | 何番目のパケットであるかを識別するための番号(所謂シーケンス番号) としての意味を持つ。最初の送信時はこのフィールド値に 1 を設定し、 以降送信する毎に、 2 -> 3 -> 4 -> … と値を 1 ずつ加算する (但し 255 の次は 0)。 |
255-blk # | 1 | 上記のフィールド値に対する 1 の補数(つまりビットを反転したもの)を設定する。 従って、パケットを送信する毎に 254 -> 253 -> … -> 0 -> 255 -> … となる。 |
data | 128 | 送信するファイルの内容を設定する。 但し、ファイルのサイズが 128 の倍数でない場合、 最後のパケットに於いては不足分を Ctrl-Z(0x1a) で埋める*3。 |
cksum | 1 | data フィールドの各バイトの値を足した値を 256 で割った余り(つまり下位 8bit)を設定する。 |
3) 2) のデータを受信した受信側は、SOH によりパケットの開始と判断し、残りの 131 bytes のデータを受信・解析した結果
- blk # および 255-blk # のフィールド値が期待したとおりか?
- data フィールド値の各バイトを合計した値の下位 8 bit を計算し、cksum フィールド値と一致するか?
を確認する。これらの条件を見たしていれば ACK(0x06) を、そうでなければ NAK(0x15) を返す。
4) 送信側は、受信側から NAK を受けた場合は、前回送信したパケットを再送する。ACK を受けた場合は、続きのデータがあれば次のパケットを送信し、そうでなければ EOT(0x04) を送信する。
5) 受信側が続きのデータのパケットを受け取った場合は 3) へ、EOT を受けた場合は ACK を返し、一連の転送処理が終了する。
例
上記の参考文献に記載されていた例を、以下に引用する。
Receiving Transmitting Computer Computer Ready to Ready to Receive Transmit | | | | |---------------------\NAK\--------------------->| | | |<------/SOH/Blk #1/Blk #1/Good Data/CkSum/------| | | |---------------------\ACK\--------------------->| | | |<------/SOH/Blk #2/Blk #2/Good Data/CkSum/------| | | |---------------------\ACK\--------------------->| | | |<------/SOH/Blk #3/Blk #3/Garbled Data/CkSum/---| | | |---------------------\NAK\--------------------->| | | |<------/SOH/Blk #3/Blk #3/Good Data/CkSum/------| | | |---------------------\ACK\--------------------->| | | |<--------------------/EOT/----------------------| | | |---------------------\ACK\--------------------->| | | V V File File Receipt Transmit Ends Ends
この例では、1 番目と 2 番目のパケットの転送はうまくいっているが、3 番目は転送途中でデータが化けて(garbled)しまい、受信側がこれを検出して NAK を返している。
NAK を受けた送信側は 3 番目のパケットを再送しており、今度は化けなかったため受信側は ACK を返している。
データは全体で 128×3 = 384 bytes 以下だったため、送信側は 4 番目のパケットを送ることなく EOT を返している。これで通信が終了する。
エラー処理
送信側が送信したパケットの内容が化けてしまった場合は、(既に述べた通り)受信側が NAK を返して送信側に再送させることで対処する。
それ以外のエラーとして、
- タイムアウト
- 試行回数の超過
- 受信側からの応答が化けた
が考えられる。
タイムアウトは、受信側が ACK/NAK(通信開始の NAK を含む)を送信側に応答したにもかかわらず、(断線等により)送信側からデータが届かない場合に発生する。この場合、送信側からデータが届くまで受信側は 10 秒毎に NAK を送る。
なお、受信側が NAK を 10 回連続して応答しても通信が開始されない場合、受信側はファイルの受信を諦める(つまり、受信側プログラムを再起動するなりして、明示的に再試行させない限り、送信側に NAK を投げなくなってしまう)。
受信側 -> 送信側への ACK 応答が化けた場合は、以下の方法により解決を図る:
送信側 | NAK 応答として扱う(つまり前回分のパケットを再送する)、 |
受信側 | 送信側の対処により、ACK を送ったのに前回と同じパケットが届くことになるが、 この場合は ACK を返すようにする。 |
NAK 応答が偶然 ACK に化けてしまった場合、これを受けた送信側は、受信側が期待するものより一つ進んだパケットを送信することになる。これは回復のしようがないので、後述の CAN により今までの転送分を一旦破棄し、転送を最初からやり直す。
転送処理の破棄
転送途中で回復不能なエラーを検出した場合は、CAN(0x18)を対向に送信する。CAN を送信した方も、受信した方も一連の転送処理を破棄する。
もし CAN を受信したのが受信側ならば、ACK を返して転送処理を破棄するし、
送信側ならば、次回のパケット送出時は前回の続きからではなく、最初のパケットからやり直すようにしなければならない。
なお、ACK/NAK 応答や EOT、SOH が偶然 CAN に化けてしまうと、それまでの転送分は水泡に帰す。これに対し、多くの実装では二回連続で CAN を受け取った場合のみ、転送を破棄することで対策している(本当に破棄したい場合は、二回以上連続して CAN を送るようにする必要がある)。
派生プロトコルの仕様
XMODEM/CRC
XMODEM/CRC は上記で説明した XMODEM プロトコルに、以下の変更を施したものである。
- 受信側 -> 送信側への最初の送信要求が NAK ではなく 'C'(0x43)
受信側は、転送開始時の送信要求の際、NAK の代わりに 'C' を送信する。送信側は 'C' を受信することにより、転送データを格納するパケットを、XMODEM/CRC 用のもの(後述)にする。なお、何回か*4 'C' を送っても送信側からパケットが送られてこない場合は、送信側が XMODEM/CRC プロトコルを喋れないものとして、通常の XMODEM による転送を試みる(つまり NAK を送信する)。
- 送信側 -> 受信側へのパケットのフォーマットの変更
送信側が送出するパケットに於いて、1 byte の checksum を廃し、代わりに 2 bytes の CRC-16 を付加する。
XMODEM-1k
XMODEM-1k は上記で説明した XMODEM プロトコルに、以下の変更を施したものである。
実装例
1 #include <string.h> 2 #include "sci_async.h" 3 #include "xmodem.h" 4 5 #define MAX_RETRY (9) 6 7 #define SOH (0x01) 8 #define STX (0x02) 9 #define EOT (0x04) 10 #define ACK (0x06) 11 #define NAK (0x15) 12 #define CAN (0x18) 13 #define CTRLZ (0x1a) 14 15 #define BLKNO_OFFSET (0) 16 #define INV_BLKNO_OFFSET (1) 17 #define DATA_OFFSET (2) 18 #define CSUM_OFFSET(datalen) (2 + (datalen)) 19 #define CRCH_OFFSET(datalen) (2 + (datalen)) 20 #define CRCL_OFFSET(datalen) (2 + (datalen) + 1) 21 #define RX_SIZE(datalen, use_crc) (2 + (datalen) + ((use_crc) ? 2 : 1)) 22 23 #define FLUSH_TIMEOUT (1000) 24 25 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 26 27 28 static uint8_t blkbuf[1024 + 2 + 2]; 29 static int pushedback = -1; 30 31 static int 32 xmodem_ungetc(int c) 33 { 34 if (c == -1) 35 return -1; 36 37 return (pushedback = (c & 0xff)); 38 } 39 40 static int 41 xmodem_getc(int timeout) 42 { 43 int c; 44 45 if (pushedback != -1) { 46 c = pushedback; 47 pushedback = -1; 48 } else 49 c = sci_raw_getc(timeout); 50 51 return c; 52 } 53 54 static int 55 xmodem_putc(int c) 56 { 57 return sci_raw_putc(c); 58 } 59 60 static int 61 xmodem_read_block(uint8_t *buf, size_t n) 62 { 63 int c; 64 65 while (n-- > 0) { 66 if ((c = xmodem_getc(1000)) == -1) 67 return -1; 68 *buf++ = (uint8_t)(c & 0xff); 69 } 70 71 return 0; 72 } 73 74 static uint16_t 75 calc_crc(uint8_t *buf, size_t n) 76 { 77 uint16_t crc; 78 size_t i; 79 80 crc = 0; 81 while (n-- > 0) { 82 crc = crc ^ (uint16_t)*buf++ << 8; 83 84 for (i = 0; i < 8; i++) 85 if (crc & 0x8000) 86 crc = crc << 1 ^ 0x1021; 87 else 88 crc = crc << 1; 89 } 90 91 return (crc & 0xffff); 92 } 93 93 94 static uint8_t 95 calc_csum(uint8_t *buf, size_t n) 96 { 97 uint8_t csum; 98 99 csum = 0; 100 while (n-- > 0) 101 csum += *buf++; 102 103 return csum; 104 } 105 106 size_t 107 xmodem_rx(uint8_t *buf, size_t size, int use_crc) 108 { 109 size_t nretry; 110 uint8_t expected_blkno; 111 size_t nrecv; 112 uint16_t crch, crcl; 113 size_t datalen; 114 int c; 115 116 if (use_crc) 117 use_crc = MAX_RETRY + 1; 118 119 while (use_crc) { 120 /* mode setting handshake: 121 attempt to start XMODEM/CRC transmission */ 122 xmodem_putc('C'); 123 124 c = xmodem_getc(3000); 125 if ((c == SOH) || (c == STX)) { 126 xmodem_ungetc(c); 127 break; 128 } 129 130 switch (c) { 131 case CAN: 132 if ((c = xmodem_getc(3000)) == CAN) { 133 /* abort if two consecutive CAN received */ 134 xmodem_putc(ACK); 135 while (xmodem_getc(FLUSH_TIMEOUT) != -1) ; 136 return 0; 137 } else 138 xmodem_ungetc(c); 139 break; 140 case EOT: 141 /* abort if EOT received */ 142 xmodem_putc(ACK); 143 return 0; 144 case -1: 145 default: 146 break; 147 } 148 149 use_crc--; 150 } 151 152 if (!use_crc) { 153 /* switch to checksum mode */ 154 xmodem_putc(NAK); 155 } 156 157 expected_blkno = 1; 158 nrecv = 0; 159 nretry = MAX_RETRY; 160 while ((c = xmodem_getc(10000)) != EOT) { 161 /* confirm that first octet is SOH or STX */ 162 switch (c) { 163 case CAN: 164 if (xmodem_getc(10000) == CAN) { 165 /* abort if two consecutive CAN received */ 166 xmodem_putc(ACK); 167 while (xmodem_getc(FLUSH_TIMEOUT) != -1) ; 168 return 0; 169 } else { 170 /* request to retransmit the block */ 171 goto retrans; 172 } 173 case SOH: 174 datalen = 128; 175 break; 176 case STX: 177 if (use_crc) 178 datalen = 1024; 179 else 180 goto retrans; 181 break; 182 case -1: 183 default: 184 goto retrans; 185 } 186 187 /* read 128/1024-byte block of data */ 188 if (xmodem_read_block(blkbuf, RX_SIZE(datalen, use_crc)) == -1) 189 goto retrans; 190 191 if (size < datalen) { 192 /* not enough memory left, abort it */ 193 goto abort; 194 } 195 196 /* validate CRC or arithmetic checksum */ 197 if (use_crc) { 198 crch = blkbuf[CRCH_OFFSET(datalen)]; 199 crcl = blkbuf[CRCL_OFFSET(datalen)]; 200 if (calc_crc(&blkbuf[DATA_OFFSET], datalen) 201 != ((crch << 8) | crcl)) 202 goto retrans; 203 } else { 204 if (calc_csum(&blkbuf[DATA_OFFSET], datalen) 205 != blkbuf[CSUM_OFFSET(datalen)]) 206 goto retrans; 207 } 208 209 /* confirm that received block# is valid */ 210 if (blkbuf[INV_BLKNO_OFFSET] != (0xff - blkbuf[BLKNO_OFFSET])) 211 goto retrans; 212 213 /* confirm that sender and receiver are synchronizing */ 214 if (blkbuf[BLKNO_OFFSET] == ((expected_blkno - 1) & 0xff)) { 215 /* previously sent ACK got garbled? just resend it */ 216 xmodem_putc(ACK); 217 continue; 218 } else if (blkbuf[BLKNO_OFFSET] != expected_blkno) { 219 /* fatal loss of synchronization, abort it */ 220 goto abort; 221 } 222 223 /* successfully received */ 224 xmodem_putc(ACK); 225 226 memcpy(buf, &blkbuf[DATA_OFFSET], datalen); 227 228 buf += datalen; 229 size -= datalen; 230 nrecv += datalen; 231 expected_blkno++; 232 nretry = MAX_RETRY; 233 continue; 234 235 retrans: 236 /* something went wrong, retry or abort it */ 237 if (nretry-- > 0) 238 xmodem_putc(NAK); 239 else 240 goto abort; 241 } 242 243 xmodem_putc(ACK); 244 245 return nrecv; 246 247 abort: 248 xmodem_putc(CAN); 249 xmodem_putc(CAN); 250 xmodem_putc(CAN); 251 while (xmodem_getc(FLUSH_TIMEOUT) != -1) ; 252 253 return 0; 254 } 255 256 size_t 257 xmodem_tx(const uint8_t *buf, size_t size, int use_1k) 258 { 259 size_t nsent; 260 int use_crc; 261 uint8_t blkno; 262 uint16_t crc; 263 size_t datalen; 264 uint8_t csum; 265 int c; 266 size_t i, n; 267 268 /* confirm whether receiver uses CRC or not */ 269 do { 270 c = xmodem_getc(-1); 271 switch (c) { 272 case 'C': 273 use_crc = 1; 274 break; 275 case NAK: 276 use_crc = 0; 277 break; 278 case CAN: 279 if ((c = xmodem_getc(-1)) == CAN) { 280 /* abort if two consecutive CAN received */ 281 xmodem_putc(ACK); 282 while (xmodem_getc(FLUSH_TIMEOUT) != -1) ; 283 return 0; 284 } else 285 xmodem_ungetc(c); 286 break; 287 case -1: 288 default: 289 break; 290 } 291 } while (c != 'C' && c != NAK); 292 293 if (!use_crc) 294 use_1k = 0; 295 296 datalen = use_1k ? 1024 : 128; 297 298 blkno = 1; 299 nsent = 0; 300 while (size > 0) { 301 xmodem_putc(use_1k ? STX : SOH); 302 xmodem_putc(blkno); 303 xmodem_putc(~blkno); 304 305 n = MIN(datalen, size); 306 for (i = 0; i < n; i++) 307 xmodem_putc((int)buf[i]); 308 for (i = n; i < datalen; i++) 309 xmodem_putc(CTRLZ); 310 311 memcpy(blkbuf, buf, n); 312 memset(&blkbuf[n], CTRLZ, datalen - n); 313 if (use_crc) { 314 crc = calc_crc(blkbuf, datalen); 315 xmodem_putc((uint8_t)((crc & 0xff00) >> 8)); 316 xmodem_putc((uint8_t)(crc & 0x00ff)); 317 } else { 318 csum = calc_csum(blkbuf, datalen); 319 xmodem_putc(csum); 320 } 321 322 c = xmodem_getc(-1); 323 switch (c) { 324 case CAN: 325 if (xmodem_getc(-1) == CAN) { 326 /* abort if two consecutive CAN received */ 327 xmodem_putc(ACK); 328 while (xmodem_getc(FLUSH_TIMEOUT) != -1) ; 329 return 0; 330 } 331 /* otherwise, retransmit the block */ 332 break; 333 case ACK: 334 /* transmit the next sequential block */ 335 buf += n; 336 size -= n; 337 nsent += n; 338 blkno++; 339 break; 340 case NAK: 341 default: 342 /* retransmit the block */ 343 break; 344 } 345 } 346 347 /* all data has been successfully transmitted */ 348 do { 349 xmodem_putc(EOT); 350 c = xmodem_getc(-1); 351 } while (c != ACK); 352 353 return nsent; 354 }
前述のとおり、XMODEM の仕様自体が単純であるので、特に説明を要する箇所はないと思う。
上記の例では、受信側/送信側の両方の処理を実装している上、XMODEM-CRC、XMODEM-1k もサポートしているので、全体で 350 行強になっている。
しかし、実際の実装に際しては、用途に応じて
- 受信側の処理のみを実装
- エラー処理を省略
- XMODEM/CRC や XMODEM-1k は非サポート
とすればよい。この場合は 100 行前後で書けるので、実装もテストもかなり楽である。
GR-SAKURA で遊んでみる(5) 内蔵 Flash ROM の書き換え
RX63N の内蔵 Flash ROM の内容を書き換える方法が気になったので、調べてみた。
概要
GR-SAKURA の MCU の型名は R5F563NBDDFP で、1 MiB の ROM を内蔵している*1。この ROM の使い方は、ハードウェア・マニュアルの「47.フラッシュメモリ」に記載されている。この章には結構な頁数が割かれているが、これは FCU が持つ多くの機能について説明しているためであり、
- E2 データフラッシュへのプログラム/イレーズ
- BGO(Back Ground Operation)
- ロックビットによるプロテクト機能
- サスペンド/レジューム
- 割り込み
の機能を使用しないと割り切ってドライバを書く場合は、それほど多くを実装する必要はなさそうだ。というわけで、今回は ROM の書き込み/消去機能のみを提供するドライバを実装する。
想像だが、全機能をサポートするドライバを書こうとすると結構な量のコーディングが必要になるのではないだろうか...
内蔵 ROM の特徴
RX63N では、ROM/E2 データフラッシュに対する操作は、専用シーケンサである FCU に対してコマンドを発行することで行う。コマンド発行は、対象領域の先頭アドレスへの書き込み操作により行う。
これ自体は、NOR Flash でお馴染みの形式であるが、
- FCU 制御用コマンドが CFI(Common Flash Memory Interface) で定義されているコマンドとは異なる。
- FCU 制御用のレジスタがあり、コマンド発行とは別に、これらのレジスタの設定も必要になる。
- Flash の内容を読み出すためのアドレスと、コマンド発行先のアドレス(ハードウェアマニュアルでは「P/E 用アドレス」と呼んでいる)が異なる。
- FCU(ROM/E2 データフラッシュの書き換えを行う専用のシーケンサ)の状態遷移に従う必要がある。
- 事前に FCU のファームウェアを所定の領域に転送する必要がある。
といった違いがある。
3. に関して少し補足すると、例えば ROM に割り当てられている 0xfff80000 番地に何らかの内容を書き込みたい場合は、イレーズおよびプログラムを行うコマンドを 0x00ff8000 番地に対して発行する、ということである。
つまり、同じ領域でも
ROM 内容の読み出し時のアドレス
-> [31:24] の bit は 0xff
ROM へのプログラム/イレーズのコマンド発行先アドレス
-> [31:24] の bit は 0x00
のように、用途によってアクセスするアドレスを使い分ける。
読み出し用アドレス P/E 用アドレス +--------------------------+ 0xFFE0 0000 | | 0x00E0 0000 | | | 領域 3: 512 KiB ※ | | | 0xFFE7 FFFF | | 0x00E7 FFFF +--------------------------+ 0xFFE8 0000 | | 0x00E8 0000 | | | 領域 2: 512 KiB ※ | | | 0xFFEF FFFF | | 0x00EF FFFF +--------------------------+ 0xFFF0 0000 | | 0x00F0 0000 | | | 領域 1: 512 KiB | | | 0xFFF7 FFFF | | 0x00F7 FFFF +--------------------------+ 0xFFF8 0000 | | 0x00F8 0000 | | | 領域 0: 512 KiB | | | 0xFFFF FFFF | | 0x00FF FFFF +--------------------------+ ※ GR-SAKURA の場合は、領域 2 および 3 は存在しない
FCU の状態遷移
FCU は上図の領域 0-3 の各領域毎に状態を持っており、プログラム/イレーズ操作は FCU の状態を意識した実装にする必要がある。
ハードウェアマニュアルによれば、FCU の状態遷移は
+-----------------------------------------------------------------+ | ROM リードモード | | +-------------------------------------------------------------+ | | | | | | | +----------------------+ (B) +--------------------------+ | | | | | | ----> | | | | | | | P/E ノーマルモード | | ステータスリードモード | | | | | | | <---- | | | | | | +----------------------+ (A) +--------------------------+ | | | | ^ | ^ | ^ | | | | | | | (A) +--------------------------+ (C) | | | | | | | | +----- | | <-----+ | | | | | | | | ロックビットリードモード | | | | | | | +-------> | | ---------+ | | | | | (C) +--------------------------+ (B) | | | | | | | | | | E2 データフラッシュ P/E モード | | | +--+--------------------------------------------+-------------+ | | | | | | | FENTRYR=0080h | FENTRYR=0000h | | | V | | +-+---------------------------------------------------------+ | | | ROM/E2 データフラッシュリードモード | | | +-+---------------------------------------------------------+ | | | ^ | +----+--------------------------------------------+---------------+ | FENTRYR=0001h、0002h、0004h、00008h | | | +--+--------------------------------------------+-------------+ | | ROM P/E モード | | V | | +----------------------+ (B) +--------------------------+ | | | | ----> | | | | | P/E ノーマルモード | | ステータスリードモード | | | | | <---- | | | | +----------------------+ (A) +--------------------------+ | | | ^ | ^ | | | | (A) +--------------------------+ (C) | | | | | +----- | | <-----+ | | | | | ロックビットリードモード | | | | +-------> | | ---------+ | | (C) +--------------------------+ (B) | +-------------------------------------------------------------+ (A) : ノーマルモード移行コマンドを実行 (B) : ノーマルモード移行、ロックビットリードモード移行以外のコマンドを実行 (C) : ロックビットリードモード移行コマンドを実行
となっている。
今回は、E2 データフラッシュへのプログラム/イレーズは行わないので、E2 データフラッシュ P/E モードには遷移させない。従って、上記の状態遷移図は
+-----------------------------------------------------------+ | ROM/E2 データフラッシュリードモード | +-----------------------------------------------------------+ | ^ | FENTRYR=0001h、0002h、0004h、00008h | | | +--+--------------------------------------------+-------------+ | | ROM P/E モード | | V | | +----------------------+ (B) +--------------------------+ | | | | ----> | | | | | P/E ノーマルモード | | ステータスリードモード | | | | | <---- | | | | +----------------------+ (A) +--------------------------+ | | | ^ | ^ | | | | (A) +--------------------------+ (C) | | | | | +----- | | <-----+ | | | | | ロックビットリードモード | | | | +-------> | | ---------+ | | (C) +--------------------------+ (B) | +-------------------------------------------------------------+
のように簡略化できる。
さらに、ロックビットによるプロテクト機能も使わないので、もう少し簡略化できて
+-----------------------------------------------------------+ | ROM/E2 データフラッシュリードモード | +-----------------------------------------------------------+ | ^ | FENTRYR=0001h、0002h、0004h、00008h | | | +--+--------------------------------------------+-------------+ | | ROM P/E モード | | V | | +----------------------+ (B) +--------------------------+ | | | | ----> | | | | | P/E ノーマルモード | | ステータスリードモード | | | | | <---- | | | | +----------------------+ (A) +--------------------------+ | +-------------------------------------------------------------+
となる。これだけの状態しか考えなくてよいなら、実装もきっと楽。
各状態の簡単な説明
ROM/E2 データフラッシュリードモード
ROM/E2 データフラッシュの読み出し用アドレスから、書き込まれている内容を読み出せる状態。
P/E ノーマルモード
ROM/E2 データフラッシュリードモードの状態で、FENTRYR レジスタの値を 0x0001、0x0002、0x0004、0x0008 のいずれかにすると、この状態に遷移する。プログラム/イレーズのコマンドは、(通常は)この状態に遷移してから発行する。
ステータスリードモード
ROM の状態を読み出せるモードで、ステータスリードモード移行コマンドの発行(P/E 対象アドレスへの 0x70 の書き込み)により、この状態に遷移する。
または、コマンド発行後のレディ中もこのモードに遷移する(というより、明示的なステータスリードモード移行コマンド発行を必要とするケースが思い付かない)。この場合は、FSTATR0 レジスタを読み出して FRDY ビットが 1 になる(か、タイムアウトになる)まで待機する。
その他、ROM P/E モード時に ROM 内容を読み出そうとしたり、逆に ROM/E2 データフラッシュリードモード時にイレーズコマンドを発行する等、FCU の状態遷移を無視した暴挙を働いたり、コマンドを発行したもののエラーになってしまった場合も、ステータスリードモードに遷移する。この場合、FCU はコマンドを受け付けなくなるので、エラーをクリアして元の状態に戻す必要がある。
FENTRYR レジスタによる状態遷移の制御
上の状態遷移図からも分かるとおり、ROM/E2 データフラッシュリードモード <-> ROM P/E モード間の遷移は、Flash P/E モードエントリレジスタ(FENTRYR) の書き換えによって行う。
但し、領域 0 から 3 の内、同時に二つ以上が ROM P/E モードになることはできない。つまり、どれか一つが ROM P/E モードの間、他の 3 つは ROM/E2 データフラッシュリードモードの状態である。
4つの領域の内、どれを ROM P/E モードとするかは、FENTRYR レジスタの下位 4 bit により制御する。以下に FENTRYR レジスタの構成を示す。
bit | シンボル | 機能 | R/W |
---|---|---|---|
0 | FENTRYR0 | 0: 領域 0 は ROM リードモード 1: 領域 0 は ROM P/E モード |
R/W |
1 | FENTRYR1 | 0: 領域 1 は ROM リードモード 1: 領域 1 は ROM P/E モード |
R/W |
2 | FENTRYR2 | 0: 領域 2 は ROM リードモード 1: 領域 2 は ROM P/E モード |
R/W |
3 | FENTRYR3 | 0: 領域 3 は ROM リードモード 1: 領域 3 は ROM P/E モード |
R/W |
4-6 | - | 予約 | R/W |
7 | FENTRYRD | 0: E2 データフラッシュはリードモード 1: E2 データフラッシュは P/E モード |
R/W |
8-15 | FEKEY[7:0] | FENTRYR レジスタの書き換える場合、 このフィールドには 0xaa を書く |
R/W |
GR-SAKURA の場合は、ROM が 1MiB、つまり領域 0 と 1 しかないので、実際には FENTRYR2 および FENTRYR3 に 1 を書くことはない。
プログラムの手順
ROM へのプログラムは、対象領域を P/E ノーマルモードに遷移後、以下の手順でコマンドを発行することにより行う。
- プログラム対象アドレスに 0xE8 を書き込む。
- プログラム対象アドレスに 0x40 を書き込む。
- プログラム対象アドレスに書き込みデータを順に 64 回 2bytes 単位で書き込む。
- プログラム/イレーズ用アドレスに 0xD0 を書き込む。
- FSTATR0.FRDY が 1 になるのを待機する。待機時間は、最大
(FCLK = 4MHz の場合) tP128 × 1.1 = 28 × 1.1 = 30.8 ms
(20MHz <= FCLK <= 50MHz の場合) tP128 × 1.1 = 10 × 1.1 = 11 ms - エラー確認
上記の「プログラム対象アドレス」は、128 bytes 境界にアラインしたアドレスである(つまり、書き込みは 128 bytes 単位で区切られた小領域に対して行う)。書き込み対象外の箇所には 0xffff を書き込む。例えば、128 bytes 境界の箇所から 4 bytes 後に "Hello World!" の文字列だけを書き込みたければ、手順 3. にて書き込みデータを
0xffff -> 0xffff -> 0x6548 -> 0x6c6c -> 0x206f -> 0x6f57 -> 0x6c72 -> 0x2164 -> 0xffff -> ... -> 0xffff
の順にする。
なお、内蔵 ROM 上の各 128 bytes 領域は、一回の消去につき一回しか書けない(上書き不可)。既に書き込まれている内容に対して、同じ内容を上書きしようとした場合であろうが、0xffff を上書きしようとした場合であろうが、プログラム手順を実行した場合はプログラムエラーが発生し、コマンドロック状態に陥る。この場合、エラーは手順 6. の段階で検出される。
イレーズの手順
ROM のイレーズは、対象領域を P/E ノーマルモードに遷移後、以下の手順でコマンドを発行することにより行う。
- イレーズ対象アドレスに 0x20 を書き込む。
- イレーズ対象アドレスに 0xD0 を書き込む。
- FSTATR0.FRDY が 1 になるのを待機する。待機時間は、最大
(FCLK = 4MHz の場合) tP128 × 1.1 = 28 × 1.1 = 30.8 ms
(20MHz <= FCLK <= 50MHz の場合) tP128 × 1.1 = 10 × 1.1 = 11 ms - エラー確認
上記のイレーズ対象アドレスは、128 bytes 境界にアラインしたものである必要はない。
ドライバの実装
内蔵 Flash ROM ドライバの実装例を以下に示す。
1 #include "string.h" 2 #include "io.h" 3 4 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 5 6 #define T_FCUR (35) 7 #define T_PCKA (120) 8 #define T_P128 (33600) 9 #define T_E16K (576000) 10 11 static const struct { 12 uint32_t start_addr; 13 uint32_t end_addr; 14 uint32_t area_no; 15 } block_info[] = { 16 /* 4 KiB * 8 blocks */ 17 { 0xfffff000, 0xffffffff, 0 }, 18 { 0xffffe000, 0xffffefff, 0 }, 19 { 0xffffd000, 0xffffdfff, 0 }, 20 { 0xffffc000, 0xffffcfff, 0 }, 21 { 0xffffb000, 0xffffbfff, 0 }, 22 { 0xffffa000, 0xffffafff, 0 }, 23 { 0xffff9000, 0xffff9fff, 0 }, 24 { 0xffff8000, 0xffff8fff, 0 }, 25 26 /* 16 KiB * 22 blocks */ 27 { 0xffff4000, 0xffff7fff, 0 }, 28 { 0xffff0000, 0xffff3fff, 0 }, 29 { 0xfffec000, 0xfffeffff, 0 }, 30 { 0xfffe8000, 0xfffebfff, 0 }, 31 { 0xfffe4000, 0xfffe7fff, 0 }, 32 { 0xfffe0000, 0xfffe3fff, 0 }, 33 { 0xfffdc000, 0xfffdffff, 0 }, 34 { 0xfffd8000, 0xfffdbfff, 0 }, 35 { 0xfffd4000, 0xfffd7fff, 0 }, 36 { 0xfffd0000, 0xfffd3fff, 0 }, 37 { 0xfffcc000, 0xfffcffff, 0 }, 38 { 0xfffc8000, 0xfffcbfff, 0 }, 39 { 0xfffc4000, 0xfffc7fff, 0 }, 40 { 0xfffc0000, 0xfffc3fff, 0 }, 41 { 0xfffbc000, 0xfffbffff, 0 }, 42 { 0xfffb8000, 0xfffbbfff, 0 }, 43 { 0xfffb4000, 0xfffb7fff, 0 }, 44 { 0xfffb0000, 0xfffb3fff, 0 }, 45 { 0xfffac000, 0xfffaffff, 0 }, 46 { 0xfffa8000, 0xfffabfff, 0 }, 47 { 0xfffa4000, 0xfffa7fff, 0 }, 48 { 0xfffa0000, 0xfffa3fff, 0 }, 49 50 /* 16 KiB * 8 blocks */ 51 { 0xfff9c000, 0xfff9ffff, 0 }, 52 { 0xfff98000, 0xfff9bfff, 0 }, 53 { 0xfff94000, 0xfff97fff, 0 }, 54 { 0xfff90000, 0xfff93fff, 0 }, 55 { 0xfff8c000, 0xfff8ffff, 0 }, 56 { 0xfff88000, 0xfff8bfff, 0 }, 57 { 0xfff84000, 0xfff87fff, 0 }, 58 { 0xfff80000, 0xfff83fff, 0 }, 59 60 /* 32 KiB * 8 blocks */ 61 { 0xfff78000, 0xfff7ffff, 1 }, 62 { 0xfff70000, 0xfff77fff, 1 }, 63 { 0xfff68000, 0xfff6ffff, 1 }, 64 { 0xfff60000, 0xfff67fff, 1 }, 65 { 0xfff58000, 0xfff5ffff, 1 }, 66 { 0xfff50000, 0xfff57fff, 1 }, 67 { 0xfff48000, 0xfff4ffff, 1 }, 68 { 0xfff40000, 0xfff47fff, 1 }, 69 70 /* 32 KiB * 8 blocks */ 71 { 0xfff38000, 0xfff3ffff, 1 }, 72 { 0xfff30000, 0xfff37fff, 1 }, 73 { 0xfff28000, 0xfff2ffff, 1 }, 74 { 0xfff20000, 0xfff27fff, 1 }, 75 { 0xfff18000, 0xfff1ffff, 1 }, 76 { 0xfff10000, 0xfff17fff, 1 }, 77 { 0xfff08000, 0xfff0ffff, 1 }, 78 { 0xfff00000, 0xfff07fff, 1 }, 79 80 #ifdef NEVER /* R5F563NBDDFP does NOT have following blocks */ 81 /* 64 KiB * 8 blocks */ 82 { 0xffef0000, 0xffefffff, 2 }, 83 { 0xffee0000, 0xffeeffff, 2 }, 84 { 0xffed0000, 0xffedffff, 2 }, 85 { 0xffec0000, 0xffecffff, 2 }, 86 { 0xffeb0000, 0xffebffff, 2 }, 87 { 0xffea0000, 0xffeaffff, 2 }, 88 { 0xffe90000, 0xffe9ffff, 2 }, 89 { 0xffe80000, 0xffe8ffff, 2 }, 90 91 /* 64 KiB * 8 blocks */ 92 { 0xffe70000, 0xffe7ffff, 3 }, 93 { 0xffe60000, 0xffe6ffff, 3 }, 94 { 0xffe50000, 0xffe5ffff, 3 }, 95 { 0xffe40000, 0xffe4ffff, 3 }, 96 { 0xffe30000, 0xffe3ffff, 3 }, 97 { 0xffe20000, 0xffe2ffff, 3 }, 98 { 0xffe10000, 0xffe1ffff, 3 }, 99 { 0xffe00000, 0xffe0ffff, 3 } 100 #endif /* NEVER */ 101 }; 102 103 int 104 flash_get_block_info(uint32_t addr, uint32_t *start, uint32_t *end, 105 size_t *area_no, size_t *block_no) 106 { 107 size_t i; 108 109 for (i = 0; i < sizeof(block_info) / sizeof(block_info[0]); i++) 110 if (addr >= block_info[i].start_addr 111 && addr <= block_info[i].end_addr) { 112 if (start != NULL) 113 *start = block_info[i].start_addr; 114 if (end != NULL) 115 *end = block_info[i].end_addr; 116 if (area_no != NULL) 117 *area_no = block_info[i].area_no; 118 if (block_no != NULL) 119 *block_no = i; 120 121 return 0; 122 } 123 124 return -1; /* out of ROM area */ 125 } 126 127 static void 128 reset_fcu(void) 129 { 130 /* forcibly terminate programming/erasure operations */ 131 writew(FRESETR_ADDR, 0xcc01); 132 133 /* start CMT1 */ 134 writew(CMT1_CMCR_ADDR, 0x0040); 135 writew(CMT1_CMCOR_ADDR, T_FCUR * (PCLK / 8) / 1000000); 136 writew(CMT1_CMCNT_ADDR, 0x00000); 137 writew(CMSTR0_ADDR, readw(CMSTR0_ADDR) | 0x0002); 138 139 while (!readb(IR29_ADDR)) ; 140 141 /* stop CMT1 */ 142 writew(CMSTR0_ADDR, readw(CMSTR0_ADDR) & ~0x0002); 143 writeb(IR29_ADDR, 0x00); 144 145 /* release reset state */ 146 writew(FRESETR_ADDR, 0xcc00); 147 148 #ifdef DEBUG 149 /* turn on user LED(D1) */ 150 writeb(PORTA_PODR_ADDR, readb(PORTA_PODR_ADDR) | 0x01); 151 writeb(PORTA_PDR_ADDR, readb(PORTA_PDR_ADDR) | 0x01); 152 #endif /* DEBUG */ 153 } 154 155 static int 156 check_fstatr(uint32_t peaddr) 157 { 158 if (readb(FSTATR1_ADDR) & 0x80) { 159 /* FCU error occured: reset FCU */ 160 reset_fcu(); 161 return -1; 162 } 163 164 if (readb(FSTATR0_ADDR) & 0x40) { 165 /* illegal command error occured */ 166 if (readb(FASTAT_ADDR) & 0x10) { 167 /* command lock state: 168 clear FASTAT.ROMAE, DLFAE, DFLRPE and DFLWPE */ 169 writeb(FASTAT_ADDR, 0x10); 170 #ifdef DEBUG 171 /* turn on user LED(D2) */ 172 writeb(PORTA_PODR_ADDR, readb(PORTA_PODR_ADDR) | 0x02); 173 writeb(PORTA_PDR_ADDR, readb(PORTA_PDR_ADDR) | 0x02); 174 #endif /* DEBUG */ 175 } 176 177 /* issue a status register clear command */ 178 writel(peaddr, 0x50); 179 180 return -1; 181 } 182 183 if (readb(FSTATR0_ADDR) & 0x30) { 184 /* erasure error and/or programmming error occured */ 185 /* issue a status register clear command */ 186 writel(peaddr, 0x50); 187 #ifdef DEBUG 188 /* turn on user LED(D3) */ 189 writeb(PORTA_PODR_ADDR, readb(PORTA_PODR_ADDR) | 0x04); 190 writeb(PORTA_PDR_ADDR, readb(PORTA_PDR_ADDR) | 0x04); 191 #endif /* DEBUG */ 192 return -1; 193 } 194 195 return 0; 196 } 197 198 static int 199 wait_frdy(uint32_t timeout) 200 { 201 int frdy; 202 203 timeout = (timeout + 99) / 100; /* round up to a multiple of 100 usec */ 204 while (timeout-- > 0) { 205 /* start CMT1 */ 206 writew(CMT1_CMCR_ADDR, 0x00c0); 207 writew(CMT1_CMCOR_ADDR, (PCLK / 8) / 10000); 208 writew(CMT1_CMCNT_ADDR, 0x0000); 209 writew(CMSTR0_ADDR, readw(CMSTR0_ADDR) | 0x0002); 210 211 frdy = 0; 212 while (!readb(IR29_ADDR)) 213 if (readb(FSTATR0_ADDR) & 0x80) { 214 frdy = 1; 215 break; 216 } 217 218 /* stop CMT1 */ 219 writew(CMSTR0_ADDR, readw(CMSTR0_ADDR) & ~0x0002); 220 writew(IR29_ADDR, 0x00); 221 } 222 223 return frdy ? 0 : -1; 224 } 225 226 static int 227 switch_to_read(uint32_t peaddr) 228 { 229 int ret; 230 231 /* check FRDY bit(tWAIT = tE16K) */ 232 if (wait_frdy((uint32_t)(T_E16K)) == -1) { 233 reset_fcu(); 234 return -1; 235 } 236 237 /* error check */ 238 ret = check_fstatr(peaddr); 239 240 /* clear FENTRYR.FENTRYn(n = 0 to 3) bits to 0 */ 241 writew(FENTRYR_ADDR, 0xaa00); 242 while (readw(FENTRYR_ADDR) != 0x0000) ; 243 244 /* disable programming and erasure of the ROM/E2 DataFlash, 245 programming and erasure of lock bits, reading of lock bits, 246 and blank checking */ 247 writeb(FWEPROR_ADDR, 0x02); 248 249 return ret; 250 } 251 252 static int 253 notify_fclk(uint32_t peaddr) 254 { 255 /* set frequency of Flash IF clock(FCLK) in PCKAR */ 256 writew(PCKAR_ADDR, (FCLK + 500000) / 1000000); 257 258 /* write 0xE9 to the destination address 259 for peripheral clock notification */ 260 writeb(peaddr, 0xe9); 261 262 /* write 0x03 to the destination address 263 for peripheral clock notification */ 264 writeb(peaddr, 0x03); 265 266 /* write 0x0f0f to the destination address 267 for peripheral clock notification three times(as a word) */ 268 writew(peaddr, 0x0f0f); 269 writew(peaddr, 0x0f0f); 270 writew(peaddr, 0x0f0f); 271 272 /* write 0xd0 to the destination address 273 for peripheral clock notification */ 274 writeb(peaddr, 0xd0); 275 276 /* FRDY bit check(tWAIT = tPCKA) */ 277 if (wait_frdy(T_PCKA) == -1) { 278 reset_fcu(); 279 return -1; 280 } 281 282 /* error check */ 283 return check_fstatr(peaddr); 284 } 285 286 static int 287 switch_to_pe(size_t peaddr) 288 { 289 static int fclk_notified = 0; 290 size_t area_no; 291 292 /* confirm whether destination address is within the range of ROM area */ 293 if (flash_get_block_info(peaddr | 0xff000000, 294 NULL, NULL, &area_no, NULL) == -1) 295 return -1; 296 297 /* enable programming and erasure */ 298 writeb(FWEPROR_ADDR, 0x01); 299 300 /* force FENTRYR to be cleared */ 301 writew(FENTRYR_ADDR, 0xaa00); 302 while (readw(FENTRYR_ADDR) != 0x0000) ; 303 304 /* set FENTRYR.FENTRYn bit to 1 */ 305 writew(FENTRYR_ADDR, 0xaa00 | (0x0001 << area_no)); 306 307 /* error check */ 308 if (check_fstatr(peaddr)) 309 return -1; 310 311 if (!fclk_notified) { 312 /* issue a peripheral clock notification command */ 313 if (notify_fclk(peaddr)) 314 return -1; 315 316 fclk_notified = 1; 317 } 318 319 return 0; 320 } 321 322 void 323 flash_init(void) 324 { 325 volatile uint16_t prcr_bak; 326 327 /* cancel module-stop state of CMT1 */ 328 prcr_bak = readw(PRCR_ADDR) & 0x00ff; 329 writew(PRCR_ADDR, 0xa503); 330 writel(MSTPCRA_ADDR, readl(MSTPCRA_ADDR) & ~0x00004000); 331 writew(PRCR_ADDR, 0xa500 | prcr_bak); 332 333 /* transit to lock bit mode if 0x71 command is issued */ 334 writeb(FMODR_ADDR, 0x00); 335 336 /* disable flash ready interrupt */ 337 writeb(FRDYIE_ADDR, 0x00); 338 339 /* disable flash access error interrupt */ 340 writeb(FAEINT_ADDR, 0x00); 341 342 /* clear FENTRYR */ 343 writew(FENTRYR_ADDR, 0xaa00); 344 345 /* enable write protection */ 346 writeb(FWEPROR_ADDR, 0x00); 347 348 /* we'll never access to E2 DataFlash area */ 349 writew(DFLRE0_ADDR, 0x2d00); 350 writew(DFLRE1_ADDR, 0xd200); 351 writew(DFLWE0_ADDR, 0x1e00); 352 writew(DFLWE1_ADDR, 0xe100); 353 354 /* enable access to the FCU RAM */ 355 writew(FCURAME_ADDR, 0xc401); 356 357 /* transfer the FCU firmware to FCU RAM area */ 358 memcpy((void *)FCU_RAM_ADDR, (void *)FCU_FRM_ADDR, FCU_FRM_SIZE); 359 } 360 361 void 362 flash_deinit(void) 363 { 364 /* nothing to do */ 365 } 366 367 int 368 flash_write_rom(uint32_t dest, const uint8_t *src, uint32_t n) 369 { 370 uint32_t ra, wa; 371 size_t npad_lead, npad_trail; 372 size_t l, m; 373 uint16_t *p = (uint16_t *)src; 374 int odd = 0; 375 376 if ((dest & 0xff000000) != 0xff000000) 377 return -1; /* given address is not for reading */ 378 379 while (n > 0) { 380 /* get address for programming/erasure */ 381 ra = dest & 0x00ffffff; 382 wa = ra & ~((uint32_t)(128 - 1)); 383 384 l = m = MIN(128 - (ra - wa), n); 385 npad_lead = ra - wa; 386 npad_trail = 128 - (npad_lead + m); 387 388 /* switch to P/E normal mode */ 389 if (switch_to_pe(wa) == -1) 390 return -1; 391 392 /* disable lock bit protection */ 393 writew(FPROTR_ADDR, 0x5501); 394 395 writeb(wa, 0xe8); 396 writeb(wa, 0x40); 397 398 /* program leading 0xff bytes */ 399 while (npad_lead > 1) { 400 writew(wa, 0xffff); 401 npad_lead -= 2; 402 } 403 if (npad_lead == 1 && m >= 1) { 404 writew(wa, (*p << 8) | 0x00ff); 405 m--; 406 odd = 1; 407 } 408 409 /* program words specified in the seconds argument */ 410 if (odd) { 411 while (m > 1) { 412 writew(wa, (*p >> 8) | (*(p + 1) << 8)); 413 p++; 414 m -= 2; 415 } 416 } else 417 while (m > 1) { 418 writew(wa, *p++); 419 m -= 2; 420 } 421 422 /* program trailing 0xff bytes */ 423 if (m == 1) { 424 writew(wa, *p++ | 0xff00); 425 npad_trail--; 426 } 427 while (npad_trail > 0) { 428 writew(wa, 0xffff); 429 npad_trail -= 2; 430 } 431 432 writeb(wa, 0xd0); 433 434 /* check FRDY bit(tWAIT = tP128 * 1.1) */ 435 if (wait_frdy(T_P128 * 11 / 10) == -1) { 436 reset_fcu(); 437 return -1; 438 } 439 440 /* switch to ROM/E2 DataFlash read mode */ 441 if (switch_to_read(wa) == -1) 442 return -1; 443 444 dest += l; 445 n -= l; 446 } 447 448 return 0; 449 } 450 451 int 452 flash_erase_rom(uint32_t dest) 453 { 454 uint32_t ba; 455 456 if ((dest & 0xff000000) != 0xff000000) 457 return -1; /* given address is not for reading */ 458 459 /* get address for programming/erasure */ 460 ba = dest & 0x00ffffff; 461 462 /* switch to P/E normal mode */ 463 if (switch_to_pe(ba) == -1) 464 return -1; 465 466 /* disable lock bit protection */ 467 writew(FPROTR_ADDR, 0x5501); 468 469 writeb(ba, 0x20); 470 writeb(ba, 0xd0); 471 472 /* check FRDY bit(tWAIT = tE16K * 1.1) */ 473 if (wait_frdy((uint32_t)(T_E16K * 11 / 10)) == -1) { 474 reset_fcu(); 475 return -1; 476 } 477 478 /* switch to ROM/E2 DataFlash read mode */ 479 return switch_to_read(ba); 480 }
補遺
実はドライバを書き上げてから気付いたのだが、内蔵 ROM は僅か 1,000 回までしか書き換えられないらしい*2
# SPANSION の FM3 あたりは 100,000 回なのに?
そうとは知らず、GR-SAKURA を入手したての頃は、HEW で ROM 上のコードに S/W ブレークを張りまくったり、FDT で頻繁に ROM 内容を書き換えていたような...
せっかく実装したけど、このドライバを使うことはなさそうだ (--;
※2018/JUN/15 追記
下記にコメント頂いているとおり、ROM(コード格納用フラッシュメモリ)は 1000 回までしか保証されていないが、E2 データフラッシュはより多くの書き換えに耐えられる(RX63N の場合は 100,000 回まで保証されている)。
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!" が出力された
GR-SAKURA で遊んでみる(3) GNU toolchain による開発環境の構築
前回 LED の点滅プログラムを書いたものの、Renesas 製のツールチェインにてビルドすることを前提としたもので、Windows PC でないとビルドできない。
普段は NetBSD や Linux を使用しているので、ちょっとしたことを GR-SAKURA で試すのにもわざわざ Windows PC を起動してビルドするのは面倒である。
そこで GNU toolchain による RX 向けのクロス開発環境を整備した。ソースからのビルド手順は ARM や SH 等の他アーキテクチャの場合と同様であり、特に難しいものではない。しかし一からやり直すとなると、地道な試行錯誤を繰り返す羽目になるので、備忘録を兼ねて以下に手順をまとめておく。なお、この手順は NetBSD 6.1.2、Linux(Slackware-14.0)、MinGW(Windows XP) にて試行した*1。
binutils のインストール
RX 向けの as(1)、ld(1)、objdump(1)は、binutils をインストールすることで使用可能になる。
後述する gcc(1) と異なり、binutils は多倍長/浮動小数点に関するライブラリ群には依存しない。binutils のソースの入手、ビルドおよびインストールの手順を以下に示す。
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/binutils/binutils-2.24.tar.gz $ tar zxvf binutils-2.24.tar.gz $ cd binutils-2.24 $ ./configure --prefix=/usr/local/rx-elf --target=rx-elf $ make # make install
※ # で始まる行は root 権限で行う必要があることを示す(MinGW 環境下では、特に root 権限を意識する必要はない)
binutils の動作確認結果:
$ cat > loop.s .text .globl _start _start: nop nop nop bra . .end (Ctrl-D を入力) $ rx-elf-as -o loop.o loop.s $ rx-elf-objdump -Dx loop.o loop.o: file format elf32-rx-le loop.o architecture: rx, flags 0x00000010: HAS_SYMS start address 0x00000000 private flags = 0x8:32-bit doubles, no dsp, no pid, RX ABI Sections: Idx Name Size VMA LMA File off Algn 0 P 00000005 00000000 00000000 00000034 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 D_1 00000000 00000000 00000000 00000039 2**0 CONTENTS, ALLOC, LOAD, DATA 2 B_1 00000000 00000000 00000000 00000039 2**0 ALLOC SYMBOL TABLE: 00000000 l d P 00000000 P 00000000 l d D_1 00000000 D_1 00000000 l d B_1 00000000 B_1 00000000 g P 00000000 _start Disassembly of section P: 00000000 <_start>: 0: 03 nop 1: 03 nop 2: 03 nop 3: 2e 00 bra.b 3 <_start+0x3>
objdump(1) の出力結果にて、コードに割り当てられたセクション名が(.text ではなく) P になっている点に注目されたい。
RX 向けの as(1) では、単に ".text" と記述すると、割り当てるセクションの名前は .text ではなく P になる*2。同様に擬似命令 ".data" および ".bss" は、それぞれ D_1 および B_1 セクションを割り当てる。もちろん後述の gcc(1) で C のソースをコンパイルした場合、.text/.data/.rodata/.bss の代わに P/D_1/C/B_1 を生成する。
GCC のインストール
GCC は binutils に加えて、GMP(GNU Multiple Precision Library)、MPFR(GNU Multiple Precision Floating-Point Reliably) および MPC の多倍長/浮動小数点数を扱うライブラリ群に依存している。従って、これらを先にインストールする必要がある。
各ライブラリのソースの入手からインストールまでの手順を以下に示す。
GMP:
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/gmp/gmp-4.3.2.tar.gz $ tar zxvf gmp-4.3.2.tar.gz $ cd gmp-4.3.2 $ ./configure && make # make install $ make check
MPFR:
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/mpfr/mpfr-2.4.2.tar.gz $ tar zxvf mpfr-2.4.2.tar.gz $ cd mpfr-2.4.2 $ ./configure --with-gmp=/usr/local && make # make install
MPC:
$ wget http://www.multiprecision.org/mpc/download/mpc-0.8.2.tar.gz $ tar zxvf mpc-0.8.2.tar.gz $ cd mpc-0.8.2 $ ./configure --with-gmp=/usr/local && make # make install
次に gcc(1) のソースを入手し、ビルドおよびインストールを行う。
ここでは、4.7.x 系のバージョンの内、2014 年 9 月 24 日時点での最新版である 4.7.4 をビルドした。
4.8.x や 4.9.x の場合、ビルドの途中でエラーになる*3。
$ wget ftp://ftp.jaist.ac.jp/pub/GNU/gcc/gcc-4.7.4/gcc-4.7.4.tar.gz $ tar zxvf gcc-4.7.4.tar.gz $ mkdir gcc-4.7.4.build && cd gcc-4.7.4.build $ ../gcc-4.7.4/configure --prefix=/usr/local/rx-elf --target=rx-elf --with-gmp=/usr/local --disable-libssp --enable-languages=c $ LD_LIBRARY_PATH=/usr/local/lib gmake # gmake install
※ 上記で(make ではなく)gmake を使用しているのは、GCC のソースに含まれる configure が GNU 拡張文法を使用した Makefile を生成することに因る(*BSD のユーザランドに含まれる make では解析に失敗する)。Linux や MinGW 環境下では通常 GNU make が標準なので、gmake とせずに make としても問題ない。
GCC 版 LED 点滅プログラム
前回の LED 点滅プログラムを gcc 向けに書き直した。ソース一式は以下に置いている。
http://he4.seikyou.ne.jp/home/tsukimizake/gr-sakura-dev/blink_gcc.tar.gz
各々のソースの内容は以下のとおり:
blink.S:
1 #define SCKCR 0x00080020 2 #define SCKCR2 0x00080024 3 #define SCKCR3 0x00080026 4 5 #define PLLCR_ADDR 0x00080028 6 #define PLLCR2_ADDR 0x0008002a 7 #define MOSCCR_ADDR 0x00080032 8 #define MOSCWTCR_ADDR 0x000800a2 9 #define PLLWTCR_ADDR 0x000800a6 10 11 #define IR12_ADDR 0x0008701c 12 #define IPR004_ADDR 0x00087304 13 #define IER03_ADDR 0x00087203 14 15 #define PRCR_ADDR 0x000803fe 16 #define MSTPCRA_ADDR 0x00080010 17 #define MSTPCRB_ADDR 0x00080014 18 #define MSTPCRC_ADDR 0x00080018 19 20 #define PORT0_PDR_ADDR 0x0008c000 21 #define PORT1_PDR_ADDR 0x0008c001 22 #define PORT2_PDR_ADDR 0x0008c002 23 #define PORT3_PDR_ADDR 0x0008c003 24 #define PORT4_PDR_ADDR 0x0008c004 25 #define PORT5_PDR_ADDR 0x0008c005 26 #define PORT6_PDR_ADDR 0x0008c006 27 #define PORT7_PDR_ADDR 0x0008c007 28 #define PORT8_PDR_ADDR 0x0008c008 29 #define PORT9_PDR_ADDR 0x0008c009 30 #define PORTA_PDR_ADDR 0x0008c00a 31 #define PORTB_PDR_ADDR 0x0008c00b 32 #define PORTC_PDR_ADDR 0x0008c00c 33 #define PORTD_PDR_ADDR 0x0008c00d 34 #define PORTE_PDR_ADDR 0x0008c00e 35 #define PORTF_PDR_ADDR 0x0008c00f 36 #define PORTG_PDR_ADDR 0x0008c010 37 #define PORTJ_PDR_ADDR 0x0008c012 38 #define PORTA_PODR_ADDR 0x0008c02a 39 40 #define CMSTR0_ADDR 0x00088000 41 #define CMT0_CMCR_ADDR 0x00088002 42 #define CMT0_CMCNT_ADDR 0x00088004 43 #define CMT0_CMCOR_ADDR 0x00088006 44 45 .text 46 47 .globl _start 48 .globl intvect 49 .globl unhandled_interrupt 50 .globl unhandled_exception 51 .globl cmt0_interrupt 52 53 _start: 54 ; disable interrupts 55 clrpsw i 56 57 mvtc #0x00020000, usp 58 mvtc #0x0001fc00, isp 59 mvtc #intvect, intb 60 61 ; stop unused peripherals 62 bsr stop_modules 63 64 ; initialize non-existent I/O port 65 ; see following: 66 ; 67 ; RX63N Group, RX631 Group 68 ; User's Manual:Hardware 69 ; 21.5.1 Products fewer than 176 Pins 70 bsr init_nonexistent_port 71 72 ; enable CMT0 73 mov.l #PRCR_ADDR, r1 74 mov.w #0xa503, [r1] 75 mov.l #MSTPCRA_ADDR, r1 76 mov.l [r1], r2 77 bclr #15, r2 78 mov.l r2, [r1] 79 80 ; if the main clock stabilization waiting time(tMAINOSC) is 20 ms, 81 ; we should set MOSCWTCR.MSTS[4:0] to 14 82 ; (262,144 cycle > 20.00 ms = 240,000 cycles), 83 ; so we need to wait 84 ; tMAINOSC + (262,144 + 16384)/ fMAIN 85 ; = 20 + (262,144 + 16,384) / 12,000 = approx. 43.23 ms 86 mov.l #MOSCWTCR_ADDR, r1 87 mov.b #0xe, [r1] ; MOSCWTCR.MSTS[4:0]=14 88 89 ; run the main clock oscillator 90 mov.l #MOSCCR_ADDR, r1 91 bclr #0, [r1].b ; MOSCCR.MOSTP=0 92 ; confirm that MOSCCR.MOSTP has actually been updated 93 1: movu.b [r1], r2 94 tst r2, r2 95 bnz 1b 96 97 mov.l #44, r1 98 bsr mdelay 99 100 ; configure the PLL clock: 101 ; to generate the PLL clock from main clock by dividing by 1 and 102 ; multiplying by 8(fPLL = 12.0 * 8 = 96.0 MHz), 103 ; we should set PLLWTCR.PSTS to 9 104 ; (n = 65536 cycles = 682.7 us), 105 ; so we need to wait 106 ; tPLL1 + (n + 131,072) / fPLL 107 ; = 0.5 + (65,536 + 131,072)/ 96,000 = 2.548 ms 108 mov.l #PLLCR_ADDR, r1 109 mov.w #0x0700, [r1] 110 mov.l #PLLWTCR_ADDR, r1 111 mov.b #9, [r1] 112 mov.l #PLLCR2_ADDR, r1 113 bclr #0, r1 ; PLLCR2.PLLEN=0 114 115 mov.l #3, r1 116 bsr mdelay 117 118 mov.l #SCKCR, r1 119 mov.l #0x22c23311, [r1] ; ICLK -> 1/4, PCLK -> 1/8 120 mov.l #SCKCR2, r1 121 mov.w #0x0002, [r1] 122 mov.l #SCKCR3, r1 123 mov.w #0x0400, [r1] ; use PLL clock 124 125 mov.l #PRCR_ADDR, r1 126 mov.w #0xa500,[r1] ; write protection for clock control registers 127 128 ; turn off user LEDs 129 mov.l #PORTA_PODR_ADDR, r1 130 bclr #0, [r1].b ; Set PA0 output level to L 131 bclr #1, [r1].b ; Set PA1 output level to L 132 bclr #2, [r1].b ; Set PA2 output level to L 133 bclr #6, [r1].b ; Set PA6 output level to L 134 mov.l #PORTA_PDR_ADDR, r1 135 bset #0, [r1].b ; Set PA0 direction to output 136 bset #1, [r1].b ; Set PA1 direction to output 137 bset #2, [r1].b ; Set PA2 direction to output 138 bset #6, [r1].b ; Set PA6 direction to output 139 140 ; initialize .bss section 141 mov.l #__bss_start, r1 142 mov.l #0, r2 143 mov.l #__bss_end, r3 144 sub r1, r3 145 sstr.b 146 147 mov.l #CMT0_CMCOR_ADDR, r1 148 mov.w #0x05dc, [r1] ; Set CMCOR 149 mov.l #CMT0_CMCR_ADDR, r1 150 mov.w #0x0040, [r1] ; Set CMCR 151 mov.l #CMT0_CMCNT_ADDR, r1 152 mov.w #0x0000, [r1] ; Clear CMCNT 153 154 ; enable interrupts 155 mov.l #IPR004_ADDR, r1 156 mov.b #0x01, [r1] ; Set IPR004 157 mov.l #IER03_ADDR, r1 158 bset #4, [r1].b ; Set IER03.IEN4 159 setpsw i ; Set I bit 160 161 mov.l #CMSTR0_ADDR, r1 162 mov.w #0x0001, [r1] ; Start CMT0 163 164 bra . 165 166 mdelay: 167 pushm r1-r3 168 169 mov.l #CMT0_CMCOR_ADDR, r2 170 mov.w #0x0010, [r2] 171 mov.l #CMT0_CMCR_ADDR, r2 172 mov.w #0x0040, [r2] 173 mov.l #CMT0_CMCNT_ADDR, r2 174 mov.w #0x0000, 4[r2] 175 176 mov.l #IPR004_ADDR, r2 177 bset #0, [r2].b ; Set IPR004 178 mov.l #IER03_ADDR, r2 179 bset #4, [r2].b ; Set IER03.IEN4 180 181 tst r1, r1 182 1: bz 3f 183 184 mov.l #CMSTR0_ADDR, r2 185 mov.w #0x0001, [r2] ; Start CMT0 186 187 2: mov.l #IR12_ADDR, r2 188 movu.b [r2], r3 189 tst r3, r3 190 bz 2b 191 mov.b #0x00, [r2] 192 193 mov.l #CMSTR0_ADDR, r2 194 mov.w #0x0000, [r2] ; Stop CMT0 195 196 sub #1, r1 197 bra 1b 198 199 3: mov.l #IER03_ADDR, r2 200 bclr #4, [r2].b ; Clear IER03.IEN4 201 mov.l #IPR004_ADDR, r2 202 bclr #0, [r2].b ; Clear IPR004 203 204 popm r1-r3 205 rts 206 207 stop_modules: 208 ; disable write protection for low power consumption module 209 mov.l #PRCR_ADDR, r1 210 mov.w #0xa502,[r1] 211 212 ; stop unused peripherals(DMA and EXDMA) 213 mov.l #MSTPCRA_ADDR, r1 214 mov.l [r1], r2 215 bset #28, r2 ; DMA 216 bset #29, r2 ; EXDMA 217 mov.l r2, [r1] 218 219 mov.l #PRCR_ADDR, r1 220 mov.w #0xa500, [r1] 221 rts 222 223 init_nonexistent_port: 224 ; set non-existent I/O ports' direction to output 225 push.l r1 226 mov.l #PORT0_PDR_ADDR, r1 227 mov.b #0x0f, [r1] ; P00-P03 228 mov.l #PORT1_PDR_ADDR, r1 229 mov.b #0x03, [r1] ; P10-P11 230 mov.l #PORT2_PDR_ADDR, r1 231 mov.b #0x00, [r1] ; none 232 mov.l #PORT3_PDR_ADDR, r1 233 mov.b #0x00, [r1] ; none 234 mov.l #PORT4_PDR_ADDR, r1 235 mov.b #0x00, [r1] ; none 236 mov.l #PORT5_PDR_ADDR, r1 237 mov.b #0xc0, [r1] ; P56-P57 238 mov.l #PORT6_PDR_ADDR, r1 239 mov.b #0xff, [r1] ; P60-67 240 mov.l #PORT7_PDR_ADDR, r1 241 mov.b #0xff, [r1] ; P70-77 242 mov.l #PORT8_PDR_ADDR, r1 243 mov.b #0xff, [r1] ; P80-87 244 mov.l #PORT9_PDR_ADDR, r1 245 mov.b #0xff, [r1] ; P90-97 246 mov.l #PORTA_PDR_ADDR, r1 247 mov.b #0x00, [r1] ; none 248 mov.l #PORTB_PDR_ADDR, r1 249 mov.b #0x00, [r1] ; none 250 mov.l #PORTC_PDR_ADDR, r1 251 mov.b #0x00, [r1] ; none 252 mov.l #PORTD_PDR_ADDR, r1 253 mov.b #0x00, [r1] ; none 254 mov.l #PORTE_PDR_ADDR, r1 255 mov.b #0x00, [r1] ; none 256 mov.l #PORTF_PDR_ADDR, r1 257 mov.b #0x3f, [r1] ; PF0-PF5 258 mov.l #PORTG_PDR_ADDR, r1 259 mov.b #0xff, [r1] ; PG0-OG7 260 mov.l #PORTJ_PDR_ADDR, r1 261 mov.b #0x20, [r1] ; PJ5 262 pop r1 263 rts 264 265 unhandled_exception: 266 ; turn on LED1 267 mov.l #PORTA_PODR_ADDR, r1 268 bset #0, [r1].b 269 mov.l #PORTA_PDR_ADDR, r1 270 bset #0, [r1].b 271 bra . 272 273 unhandled_interrupt: 274 ; turn on LED2 275 mov.l #PORTA_PODR_ADDR, r1 276 bset #1, [r1].b 277 mov.l #PORTA_PDR_ADDR, r1 278 bset #1, [r1].b 279 bra . 280 281 cmt0_interrupt: 282 pushm r1-r2 283 mov.l #count, r1 284 mov.l [r1], r2 285 tst r2, r2 286 bnz 1f 287 mov.l #0x01f4, r2 288 289 ; toggle output level 290 mov.l #PORTA_PODR_ADDR, r1 291 bnot #6, [r1].b 292 293 mov.l #count, r1 294 1: sub #1, r2 295 mov.l r2, [r1] 296 297 popm r1-r2 298 rte 299 300 301 .bss 302 303 count: .long 0x00000000 304 305 .end
vect.S:
1 .section .intvect, "a" 2 3 .globl unhandled_interrupt 4 .globl cmt0_interrupt 5 .globl intvect 6 7 .align 4 8 intvect: 9 .long unhandled_interrupt 10 .long unhandled_interrupt (中略) 36 .long unhandled_interrupt 37 .long cmt0_interrupt 38 .long unhandled_interrupt 39 .long unhandled_interrupt (中略) 264 .long unhandled_interrupt 265 266 267 .section .fixedvect, "a" 268 .globl fixedvect 269 .globl unhandled_exception 270 .globl _start 271 272 .align 4 273 fixedvect: 274 .long unhandled_exception 275 .long unhandled_exception 276 .long unhandled_exception 277 .long unhandled_exception 278 .long unhandled_exception 279 .long unhandled_exception 280 .long unhandled_exception 281 .long unhandled_exception 282 .long unhandled_exception 283 .long unhandled_exception 284 .long unhandled_exception 285 .long _start 286 287 .end
※セクション名に続く "a" は、allocatable なセクションであることを示す。これがないと、objcopy(1) による ELF -> S-recrd への変換時に、セクションの内容が破棄されてしまう。
blink.ld(リンカスクリプト):
1 SECTIONS 2 { 3 . = 0x00000000; 4 __bss_start = .; 5 B_1 : { *(B_1) } 6 __bss_end = .; 7 8 . = 0xfff80000; 9 P : { *(P) } 10 .intvect : { *(.intvect) } 11 . = 0xffffffd0; 12 .fixedvect : { *(.fixedvect) } 13 }
GNU-toolchain によるアセンブル、リンク、ELF -> S-recrd 変換の手順を以下に示す。
rx-elf-gcc -Wall -c -o blink.o blink.S rx-elf-gcc -Wall -c -o vect.o vect.S rx-elf-ld -o blink -T blink.ld blink.o vect.o rx-elf-objcopy -O srec blink blink.mot
気になった点
double foo(double a, double b) { return a + b; }
という C のソースを gcc(1) でコンパイルすると、その結果は
0: 7e aa push.l r10 2: 7e a6 push.l r6 4: 71 06 f8 add #-8, r0, r6 7: ef 60 mov.l r6, r0 9: e3 61 mov.l r1, [r6] b: a0 6a mov.l r2, 4[r6] d: ec 6a mov.l [r6], r10 f: fc 89 6a 01 fadd 4[r6].l, r10 13: ef a1 mov.l r10, r1 15: 62 80 add #8, r0 17: 7e b6 pop r6 19: 7e ba pop r10 1b: 02 rts
となった。浮動小数点数の加算に対して fadd 命令を使ったコードを生成するということは、RX200 シリーズは対応しないということか。RX 向けの gcc(1) では特に RX200 シリーズ向けのオプションは無かったし...
ともあれ、これで GNU toolchain でビルドできる環境が整った。とはいえ、FDT で S-record のファイルを書き込む際には、結局 Windows PC が必要になるのだが。
# ブートローダがあれば...
GR-SAKURA で遊んでみる(2)
先日購入したGR-SAKURA の JTAG I/F の場所にピンヘッダを半田付けした。
これで、E1 エミュレータを用いたデバッグが可能になる。
開発に必要なツール群は、以下の URL より入手した。
【無償評価版】RXファミリ用C/C++コンパイラパッケージ V2 (統合開発環境なし) V2.02.00:
http://japan.renesas.com/support/downloads/download_results/C1000000-C9999999/tools/evaluation_software_cc_rx.jsp
Renesas E-Series USBドライバ V.1.02 Release 00
http://japan.renesas.com/support/downloads/download_results/C1000000-C9999999/tools/upgrades_usb_driver_for_e-series.jsp
フラッシュ開発ツールキット V.4.09 Release 02 アップデート
http://japan.renesas.com/support/downloads/download_results/C1000000-C9999999/tools/evaluation_software_cc_rx.jsp
(OS を搭載したPC 上で)セルフ開発環境の整備後に最初に書くであろうプログラムは
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { printf("Hello World!\n"); exit(EXIT_SUCCESS); }
というものであるが、OS も何も書き込まれていない白紙状態のボードで、新規にコードを書き起こして遊ぶ際には大抵 LED 点滅プログラムを出発点とする。
単に LED を点滅させるだけでも
- I/Oポート
- タイマ
- 割り込み
- PLL
の機能を試すことができ、後々作成するプログラムにも流用できる。
そこで、LED の点滅を行うプログラムを実際に書いてみた。
blink.src:
1 SCKCR .equ 000080020h 2 SCKCR2 .equ 000080024h 3 SCKCR3 .equ 000080026h 4 5 PLLCR_ADDR .equ 000080028h 6 PLLCR2_ADDR .equ 00008002ah 7 MOSCCR_ADDR .equ 000080032h 8 MOSCWTCR_ADDR .equ 0000800a2h 9 PLLWTCR_ADDR .equ 0000800a6h 10 11 IR12_ADDR .equ 00008701ch 12 IPR004_ADDR .equ 000087304h 13 IER03_ADDR .equ 000087203h 14 15 PRCR_ADDR .equ 0000803feh 16 MSTPCRA_ADDR .equ 000080010h 17 MSTPCRB_ADDR .equ 000080014h 18 MSTPCRC_ADDR .equ 000080018h 19 20 PORT0_PDR_ADDR .equ 00008c000h 21 PORT1_PDR_ADDR .equ 00008c001h 22 PORT2_PDR_ADDR .equ 00008c002h 23 PORT3_PDR_ADDR .equ 00008c003h 24 PORT4_PDR_ADDR .equ 00008c004h 25 PORT5_PDR_ADDR .equ 00008c005h 26 PORT6_PDR_ADDR .equ 00008c006h 27 PORT7_PDR_ADDR .equ 00008c007h 28 PORT8_PDR_ADDR .equ 00008c008h 29 PORT9_PDR_ADDR .equ 00008c009h 30 PORTA_PDR_ADDR .equ 00008c00ah 31 PORTB_PDR_ADDR .equ 00008c00bh 32 PORTC_PDR_ADDR .equ 00008c00ch 33 PORTD_PDR_ADDR .equ 00008c00dh 34 PORTE_PDR_ADDR .equ 00008c00eh 35 PORTF_PDR_ADDR .equ 00008c00fh 36 PORTG_PDR_ADDR .equ 00008c010h 37 PORTJ_PDR_ADDR .equ 00008c012h 38 PORTA_PODR_ADDR .equ 00008c02ah 39 40 CMSTR0_ADDR .equ 000088000h 41 CMT0_CMCR_ADDR .equ 000088002h 42 CMT0_CMCNT_ADDR .equ 000088004h 43 CMT0_CMCOR_ADDR .equ 000088006h 44 45 .section ResetPRG,code 46 .glb _start 47 .glb intvect 48 _start: 49 ; disable interrupts 50 clrpsw i 51 52 mvtc #000020000h, usp 53 mvtc #00001fc00h, isp 54 mvtc #intvect, intb 55 56 ; stop unused peripherals 57 bsr stop_modules 58 59 ; initialize non-existent I/O port 60 ; see following: 61 ; 62 ; RX63N Group, RX631 Group 63 ; User's Manual:Hardware 64 ; 21.5.1 Products fewer than 176 Pins 65 bsr init_nonexistent_port 66 67 ; enable CMT0 68 mov.l #PRCR_ADDR, r1 69 mov.w #0a503h,[r1] 70 mov.l #MSTPCRA_ADDR, r1 71 mov.l [r1], r2 72 bclr #15, r2 73 mov.l r2, [r1] 74 75 ; if the main clock stabilization waiting time(tMAINOSC) is 20 ms, 76 ; we should set MOSCWTCR.MSTS[4:0] to 14 77 ; (262,144 cycle > 20.00 ms = 240,000 cycles), 78 ; so we need to wait 79 ; tMAINOSC + (262,144 + 16384)/ fMAIN 80 ; = 20 + (262,144 + 16,384) / 12,000 = approx. 43.23 ms 81 mov.l #MOSCWTCR_ADDR, r1 82 mov.b #0eh, [r1] ; MOSCWTCR.MSTS[4:0]=14 83 84 ; run the main clock oscillator 85 mov.l #MOSCCR_ADDR, r1 86 bclr #0, [r1] ; MOSCCR.MOSTP=0 87 ; confirm that MOSCCR.MOSTP has actually been updated 88 ?: movu.b [r1], r2 89 tst r2, r2 90 bnz ?- 91 92 mov.l #44, r1 93 bsr mdelay 94 95 ; configure the PLL clock: 96 ; to generate the PLL clock from main clock by dividing by 1 and 97 ; multiplying by 8(fPLL = 12.0 * 8 = 96.0 MHz), 98 ; we should set PLLWTCR.PSTS to 9 99 ; (n = 65536 cycles = 682.7 us), 100 ; so we need to wait 101 ; tPLL1 + (n + 131,072) / fPLL 102 ; = 0.5 + (65,536 + 131,072)/ 96,000 = 2.548 ms 103 mov.l #PLLCR_ADDR, r1 104 mov.w #00700h, [r1] 105 mov.l #PLLWTCR_ADDR, r1 106 mov.b #9, [r1] 107 mov.l #PLLCR2_ADDR, r1 108 bclr #0, r1 ; PLLCR2.PLLEN=0 109 110 mov.l #3, r1 111 bsr mdelay 112 113 mov.l #SCKCR, r1 114 mov.l #022c23311h, [r1] ; ICLK -> 1/4, PCLK -> 1/8 115 mov.l #SCKCR2, r1 116 mov.w #00002h, [r1] 117 mov.l #SCKCR3, r1 118 mov.w #0400h, [r1] ; use PLL clock 119 mov.l #0000803feh, r1 120 mov.w #0a500h,[r1] ; write protection for clock control registers 121 122 ; turn off user LEDs 123 mov.l #PORTA_PODR_ADDR, r1 124 bclr #0, [r1] ; Set PA0 output level to L 125 bclr #1, [r1] ; Set PA1 output level to L 126 bclr #2, [r1] ; Set PA2 output level to L 127 bclr #6, [r1] ; Set PA6 output level to L 128 mov.l #PORTA_PDR_ADDR, r1 129 bset #0, [r1] ; Set PA0 direction to output 130 bset #1, [r1] ; Set PA1 direction to output 131 bset #2, [r1] ; Set PA2 direction to output 132 bset #6, [r1] ; Set PA6 direction to output 133 134 ; initialize section "B" 135 mov.l #(TOPOF B), r1 136 mov.l #0, r2 137 mov.l #(SIZEOF B), r3 138 sstr.b 139 140 mov.l #CMT0_CMCOR_ADDR, r1 141 mov.w #005dch, [r1] ; Set CMCOR 142 mov.l #CMT0_CMCR_ADDR, r1 143 mov.w #00040h, [r1] ; Set CMCR 144 mov.l #CMT0_CMCNT_ADDR, r1 145 mov.w #00000h, [r1] ; Clear CMCNT 146 147 ; enable interrupts 148 mov.l #IPR004_ADDR, r1 149 mov.b #001h, [r1] ; Set IPR004 150 mov.l #IER03_ADDR, r1 151 bset #4, [r1] ; Set IER03.IEN4 152 setpsw i ; Set I bit 153 154 mov.l #CMSTR0_ADDR, r1 155 mov.w #00001h, [r1] ; Start CMT0 156 157 bra $ 158 159 mdelay: 160 pushm r1-r3 161 162 mov.l #CMT0_CMCOR_ADDR, r2 163 mov.w #00010h, [r2] 164 mov.l #CMT0_CMCR_ADDR, r2 165 mov.w #00040h, [r2] 166 mov.l #CMT0_CMCNT_ADDR, r2 167 mov.w #00000h, 4[r2] 168 169 mov.l #IPR004_ADDR, r2 170 bset #0, [r2] ; Set IPR004 171 mov.l #IER03_ADDR, r2 172 bset #4, [r2] ; Set IER03.IEN4 173 174 tst r1, r1 175 L1: bz L2 176 177 mov.l #CMSTR0_ADDR, r2 178 mov.w #00001h, [r2] ; Start CMT0 179 180 ?: mov.l #IR12_ADDR, r2 181 movu.b [r2], r3 182 tst r3, r3 183 bz ?- 184 mov.b #000h, [r2] 185 186 mov.l #CMSTR0_ADDR, r2 187 mov.w #00000h, [r2] ; Stop CMT0 188 189 sub #1, r1 190 bra L1 191 192 L2: mov.l #IER03_ADDR, r2 193 bclr #4, [r2] ; Clear IER03.IEN4 194 mov.l #IPR004_ADDR, r2 195 bclr #0, [r2] ; Clear IPR004 196 197 popm r1-r3 198 rts 199 200 stop_modules: 201 ; disable write protection for low power consumption module 202 mov.l #PRCR_ADDR, r1 203 mov.w #0a502h,[r1] 204 205 ; stop unused peripherals(DMA and EXDMA) 206 mov.l #MSTPCRA_ADDR, r1 207 mov.l [r1], r2 208 bset #28, r2 ; DMA 209 bset #29, r2 ; EXDMA 210 mov.l r2, [r1] 211 212 mov.l #PRCR_ADDR, r1 213 mov.w #0a500h, [r1] 214 rts 215 216 init_nonexistent_port: 217 ; set non-existent I/O ports' direction to output 218 push.l r1 219 mov.l #PORT0_PDR_ADDR, r1 220 mov.b #00fh, [r1] ; P00-P03 221 mov.l #PORT1_PDR_ADDR, r1 222 mov.b #003h, [r1] ; P10-P11 223 mov.l #PORT2_PDR_ADDR, r1 224 mov.b #000h, [r1] ; none 225 mov.l #PORT3_PDR_ADDR, r1 226 mov.b #000h, [r1] ; none 227 mov.l #PORT4_PDR_ADDR, r1 228 mov.b #000h, [r1] ; none 229 mov.l #PORT5_PDR_ADDR, r1 230 mov.b #0c0h, [r1] ; P56-P57 231 mov.l #PORT6_PDR_ADDR, r1 232 mov.b #0ffh, [r1] ; P60-67 233 mov.l #PORT7_PDR_ADDR, r1 234 mov.b #0ffh, [r1] ; P70-77 235 mov.l #PORT8_PDR_ADDR, r1 236 mov.b #0ffh, [r1] ; P80-87 237 mov.l #PORT9_PDR_ADDR, r1 238 mov.b #0ffh, [r1] ; P90-97 239 mov.l #PORTA_PDR_ADDR, r1 240 mov.b #000h, [r1] ; none 241 mov.l #PORTB_PDR_ADDR, r1 242 mov.b #000h, [r1] ; none 243 mov.l #PORTC_PDR_ADDR, r1 244 mov.b #000h, [r1] ; none 245 mov.l #PORTD_PDR_ADDR, r1 246 mov.b #000h, [r1] ; none 247 mov.l #PORTE_PDR_ADDR, r1 248 mov.b #000h, [r1] ; none 249 mov.l #PORTF_PDR_ADDR, r1 250 mov.b #03fh, [r1] ; PF0-PF5 251 mov.l #PORTG_PDR_ADDR, r1 252 mov.b #0ffh, [r1] ; PG0-OG7 253 mov.l #PORTJ_PDR_ADDR, r1 254 mov.b #020h, [r1] ; PJ5 255 pop r1 256 rts 257 258 .section IntPRG,code 259 .glb unhandled_interrupt 260 .glb unhandled_exception 261 .glb cmt0_interrupt 262 263 unhandled_exception: 264 ; turn on LED1 265 mov.l #PORTA_PODR_ADDR, r1 266 bset #0, [r1] 267 mov.l #PORTA_PDR_ADDR, r1 268 bset #0, [r1] 269 bra $ 270 271 unhandled_interrupt: 272 ; turn on LED2 273 mov.l #PORTA_PODR_ADDR, r1 274 bset #1, [r1] 275 mov.l #PORTA_PDR_ADDR, r1 276 bset #1, [r1] 277 bra $ 278 279 cmt0_interrupt: 280 pushm r1-r2 281 mov.l #count, r1 282 mov.l [r1], r2 283 tst r2, r2 284 bnz out 285 mov.l #001f4h, r2 286 287 ; toggle output level 288 mov.l #PORTA_PODR_ADDR, r1 289 bnot #6, [r1] 290 291 mov.l #count, r1 292 out: sub #1, r2 293 mov.l r2, [r1] 294 295 popm r1-r2 296 rte 297 298 .section B,DATA,align=4 299 count: .blkl 1 300 301 .end
vect.src:
1 .section C$VECT,ROMDATA,align=4 2 .glb unhandled_interrupt 3 .glb cmt0_interrupt 4 .glb intvect 5 6 intvect: 7 .lword unhandled_interrupt 8 .lword unhandled_interrupt (中略) 34 .lword unhandled_interrupt 35 .lword cmt0_interrupt 36 .lword unhandled_interrupt 37 .lword unhandled_interrupt (中略) 262 .lword unhandled_interrupt 263 264 265 .section FIXEDVECT,ROMDATA,align=4 266 .glb fixedvect 267 .glb unhandled_exception 268 .glb _start 269 270 fixedvect: 271 .lword unhandled_exception 272 .lword unhandled_exception 273 .lword unhandled_exception 274 .lword unhandled_exception 275 .lword unhandled_exception 276 .lword unhandled_exception 277 .lword unhandled_exception 278 .lword unhandled_exception 279 .lword unhandled_exception 280 .lword unhandled_exception 281 .lword unhandled_exception 282 .lword _start 283 284 .end
blink.src がボードの初期化処理および LED の点滅処理を、vect.src が可変ベクタテーブルおよび固定ベクタテーブルの内容を記述したものである。
これらのプログラムを GR-SAKURA 向けにアセンブルする場合、asrx コマンドを -cpu=rx600 オプションを付けて呼び出せばよい*1。以下にアセンブルおよびリンクの具体例を示す。
> asrx -cpu=rx600 blink.src RX Family Assembler V2.01.00.04 [01 Aug 2013] Copyright (C) 2008, 2013 Renesas Electronics Corporation All rights reserved. ( blink.src ) macro processing now --- assembler processing now --- TOTAL ERROR(S) 00000 TOTAL WARNING(S) 00000 TOTAL LINE(S) 00301 LINES CODE 0000000513(00000201H) ResetPRG DATA 0000000004(00000004H) B CODE 0000000073(00000049H) IntPRG > asrx -cpu=rx600 vect.src RX Family Assembler V2.01.00.04 [01 Aug 2013] Copyright (C) 2008, 2013 Renesas Electronics Corporation All rights reserved. ( vect.src ) macro processing now -- assembler processing now -- TOTAL ERROR(S) 00000 TOTAL WARNING(S) 00000 TOTAL LINE(S) 00284 LINES ROMDATA 0000001024(00000400H) C$VECT ROMDATA 0000000048(00000030H) FIXEDVECT > rlink -S9 -form=stype -start=B/00000000,ResetPRG,IntPRG,C$VECT/fff80000,FIXEDVECT/ffffffd0 blink.obj vect.obj Renesas Optimizing Linker W1.01.00 [19 Jul 2013] Copyright (C) 2011, 2013 Renesas Electronics Corporation The evaluation period has expired. Renesas Optimizing Linker Completed
生成された blink.mot は、GR-SAKURA ボードを E1 エミュレータに接続後、フラッシュ開発ツールキット(FDT)を用いて書き込むことができる*2。
プログラムの書き込み後、E1 を取り外し、USB または DC ジャックから電源を供給すると LED が 0.5 秒間隔で点燈/消燈を繰り返す。
ついでに、make(1) にてビルド可能なものを以下の URL に置いた。
http://he4.seikyou.ne.jp/home/tsukimizake/gr-sakura-dev/blink.zip
プログラムの処理内容
初期化処理の内容は
- 割り込みを無効化
- スタックポインタの設定
- 可変ベクタテーブルの設定
- 使用しない周辺機能の無効化
- 存在しないポートの初期化
- クロックの初期化
- セクションの初期化*3
というものである。いずれも初期化処理としてはごく一般的なものである。
ただし注意が必要な箇所があり、それらについては次節にて後述する。
初期化処理の終了後はタイマを動作させて無限ループに入る。
154 mov.l #CMSTR0_ADDR, r1 155 mov.w #00001h, [r1] ; Start CMT0 156 157 bra $
LED の点燈/消燈処理は、コンペアマッチタイマ 0(CMT0)の割り込みハンドラにさせている。
280 cmt0_interrupt: 281 pushm r1-r2 282 mov.l #count, r1 283 mov.l [r1], r2 284 tst r2, r2 285 bnz out 286 mov.l #001f4h, r2 287 288 ; toggle output level 289 mov.l #PORTA_PODR_ADDR, r1 290 bnot #6, [r1] 291 292 mov.l #count, r1 293 out: sub #1, r2 294 mov.l r2, [r1] 295 296 popm r1-r2 297 rte
消費電力低減機能
最近の MCU は、消費電力を下げる目的で、リセット直後はほとんどの機能を停止させている(クロックを供給していない)ものが多い。RX もそのような MCU の一つで、リセット直後ではコンペアマッチタイマすら使用できない。
このため、モジュールストップコントロールレジスタ A(MSTPCRA)を事前に書き換え、クロックを供給する必要があるのだが、この MSTPCRA はプログラム暴走時に意図せぬ書き換えが起きないよう保護の対象となっている。MSTPCRA を書き換えるためには、さらにプロテクトレジスタの書き換えも必要である(つまり二重に鍵が掛けられている状態である)。
blink.src の 67 行目から 73 行目にかけてのコードはこれらの処理を実行し、コンペアマッチタイマ 0(CMT0)へのクロック供給を開始するものである。
67 ; enable CMT0 68 mov.l #PRCR_ADDR, r1 69 mov.w #0a503h,[r1] 70 mov.l #MSTPCRA_ADDR, r1 71 mov.l [r1], r2 72 bclr #15, r2 73 mov.l r2, [r1]
PLL クロックの使用
GR-SAKURA ボードでは、RX に 12MHz の水晶発振子が外付けされており、これをメインクロックとして用いることができる。今回のプログラムでは、12MHz のメインクロックを PLL により 1 分周・8 逓倍したものを生成し、それを 4 分周したものをシステムクロック(ICLK)として、8 分周したものを周辺クロック(PCLK)として使用した。したがって ICLK は 24 MHz、PCLK は 12 MHz となる。
(RX に限らず)使用するクロックを切り替える際には、切り替え先のクロックが安定するのをソフトウェアで待つ必要がある。RX の場合は、この時間をメイン発振器ウェイトコントロールレジスタ(MOSCWTCR)の [4:0] の bit で指定する。このビットに指定可能な値と、その値が何サイクルに相当するかの対応を以下に示す。
b4 | b3 | b2 | b1 | b0 | 機能 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 待機時間=2サイクル |
0 | 0 | 0 | 0 | 1 | 待機時間=4サイクル |
0 | 0 | 0 | 1 | 0 | 待機時間=8サイクル |
0 | 0 | 0 | 1 | 1 | 待機時間=16サイクル |
0 | 0 | 1 | 0 | 0 | 待機時間=32サイクル |
0 | 0 | 1 | 0 | 1 | 待機時間=64サイクル |
0 | 0 | 1 | 1 | 0 | 待機時間=512サイクル |
0 | 0 | 1 | 1 | 1 | 待機時間=1,024サイクル |
0 | 1 | 0 | 0 | 0 | 待機時間=2,048サイクル |
0 | 1 | 0 | 0 | 1 | 待機時間=4096サイクル |
0 | 1 | 0 | 1 | 0 | 待機時間=16,384サイクル |
0 | 1 | 0 | 1 | 1 | 待機時間=32,768サイクル |
0 | 1 | 1 | 0 | 0 | 待機時間=65,536サイクル |
0 | 1 | 1 | 0 | 1 | 待機時間=131,072サイクル |
0 | 1 | 1 | 1 | 0 | 待機時間=262,144サイクル |
0 | 1 | 1 | 1 | 1 | 待機時間=524,288サイクル |
ここで指定したサイクル数 + 16384 サイクル分だけメインクロックをカウントすると、MCU 内部へのメインクロック供給が開始される。
水晶発振子が安定するまでの時間()が 20.0 ms と仮定*4すると、周波数 12 MHz のメインクロックでは 240,000 サイクルに相当するので、上記の表より MOSCWTCR の [4:0] には 01110 を設定すればよい。 したがって、ソフトウェアで待機する時間は、
となる。
81 行目から 93 行目にかけてのコードは、これらの処理を行うものである。
81 mov.l #MOSCWTCR_ADDR, r1 82 mov.b #0eh, [r1] ; MOSCWTCR.MSTS[4:0]=14 83 84 ; run the main clock oscillator 85 mov.l #MOSCCR_ADDR, r1 86 bclr #0, [r1] ; MOSCCR.MOSTP=0 87 ; confirm that MOSCCR.MOSTP has actually been updated 88 ?: movu.b [r1], r2 89 tst r2, r2 90 bnz ?- 91 92 mov.l #44, r1 93 bsr mdelay
今回の場合は PLL を使用するので、加えて PLL の安定待ちも必要になる。PLL の発振安定時間は最大 0.5 ms なので、PLL ウェイトコントロールレジスタ(PLLWTCR)には 9 (約 0.6827 ms 相当)を設定する。したがって、ソフトウェアで待機する時間は、
となる(実際には約 3 ms 待機している)。
105 mov.l #PLLWTCR_ADDR, r1 106 mov.b #9, [r1] 107 mov.l #PLLCR2_ADDR, r1 108 bclr #0, r1 ; PLLCR2.PLLEN=0 109 110 mov.l #3, r1 111 bsr mdelay
この辺りの詳しい内容は、以下のアプリケーションノートに記載されている。
RX63Nグループ、RX631グループ 初期設定例:
http://documentation.renesas.com/doc/products/mpumcu/apn/rx/r01an1245jj0110_rx63n.pdf
GR-SAKURA で遊んでみる(1)
以前オープンソースカンファレンス 2012 Kansai@Kyoto に立ち寄った際、GR-SAKURA ボードの紹介をみて興味を持ったのだが、結局何もしないまま二年が経過してしまった。
ブースでお話を伺った方からは「このボードでどんなことをしてみたいですか?」と尋ねられ、「とりあえず何かしら遊んでみたい」と答えたのに...
しかし、先日日本橋の共立電子に立ち寄ったところ、GR-SAKURA が販売されているのを見掛けた。
二種類の GR-SAKURA
SDカードソケット、LANコネクタ、DCジャック、ピンソケットが実装済の GR-SAKURA-FULL と、未実装の GR-SAKURA の二種類が販売されていた。
前者は 5,081 円、後者は 3,785 円であるが、半田付けが苦手なので前者を購入した。
欲を言えば、JTAG I/F のピンヘッダも半田付けしてくれていれば最高だった。
家に帰ってから、早速開封してみた。
ボードの他に、MCU の各端子がピンソケットのどの箇所に対応するかを図示したカードと、USB ホストコネクタが同梱されていた。
GR-SAKURA は MCU に Renesas の RX63N を搭載したボードである。
※より具体的な MCU の型名は「R5F563NBDDFP」(ROM:1 MiB/RAM:128 KiB/LQFP)
遊んでみる前に、この MCU のソフトウェアマニュアルとハードウェアマニュアルを入手し、少し勉強してみた。
以下に RX の特徴を、備忘録として残しておく。
Renesas RX の特徴
RX は、Renesas の既存の MCU である H8S、H8SX、M16C、R32C の後継機種という位置付けにあたる。H8S/H8SX からは
という長所を、M16C/R32C からは
- 割り込み/ユーザの 2 スタックポインタ
- FPU*1
という長所を「いいとこどり」している。
プログラマの視点からは、 RX は CISC の特徴を備えた MCU に見える。すなわち、
- 可変長命令
- 命令の種類が豊富
- 豊富なアドレッシングモードと直交性の高い命令セット
により、高いコード密度を期待できる。
しかし MCU 内部では、CISC よりはむしろ RISC に近い動作をするようだ。
幾つかの命令は、内部で複数の mop(micro-operation)に置き換えられ、mop 単位でパイプライン処理が行われる。
例えば加算を行う add 命令の場合、レジスタ直接アドレッシングだと
+----+---+---+----+ add r1, r2 | IF | D | E | WB | (mop) add +----+---+---+----+
のように、レジスタ間の加算を行う単一の mop に変換される。
一方、レジスタ間接アドレッシングを使用した add 命令の場合、
+----+---+---+-------+ add [r1], r2 | IF | D | E | M1 | (mop) load +----+---+---+-------+---+----+ | D | stall | E | M1 | (mop) add +---+-------+---+----+
のように、メモリからのロードを行う mop と、レジスタ間の加算を行う mop に分解される。
なお、割り込みは(mop 単位ではなく)命令の区切りで受け付ける。割り込み処理から復帰すると、次の命令から開始する。
但し、積和演算命令(rmpa)およびストリング操作命令群(scmpu/smovb/smovf/smovu/sstr/suntil/swhile)のみ例外で、命令の途中であっても割り込みを受け付け、割り込み処理終了後に命令の続きを再開する。