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 .endblink.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)のみ例外で、命令の途中であっても割り込みを受け付け、割り込み処理終了後に命令の続きを再開する。





