I2CセンサデバイスのArduino IDEからMATLAB Suport Package for Arduino Hardwareへの移植のコツ

###１．はじめに
MATLABでArduinoをプログラムするには、MATLAB Support Package for Arduino Hardwareを用いるわけですが、新しいI2Cデバイスを動かしてみたい、と思っても、ほとんどの場合は、サンプルプログラムはありません。一方でArduino IDE用のサンプルプログラムはあったりします。

本記事では、今年のEXPO LTで使いました圧力センサDPS368を例にとり、Arduino IDEのI2Cを含むプログラムをMATLAB用に移植する場合のコツをまとめました。
全体のプログラムは[MATLAB CentralのFile exchange](https://jp.mathworks.com/matlabcentral/fileexchange/83238-dps368-sensor-data-acquisition-with-arduino-matlab)にもアップロードしてあります。

###２．最近のセンサデバイスについて
古いセンサデバイスは単機能で、温度なら温度、圧力なら圧力というように１種類のデータしか測定できないものが多く、また、精度などのパラメータも変換できないことが多かったです。
それに対して最近は、１つのデバイスで複数種類のデータを出力でき、また、条件に合わせて調整が可能になっています。
そのため、センサデバイスはセンサとインターフェースだけでなく、マイコンを内蔵したようなものになってきています。
測定条件は内部のレジスタに書き込む形になっています。

###３．温度＆圧力センサDPS368に関して
ここで取り上げます温度と圧力を測定できるインフィニオン社のDPS368も複数のレジスタを持ち、ここにデータを書き込むことで、測定条件を設定します。
下の表がレジスタマップです（Infineonのデータシートから引用） 。
![レジスターマップ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/1c543d8d-d82e-20fd-159b-ae7c316a99e2.jpeg)

Arduino Unoとの接続は下記のようになります。今回はDPS368 Pressure Shield2GO用評価ボードを用いました（Digi-keyから買えます）。DPS368に５V系の電源・信号を接続すると壊れるので、電源は3.3V、信号線はレベル変換器を介して接続します。
![DPS368接続図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/af558661-932c-a9ef-87f9-672983b51873.png)

###４．動作の流れの概略
動作の流れを説明します。
０）レジスタのアドレスとか、あらかじめ決まっている初期値の定義
１）センサとArduinoの通信を確立する
２）センサ固有の値を取得する
３）センサにパラメータを書き込む
４）データ要求と取得
５）通信の終了

###５．Arduino IDEにおける構文とMATLABへの翻訳
####５－１．値を１つだけ書き込む
リセットなどで用います。
Arduino IDEでは

```C
Wire.beginTransmission(DPS368_address);
    Wire.write(reset);
Wire.endTransmission();
```
MATLABに翻訳すると、

```matlab
write(DPS368,Reset,'uint8');% リセット
```
####５－２．レジスタに値を書き込む

```C
Wire.beginTransmission(DPS368_address);
    Wire.write(MEAS_CFG); // 0x08
    Wire.write(0xC0);
Wire.endTransmission();
```
MATLABに翻訳すると、

```matlab
writeRegister(DPS368,MEAS_CFG,hex2dec('C0'),'uint8');
```

####５－３．データを１つだけ読み出す

```C
Wire.beginTransmission(DPS368_address);
    Wire.write(MEAS_CFG); //0x08
    Wire.endTransmission(false);
    Wire.requestFrom(DPS368_address,1);
Flag = Wire.read();
```
MATLABに翻訳すると、

```matlab
write(DPS368,MEAS_CFG,'uint8');% 
Flag=read(DPS368,1,'uint8');
```

####５－４．データを複数個（ｍ個）読み出す

```C
for ( int i = 0 ; i < m ; i++ ) {
   Wire.beginTransmission(DPS368_address);
        Wire.write(COEF + i);
   Wire.endTransmission();
   Wire.requestFrom(DPS368_address,1);
        readbuffer[i] = Wire.read();
}
```

MATLABに翻訳すると、

```matlab
for t=0:m-1
    write(DPS368,COEF+t,'uint8');% 係数を要求
    readbuffer(t+1)= read(DPS368,1,'uint8');% データ取込み
end
```

もしくは、あまりデータ数が多くない場合には（例えば３個）、

```C
Wire.requestFrom(DPS368_address,3);
    readbuffer[0] = Wire.read(); 
    readbuffer[1] = Wire.read(); 
    readbuffer[2] = Wire.read();
```

MATLABに翻訳すると、

```matlab
data=read(DPS368,3,'uint8');%dataは1x3の配列になる
```
となります。

###６．動作の全体
ということで、Arduino IDEとMATLABをまとめて示します。

まず、Arduino IDEのコードから。

```C
//０）レジスタのアドレスとか、あらかじめ決まっている初期値の定義
#include <Wire.h>
#define DPS368_address 0x77
#define PSR_B0 0x02
#define PSR_B1 0x01
#define PSR_B2 0x00
#define TMP_B0 0x05
#define TMP_B1 0x04
#define TMP_B2 0x03
 
#define MEAS_CFG 0x08
#define PRS_CFG 0x06
#define TMP_CFG 0x07
#define CFG_REG 0x09
#define Kt 2088960.0 // 128times Compensation Scale Factors
#define Kp 2088960.0 // 128times Compensation Scale Factors
#define RESET 0x0C
 
#define COEF 0x10 //0x10 - 0x21
byte readbuffer[30];
int16_t c0,c1,c01,c11,c20,c21,c30;
int32_t c00,c10;
int32_t dum,dum1,dum2;
byte Flag;
 
void setup() {
  delay(1000);//これはあまり意味はないかも

// １）センサとArduinoの通信を確立する
    Serial.begin(9600);
    Wire.begin();
    Wire.beginTransmission(DPS368_address);
        Wire.write(RESET);
    Wire.endTransmission();
    delay(100);

// ２）センサ固有の値を取得する
    for ( int i = 0 ; i < 19 ; i++ ) {
       Wire.beginTransmission(DPS368_address);
            Wire.write(COEF + i);
       Wire.endTransmission();
       Wire.requestFrom(DPS368_address,1);
            readbuffer[i] = Wire.read();
    }
    
        c0 = (readbuffer[0] << 8 | readbuffer[1] & 0xf0 ) >> 4; 
        c0 = -(c0 & 0b100000000000) | (c0 & 0b011111111111);
        c1 = (readbuffer[1] & 0x0f ) << 8 | readbuffer[2] ; 
        c1 = -(c1 & 0b100000000000) | (c1 & 0b011111111111);
          dum=(int32_t)readbuffer[3];
        c00 = (dum*4096 + readbuffer[4]*16 + (readbuffer[5]/16 ));

        if(c00>(int32_t)524287)
        {
          c00=c00-(int32_t)(1048576);
        }
         dum=(int32_t)(readbuffer[5] & 0x0f);
         dum2=(int32_t)(readbuffer[6]);
        c10 = (int32_t)((dum <<16) +  (dum2 << 8) + readbuffer[7]);
        if(c10>(int32_t)(2^19-1))
        {
          c10=c10-(int32_t)(1048576);
        }
        c01 = readbuffer[8] << 8 | readbuffer[9];
        c01 = -(c01 & 0b1000000000000000) | (c01 & 0b0111111111111111);
        c11 = readbuffer[10] << 8 | readbuffer[11];
        c11 = -(c11 & 0b1000000000000000) | (c11 & 0b0111111111111111);
        c20 = readbuffer[12] << 8 | readbuffer[13];
        c20 = -(c20 & 0b1000000000000000) | (c20 & 0b0111111111111111);
        c21 = readbuffer[14] << 8 | readbuffer[15];
        c21 = -(c21 & 0b1000000000000000) | (c21 & 0b0111111111111111);
        c30 = readbuffer[16] << 8 | readbuffer[17];
        c30 = -(c30 & 0b1000000000000000) | (c30 & 0b0111111111111111);

// ３）センサにパラメータを書き込む
    Wire.beginTransmission(DPS368_address);
        Wire.write(MEAS_CFG); // 0x08
        Wire.write(0xC0);
    Wire.endTransmission();

    Wire.beginTransmission(DPS368_address);
        Wire.write(RESET); // 0x0C
        Wire.write(0x80);
    Wire.endTransmission();

    Wire.beginTransmission(DPS368_address);
        Wire.write(TMP_CFG); // 0x07
        Wire.write(0xA7);
    Wire.endTransmission();
 
    Wire.beginTransmission(DPS368_address);
        Wire.write(PRS_CFG); // 0x06
        Wire.write(0x23);
    Wire.endTransmission();
 
    Wire.beginTransmission(DPS368_address);
        Wire.write(CFG_REG); // 0x09
        Wire.write(0x0C);
    Wire.endTransmission();
}
 
//４）データ要求と取得

void loop() {
//温度データ取得
    Wire.beginTransmission(DPS368_address);
        Wire.write(MEAS_CFG); //0x08
        Wire.write(0xC2);
    Wire.endTransmission();
    delay(100);
 
    Wire.beginTransmission(DPS368_address);
        Wire.write(TMP_B2);
    Wire.endTransmission(false);
    delay(100);
 
    Wire.requestFrom(DPS368_address,3);
        readbuffer[0] = Wire.read(); 
        readbuffer[1] = Wire.read(); 
        readbuffer[2] = Wire.read();
        dum = (int32_t)readbuffer[0];
        dum1 = (int32_t)readbuffer[1];
        dum2 = (int32_t)readbuffer[2];
     int32_t Temp0 = 65536*dum + 256*dum1 + dum2; 
     dum = (int32_t)(2^23-1);
     dum1 = (int32_t)(2^24);

     if(Temp0>dum)
     {
       Temp0 = Temp0 - dum1;
     }
    // Serial.print("Temp0= ");Serial.println(Temp0);
    float Temp = c0 * 0.5 + c1 * Temp0 / Kt;
    Serial.print("Temp ");
    Serial.println(Temp);

//圧力データ取得

    Wire.beginTransmission(DPS368_address);
        Wire.write(TMP_CFG); // 0x07
        Wire.write(0x87);
    Wire.endTransmission();

    Wire.beginTransmission(DPS368_address);
        Wire.write(PRS_CFG); // 0x06
        Wire.write(0x07);
    Wire.endTransmission();

    Wire.beginTransmission(DPS368_address);
        Wire.write(CFG_REG); // 0x09
        Wire.write(0x0C);
    Wire.endTransmission();

    Wire.beginTransmission(DPS368_address);
        Wire.write(MEAS_CFG); //0x08
        Wire.write(0xC1);
    Wire.endTransmission();
    delay(100);

    Flag=0;

    do{
        Wire.beginTransmission(DPS368_address);
          Wire.write(MEAS_CFG); //0x08
         Wire.write(0xC1);
        Wire.endTransmission();
        delay(100);
        Wire.beginTransmission(DPS368_address);
          Wire.write(MEAS_CFG); //0x08
        Wire.endTransmission(false);
        Wire.requestFrom(DPS368_address,1);
        Flag = Wire.read();
        Flag = Flag & 0x10;
        delay(100);
    } while (Flag <1);
        
    delay(100);
 
    Wire.beginTransmission(DPS368_address);
        Wire.write(PSR_B2);
    Wire.endTransmission(false);
    delay(100);
 
    Wire.requestFrom(DPS368_address,3);
        readbuffer[0] = Wire.read(); 
        readbuffer[1] = Wire.read(); 
        readbuffer[2] = Wire.read();

        dum=(int32_t)readbuffer[0];
        dum1=(int32_t)readbuffer[1];
        dum2=(int32_t)readbuffer[2];
     int32_t P0 = 65536*dum + 256*dum1 + dum2; 
     dum=(int32_t)(8388607);
     dum1=(int32_t)(16777216);
     if(P0>dum)
     {
       P0=P0-dum1;
     }

    float Praw = P0 / Kp;
    
    float P = c00 + Praw * (c10 + Praw * (c20 + Praw * c30)) + (Temp0 / Kt) * c01 + (Temp0 / Kt) * Praw * (c11 + Praw * c21);
    Serial.print("P ");Serial.println(P);
    Serial.println(" ");
    delay(100);
}

// ５）通信の終了は無限ループなのでありません。
```
ポイントとしては、圧力の測定完了を確認してからデータを取得しないとゼロしか出てこないところかと思います。測定完了までループを回して待ちます。
次にMATLABのコードです。

```matlab
% ０）レジスタのアドレスとか、あらかじめ決まっている初期値の定義
Reset=0x0c;
% PSR_B0=0x02;
%PSR_B1=0x01;
PSR_B2=0x00;
% TMP_B0=0x05;
% TMP_B1=0x04;
TMP_B2=0x03;
 
MEAS_CFG=0x08;
PRS_CFG=0x06;
TMP_CFG=0x07;
CFG_REG=0x09;
Kt=2088960.0;% 128times Compensation Scale Factors
Kp=2088960.0;% 128times Compensation Scale Factors

COEF=0x10; %0x10 - 0x21

readbuffer=zeros(1,30);


% １）センサとArduinoの通信を確立する
a=arduino('COM9','Uno','Libraries','I2C');%COM番号、ボード名は適宜変更
DPS368=device(a,'I2CAddress','0x77');% I2Cデバイスをつくる

%%% Reset %%%
write(DPS368,Reset,'uint8');% リセット

pause(0.1);

% ２）センサ固有の値を取得する
for t=0:18
    write(DPS368,COEF+t,'uint8');% 係数を要求
    readbuffer(t+1)= read(DPS368,1,'uint8');% データ取込み
end

% 係数C0,C1,C00,C10,C01,C11,C20,C21,C30を計算
C0Lo=fix(readbuffer(2)/16);
C0=readbuffer(1)*16+C0Lo;
if C0> (2^11-1)
    C0=C0-2^12;
end

C1=(readbuffer(2)-C0Lo*16)*2^8+readbuffer(3);
if C1> (2^11-1)
    C1=C1-2^12;
end

C00Lo=fix(readbuffer(6)/16);
C00=readbuffer(4)*2^12+readbuffer(5)*2^4+C00Lo;
if C00> (2^19-1)
    C00=C00-2^20;
end

C10=(readbuffer(6)-C00Lo*16)*2^16+readbuffer(7)*2^8+readbuffer(8);
if C10> (2^19-1)
    C10=C10-2^20;
end

C01=readbuffer(9)*2^8+readbuffer(10);
if C01> (2^15-1)
    C01=C01-2^16;
end

C11=readbuffer(11)*2^8+readbuffer(12);
if C11> (2^15-1)
    C11=C11-2^16;
end

C20=readbuffer(13)*2^8+readbuffer(14);
if C20> (2^15-1)
    C20=C20-2^16;
end

C21=readbuffer(15)*2^8+readbuffer(16);
if C21> (2^15-1)
    C21=C21-2^16;
end

C30=readbuffer(17)*2^8+readbuffer(18);
if C30> (2^15-1)
    C30=C30-2^16;
end

% ３）センサにパラメータを書き込む
writeRegister(DPS368,MEAS_CFG,hex2dec('C0'),'uint8');% Standby Mode

writeRegister(DPS368,Reset,hex2dec('80'),'uint8');% FIFO flush

writeRegister(DPS368,TMP_CFG,hex2dec('A7'),'uint8');
% MEMSのセンサを使用、4samples/sec, 128oversampling

writeRegister(DPS368,PRS_CFG,hex2dec('23'),'uint8');% 4samples/sec, 8oversampling（仮仕様）


writeRegister(DPS368,CFG_REG,hex2dec('0C'),'uint8');% P-SHIFT, T-SHIFT enable

% ４）データ要求と取得
%%%%%%%%%%%%%%%%ここからループ%%%%%%%%%%%%%

for s=1:10 %　取り込み回数は10回

    %%%温度測定
    writeRegister(DPS368,MEAS_CFG,hex2dec('C2'),'uint8');% 温度測定モード
    pause(0.1);
    write(DPS368,TMP_B2,'uint8');% 
    pause(0.1);
    data=read(DPS368,3,'uint8');
    Temp0=data(1)*2^16+data(2)*2^8+data(3);
    if Temp0> (2^23-1)
        Temp0=Temp0-2^24;
    end
    Temp=C0*0.5 + C1*Temp0/Kt;
    
    disp(['Temp= ', num2str(Temp)]);

    %%%圧力測定
    writeRegister(DPS368,TMP_CFG,hex2dec('87'),'uint8');
    writeRegister(DPS368,PRS_CFG,hex2dec('07'),'uint8');%128oversampling
    writeRegister(DPS368,CFG_REG,hex2dec('0C'),'uint8');
    writeRegister(DPS368,MEAS_CFG,hex2dec('C1'),'uint8');% 圧力測定
    pause(0.1);

    Flag=0;
    while Flag<1 %圧力測定完了を待つループ
        writeRegister(DPS368,MEAS_CFG,hex2dec('C1'),'uint8');
        pause(0.1);
        write(DPS368,MEAS_CFG,'uint8');% 
        Flag=read(DPS368,1,'uint8');
        Flag=bitand(Flag,0x10);
        pause(0.1)
    end

    pause(0.1);
    write(DPS368,PSR_B2,'uint8');% 
    pause(0.1);
    data=read(DPS368,3,'uint8');
    Pres0=data(1)*2^16+data(2)*2^8+data(3);
    if Pres0> (2^23-1)
        Pres0=Pres0-2^24;
    end
    Praw=Pres0 /Kp;
    Pres= C00 + Praw * (C10 + Praw * (C20 + Praw * C30)) + (Temp0 / Kt) * C01 + (Temp0 / Kt) * Praw * (C11 + Praw * C21);
    pause(0.1);
    disp(['Pres= ', num2str(Pres)]);

end

% ５）通信の終了
clear DPS368 a;
```
ArduinoのC言語（もどき？）をMATLABに移植していて思ったけど、MATLABって"do～while"がないですね。
あと、Arduino IDEでI2Cを記述する際の"Wireなんちゃら"に較べて、行数は確実に減ります（が、とにかくサンプルプログラムがほとんどないところが問題）。

ということで。
おつかれさまでした。