
M5ATOM Lite外付けEEPROM 24FC512ドライバー製作
Microchip社製EEPROM 512kbits 24FC512 をArduino Wireクラスで動かします。今まで、PICマイコンを主に使っていたので、C言語オンリーでコードを書いていました。arduino,M5Stackになってオブジェクト指向で書くようになったので、なかなか、苦戦しております。それでも、C++言語のクラスは使い勝手がよく、もう自分のデフォルト言語をC++やC#にしていきたいところです。
話をもどして、AruduinoのWireクラスの話になります。
Wireの通信バッファがデフォルトで、128バイト。外付けEEPROM 24FC512の1ページが128バイト。一気にページライトできるかな、と思ったらハマりました。何度やっても、126バイトしか書きこめません。
解決策はのちほど。まずは、EEPROMの仕様から理解します。
1.EEPROM 24FC512 Microchip社
<スペック>
メモリ: 512kbits
動作電圧 1.8~5.5v
Max Clock Frequency : max 1MHz
Page Size : 128 bytes
<I2Cスレーブアドレス>
0x50~0x57までの8種類から選択。選択の仕方は、ICピンの1番から3番ピンA0,A1,A2をそれぞれ、High:1 または、 Low:0 に設定します。3ビットで
指定するので、8種類のアドレスを選択できます。
今回は、A0をグランドに落とし、デバイスアドレスを01010110b=0x56に設定しました。アドレスの下位3ビットがA2,A1,A0の担当です。


<メモリの大きさ 512kbits>
512kbits / 8bits = 64kbytes = 64,000 bytes
64kbytes / 16 = 4k records = 4000 records
※recordとは自分がEEPROMを使うときの、リードライトする単位です。
16バイトを一束にして、送信すると後で解説する境界線に対して、都合がいいからです。
データロガーなどで、データを送信するときは、16バイトを単位として
データ構成をすると、スッキリします。

<ページ書き込み 境界線>
EEPROM 24FC512は、一旦、128バイトの受信バッファでマスターからのデータを受信します。なので、一回に送信できるデータの大きさは最大で128バイトになります。マスターがStop状態を送信した後に、実メモリに受信データが転送されます。この仕組みのため、24FC512は、128バイトを1ページとして扱います。この大きさ以上の書き込みはできません。
この受信バッファのからくりのほか、
実メモリは、先頭アドレス0x0000から128byte区切りで、パーティションが
区切られています。
たとえば、0x0000から、0x007F(127Decimal)が、1ページ目になります。
2ページ目は0x0080から0x00FF(255Decimal)になります。
ページ128バイトの整数倍で、メモリ全体にパーティションが区切られています。注意したいのは、128バイトの整数倍である、0x007Fや0x00FFが境界線になっており、ページを超えるような連続書き込み方をすると、次のページに書き込まず、同一ページの先頭アドレスに書き込むことになってしまします。ここがトリッキーな仕組みになっています。
なので、複数バイトをストップせずに連続して書きこむときは、
128バイト刻みの境界越えをしないように注意する必要があります。0x007Fから0x0080に連続して書きこむことはできない、ということです。
128bytes / 16bytes = 8records
2.外付けEEPROMにおいて、arduino Wireクラスの使い方。
今回、EEPROMのドライバーは自作です。EEPROM専用のクラスをつくりました。少しわかりにくくなりますが、できるだけ、Wireの一般的な使い方の説明になるようにします。
<myEEPROMクラス>
#ifndef __MYEEPROM_HPP_
#define __MYEEPROM_HPP_
#include <M5Atom.h>
#include <Wire.h>
/*
endTransmission() returns:
0: success.
1: data too long to fit in transmit buffer.
2: received NACK on transmit of address.
3: received NACK on transmit of data.
4: other error.
5: timeout
*/
#define eeprom_status uint8_t
#define e24LC512_address 0x56
#define e24LC512_MAX_Address 0xF9FF
#define e24LC512_PageSize 128
class myEEPROM
{
public:
TwoWire *ee_wire;
uint8_t deviceAddress;
uint8_t pageSize;
uint8_t max_address;
public:
myEEPROM(TwoWire *_wire, uint8_t _deviceAddress, uint8_t _pageSize, uint16_t _max_address);
eeprom_status writeByte(uint16_t address,uint8_t data);
eeprom_status readByte(uint16_t address,uint8_t* data);
eeprom_status writeNByte(uint16_t address, uint8_t* data, size_t sz);
eeprom_status readNByte(uint16_t address, uint8_t* data, size_t sz);
eeprom_status writePage(uint16_t address,uint8_t* data);
eeprom_status readPage(uint16_t address,uint8_t* data);
void eeprom_Test();
};
extern myEEPROM *eeprom;
#endif
eeprom_status:Wireクラスの関数は、通信中、通信完了時にステータスレスポンスを返してきます。これを受け取れるようにメンバー関数をつくりました。通信失敗時のリカバリに使用したいからです。
arduinoのWire0と、Wire1を選択できるようにしました。
複数のEEPROMを扱えるようにしています。
24LC512以外のEEPROMを増設する場合、アドレス、上限アドレス、ページサイズをdefine定義します。
コンストラクタで、Wire,アドレス、ページサイズ、上限アドレスを設定します。
コンストラクタは、Wireインスタンスが生成された後に実行します。loop関数内でnewを使ってインスタンスを生成します。
<クラスコンストラクタ>
#include "myEEPROM.hpp"
myEEPROM *eeprom;
/// @brief myEEPROMコンストラクタ
/// @param _wire I2Cバスポインタ
/// @param _deviceAddress I2Cデバイスアドレス
/// @param _pageSize ページサイズ
/// @param _max_add 最後尾アドレス
myEEPROM::myEEPROM(TwoWire *_wire, uint8_t _deviceAddress,uint8_t _pageSize, uint16_t _max_add)
{
ee_wire = _wire;
deviceAddress = _deviceAddress;
pageSize = _pageSize;
max_address = _max_add;
}
コンストラクタで、Wireインスタンスの確保、デバイスアドレスの設定ができたので、実際にEEPROM 24FC512と通信させていきます。
<1バイト書き込み>
/// @brief EEPROM 1byte Read
/// @param address rom address
/// @param data
/// @return I2C state
eeprom_status myEEPROM::writeByte(uint16_t address,byte data)
{
ee_wire->beginTransmission(eeprom->deviceAddress);
ee_wire->write((int)(address >> 8)); // 上位バイト
ee_wire->write((int)(address & 0xFF)); // 下位バイト
ee_wire->write(data);
eeprom_status status = ee_wire->endTransmission(true);
delay(10); // 書き込み処理に対する待ち時間
return status;
}
バイト書き込みは、ネット検索定番のコードです。
できる限り、関数内で、通信ステータスを拾ってreturnしています。
ee_wire->は、Wireのインスタンスへのポインタです。
<1バイト読み込み>
/// @brief EEPROM byte Read
/// @param address rom address
/// @param data readbuffer pointer
/// @return I2C state
eeprom_status myEEPROM::readByte(uint16_t address, uint8_t* data)
{
ee_wire->beginTransmission(eeprom->deviceAddress);
ee_wire->write((int)(address >> 8)); // 上位バイト
ee_wire->write((int)(address & 0xFF)); // 下位バイト
ee_wire->endTransmission(false); //non stop restart
//オーバーライドが多数あるので、引数の型合わせが必要。
ee_wire->requestFrom((uint8_t)eeprom->deviceAddress,(uint8_t)1);
if (ee_wire->available())
{
*data = ee_wire->read();
}
eeprom_status status = ee_wire->endTransmission(true);//stop
delay(10);
return status;
}
バイト読込のコードで注意するのは、WireクラスのrequestFromメソッドです。オーバーライドが複数存在するので、自分の意図したメソッドの引数の型を合わせる必要があります。
Wireクラスのavailable()メソッドは、WireクラスメンバのrxLength(受信数)とrxIndex(読取り数)の差分をreturnしています。read()メソッドが実行されるたびに、rxIndexが+1されていきます。最後に差分が0になると、読み出し完了になります。
<ページ書き込み>
EEPROM 24LC512は、1ページが128bytesになっています。128byteを連続して、書きこみます。たとえば、EEPROMのメモリを一度オールクリアするときになどに使用できます。
Wireクラスの通信バッファは、デフォルトで、128byteになっています。
なので、Wireクラスメソッドのwriteを128回実行して送信すればいいのかと思いましたが、うまく動きません。送信数が126回で止まってしまいます。
原因を探ってみます。writeメソッドの定義を見てみます。
size_t TwoWire::write(uint8_t data)
{
if (txBuffer == NULL){
log_e("NULL TX buffer pointer");
return 0;
}
if(txLength >= bufferSize) {
return 0;
}
txBuffer[txLength++] = data;
return 1;
}
どうやら、txLengthがbufferSizeを超えてしまっているようです。Wire.hに
#ifndef I2C_BUFFER_LENGTH
// Default size, if none is set using Wire::setBuffersize(size_t)
#define I2C_BUFFER_LENGTH 128
#endif
と、128byteのバッファサイズのdefine定義がされています。
128バイト送信できるはずなのに、なぜ126バイトで送信が止まるのか?
私が書いた、ページ書き込みのコードを見てみます。
/// @brief EEPROM ページ書き込み
/// @param address eeprom address
/// @param data array data
/// @return I2C state
eeprom_status myEEPROM::writePage(uint16_t address, uint8_t* data)
{
size_t sz;
sz=0;
ee_wire->beginTransmission(eeprom->deviceAddress);
ee_wire->write((int)(address >> 8)); // 上位バイト
ee_wire->write((int)(address & 0xFF)); // 下位バイト
//sz = ee_wire->write(data,(size_t)pageSize);
for(int i=0; i<pageSize; i++)
{
if(ee_wire->write(data[i]))
{
sz++;
}else
{
return 4;
}
}
eeprom_status status = ee_wire->endTransmission(true); //stop
delay(10);
if(sz != pageSize)
{
return sz;
}
return status;
}
よく見ると、writeメソッドは(2+pageSize)回、実行されています。
デバイスアドレス上位、下位の送信で、2回実行。そのあとに、送信したいデータ、1ページ128バイト分の128回。合計で130回実行されています。
これで、Wireクラスのバッファより2バイト多く送信していたと、分かりました。これが、126バイトしか送信できなかった理由です。
以降、これを具体的に、解決していきます。
Wireクラス setBufferSizeメソッド
Wireクラスデフォルトのバッファサイズを128byteから130byteに変更すれば、問題が解決しそうです。
size_t setBufferSize(size_t bSize);
このメソッドの引数を130にしたら、問題なく128バイトのページリードライトができるようになりました。setup関数内で、使用するWireインスタンスが生成された後、Wire1.setBufferSize(130);を実行します。
<ページ読み込み>
/// @brief EEPROM ページ読込
/// @param address eeprom address
/// @param data array data
/// @return I2C state
eeprom_status myEEPROM::readPage(uint16_t address, uint8_t* data)
{
int i;
ee_wire->beginTransmission(eeprom->deviceAddress);
ee_wire->write((int)(address >> 8)); // 上位バイト
ee_wire->write((int)(address & 0xFF)); // 下位バイト
ee_wire->endTransmission(false); //non stop restart
//オーバーライドが多数あるので、引数の型合わせが必要。
ee_wire->requestFrom((uint8_t)eeprom->deviceAddress,(uint8_t)pageSize);
i=0;
while(ee_wire->available()!=0)
{
data[i] = ee_wire->read();
i++;
if(i>pageSize)
{
return i;
}
}
eeprom_status status = ee_wire->endTransmission(true);//stop
delay(10);
return status;
}
<Nバイト書き込み>
// @brief eeprom 複数Nバイト書き込み
/// @param address eeprom address
/// @param data array data
/// @param sz array data size
eeprom_status myEEPROM::writeNByte(uint16_t address, uint8_t* data, size_t sz)
{
ee_wire->beginTransmission(eeprom->deviceAddress);
ee_wire->write((int)(address >> 8)); // 上位バイト
ee_wire->write((int)(address & 0xFF)); // 下位バイト
//ee_wire->write(data,sz);
for(int i=0; i<sz; i++)
{
ee_wire->write(data[i]);
}
eeprom_status status = ee_wire->endTransmission(true);
delay(10); // 書き込み処理に対する待ち時間
return status;
}
<Nバイト読み込み>
/// @brief eeprom 複数Nバイト読込
/// @param address
/// @param data
/// @param sz
/// @return
eeprom_status myEEPROM::readNByte(uint16_t address, uint8_t* data, size_t sz)
{
int i;
ee_wire->beginTransmission(eeprom->deviceAddress);
ee_wire->write((int)(address >> 8)); // 上位バイト
ee_wire->write((int)(address & 0xFF)); // 下位バイト
ee_wire->endTransmission(false); //non stop restart
delay(1);
//オーバーライドが多数あるので、引数の型合わせが必要。
ee_wire->requestFrom((uint8_t)eeprom->deviceAddress,(uint8_t)sz);
i=0;
while(ee_wire->available()!=0)
{
data[i] = ee_wire->read();
i++;
if(i>sz)
{
return i;
}
}
eeprom_status status = ee_wire->endTransmission(true);//stop
delay(10);
return status;
}
実行画面
