見出し画像

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の担当です。

制御バイト列
24xxEEPROMピン配

<メモリの大きさ 512kbits>

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


eeprom 24FC512 memory map

<ページ書き込み 境界線>

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;
}

実行画面

シリアル出力

この記事が気に入ったらサポートをしてみませんか?