1.はじめに
MATLABでArduinoをプログラムするには、MATLAB Support Package for Arduino Hardwareを用いるわけですが、新しいI2Cデバイスを動かしてみたい、と思っても、ほとんどの場合は、サンプルプログラムはありません。一方でArduino IDE用のサンプルプログラムはあったりします。
本記事では、今年のEXPO LTで使いました圧力センサDPS368を例にとり、Arduino IDEのI2Cを含むプログラムをMATLAB用に移植する場合のコツをまとめました。
全体のプログラムはMATLAB CentralのFile exchangeにもアップロードしてあります。
2.最近のセンサデバイスについて
古いセンサデバイスは単機能で、温度なら温度、圧力なら圧力というように1種類のデータしか測定できないものが多く、また、精度などのパラメータも変換できないことが多かったです。
それに対して最近は、1つのデバイスで複数種類のデータを出力でき、また、条件に合わせて調整が可能になっています。
そのため、センサデバイスはセンサとインターフェースだけでなく、マイコンを内蔵したようなものになってきています。
測定条件は内部のレジスタに書き込む形になっています。
3.温度&圧力センサDPS368に関して
ここで取り上げます温度と圧力を測定できるインフィニオン社のDPS368も複数のレジスタを持ち、ここにデータを書き込むことで、測定条件を設定します。
下の表がレジスタマップです(Infineonのデータシートから引用) 。
Arduino Unoとの接続は下記のようになります。今回はDPS368 Pressure Shield2GO用評価ボードを用いました(Digi-keyから買えます)。DPS368に5V系の電源・信号を接続すると壊れるので、電源は3.3V、信号線はレベル変換器を介して接続します。
4.動作の流れの概略
動作の流れを説明します。
0)レジスタのアドレスとか、あらかじめ決まっている初期値の定義
1)センサとArduinoの通信を確立する
2)センサ固有の値を取得する
3)センサにパラメータを書き込む
4)データ要求と取得
5)通信の終了
5.Arduino IDEにおける構文とMATLABへの翻訳
5-1.値を1つだけ書き込む
リセットなどで用います。
Arduino IDEでは
Wire.beginTransmission(DPS368_address);
Wire.write(reset);
Wire.endTransmission();
MATLABに翻訳すると、
write(DPS368,Reset,'uint8');% リセット
5-2.レジスタに値を書き込む
Wire.beginTransmission(DPS368_address);
Wire.write(MEAS_CFG); // 0x08
Wire.write(0xC0);
Wire.endTransmission();
MATLABに翻訳すると、
writeRegister(DPS368,MEAS_CFG,hex2dec('C0'),'uint8');
5-3.データを1つだけ読み出す
Wire.beginTransmission(DPS368_address);
Wire.write(MEAS_CFG); //0x08
Wire.endTransmission(false);
Wire.requestFrom(DPS368_address,1);
Flag = Wire.read();
MATLABに翻訳すると、
write(DPS368,MEAS_CFG,'uint8');%
Flag=read(DPS368,1,'uint8');
5-4.データを複数個(m個)読み出す
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に翻訳すると、
for t=0:m-1
write(DPS368,COEF+t,'uint8');% 係数を要求
readbuffer(t+1)= read(DPS368,1,'uint8');% データ取込み
end
もしくは、あまりデータ数が多くない場合には(例えば3個)、
Wire.requestFrom(DPS368_address,3);
readbuffer[0] = Wire.read();
readbuffer[1] = Wire.read();
readbuffer[2] = Wire.read();
MATLABに翻訳すると、
data=read(DPS368,3,'uint8');%dataは1x3の配列になる
となります。
6.動作の全体
ということで、Arduino IDEとMATLABをまとめて示します。
まず、Arduino IDEのコードから。
//0)レジスタのアドレスとか、あらかじめ決まっている初期値の定義
# 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);//これはあまり意味はないかも
// 1)センサとArduinoの通信を確立する
Serial.begin(9600);
Wire.begin();
Wire.beginTransmission(DPS368_address);
Wire.write(RESET);
Wire.endTransmission();
delay(100);
// 2)センサ固有の値を取得する
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);
// 3)センサにパラメータを書き込む
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();
}
//4)データ要求と取得
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);
}
// 5)通信の終了は無限ループなのでありません。
ポイントとしては、圧力の測定完了を確認してからデータを取得しないとゼロしか出てこないところかと思います。測定完了までループを回して待ちます。
次にMATLABのコードです。
% 0)レジスタのアドレスとか、あらかじめ決まっている初期値の定義
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);
% 1)センサとArduinoの通信を確立する
a=arduino('COM9','Uno','Libraries','I2C');%COM番号、ボード名は適宜変更
DPS368=device(a,'I2CAddress','0x77');% I2Cデバイスをつくる
%%% Reset %%%
write(DPS368,Reset,'uint8');% リセット
pause(0.1);
% 2)センサ固有の値を取得する
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
% 3)センサにパラメータを書き込む
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
% 4)データ要求と取得
%%%%%%%%%%%%%%%%ここからループ%%%%%%%%%%%%%
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
% 5)通信の終了
clear DPS368 a;
ArduinoのC言語(もどき?)をMATLABに移植していて思ったけど、MATLABって"do~while"がないですね。
あと、Arduino IDEでI2Cを記述する際の"Wireなんちゃら"に較べて、行数は確実に減ります(が、とにかくサンプルプログラムがほとんどないところが問題)。
ということで。
おつかれさまでした。