Serial EEPROM を読み書きする
(2004年 2月 8日 (C) 岡田 紀雄)
ロボットを動かす際、モーションデータを容易に作成し、また、取り替えて動かしたい、という気持ちになることが良くあります。 そこで、森永さんや、宮田さんの使われている ATMEL社製の Serial-EEPROM (IIC, I2Cではない)のAT24C1024をSH2/7047Fにつなげて読み書きしてみることにしました。 AT24C1024のマニュアル以外にはあまり資料がなかったので、試行錯誤して作ったので、どこかに誤りがあるかもしれません。 見つけられた方、メール等でお知らせ頂けると、幸いです。
実装編
データ交換がしやすいように、というのが最初のコンセプトですので、ICそのものを抜き差しするよりも、ちょっとしたカートリッジにして置くほうがよいかと思い、下の写真のようにしてみました。 カートリッジには、8pinDIP用ソケット、信号3ピン+電源・GND端子(5ピン)、プルアップ用 2kΩ抵抗2本という構成です。
回路編
至って単純な回路ですので、簡単にピン接続方式だけを箇条書きで列挙しておきます。
SH2/7047F プログラム
GDL(Gcc Developer Lite) で使う関係上、プログラム部分は eeprom.h としてヘッダに収める形にしました。 今回、一挙このソース公開! 基本的には、ATMEL AT24C1024 マニュアル中の p.7, 8 のタイミング操作部と、p11, 12 にある制御コード生成&検査部に分かれる。 まずは、IO周りの制御用定数設定関連です。
101: /* ************************************************************************ 102: 定数・I/O 定義 103: ************************************************************************ */ 104: #define EEPROM_WP (PA.DRL.BIT.B11) // WP (O) / Write Protect 105: #define EEPROM_SCL (PA.DRL.BIT.B13) // SCL (O) / Serial CLock 106: #define EEPROM_SDA (PA.DRL.BIT.B15) // SDA (I/O)/ Serial DAta 107: #define EEPROM_MAX_PAGE (512) 108: #define EEPROM_PAGE_LEN (256) 109: #define _EEPROM_A1 (0) // 0 = Hardwired to GND 110: /* ************************************************************************ 111: マクロ定義 112: ************************************************************************ */ 113: #define _EEPROM_ENABLE_OUTPUT_SDA() { PFC.PAIORL.BIT.B15 = 1; } 114: #define _EEPROM_ENABLE_INPUT_SDA() { PFC.PAIORL.BIT.B15 = 0; } 115: #define _EEPROM_WP_OFF() { EEPROM_WP = 0; } // 0 Write Free 116: #define _EEPROM_WP_ON() { EEPROM_WP = 1; } // 1 Write Protect 117: #define _EEPROM_WAIT() _EEPROM_wait(1) // 1 = 4 u-sec. wait 118: /* ************************************************************************ 119: ローカルサブルーチン部 120: ************************************************************************ */ 121: // ウェイト 122: #pragma volatile _EEPROM_wait 123: void _EEPROM_wait( 124: int time 125: ) 126: { 127: volatile int i; 128: 129: for( i = 0; i < time; i++ ) ; 130: } |
SH2/7047F 内臓ROM にプログラムを書き込んで動作させる場合、ウェイト部分の時間を調整しないとタイミングズレが生じるかもしれないので、注意してほしい。 今回 BestTechnology製のCPUボードに搭載されたSRAM上にプログラムを書き込んでテストしたので上記空ループをいれることにした。 今から思えば、もしかすると SRAM上のプログラムを読み込み動作させる時間があるので、ウェイトルーチンは不要だったのかもしれない。。。まぁ、ROM化したときに必要になるかと思いますので、今回はそのままとします。
ちょっと解説を入れておきます。 接続ピンは先の説明どおりですが、WP, SCL
へ接続するピンについてはアウトプットのみとなりますが、SDA は入出力となります。 そこで、113,
114行にピンの信号方向を切り替えるマクロを作っておいた。 また、WP(Write
Protect) は 1 (High)でプロテクト有効、 0(Low) で書き込み許可となるので、それもマクロとして定義しておきました。 さらに、109行目の定数は、AT24C1024
の pin2(A1) をハード的に GND 結線しているので、0を指定。 もし、2個のAT24C1024を接続したい場合は、1つを
GND, 1つを Vcc に接続し、ソフト上で 0,1 を切り替えることで2つの制御が可能になります。
続いて、1ビット読み書き、また、その応用で1バイト読み書きルーチンです。
201: // 202: // 注)SDA の読み書きタイミングについて (Data Validity in p.8) 203: // 出力時: SCL が High時は SDA が安定している 204: // △ SCL の High 時間 t_HIGH は 0.4μsec.以上必要 (@4.5V <= Vcc <= 5.5V in p.5) 205: // 入力時: SCL が High時だけ SDA が安定保障されているので、その間にデータを読み取る 206: // 207: // シリアルクロック出力 & 1ビットデータ出力 208: static void _EEPROM_putbit( 209: unsigned char data 210: ) 211: { 212: _EEPROM_ENABLE_OUTPUT_SDA(); // I/O ポートの出力指定 213: 214: EEPROM_SCL = 0; // SCL LOW 指定 215: _EEPROM_WAIT(); // 一定時間ウェイト 216: EEPROM_SDA = data; // SDA に1ビットデータ出力 217: EEPROM_SCL = 1; // SCL HIGH 指定 218: _EEPROM_WAIT(); // 一定時間ウェイト 219: EEPROM_SCL = 0; // SCL LOW 指定 220: EEPROM_SDA = 0; // SDA を LOW にして終了 221: } 222: // シリアルクロック出力 & 1ビットデータ受信 223: static unsigned char _EEPROM_getbit( void ) 224: { 225: unsigned char data; 226: 227: _EEPROM_ENABLE_INPUT_SDA(); // I/O ポートの入力指定 228: EEPROM_SCL = 1; // SCL HIGH 指定 229: data = EEPROM_SDA; // SDA から1ビットデータの受信 230: EEPROM_SCL = 0; // SCL LOW 指定 231: return data; 232: } 233: // シリアル1バイトデータ出力 234: static void _EEPROM_putchar( 235: unsigned char data 236: ) 237: { 238: _EEPROM_putbit( ( data & 0x80 ) != 0 ); 239: _EEPROM_putbit( ( data & 0x40 ) != 0 ); 240: _EEPROM_putbit( ( data & 0x20 ) != 0 ); 241: _EEPROM_putbit( ( data & 0x10 ) != 0 ); 242: _EEPROM_putbit( ( data & 0x08 ) != 0 ); 243: _EEPROM_putbit( ( data & 0x04 ) != 0 ); 244: _EEPROM_putbit( ( data & 0x02 ) != 0 ); 245: _EEPROM_putbit( ( data & 0x01 ) != 0 ); 246: } 247: // シリアル1バイトデータ受信 248: static unsigned char _EEPROM_getchar( void ) 249: { 250: unsigned char data; 251: 252: data = _EEPROM_getbit(); 253: data <<= 1; 254: data |= _EEPROM_getbit(); 255: data <<= 1; 256: data |= _EEPROM_getbit(); 257: data <<= 1; 258: data |= _EEPROM_getbit(); 259: data <<= 1; 260: data |= _EEPROM_getbit(); 261: data <<= 1; 262: data |= _EEPROM_getbit(); 263: data <<= 1; 264: data |= _EEPROM_getbit(); 265: data <<= 1; 266: data |= _EEPROM_getbit(); 267: return data; 268: } |
1ビット出力ルーチンとして、_EEPROM_putbit() (208-221行目) を示します。 ポイントは、SCLが LowからHigh になるときに SDAに指定された 0 or 1 がデータとなりますので、@ SDA に出力するためのポート指定、A SCLを Low 指定、B SDA にデータ出力(0 or 1)、C SCL を High に移行という流れになってます。
同様に1ビット入力ルーチンとして、_EEPROM_getbit() (223-232行目)を示します。 ポイントは、SCLが High から Low になるときに SDAの状態を読み取ることですので、@ SDA に入力するためのポート指定、A SCL を High 指定、B SDA の値を読む、C SCL を Low 指定。
おまけですが(アルゴリズム的に最適かどうか自信はありませんが)、1バイト(8ビット)のデータ入出力ルーチンを _EEPROM_putchar(), _EEPROM_getchar() として234-236行目と248-268行目に記載しておきます。 ご参考まで。
301: // スタートビット 302: static void _EEPROM_start( void ) 303: { 304: _EEPROM_ENABLE_OUTPUT_SDA(); 305: EEPROM_SDA = 1; // SDA DATA=High 306: EEPROM_SCL = 1; // SCL High 指定 307: _EEPROM_WAIT(); // 一定時間ウェイト 308: EEPROM_SDA = 0; // SDA DATA=Low 309: EEPROM_SCL = 0; // SCL Low 指定 310: } 311: // ストップビット 312: static void _EEPROM_stop( void ) 313: { 314: _EEPROM_ENABLE_OUTPUT_SDA(); 315: EEPROM_SCL = 0; // SCL Low指定 316: EEPROM_SDA = 0; // SDA DATA=Low 317: _EEPROM_WAIT(); // 一定時間ウェイト 318: EEPROM_SCL = 1; // SCL High指定 319: EEPROM_SDA = 1; // SDA DATA=High 320: _EEPROM_ENABLE_INPUT_SDA(); // 保険 321: } 322: // I/O ポートの初期化 323: void EEPROM_init( void ) 324: { 325: int i; 326: 327: PFC.PACRL3.BIT.PA11MD = 0; // PA11 通常I/Oポートとして指定 (WP接続) 328: PFC.PACRL1.BIT.PA11MD = 0; 329: PFC.PAIORL.BIT.B11 = 1; // Output 330: PFC.PACRL3.BIT.PA13MD = 0; // PA13 通常I/Oポートとして指定 (SCL接続) 331: PFC.PACRL1.BIT.PA13MD = 0; 332: PFC.PAIORL.BIT.B13 = 1; // Output 333: PFC.PACRL3.BIT.PA15MD = 0; // PA15 通常I/Oポートとして指定 (SDA接続) 334: PFC.PACRL1.BIT.PA15MD = 0; 335: PFC.PAIORL.BIT.B15 = 0; // Input 336: _EEPROM_WP_ON(); // Write Protect 337: } |
ページ単位の読み書きの際、信号のstart, stop を処理するルーチンが、_EEPROM_start()
(302-310行目)と_EEPROM_stop() (312-321行目)となります。
また、Serial-EEPROM を使用する際、初めに SH2/7047F のポート初期化が必要になりますので、その処理をまとめたのが
EEPROM_init() (323-337行目) となります。 EEPROM_init() は main()から呼び出すようにしておけば良いでしょう。
次に連続データ読み込みルーチンを示します。 引数としては、読み込みページ(9bit指定)、読み込みバッファポインタ、読み込みデータ長(最大256バイト)です。
401: // 連続データ読み込み 402: // 帰り値 読み込みバイト数(エラー時:256以上) 403: unsigned int EEPROM_read( 404: unsigned int page, // 読み込みページ(512ページ) 405: unsigned char *buf, // 読み込み先 406: unsigned int len // バッファ長(最大256バイト) 407: ) 408: { 409: int i; 410: unsigned char p0; 411: 412: _EEPROM_WP_ON(); // Write Protect 413: _EEPROM_start(); 414: p0 = ( page >> 8 ) & 0x01; 415: // 読み込み指定ページ番号の指定処理 416: _EEPROM_putbit( 1 ); 417: _EEPROM_putbit( 0 ); 418: _EEPROM_putbit( 1 ); 419: _EEPROM_putbit( 0 ); 420: _EEPROM_putbit( 0 ); 421: _EEPROM_putbit( _EEPROM_A1 ); // 17bit (A1) 422: _EEPROM_putbit( p0 ); // 16bit (P0) 423: _EEPROM_putbit( 0 ); // 1:Read / 0:Write 424: if( _EEPROM_getbit() == 1 ) { 425: _EEPROM_stop(); return 1000; 426: } 427: _EEPROM_putchar( page & 0x00FF ); 428: if( _EEPROM_getbit() == 1 ) { 429: _EEPROM_stop(); return 1001; 430: } 431: _EEPROM_putchar( 0 ); 432: if( _EEPROM_getbit() == 1 ) { 433: _EEPROM_stop(); return 1002; 434: } 435: // 指定ページの読み込み処理 436: _EEPROM_start(); 437: _EEPROM_putbit( 1 ); 438: _EEPROM_putbit( 0 ); 439: _EEPROM_putbit( 1 ); 440: _EEPROM_putbit( 0 ); 441: _EEPROM_putbit( 0 ); 442: _EEPROM_putbit( _EEPROM_A1 ); // 17bit (A1) 443: _EEPROM_putbit( p0 ); // 16bit (P0) 444: _EEPROM_putbit( 1 ); // 1:Read / 0:Write 445: if( _EEPROM_getbit() == 1 ) { 446: _EEPROM_stop(); return 1003; 447: } 448: // 連続したデータ読み込み処理 449: if( len > EEPROM_PAGE_LEN ) { 450: len = EEPROM_PAGE_LEN; 451: } 452: for( i = 0; i < len; i++ ) { 453: buf[ i ] = _EEPROM_getchar(); 454: if( i + 1 == len ) _EEPROM_putbit( 1 ); 455: else _EEPROM_putbit( 0 ); 456: } 457: _EEPROM_stop(); 458: return i; 459: } |
ページ単位での読み込みにしてますので、下位アドレスは 0 で、上位アドレスが指定ページ番号になります。 それを実現しているのが、421行目と427行目、431行目となります。 大まかなところでは、アドレス書き込み部分(415-434行目)、指定ページ読み込み部分(435-456行目)という流れです。
続いて、ページ単位での書き込みルーチンです。 引数としては、書き込みページ(9bit指定)、書き込みデータポインタ、書き込みサイズ(256バイト以下)です。 書き込みサイズが256バイト以下の場合は、残りのバイト数は0で埋めるようにしています。 不要な場合は545-550行目を削除してください。
501: // 連続データ書き込み 502: // 帰り値 書き込みバイト数(エラー時:256以上) 503: int EEPROM_write( 504: int page, // 書き込みページ(512ページ) 505: unsigned char *buf, // 書き込みデータ 506: unsigned int len // 書き込みサイズ(最大256バイト) 507: ) 508: { 509: int i; 510: unsigned char p0; 511: 512: _EEPROM_WP_OFF(); // Write Free 513: _EEPROM_start(); 514: p0 = ( page >> 8 ) & 0x01; 515: // 書き込み指定ページ番号の指定処理 516: _EEPROM_putbit( 1 ); 517: _EEPROM_putbit( 0 ); 518: _EEPROM_putbit( 1 ); 519: _EEPROM_putbit( 0 ); 520: _EEPROM_putbit( 0 ); 521: _EEPROM_putbit( _EEPROM_A1 ); // 17bit (A1) 522: _EEPROM_putbit( p0 ); // 16bit (P0) 523: _EEPROM_putbit( 0 ); // 1:Read / 0:Write 524: if( _EEPROM_getbit() == 1 ) { 525: _EEPROM_stop(); return 2000; 526: } 527: _EEPROM_putchar( page & 0x00FF ); 528: if( _EEPROM_getbit() == 1 ) { 529: _EEPROM_stop(); return 2001; 530: } 531: _EEPROM_putchar( 0 ); 532: if( _EEPROM_getbit() == 1 ) { 533: _EEPROM_stop(); return 2002; 534: } 535: // 指定ページへの連続データ読み込み処理 536: if( len > EEPROM_PAGE_LEN ) { 537: len = EEPROM_PAGE_LEN; 538: } 539: for( i = 0; i < len; i++ ) { 540: _EEPROM_putchar( buf[ i ] ); 541: if( _EEPROM_getbit() == 1 ) { 542: _EEPROM_stop(); return 2003; 543: } 544: } 545: for( i = len; i < EEPROM_PAGE_LEN; i++ ) { // ページ単位書き込みの補充(不要なら削除) 546: _EEPROM_putchar( 0 ); // 不足分は 0 で埋める 547: if( _EEPROM_getbit() == 1 ) { 548: _EEPROM_stop(); return 2004; 549: } 550: } 551: _EEPROM_stop(); 552: _EEPROM_WP_ON(); // Write Protect 553: return i; 554: } |
読み込みと同じく、ページ単位での書き込み処理にしてますので、下位アドレスは 0 で、上位アドレスが指定ページ番号になります。 それを実現しているのが、522行目と527行目、531行目となります。 大まかなところでは、アドレス書き込み部分(515-534行目)、指定ページへの書き込み部分(539-544(550)行目)という流れです。
最後になりましたが、このページ単位の読み書きを連続して行う場合、書き込みを行った後、単純ループにして 5万回程度のループ処理を入れ時間を置くようにしないと、次のアドレス指定をした際、エラーコードが帰ってきてしまいますので、注意してください。 私はここでハマリました。 他のルーチンがおかしいのかと何度もデバッグしたり、テスターで各ピンの ON/OFF をチェックしたり結構時間を食ってしまいました。 動作不安定な場合は、WAIT を入れる、これがデバッグのポイントということですね。
これを利用して、いろいろな展開がされることを期待しております。