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

*1:但し、現状では XBee での動作確認はできていない。不慮の事故により、確認前に手持ちの XBee を壊してしまった(--;

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

まず(/CRC や -1k でない方の)XMODEM プロトコルについて説明する。

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 プロトコルに、以下の変更を施したものである。

  • 送信側 -> 受信側へのパケットのフォーマットの変更
    ファイル内容を 1024 bytes ずつ転送するのを目的としているので、data のフィールド長は 128 bytes -> 1024 bytes となる。またパケットの最初の 1 byte は SOH の代わりに STX(0x02)である。これにより受信側は、パケットを XMODEM-1k 用のものと認識する。更に XMODEM/CRC と同様に、1 byte の checksum を廃し、代わりに 2 bytes の CRC-16 を付加する。

実装例

xmodem.c:

      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 行前後で書けるので、実装もテストもかなり楽である。

*1:ちなみに Ward Christensen 氏は電子掲示板の創始者の一人でもある

*2:正確には、ファイル受信側に於いては 128 bytes 単位で切り上げたサイズしか分からない

*3:当時 Ward Christensen 氏が使っていた OS の CP/M に於いては、ファイルは 128 bytes 単位でなければならず、ファイルの終端を認識させるために Ctrl-Z を入れておく必要があった、という事情に因る

*4:具体的に何回なのか、仕様には明確な記述が見当たなかった

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 でお馴染みの形式であるが、

  1. FCU 制御用コマンドが CFI(Common Flash Memory Interface) で定義されているコマンドとは異なる。
  2. FCU 制御用のレジスタがあり、コマンド発行とは別に、これらのレジスタの設定も必要になる。
  3. Flash の内容を読み出すためのアドレスと、コマンド発行先のアドレス(ハードウェアマニュアルでは「P/E 用アドレス」と呼んでいる)が異なる。
  4. FCU(ROM/E2 データフラッシュの書き換えを行う専用のシーケンサ)の状態遷移に従う必要がある。
  5. 事前に 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 ノーマルモードに遷移後、以下の手順でコマンドを発行することにより行う。

  1. プログラム対象アドレスに 0xE8 を書き込む。
  2. プログラム対象アドレスに 0x40 を書き込む。
  3. プログラム対象アドレスに書き込みデータを順に 64 回 2bytes 単位で書き込む。
  4. プログラム/イレーズ用アドレスに 0xD0 を書き込む。
  5. 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
  6. エラー確認

上記の「プログラム対象アドレス」は、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 ノーマルモードに遷移後、以下の手順でコマンドを発行することにより行う。

  1. イレーズ対象アドレスに 0x20 を書き込む。
  2. イレーズ対象アドレスに 0xD0 を書き込む。
  3. 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
  4. エラー確認

上記のイレーズ対象アドレスは、128 bytes 境界にアラインしたものである必要はない。

ドライバの実装

内蔵 Flash ROM ドライバの実装例を以下に示す。

flash.c:

    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 回まで保証されている)。

*1:配布されている回路図では型名が R5F563NEDDFP になっているが、これは誤記と思われる

*2:正確には、ハードウェアマニュアルの「電気的特性」の章で最低 1,000 回は書き換えられる旨が記述されている

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

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

一般的な方法としては、

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

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

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

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

2 の方法では

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

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

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

この方法では、構成上

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

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

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

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

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

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

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

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

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

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

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

シリアルドライバの実装

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

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

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

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

初期化処理の実装

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

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

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

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

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

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

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

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

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

送信処理の実装

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

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

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

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

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

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

TXIの割り込みハンドラ:

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

TEIの割り込みハンドラ:

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

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

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

受信処理の実装

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

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

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

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

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

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

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

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

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

Hello World

main 関数は

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

で実装した。

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

  $ cu -s 9600 -l /dev/ttyUSB0

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

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

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

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

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

GR-SAKURA で遊んでみる(3) GNU toolchain による開発環境の構築

前回 LED の点滅プログラムを書いたものの、Renesas 製のツールチェインにてビルドすることを前提としたもので、Windows PC でないとビルドできない。

普段は NetBSDLinux を使用しているので、ちょっとしたことを 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 を生成する。

この点は他アーキテクチャ向けの binutils と異なるので、リンカスクリプトを自前で作成する際は注意が必要である。

GCC のインストール

GCCbinutils に加えて、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 では解析に失敗する)。LinuxMinGW 環境下では通常 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 が必要になるのだが。
# ブートローダがあれば...

*1:MinGWCygwin 上ではファイル I/O が遅く、ビルドにかなりの時間を要する。これらの環境下では、(就寝前など)数時間放置可能な状況下でビルドされることを奨める

*2:もちろん明示的に ".section .text" と記述すれば、セクション名を .text にできる。しかし、C コンパイラが .text/.data/.rodata/.bss ではなく P/D_1/C/B_1 セクションに割り付ける以上、これは避けた方が良さそうだ。

*3:libgcc の configure 中にエラーになっている模様

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

プログラムの処理内容

初期化処理の内容は

  1. 割り込みを無効化
  2. スタックポインタの設定
  3. 可変ベクタテーブルの設定
  4. 使用しない周辺機能の無効化
  5. 存在しないポートの初期化
  6. クロックの初期化
  7. セクションの初期化*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 内部へのメインクロック供給が開始される。

水晶発振子が安定するまでの時間({t_{MAINOSC})が 20.0 ms と仮定*4すると、周波数 12 MHz のメインクロックでは 240,000 サイクルに相当するので、上記の表より MOSCWTCR の [4:0] には 01110 を設定すればよい。 したがって、ソフトウェアで待機する時間は、
{t_{MAINOSC} (262144 + 16384)/ f_{MAIN} = 20.0 + (262144 + 16384) / 12000 = approx. 43.23 ms}
となる。

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 相当)を設定する。したがって、ソフトウェアで待機する時間は、
{t_{PLL1} + (65,536 + 131,072)/ f_{MAIN} = 0.5 + (65,536 + 131,072) / 96000 = approx. 2.548 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

*1:当然だが、asrx や rlink がインストールされているディレクトリに PATH を通しておく必要がある。

*2:実は書き込みだけであれば、E1 は不要らしい

*3:このプログラムでは、初期化が必要なのは B セクションのみ

*4:配線パターン、発振定数により、安定するまでの時間は異なる。GR-SAKURA ボードに関する資料を漁ってみたが、この値は記載されていなかった

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

何に使うのかよくわからない命令群

命令セットに目を通してみて、ストリング操作命令群(scmpu/smovb/smovf/smovu/sstr/suntil/swhile)が何のために存在するのかが、よくわからなかった。

例えば memcpy(3) を

          ; void *memcpy(void *dst, const void *src, size_t len);
  _memcpy:
          pushm   r1-r3
          smovf
          popm    r1-r3
          rts

の僅か 4 行で実装できる*2 *3、という程度の事柄は理解できるが、それ以外の用途は…?

後は、実際に動かしながら勉強していくことにしよう。

*1:RX100 シリーズと RX200 シリーズは対応していない

*2:ここでは ABI を、第一引数〜第三引数をそれぞれ R1 〜 R3 に割り当てるものと仮定している。なお、R2 や R3 をスクラッチレジスタとして扱う ABI の場合は、これらのレジスタの退避は不要である。

*3:memmove(3)の場合は、コピー元とコピー先の重なり具合に応じて、smovf と smovb を使い分ける必要がでてくる