このところSeeed Studio社製のGD32VF103VBT6ボードをSipeed社Longan nanoと見立てて動かしてきています。まったく問題なし。しかし、Seeed社製のボードにはLongan nanoには搭載されていないデバイスも搭載されとります。その一つがAT24C02、EEPROMです。今回は、このシリアルEEPROMの読み書きをやってみました。
※「鳥なき里のマイコン屋」投稿順Indexはこちら
まずはさて、Atmel AT24C02 です。AT24シリーズはI2C接続のシリアルEEPROMです。EEPROMはFlash同様、電気的に消去、書き換えできるROM(電気的に消去できないEPROMというものもあったのです、今は昔。)の一種ですが、Flashに比べると「容量がかなり小さめ」な一族です。しかし、書き換え回数の制限がきつかったり、記憶を保持できる保証期間が短かったり、大きなブロック単位でしか書き換えできなかったりするFlashと比べると、書き換え可能な回数は多く、記憶の保持時間が長く、そしてバイト単位で書き換えできたりと良い性質も多くもっています。そのため、IDなどの保持は勿論、電源切る度に記憶しておきたい設定値とかを頻繁に書き換える用途など、家電や組み込み用途でも使用されます。このAT24シリーズの場合、
-
- 100万回書き換え
- 100年保持
です。AT24C02の02は2Kビット品を示します。1ワード8ビット構成なので、ソフトウエアからみると256バイトの不揮発メモリです。
ご存知のようにATMEGAマイコンで有名なAtmel社は、PICマイコンで有名なMicrochip社に買収されてしまったので、その最新のデータシートは、Microchip社の以下のところからダウンロードできます。
Microchip、AT24C02C 2Kb I2C compatible 2-wire Serial EEPROM
GD32VF103VBT6ボード上に搭載されているAT24C02の写真をアイキャッチ画像に貼り付けました。そのマーキングからは、ATMLの文字は読み取れるものの、AT24C02とは読めません。1行目はグレードとか製造期日で、2行目の02Cが、AT24C02Cのことを示し、Mは動作最低電圧が1.7Vであることを示すようです。この辺は最新版のデータシートのマーキング情報を参照しないとなりません。
このAT24C02Cは、GD32VF103VBT6ボード上、I2C0に接続されています。I2C0を使うための設定(実際にはあちこちに分散していましたが)をかき集めると以下のようです。
なお、I2C_ADDFORMAT_7BITSの7に引きずられて、AT24C02のアドレスをAT24C02_ADDRESS7という定数名にしていますが、これは8ビットアドレスというべきかもしれません。R/Wを示すLSB分下駄を履いている値です。なお、ボード上、AT24C02のA0,A1,A2端子は全てグランドに落ちているのでデバイス・アドレスの下3ビットは000です。
#define AT24C02_ADDRESS7 (0xA0) // Enable Peripheral clock rcu_periph_clock_enable(RCU_I2C0); // PB6 ... I2C0 SCL // PB7 ... I2C0 SDA gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7); //I2C0 // I2C configuration i2c_clock_config(I2C0, 100000, I2C_DTCY_2); i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0); i2c_enable(I2C0); i2c_ack_config(I2C0, I2C_ACK_ENABLE);
続いて、書き込み用の関数です。AT24C02の場合、バイト単位およびページ単位(ページといっても8バイト)での読み書きが可能なのですが、折角なので(?)バイト書き込み用です。これは、Longan nanoにAQM1602を取り付けてみたときのコードとほぼ同じ(もっと言えば、SDKのExampleほぼそのまま。)デバイスアドレス送って、メモリのバイトアドレスを送って、最後にデータを1バイト送る。
void write1ByteAT24C02(uint8_t adr, uint8_t dat) { while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)); i2c_start_on_bus(I2C0); while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)); i2c_master_addressing(I2C0, AT24C02_ADDRESS7, I2C_TRANSMITTER); while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)); i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); while(!i2c_flag_get(I2C0, I2C_FLAG_TBE)); i2c_data_transmit(I2C0, adr); while(!i2c_flag_get(I2C0, I2C_FLAG_TBE)); i2c_data_transmit(I2C0, dat); while(!i2c_flag_get(I2C0, I2C_FLAG_TBE)); i2c_stop_on_bus(I2C0); while(I2C_CTL0(I2C0)&0x0200); }
それに対してバイトリード(ランダムリード)はちょっとトリッキーです。最初に、書き込みするかの如く、デバイスアドレス送って、メモリのバイトアドレスを送って、と「ダミーライト」を掛けます。ただしデータを送らずに処理をぶった切って改めてスタートビットを立てて、デバイスにリードをかけるのです。デバイスアドレス送って、データをリードすると。すると、前回ライトのメモリアドレスが残っていて、そこの番地のデータが読み出されるという仕組み。
void read1ByteAT24C02(uint8_t adr, uint8_t *dat) { //DUMMY WRITE(SET memory address) while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)); i2c_start_on_bus(I2C0); while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)); i2c_master_addressing(I2C0, AT24C02_ADDRESS7, I2C_TRANSMITTER); while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)); i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); while(!i2c_flag_get(I2C0, I2C_FLAG_TBE)); i2c_data_transmit(I2C0, adr); while(!i2c_flag_get(I2C0, I2C_FLAG_TBE)); //READ 1 Byte i2c_start_on_bus(I2C0); while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)); i2c_master_addressing(I2C0, AT24C02_ADDRESS7, I2C_RECEIVER); while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)); i2c_ack_config(I2C0, I2C_ACK_DISABLE); i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); i2c_stop_on_bus(I2C0); while(!i2c_flag_get(I2C0, I2C_FLAG_RBNE)); /* read a data from I2C_DATA */ *dat = i2c_data_receive(I2C0); while(I2C_CTL0(I2C0)&0x0200); i2c_ack_config(I2C0, I2C_ACK_ENABLE); }
これで所望のアドレスからバイトリードができました。
実際に、テストプログラムをビルドして動作させてみたのがこちら。ループカウンタ+100になるような値をEEPROMに書いて後、読み出しているのでこんな感じ。
256バイトとは言え、不揮発で何時でも簡単に読み書きできるメモリがあれば、便利便利。