SPIバスは、I2Cバスより高速転送の用途で使われます。I2Cはデフォルトで100kHz、少し高速では400kHzが多いようです。規格自体は1MHzを超える速度をサポートしますし、対応するセンサも少なからずあります。
SPIバスは、CS(チップ・セレクト信号。別名CE、SS)をデバイスごとに用意しなくてはなりませんが、クロックSCK、MOSI、MISOは共通に配線できます。
前回と同様に、マイコンはArduino MKR ZEROを使います。
24ビットADCのADS1256を利用
アマゾンやebayで3500円前後で販売されているADCボードがあります。入力は8チャネルあり、差動で使うときは4チャネルです。デフォルトでCh0とCh1の差動入力になっています。
裏面に接続端子名が書かれています。

DRDYを除いて、次のように配線しました。
ADCボード | MKRZero |
---|---|
5V | 5V |
GND | GND |
SCLK | SCK |
Din | MOSI |
Dout | MISO |
DRDY | - |
CS | 7 |
POWN | Vcc |
製品には回路図がついていません。ボードの電源は5VとPOWVの2本があります。アナログ・デバイセズの基準電圧源ADR03が載っています。3.0V用ですが、
- 5V端子に5V、POWV端子に3.3Vを接続したとき、Vref出力は2.5V
- 5V端子とPOWV端子に3.3Vを接続したとき、Vref出力は2.0V
でした。
データシートによれば、5Vはアナログ電源、POWVはディジタル電源のようです。
24ビットADCが搭載されています。データは分解能は24ビットですが、確度が24ビットあるわけではありません。24ビットが確保できれば、DMMでいうところの6 1/2桁から7 1/2桁に相当します。市販では15~50万円します。
利用できる関数
matlabのアドオン管理を立ち上げ、ドキュメンテーションを開くからFunctionsのタブを開きます。
二つしかありません。
初期化の部分でdeviceを使います。チップ・セレクトSPIChipSelectPinは7ピンを使います。モードSPIModeは通常使われる0ではなく1を指定します。テキサス・インスツルメンツのADCでは一般的です。さらに、データが用意できたことを示す信号DRDYが用意されています。
データ転送速度BitRateは100kbpsと遅くしました。BitOrderはデフォルトがmsbfirstなので、書かなくてもよいです。
clear all
clear a;
a = arduino('COM10', 'MKRZero', 'Libraries', 'SPI');
ads1256 = device(a, 'SPIChipSelectPin', 'D7', 'SPIMode', 1, 'BitRate', 100000, 'BitOrder', 'msbfirst');
チップ・セレクト信号は自動
SPIのリード/ライトは、CS信号をHighからLowに変化させるところから始まります。すべてのやり取りが終わったら、CS信号をLowからHighに変化させて終了です。ArduinoやPython、C言語のライブラリでは、このCSをプログラムで制御しますが、matlabは自動です。
writeRead()はリードとライトの両方に使う関数
基本形は、writeRead(ads1256, data, 'uint8')
です。ads1256は、初期化のところで作ったデバイスのインスタンス名です。型'uint8'は省略できます。
リセットは単純です。1バイトだけを書き込みます。
RESET = 0xfe;
writeRead(ads1256, RESET);
pause(1);
オシロスコープの上(赤色)はクロックで、下の青色がマイコンから出ていく信号MOSIです。
次に、ステータスを読み出します。RREG | statusRegister
したレジスタを読みますが、0と0xffというダミーの合計3バイトを送っています。3バイトをMOSIから送ると、MISOから3バイト受信するのがSPIのプロトコルです。最後の3バイト目にデータが入っています。送るデータは読み出しなので、dataInという変数名を使っています。
受け取ったデータは0x00110000もしくは0x00110001です。LSBはDRDYビットなので、タイミングによってどちらのデータになるかはわかりません。上位バイトの0011はIDなので、デバイスによって異なるかもしれません(リード・オンリ)。
RREG = 0x10;
statusRegister = 0x00;
readstatusRegister = bitor(RREG, statusRegister);
dataIn = [readstatusRegister 0 0xff];
readstatusRegisterData = writeRead(ads1256, dataIn, 'uint8');
fprintf('\nread status is %08s \n', dec2bin(readstatusRegisterData(3)));

次は、デフォルトでは行われていないオート・キャリブレーションをONにします。
WREG | statusRegister
を書きこみたいです。このオート・キャリブレーションはステータス・レジスタの下から3ビット目です。3バイト目のautoCaliblationは、その3ビット目を1にしたデータです。ほかを全部0にしても、問題は起こりません。2バイト目はダミー・データの0です。送るデータは書き込みなので、outという変数名を使っています。
0.1秒たって、ステータスを読み出します。3バイト目にステータスが入っています。受け取ったデータは0x00110100でした。正しく書き込めています。
WREG = 0x50;
autoCaliblation = 0b00000100;
autoCaliblationData = bitor(WREG, statusRegister);
out = [autoCaliblationData 0 autoCaliblation];
writeRead(ads1256, out, 'uint8') ; % write
pause(0.1);
dataIn = [readstatusRegister 0 0xff];
read_statusRegister = writeRead(ads1256, dataIn, 'uint8');
fprintf('\nread status is %08s \n', dec2bin(read_statusRegister(3)));
次は、内蔵アンプPGAの設定です。デフォルトの1倍のまま書き込んで、読み出しています。
読み出したのは、00100000です。
PGAReg = 0x02;
PGAgainData = bitor(WREG, PGAReg);
out = [PGAgainData 0 0b00100000]; % x1
writeRead(ads1256, out, 'uint8'); % write
pause(0.5);
readPGAReg = bitor(RREG, PGAReg);
dataIn = [readPGAReg 0 0xff];
read_PGAReg = writeRead(ads1256, dataIn, 'uint8');
fprintf('\nread PGAReg is %08s \n', dec2bin(read_PGAReg(3)));
最後に、データ変換レートを設定し、読みだします。デフォルトは最大速の30,000SPSです。それを、一番遅い2.5SPSに変更します。遅くすると、内部で平均化処理をしてくれます。
ADDataRateReg = 0x03;
DataRateReg = bitor(WREG, ADDataRateReg);
out = [DataRateReg 0 0b00000011]; % = 2.5SPS
writeRead(ads1256, out, 'uint8'); % write
pause(0.5);
readDataRateReg = bitor(RREG, ADDataRateReg);
dataIn = [readDataRateReg 0 0xff];
read_ADDataRateReg = writeRead(ads1256, dataIn, 'uint8');
fprintf('\nread DataRateReg is %08s \n', dec2bin(read_ADDataRateReg(3)));
このレジスタは、なぜか全部0や全部fなどになり、正しく読み出せないです。原因は不明です。0 0xffの間に少しpauseすると読めるかもしれません。
メイン
[0x01 0xff 0xff 0xff]の最初の0x01はRDATAコマンドで、残りは全部ダミー・データです~~全部ダミー・データです。~~最初の01を送ると、タイムチャートからすぐにDRDY信号が返ってきます。この信号は、DRDY端子にも出て利用できますが、ここではどこともつないでいません。本来、DRDY信号を確認してから次の動作をしますが、そういう正しい使いかたをしているわけではありません。データの転送速度との兼ね合いで、正しくDRDY信号が変化した後に次のダミーデータを送っていない場合も考えられます。
そのあとの3バイトで24ビット・データを受け取ります。最初が上位バイト、次が中位バイト、最後が下位バイトです。上位バイトを16ビット・シフトし、中位バイト8ビット・シフトし、下位バイトを加算します。これで、24ビット・データになりました。
2の補数形式なので、符号の判断をしないといけないですが、省略しています。このADCは±のデータを読めます。
pause(1);
for i=1:10
adcData = writeRead(ads1256, [0x01 0xff 0xff 0xff], 'uint8');
%fprintf('\nread data is %08s %08s %08s \n', dec2bin(adcData(2)), dec2bin(adcData(3)), dec2bin(adcData(4)) )
%fprintf('\nread data is %d %d %d \n', adcData(2), adcData(3), adcData(4))
ad = double(adcData(2))*65536 + double(adcData(3))*256 + double(adcData(4));
adc = 2 * Vref * ( ad / 8388607.0);
fprintf('read CH0-CH1 is %.8f \n', adc);
pause(0.38);
end
オシロスコープの上(赤色)がクロックで、下は、マイコンに送られてくるMISO信号です。
校正
基準電圧が2.5Vちょうどとは限りません。多桁のDMMを用いて、校正してください。筆者は、電圧発生器で、1.0000Vと0.5000Vで誤差が少なくなるようにしました。
Vref = 2.5 - 0.003;
オシロスコープは、mode0状態でデコードしているので、実際の値と異なる部分があるかもしれません。
SPIのプロトコル上クロックを発生しないと、データーを受け取れないので、頻繁にダミーデータを送っています。0xffを利用しているところは、とても一般的です。このデバイスで0x00は固有のダミーです。なくてもよいかもしれません。
次回、全チャネルの測定機能を実装して全プログラムを掲載する予定です。