PR

Arduino UNO R4 WiFiのWireで地磁気センサーからデータを取得する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Arduino UNO R4 WiFiのWireライブラリを使用するとI2C通信に対応したセンサーからデータを取得することができます。Wireライブラリの使用例として地磁気センサーからデータを取得し、OLEDに表示して動作確認を行いました。

地磁気センサーモジュールにAE-BM1422AGMV(秋月電子)を使用しています。OLEDは0.95インチ(HiLetgo製)を使用しています。

以下ではArduino UNO R4 WiFiをUNOR4-WiFiと表記します。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Wireライブラリを使用する

地磁気センサーモジュールはROHM製のBM1422地磁気センサを使用したモジュールです。Arduino環境で使用できるようにI2Cレベルコンバータや3.3Vと5Vどちらでも対応できるため様々なマイコンで使用できるのが良い点です。

BM1422は廃盤予定になっていますが、Wireライブラリでセンサからデータを取得する方法はモジュールを問わず同様なので使用例としてまとめています。以下ではWireのメンバー関数を赤文字で表記します。

Wireライブラリの初期化

#include <Wire.h>

void setup() {
  Wire.begin();
}

最初に「Wire.h」をインクルードします。begin()関数でI2Cの条件を初期化してスタートします。UNOR4-WiFiはデフォルトでSDAがA4ピン、SCLがA5ピンになります。

引数にスレーブアドレスを指定するとスレーブとして動作を開始しますが、指定しない場合はマスターとして動作を開始します。BM1422にデータを要求するのでマスターとして動作させます。

データ送信の例

/* コマンド送出 */
bool BM1422Cmd(uint8_t* cmd, uint8_t len){
  bool ret = false;

  Wire.beginTransmission(BM1422_ADRS); //スレーブが存在するか確認
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    Wire.beginTransmission(BM1422_ADRS);

    for(uint8_t i= 0; i < len; i++ ){
      Wire.write(*cmd);
      ++cmd;
    }
  
    Wire.endTransmission(); //ストップ・コンディションの発行
    ret = true;
  }

  return ret;
}

Wireでデータ送信する例を示します。BM1422Cmd()関数はコマンドを送信するための自作の関数です。

最初に5行目のbeginTransmisson()関数でスレーブアドレスを指定し初期化を行います。BM1422のアドレスはデフォルトで0x0Eです。6行目のendTransmission()関数でデータを送信します。戻り値が0でない場合はスレーブが存在せず失敗なのでコマンドの送信をせず終了します。

スレーブが存在する場合は、コマンドをセットする処理に進みます。beginTransmisson()関数でスレーブアドレスを指定した後は、write()関数で送信するデータをセット(一時保存のキューイング)します。第1引数に送信するデータのアドレスを指定します。第2引数は送信するサイズを指定します。

endTransmission()関数で生成したデータを送信します。送信に成功すると戻り値が0になるので送信の状況の確認に使用することができます。

//使用例      
cmadbuf[0] = CTRL1_ADRS; //レジスタアドレス
cmadbuf[1] = CTRL1_SET;  //コマンド
BM1422Cmd(cmadbuf, 2);

配列を準備し、レジスタアドレスとコマンドを格納してBM1422Cmd()関数の引数に指定して使用します。

PR: わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジムPython入門講座の申込

データ受信の例

/* データを取得 */
bool BM1422GetData(uint8_t adr, uint8_t* reg_data, uint16_t len){
  bool  ret = false;

  Wire.beginTransmission(BM1422_ADRS); //スタート・コンディションの発行
  Wire.write(adr); //書き込む対象のアドレスをセット(ライトで指定)
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    if( Wire.requestFrom(BM1422_ADRS, len) == len ){
      for( uint16_t i=0; i < len; i++ ){
        *reg_data = Wire.read(); //len分だけデータをリードする
        ++reg_data;
      }
      ret = true;
    }
  }
  return ret;
}

Wireでデータ受信する例を示します。BM1422GetData()関数は指定したレジスタアドレスからデータを読み込む自作の関数です。

beginTransmission()関数でスレーブアドレスを指定し、Write()関数で読み込むレジスタのアドレスをセットします。(6行目)た後にレジスタを指定して読み込む場合は送信と同様の処理を行ってレジスタを指定します。(5~9行目)

7行目のendTransmission()関数でデータを送信し、スレーブが存在する場合はデータを要求処理に移ります。

requestFrom()関数でデータを取得します。第1引数にスレーブのアドレスを指定し、第2引数に読み込むデータのサイズを指定します。

requestFrom()関数の戻り値は読み込んだデータ数になるため指定したサイズと一致したことを確認し、一時保存したデータをreadBytes()関数で読み込んで変数に格納します。第1引数に読み込んだデータを格納するアドレス、第2引数に読み込むサイズを指定します。

requestFrom()でデータを要求した場合はendTransmission()を使用する必要ありません。

//使用例
#define DATA_ADRS 0x10
uint8_t mdata[6]; //測定データ換算前

BM1422GetData(DATA_ADRS, mdata, sizeof(mdata));

BM1422GetData()関数の第1引数に読み込み対象のレジスタアドレスを指定し、第2引数に読み込んだデータを格納する配列のアドレスを指定します。第3引数に読み込むデータ数を指定します。例はBM1422のDATAXレジスタ(0x10)から6バイトのデータを読み込んでいます。

スポンサーリンク

BM1422からデータを取得する

BM1422のデータシートにデータの取得のフローチャートとコマンドの例が記載されています。データシートに記載されている手順に従って磁気データを取得する手順について説明します。

シングル測定モードで14bitデータ出力を行う手順について使用する設定に絞って手順をまとめました。

データ取得の手順
  • 手順1
    CNTL1の設定(初期化)

    CNTL1レジスタに0xC2(PC1、OUT_BIT、FS1のビットをセット)を書き込む

  • 手順2
    CNTL4の設定(初期化)

    CNTL4レジスタの2バイト分に0x00を書き込んで初期化する。

  • 手順3
    測定開始コマンドの送信

    CNTL3レジスタに0x40を書き込んで測定を開始する。

  • 手順4
    測定待ちと測定状態の確認

    STA1レジスタのRD_DRDYビットを確認して測定完了を確認し、手順5に進む。

  • 手順5
    磁気のデータを換算する

    取得したデータを磁気のデータに換算し、手順6に進む。

  • 手順6
    測定開始の待機

    測定開始するまで待機する。再度測定する場合は手順3に戻る。

手順1、手順2は初期化の内容で手順3からの測定条件を設定しています。測定条件を変更しない場合は再設定する必要がないため、初期化後は手順3~手順6を繰り返しながらデータを取得します。

データシートの例ではオフセットを設定する項目(OFF_X、OFF_Y、OFF_Z)がありますが、磁気の基準を判断する基準がないため設定せずに測定を開始しています。

PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

測定をモードで管理する

uint8_t cmadbuf[3]; //送信するデータを格納

  switch(md){
    case BM1422_STEP1:
      //CTRL1レジスタの設定
      if( BM1422Cmd(cmadbuf, 2)){
        md = BM1422_STEP2;
      }
      break;
    case BM1422_STEP2:
      //CTRL4レジスタの設定
      if( BM1422Cmd(cmadbuf, 3) ){
        md = BM1422_STEP3;
      }
      break;
    case BM1422_STEP3:
      //CTRL3レジスタの設定(データ測定開始)
      if( BM1422Cmd(cmadbuf, 2 )){
        md = BM1422_STEP4;
      }
      break;
    case BM1422_STEP4:
      uint8_t sta;
      BM1422GetData(STA1_ADRS, &sta, 1);

      if( sta &= 0x40 ){
        md = BM1422_STEP5;
      }
      break;
    case BM1422_STEP5:
        BM1422GetData(DATA_ADRS, mdata, sizeof(mdata));
        MagSet();
        md = BM1422_IDLE;
      break;
    case BM1422_IDLE:
      if( timMeas == TIME_UP ){
        timMeas = TIME_OFF;
        md = BM1422_STEP3;
      } 
      break;
  }

手順1から手順6をモードを切り替えて管理します。

  1. BM1422_STEP1~2(手順1、手順2)
  2. BM1422_STEP3(手順3)
  3. BM1422_STEP4(手順4)
  4. BM1422_STEP5(手順5)
  5. BM1422_IDLE(手順6)

1.手順1、手順2は測定の条件を設定します。

2.手順3はCTRL3レジスタに0x40を書き込むと測定を開始し、次のモード(手順4)に進みます。

3.手順4はSTA1レジスタを確認して測定完了の状態であれば測定したデータが準備できているので次のモード(手順5)に進みます。

測定時間は平均4回設定で0.5m秒なので、それ以上の遅延を設けている場合はSTA1レジスタを確認せずに次のモードに進んでも問題ありません。

4.DATAXレジスタから6バイト分のデータを読み込むことでX、Y、Z軸の測定データを取得し磁気のデータに換算します。換算後は次のモードに進みます。

5.次の測定開始条件になるまで待機します。測定開始条件になるとBM1422_STEP3(手順3)に進みます。本記事では1秒ごとに測定を行ってOLEDに表示しています。

PR:企業で求められる即戦力技術を身に付ける テックキャンプエンジニア転職

測定データの換算

/* 磁気センサーの値を換算 */
void MagSet(void){
  int16_t x;
  int16_t y;
  int16_t z;

  x = (mdata[1] << 8) + mdata[0];
  y = (mdata[3] << 8) + mdata[2];
  z = (mdata[5] << 8) + mdata[4];
  mag[0] = (float)x * 0.042;
  mag[1] = (float)y * 0.042;
  mag[2] = (float)z * 0.042;
}

DATAXレジスタから6バイトのデータを読み込んでいるのでX軸、Y軸、Z軸のデータがそれぞれのデータのLSB、MSBの順でデータが取得できています。これらのデータからMSBのデータを右に8回シフトLSBのデータを加算して2バイトのデータを生成します。(7行目~9行目)

磁気感度は0.042uT/LSB(12ビットの場合は4倍)になるので乗算すると磁気のデータに換算できます。小数点を含む計算を行うためfloatでキャストして乗算しています。

OLEDライブラリを使用する

HiLetgo製の0.95インチ7ピン 65kカラー対応のOLEDディスプレイモジュールを使用しています。通信方式は3線のSPI通信を使用します。下記リンクにモジュールに関する情報が記載されています。

0.95″ Inch 7 Pin Colorful 65K SPI OLED Display Module

OLEDのドライバーICにSSD1331を使用しているためArduinoのライブラリを使って文字や記号を表示することができます。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

Adafruit SSD1331 OLED Driverライブラリを追加する

Adafruit SSD1331 OLED Driver Library for Arduinoの追加
Adafruit SSD1331 OLED Driver Library for Arduinoの追加

Arduino IDEのライブラリマネージャの検索欄にssd1331を入力するとライブラリの候補が表示されます。候補の中から「Adafruit SSD1331 OLED Driver Library for Arduino」をインストールします。

インストールする際にライブラリの依存関係をインストールするかを尋ねられることがあります。ライブラリの依存関係が不足しているとうまく動作しないことがあるためすべてをインストールすることをお勧めします。

OLEDライブラリの使用例

#include <Adafruit_SSD1331.h>

#define CS   10
#define RST  9
#define DC   8
#define	BLACK 0x0000

Adafruit_SSD1331 oled = Adafruit_SSD1331(&SPI, CS, DC, RST);

void setup() {
  oled.begin();
  oled.fillScreen(BLACK);
}

Adafruit_SSD1331.hをインクルードします。Adafruit_SSD1331クラスの変数としてoled(任意でよい)をインスタンス化します。第1引数にSPIクラスの変数のアドレスを指定します。Arduino環境ではSPIが標準で定義されているのでSPIを指定します。

第2引数にチップセレクトで使用するピン番号を指定します。

第3引数にData/Command(DC)ピン番号を指定します。SPI通信を3線で行うためData/Commandピンでデータの方向を選択して送受信します。

第4引数にRESピン番号を指定します。RESピンはHIGHにするとモジュールのリセットを解除します。LOWにするとリセット状態になります。

setup()内でAdafruit_SSD1331クラスのメンバー関数であるbegin()関数でOLEDの初期化を行います。fillScreen()関数は画面全体のカラーを指定した色で塗りつぶします。例ではBLACK(0x0000)0指定しています。色の指定は16bitデータ(0~65535の範囲)で行います。

oled.setTextSize(1); //5×7フォント
oled.setCursor(0, 0);
oled.setTextColor(RED);
oled.print("X: "); oled.print(mag[0]); oled.println(" [uT]");

setTextSize()関数でフォントの大きさを指定します。1を指定すると5×7のフォントになります。2以上を指定すると1のフォント基準に倍数したフォントになります。0を指定すると1と同じフォントになります。

setCursor()関数で文字表示する座標を指定します。第1引数にX座標、第2引数にY座標を指定します。座標はピン側を上に見た時、OLEDの左上の座標が(X,Y)=(0,0)になります。

setTextColor()関数はテキストの色を指定します。16ビットの値(0(白)~65535(黒))で色を指定します。

print()関数は指定した引数に対して文字列に変換して書き込みます。println()関数は文字列の最後に改行コードを挿入して書き込みます。

PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

動作確認

動作確認用の回路図
動作確認用の回路図

UNOR4-WiFiのOLEDに使用するSPIライブラリはD13にRSPCKA(SCL)、D11にCOPIA(SDA)、DOにSSLA0(CS)が割り振られています。CSはDOでON/OFFを切り替えて使用します。

OLEDに信号を送信するためDCピン用のDO、RES(リセット)用のDOを準備しています。これらのピンはライブラリでON/OFFします。

BM1422で使用するWireライブラリはA4にIIC_SDA1(SDA)、A5にIIC_SCL1(SCL)が割り振られています。I2C通信はそれぞれのピンをプルアップする必要がありますが、AE-BM1422AGMVに付属しているプルアップ抵抗を使用しています。

電源を投入するとOLEDとシリアルモニターに磁気のデータを1秒ごとに表示します。

動作確認の結果(OLEDとシリアルモニターの表示)
動作確認の結果(OLEDとシリアルモニターの表示)

BM1422モジュールに磁石を近づけると測定データが更新されています。X軸を見るとN極とS極を反対にすると符号が逆転しています。磁石を近づけたり離したりすることで値に変化があることが確認できました。

スポンサーリンク

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。

#include <Wire.h>
#include <Adafruit_SSD1331.h>

#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_BM1422 100 //BM1422の計測タイマ  
#define TIME_OUT 20  //BM1422の通信タイムアウト
#define TIME_LED 50
#define BM1422_ADRS 0x0E
#define CTRL1_ADRS 0x1B
#define CTRL2_ADRS 0x1C
#define CTRL3_ADRS 0x1D
#define CTRL4_ADRS 0x5C
#define CTRL4_ADRS2 0x5D
#define STA1_ADRS 0x18
#define DATA_ADRS 0x10

#define CTRL1_SET 0xC2
#define CTRL2_SET 0x0C //未使用
#define CTRL3_SET 0x40
#define CTRL4_SET 0x00

#define CS   10
#define RST  9
#define DC   8
// Color definitions
#define	BLACK           0x0000
#define	BLUE            0x001F
#define	RED             0xF800
#define	GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0
#define WHITE           0xFFFF

typedef enum{
  BM1422_STEP1 = 0,
  BM1422_STEP2,
  BM1422_STEP3,
  BM1422_STEP4,
  BM1422_STEP5,
  BM1422_IDLE,     
  BM1422_MAX  
}BM1422_MODE;

uint32_t beforetimCnt = millis();
int16_t timbm1422;
int16_t timled;
int16_t timMeas;
BM1422_MODE md;
uint8_t cmadbuf[3]; //送信するデータを格納
uint8_t mdata[6]; //測定データ換算前
float mag[3]; //換算後データ
Adafruit_SSD1331 oled = Adafruit_SSD1331(&SPI, CS, DC, RST);

void mainApp(void);
void mainTimer(void);
bool BM1422Cmd(uint8_t* cmd, uint8_t len);
bool BM1422GetData(uint8_t adr, uint8_t* reg_data, uint16_t len);
void MagSet(void);
void OledShow(void);

void setup() {

  Serial.begin(115200);
  Wire.begin();
  oled.begin();
  oled.fillScreen(BLACK);
}

void loop() {
  mainTimer();
  mainApp();
}

/* タイマ管理 */
void mainTimer(void){

  if ( millis() - beforetimCnt > BASE_CNT ){
    beforetimCnt = millis();

    if( timbm1422 > TIME_UP ){
      --timbm1422;
    }
    if( timled > TIME_UP ){
      --timled;
    }

    if( timMeas > TIME_UP ){
      --timMeas;
    }
  }
}
/* メイン処理 */
void mainApp(void){

  switch(md){
    case BM1422_STEP1:
      cmadbuf[0] = CTRL1_ADRS;
      cmadbuf[1] = CTRL1_SET;

      if( BM1422Cmd(cmadbuf, 2)){
        timbm1422 = TIME_OUT;
        md = BM1422_STEP2;
      }
      else{
        Serial.println("STEP1 NG");
      }
      break;
    case BM1422_STEP2:
      if( timbm1422 == TIME_UP ){
        cmadbuf[0] = CTRL4_ADRS;
        cmadbuf[1] = CTRL4_SET;
        cmadbuf[2] = CTRL4_SET;

        if( BM1422Cmd(cmadbuf, 3) ){
          timbm1422 = TIME_OUT;
          md = BM1422_STEP3;
        }
        else{
          Serial.println("STEP2 NG");
        }
      }
      break;
    case BM1422_STEP3: //データ測定開始
      if( timbm1422 == TIME_UP ){
        cmadbuf[0] = CTRL3_ADRS;
        cmadbuf[1] = CTRL3_SET;

        if( BM1422Cmd(cmadbuf, 2 )){
          timbm1422 = TIME_OUT;
          md = BM1422_STEP4;
        }
        else{
          Serial.println("STEP3 NG");
        }
      }
      break;
    case BM1422_STEP4:
      if( timbm1422 == TIME_UP ){
        timbm1422 = TIME_OUT;

        uint8_t sta;
        BM1422GetData(STA1_ADRS, &sta, 1);

        if( sta &= 0x40 ){
          md = BM1422_STEP5;
          Serial.println("Measure OK");
        }
      }
      break;
    case BM1422_STEP5:
      if( timbm1422 == TIME_UP ){
        timbm1422 = TIME_OUT;

        BM1422GetData(DATA_ADRS, mdata, sizeof(mdata));
        MagSet();
        md = BM1422_IDLE;
        timMeas = TIME_BM1422;
      }
      break;
    case BM1422_IDLE:
      if( timMeas == TIME_UP ){
        timMeas = TIME_OFF;
        md = BM1422_STEP3;
      } 
      break;
  }

  if( timled == TIME_UP ){
    timled = TIME_LED;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }

}
/* 磁気センサーの値を換算 */
void MagSet(void){
  int16_t x;
  int16_t y;
  int16_t z;

  x = (mdata[1] << 8) + mdata[0];
  y = (mdata[3] << 8) + mdata[2];
  z = (mdata[5] << 8) + mdata[4];

  mag[0] = (float)x * 0.042;
  mag[1] = (float)y * 0.042;
  mag[2] = (float)z * 0.042;

  Serial.print("X: "); Serial.print(mag[0]);
  Serial.print(" Y: "); Serial.print(mag[1]);
  Serial.print(" Z: "); Serial.println(mag[2]);
  OledShow();
}

/* OLED表示*/
void OledShow(void){

  oled.fillScreen(BLACK);
  oled.setTextSize(1); //5×7フォント
  oled.setCursor(0, 0);
  oled.setTextColor(WHITE);
  oled.println("BM1422 Sensor");
  oled.println("");
  oled.setTextColor(RED);
  oled.print("X: "); oled.print(mag[0]); oled.println(" [uT]");
  oled.setTextColor(BLUE);
  oled.print("Y: "); oled.print(mag[1]); oled.println(" [uT]");
  oled.setTextColor(GREEN);
  oled.print("Z: "); oled.print(mag[2]); oled.println(" [uT]");
}

/* コマンド送出 */
bool BM1422Cmd(uint8_t* cmd, uint8_t len){
  bool ret = false;

  Wire.beginTransmission(BM1422_ADRS); //スレーブが存在するか確認
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    Wire.beginTransmission(BM1422_ADRS);

    for(uint8_t i= 0; i < len; i++ ){
      Wire.write(*cmd);
      ++cmd;
    }
  
    Wire.endTransmission(); //ストップ・コンディションの発行
    ret = true;
  }

  return ret;
}
/* データを取得 */
bool BM1422GetData(uint8_t adr, uint8_t* reg_data, uint16_t len){
  bool  ret = false;

  Wire.beginTransmission(BM1422_ADRS); //スタート・コンディションの発行
  Wire.write(adr); //書き込む対象のアドレスをセット(ライトで指定)
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    if( Wire.requestFrom(BM1422_ADRS, len) == len ){
      for( uint16_t i=0; i < len; i++ ){
        *reg_data = Wire.read(); //len分だけデータをリードする
        ++reg_data;
      }
      ret = true;
    }
  }
  return ret;
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Raspberry Pi Picoで学べるソフト開発

最後まで、読んでいただきありがとうございました。

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