PR

EEPROMにラズパイピコからArduinoIDEを使って書き込みする方法解説【I2C】

eeprom-eyecatcy

今回はちょっと情報量が多くて渋滞していますが、

使うマイコンボードはRaspberryPiPicoで、

使う統合開発環境はArduinoIDE。

いじくるICはEEPROMです。

通信方式はI2C方式を採用しました。

以前RaspberryPiPicoを制御するのに、

ArduinoIDEを使用すると、I2C通信ができない。

なんて記事を書きましたが、現在は通信できるようになりました。

その時の記事↓↓↓

arduinoIDEでラズパイピコを使ってI2C通信する方法

I2C通信ができるようになったので、

EEPROMもまあできるだろう。というわけでやってみました。めっちゃ時間かかった…(独り言)

この記事を読むことでわかること

自己紹介

東証一部上場企業でサラリーマンしてます。

主に工場(生産現場)で使用する検査装置のアプリケーション開発してます。

ヒトの作業を自動化して簡略化するアプリケーションを日々開発中。

2022年5月に転職。現役バリバリの技術者です。
現在は超大手企業の新規事業分野で装置の研究・開発をしています。

Youtubeチャンネルにさまざまな動画を上げています

↓↓↓こちらからYoutubeチャンネルにアクセス!! ↓↓↓

今回使うものを紹介。

今回使用するのは以下のリストのものです。

  • RaspberryPiPico
  • EEPROM(24LC256)

ラズパイピコで今回は制御するので、ラズパイピコは必須です。

EEPROMですが、今回は24LC256という型式を使用しました。

型式によっては若干取り扱い方法が異なるのですが、

そこの違いについてはデータシートで確認してください。

データシートはこちら↓↓↓

http://akizukidenshi.com/download/24LC256.pdf

そもそもEEPROMとは?

EEPROMがそもそも何なのかについてですが、

簡単に言うと、電気を通し続けなくても値を保持し続けられるICです。

いわゆる不揮発性メモリなんて呼ばれるものですね。

用途としては、SDカードなんかをイメージしてもらえればいいと思います。

SDカードはフラッシュメモリなんて名前で原理は異なりますが、

用途としては同じようなものです。

いろいろなところに書いてありますが、

SDカードと比べて、数バイトのデータのやり取りであれば、

書き込み単位がEEPROMのほうが小さく、小回りが利くなんて利点があるようです。

なので基板に載せるのはSDカードのような小回りの利きにくいフラッシュメモリよりも、

容量は小さくても小回りの利くEEPROMを使用するということなんですね。

ちなみに今回使用するEEPROMは256Kb保持できるものですが、

SDカードだと最近はもう128GBなんて民生品でゴロゴロしているので

容量で勝負すると確実に負けます…

EEPROMとラズパイピコを配線する。

それではEEPROMを配線していきましょう。

まずは今回使用するEEPROMの各端子についてみておきましょう。

EEPROMpackage
今回は一番左上のタイプを使用しています。データシートより抜粋。

いくつか注意点がありますが、まずは配線図をお見せします。

EEPROM-raspbrrypipico
raspberryPiPicoとEEPOMの配線。

注意点は以下2つです。

  • SDAとSCLにはプルアップ抵抗2kΩを入れる。
  • WPピンは書き込みの制限が可能。
  • I2Cのアドレスは配線によって決まる。(増設ももちろん可能)

SDAとSCLにはプルアップ抵抗2kΩを入れる。

これを忘れると永遠に通信できません。

抵抗値の2kΩですが、この値はデータシートに記載されています。

EEPROM-SDA-pullup
SDAに入れるプルアップ抵抗の説明。データシートより抜粋。

24LC256の場合は2kΩですが、他のモデルだと異なる場合があるので、

必ずデータシートを確認してプルアップ抵抗を入れておきましょう。

ちなみに…データシート上には、SDAだけプルアップ抵抗入れろと書いてあったはずなのですが、

SCLにも同じ抵抗を入れないと動作しなかったです…

WPピンは書き込みの制限が可能。

EEPROMの7番ピンですが、こちらはWPピンと名前がついています。

このWPピンは、5V電圧をかけることにより、EEPROMへの書き込みを制限できます。

今回は読み書きどちらも行いたいので、GNDに落としています。

もしEEPROMに書き込んだデータを読み込むだけで、誤って消したくない場合は、

ここに5Vをかけてあげれば書き込みされないです。

I2Cのアドレスは配線によって決まる。(増設ももちろん可能)

MCP23017の記事でも紹介しましたが、

I2Cの接続機器は、アドレスを自由に決めて、使うときに指定する方式が一般的です。

このEEPROMもアドレスが8パターン決めることができます。

それではどうやって決めているかですが、

EEPROMの1番~3番ピンをどこにつなぐかによって決まります。

今回の配線だと、1番~3番ピンまですべてをGNDに接続しています。

その場合は、アドレスは、0b000となります。

もし1番ピンを5Vに接続した場合は、アドレスは0b001となります。

ただし、アドレス指定する場合は、この配線で決めるアドレスに加えて、

前後にデータがはいります。

それも含めてI2Cのアドレスになりますので注意です。

具体的にはデータシートから、

EEPROM-i2c-address
データシートより、I2Cアドレスの決め方。

A2とA1とA0がそれぞれ3番、2番、1番ピンに対応しています。

もう一度EEPROMのピン配置を掲載しておきますね。

EEPROMpackage
EEPROMのピン配置。データシートより。

それでは今回は1番~3番までGNDに接続⇒A0~A2まで0が入る。

ということなので、アドレスとしては、

0b10100000=0x50

ということになります。

※R/Wの文字ですが、書き込みまで行う場合は0にする必要があります。

ちなみに…

EEPROMはブレッドボードに直接させるタイプと、

表面実装用のものがあります。

表面実装用のEEPROMでも、こんな感じで変換基板を使用すれば

ブレッドボードにつなげることは可能です。

はんだ付けを実際にしている動画を貼っておきます。

【はんだ付け】表面実装用のeepromを変換基板にはんだしてみた
表面実装用のEEPROMを変換基板にはんだする

ArduinoIDEでラズパイピコにI2C通信の確認をする。

配線も終わったところで、

実際にArduinoIDEでラズパイピコを使って接続確認をしましょう。

以前I2Cの接続確認方法について説明した記事を書きましたが、

そちらで使用したスケッチを再利用します。

#include <Wire.h>
void setup()
{
  Wire.setSDA(8);//()の中身はGP番号。配線によって異なります。
  Wire.setSCL(9);//()の中身はGP番号。配線によって異なります。
  Wire.begin(); // Wire communication begin
  Serial.begin(9600); // The baudrate of Serial monitor is set in 9600
  while (!Serial); // Waiting for Serial Monitor
  Serial.println("\nI2C Scanner");
}
 
void loop()
{
  byte error, address; //variable for error and I2C address
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");
      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000); // wait 5 seconds for the next I2C scan
}

ラズパイピコでI2Cに挑戦した記事はこちら↓↓↓

過去記事。よかったらご覧ください。

さて、上記スケッチを書き込んでどうなったでしょうか?

このように0x50とアドレスが表示されていればOKです。

EEPROM_I2C_address
上記スケッチを動作させた結果。0x50が出ていれば成功。

もし見つからない場合は配線が怪しいです。

もう一度配線を確認してみて下さい。

実際にEEPROMに書き込んで読み込む。

それでは最終仕上げです。

ここからEEPROMへの書き込み⇒読み込みを行います。

ArduinoIDEで書き込むスケッチをこちらに上げておきます。

#include <Wire.h>    
 
#define eeprom 0x50    //EEPROMのAddress(配線によって可変)
 
void setup(void)
{
  Wire.setSDA(8);     // RaspberryPiPicoのSDAピンを指定(今回はGP8)
  Wire.setSCL(9);     // RaspberryPiPicoのSDAピンを指定(今回はGP9)
  Serial.begin(9600);
  Wire.begin();  
 
  writeEEPROM(eeprom, 32001, 99);  // ここでEEPROMに書き込む。32001番地に99を書き込む。
}
 
void loop(){
  delay(1000);
  Serial.print(readEEPROM(eeprom, 32001)); // 32001番地を1Sec毎に読んでシリアルウィンドウに表示する。
  }
 
void writeEEPROM(int deviceaddress,int eeaddress, int data ) 
{
  Wire.beginTransmission(deviceaddress);  // I2Cのアドレスを指定して通信開始。
  
  Wire.write((int)(eeaddress >> 8));      // MSB(上位8bitのみ先にアドレス指定したい)
  Wire.write((int)(eeaddress & 0xFF));    // LSB(下位8bitをアドレス指定する)
    
  Wire.write(data);                     // 書き込みたいデータを書く。
  Wire.endTransmission();               // I2Cの通信終了。
 
  delay(5); // 一応waitも入れておく。
}
 
byte readEEPROM(int deviceaddress,int eeaddress ) 
{ 
  int rdata; 
  // ここからは学習用のprint
  Serial.print("(int)(eeaddress>>8)");
  Serial.print((int)(eeaddress>>8));
  Serial.print("::::");
  Serial.print("(int)(eeaddress & 0xFF)");
  Serial.print((int)(eeaddress & 0xFF));
  Serial.print("::::");
  // ↑ここまで学習用のprint
  Wire.beginTransmission(deviceaddress);  // I2Cのアドレスを指定して通信開始。
  Wire.write((int)(eeaddress >> 8));      // MSB(上位8bitのみ先にアドレス指定したい)
  Wire.write((int)(eeaddress & 0xFF));    // LSB(下位8bitをアドレス指定する)
  Wire.endTransmission();                 // I2Cの通信終了。
 
  Wire.requestFrom(deviceaddress,1);
  // I2Cが接続可能であることを確認したうえで、読み込んだデータをreturnデータに格納する。
  if (Wire.available())
  {
    rdata = Wire.read();
  } 
  return rdata;
}

コメント文は適宜入れていますが、

一行じゃわからないよ…ということも考えられるので、

引っかかりそうなところについては丁寧に説明していきます。

ソースコードの解説。

#include <Wire.h>    
 
#define eeprom 0x50    //EEPROMのAddress(配線によって可変)
 
void setup(void)
{
  Wire.setSDA(8);     // RaspberryPiPicoのSDAピンを指定(今回はGP8)
  Wire.setSCL(9);     // RaspberryPiPicoのSDAピンを指定(今回はGP9)
  Serial.begin(9600);
  Wire.begin();  

ここまでは大丈夫ですかね。

Arduinoとは異なるのが、setSDAとsetSCLが追加で必要なことですね。

他はEEPROMのアドレスも配線で決まっているので問題ないと思います。

  writeEEPROM(eeprom, 32001, 99);  // ここでEEPROMに書き込む。32001番地に99を書き込む。

この一文ですが、関数化している関係で解説は後回しにします。

EEPROMに書き込む関数の解説

void writeEEPROM(int deviceaddress,int eeaddress, int data ) 
{
  Wire.beginTransmission(deviceaddress);  // I2Cのアドレスを指定して通信開始。
  
  Wire.write((int)(eeaddress >> 8));      // MSB(上位8bitのみ先にアドレス指定したい)
  Wire.write((int)(eeaddress & 0xFF));    // LSB(下位8bitをアドレス指定する)
    
  Wire.write(data);                     // 書き込みたいデータを書く。
  Wire.endTransmission();               // I2Cの通信終了。
 
  delay(5); // 一応waitも入れておく。
}

さて、書き込む方法ですが、

必要なものは、

  • EEPROMのI2Cアドレス。
  • EEPROM内のどこの番地に書き込むかのアドレス。
  • 書き込むデータ。

この3つが必要になります。

I2Cアドレスは今回0x50なのでひっかからないと思いますが、

おそらく、eeaddressのところでちょっと引っかかる人がいるのではないかと思うので、

そこを↓の読み込みの関数で解説します。同じことをしているので。

EEPROMから読み込む関数の解説

byte readEEPROM(int deviceaddress,int eeaddress ) 
{ 
  int rdata; 
  // ここからは学習用のprint
  Serial.print("(int)(eeaddress>>8)");
  Serial.print((int)(eeaddress>>8));
  Serial.print("::::");
  Serial.print("(int)(eeaddress & 0xFF)");
  Serial.print((int)(eeaddress & 0xFF));
  Serial.print("::::");
  // ↑ここまで学習用のprint
  Wire.beginTransmission(deviceaddress);  // I2Cのアドレスを指定して通信開始。
  Wire.write((int)(eeaddress >> 8));      // MSB(上位8bitのみ先にアドレス指定したい)
  Wire.write((int)(eeaddress & 0xFF));    // LSB(下位8bitをアドレス指定する)
  Wire.endTransmission();                 // I2Cの通信終了。
 
  Wire.requestFrom(deviceaddress,1);
  // I2Cが接続可能であることを確認したうえで、読み込んだデータをreturnデータに格納する。
  if (Wire.available())
  {
    rdata = Wire.read();
  } 
  return rdata;
}

書き込むときと同様、必要なものは全く同じです。

それではこの2行について解説します。↓↓

  Wire.write((int)(eeaddress >> 8));      // MSB(上位8bitのみ先にアドレス指定したい)
  Wire.write((int)(eeaddress & 0xFF));    // LSB(下位8bitをアドレス指定する)

まず、【>>】この記号ですが、ビットシフトと呼ばれる処理です。

今回の使い方だと、8ビット⇒方向へシフトさせる。という使い方です。

さて、これが何を意味するかですが、

このEEPROMには、上位アドレスと下位アドレスというものが存在しています。

EEPROM-high-low-address
データシートより。書き込みアドレスの上位と下位について。

つまり、例えば8桁以内のアドレスを指定する場合、

上位アドレスはかならず0が8個並ぶはずなので特に問題になりませんね。

ただし、8桁を超えるアドレスを指定する場合は、上位アドレスに値を入れる必要があります。

具体的には、0b11111111を超える場合ですね。

0b11111111=0xFF=0d255となります。

それでは、上位アドレスを指定しなくてはいけないのでどうするか?

それは下8桁を消してしまえばいいんです。

例えば、0b100000000=0x100=0d256を指定したい場合は、

下から8桁を消して、0b1となります。

この0b1が上位アドレスに入るアドレスとなります。

他にも、0d32000という大きなアドレスを実際に入れているのが今回のスケッチです。

  // ここからは学習用のprint
  Serial.print("(int)(eeaddress>>8)");
  Serial.print((int)(eeaddress>>8));
  Serial.print("::::");
  Serial.print("(int)(eeaddress & 0xFF)");
  Serial.print((int)(eeaddress & 0xFF));
  Serial.print("::::");
  // ↑ここまで学習用のprint

学習用として、シリアルウィンドウに各パラメータを出力するようにしています。

この結果としては、

(int)(eeaddress>>8)125::::(int)(eeaddress & 0xFF)0::::

こうなります。アドレスは0d32000です。

上位アドレスは125ですから、125*256=32,000ということでしっかり一致していますね。

次に【&】ですが、これは論理積と呼ばれるものです。

この論理積を通すことで、どこのビットが1になっているかがわかります。

具体的な例としては、0b100&0b111となっていた場合は、

0b100となります。つまり0x4ですね。

今回この論理積【&】を使う必要があるのかというと、

それは下位アドレスを取得するときに、上位アドレスを無視したいからですね。

上位アドレス下位アドレス合わせて16桁で構成されているわけですが、

下位アドレスとして必要なのは右から数えて8桁まで。

ですから、0xFFと論理積を取ることによってFFより上位のアドレスについては、

一切見ないようにしているんです。

EEPROMを使うときの注意点まとめ。

ここまでお話した内容の中で注意しないといけない点をまとめました。

  • 配線周りでは、プルアップ抵抗を入れ忘れないように気を付ける。
  • Arduinoと違い、ラズパイピコの場合はSDAとSCLのピン番号指定が必要。
  • 書き込むときのアドレスは、I2Cアドレス以外に、書き込み先のアドレスも必要。
  • 書き込み先のアドレスには上位と下位がある。

細かい注意点についてはもう一度読み直して確認してみてくださいね。

今回のまとめ。

今回はEEPROMについての記事でした。

I2Cができることはわかっていましたが、

今回EEPROMの読み書きは初めてだったので時間かかりました…

読み書き自体はできましたので今回の目標は完了ですね。

EEPROM自体、書き込み回数に限界があるらしいので、

遊びすぎて壊れないように気をつけないといけませんね。とはいえ100万回とかはよゆうらしい…

コメント

タイトルとURLをコピーしました