今回はSPI通信についてまとめます。
最近、回路設計においてICを多数使うことが多く、SPI通信が必要なものが多いです。
事前にICの動作を確認したくても、メーカーのGUI開発が追い付いていないこともあるのでそういった場合は自分で環境を構築する必要があります。
よく使うのはMasterモードだと思うのでレジスタ直接制御で実現していきます。
SPI通信(Master)の概略
SPIのプロトコルについては説明は割愛しますが、クロックに同期させてデータの通信を行う同期式シリアル通信のひとつです。一つのMasterに対して複数のSlaveと通信をすることができます
目次
Arduino DueのSPI有効化手順(大きく3つです)
①クロックの供給
Arduino Due搭載のATSAM3X8EでSPIはPID24,PID25となっています。
今回はSPI0(PID24)を使っていきます。
②マイコン端子の設定
続いて、SPIで使用するマイコン端子の設定を行います。
MISO(PA25), MOSI(PA26), SPCK(PA27)
NPCSは通信対象を決めるチップセレクトですが、複数のICと通信することを想定してGPIOで実現することとします。
マイコンの端子名で書かれているのでこれをArduino Dueのピン番号に変換します。
Arduinoのピン配置を確認するとマイコンが搭載されているすぐ下のコネクタに配置されていることが分かります。GPIOは10pinを割り当てることにします。
設定するためにはこれまでの記事にも書きましたがPIO Controllerの設定を行います。
大きな変更点として、GPIOの時は汎用入出力のため、PIO_ABSRの設定を行っていませんでしたが、今回はSPIで周辺機能を使うため、設定が必要となります。あとは、PIO_PSR[0]もPIO_PERではなく、PIO_PDRの方に値を書き込む必要があります。
③SPI関連レジスタの設定
以下がSPIに関連するレジスタです。大量にありますね・・・
SPIのモードとか通信速度をはじめ、通信に必要なレジスタは最低限設定は必要です。
(ソースコードに少しコメントは残しました)
ソースコード例
//SPIの動作モード設定用のパラメータ///////////////////////////////////////////////////////////////
#define SPI_MODE 0 //SPIのモード選択(SPIモード0~3)制御対象のSPIプロトコルを確認して決定する。
#define SPI_FREQUENCY 2000 //SPIの周波数を選択(kHz単位で記入する。例:500KHz⇒500, 2MHz⇒2000)
#define SPI_BITS 16 //SPIのデータ長の設定。(最小8bit,最大16bitで1bit刻みで変更可能)
//////////////////////////////////////////////////////////////////////////////////////////////
int scbr;//SPIの周波数を決めるための変数
//SPIモード設定用の変数
int cpol;
int ncpha;
int data_filter;
//SPIのMaster設定
void spi_master_resister_setting(){
PMC->PMC_PCER0 = 1<<24 (SPI0, PID24) にクロック供給
//PIOA25:MISO, PIOA26:MOSI, PIOA27:SPCK
//PIOA25,26,27はGPIOではなくPeripheralを使用する。
PIOA->PIO_PDR = 0b0111<<25;
//PIOA25,26,27のPeripheral A/B選択はAとする。
PIOA->PIO_ABSR = PIOA->PIO_ABSR&(~(0b0111<<25));
//PIOA25,26,27の出力はオープンドレインにしない。
PIOA->PIO_MDDR = 0b0111<<25;
//SPIのCSはGPIOにて制御する。(10pin)
PIOC->PIO_OER = 0x20000000;
PIOC->PIO_PER = 0x20000000;
PIOC->PIO_MDDR = 0x20000000;
PIOC->PIO_SODR = 0x20000000;//10番ピンはイニシャルHiにしておく(CSはイニシャルHiにしておく。)
//SPI設定
SPI0->SPI_CR = 0x00000010;//レジスタ設定中はSPI通信無効化
SPI0->SPI_MR = (84<<24)+0b10011;//マスターモード、chip selectは1つ。
//割り込みは禁止する
SPI0->SPI_IDR = 0xFFFFFFFF;
//チップセレクトの割り込みはしない。
SPI0->SPI_IER = 0x00000000;
//選択されたSPI_MODEに基づいて、SPIクロックの極性と同期のタイミングを決める。
if(SPI_MODE == 0){
cpol = 0;
ncpha = 1;
}
else if(SPI_MODE == 1){
cpol = 0;
ncpha = 0;
}
else if(SPI_MODE == 2){
cpol = 1;
ncpha = 1;
}
else if(SPI_MODE == 3){
cpol = 1;
ncpha = 0;
}
//通信速度をレジスタに書き込むための演算
//SPIの周波数計算
//(84MHz)/SCBR = SPI周波数
scbr = 1000*((double)84 / SPI_FREQUENCY);
//チップセレクトレジスタ設定(送信データ長は16bit)
SPI0->SPI_CSR[0] = (3<<24)+(84<<16)+(scbr<<8)+((SPI_BITS - 8)<<4)+ (ncpha << 1) + cpol;//DLYBCTおよびDLYBSはCSをGPIOで実現するため、設定は不要。
//下位2bitはクロックの極性0:inactive Lo,1:inactive Hi
//その先の2bitは1:立ち上がりor 0:立ち下がり同期
SPI0->SPI_CR = 0x00000001;//レジスタ設定が完了したのでSPI通信有効化
}
//以下、送信設定をする関数///////////////////////////////////////////////////////////////////////////////////
//SPI送信設定(凝ったデータを送信したければ、ここを改造する)
//デフォルトではspi_send_dataを送信するデータズバリの変数となっている
void SPI_transfer(int spi_send_data){
PIOC->PIO_CODR = 0x20000000;//10Pin(CS)Low出力
delayMicroseconds(2);
//SPI通信用のデータを送信する。
SPI0->SPI_TDR = spi_send_data;
//データ送信完了まで待つ
while(((SPI0->SPI_SR >> 9)&1) == 0){};
PIOC->PIO_SODR = 0x20000000;//10Pin(Cs)HIGH出力
delayMicroseconds(2);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
コードの中に出てくるSPIのモードはデータの更新タイミングに関わってくるもので、Slave側の仕様によって変わります。制御対象のデータシートを確認して設定を行います。
Arduino Dueの仕様上、データは最大で16bitまでしか送信が出来ないため、それ以上のデータを送信したい場合は分けて送信する必要があるのでプログラムの改造が必要になります。(25bitだったら16bit+9bitに分けるとかね)
上記のプログラムはSPI通信(Master)の関数部分しか添付していないので、実際には
void setup()中でvoid spi_master_resister_setting()を呼び出してもらい、void main()でSPI_transfer(int spi_send_data)を呼び出してもらえれば通信可能だと思います。
ICによってチップセレクトをActiveにしてからクロックを受け取るまでの時間やデータの書き込み時間に制約があったりするので、必要に応じてdelay()の値を見直したりも必要に応じて行います。今回のプログラムはSlave数が1を想定して作りましたので、通信バスに複数のSlaveが存在する場合はチップセレクトを複数準備する必要があります。
長くなりましたが、今回はこのあたりで。