今回はArduino DueでSPI通信(Slave)をレジスタ直接制御にて実現します。
実際にはあまり使うことが少ないかもしれませんが、Master側から送信されたクロックをトリガーとし、データを更新して制御するノードです。
電子工作だとICが載ることもあると思いますが、この場合は、マイコン側がMaster側、IC側がSlave側となると思います。
まぁMaster側を作ったのでSlave側もやってみただけの自己満足です。
・前回のSPI通信(Master)の記事
Arduino DueのSPI有効化手順(大きく4つです)
①クロックの供給(Masterの設定と同じ)
Arduino Due搭載のATSAM3X8EでSPIはPID24,PID25となっています。
今回はSPI0(PID24)を使っていきます。
②マイコン端子の設定(Masterの設定とほぼ同じ)
続いて、SPIで使用するマイコン端子の設定を行います。
MISO(PA25), MOSI(PA26), SPCK(PA27),NPCS0(PA28)
NPCSは通信対象を決めるチップセレクトですが、今回はマイコンにあらかじめ準備されている機能を使用します。(GPIOでやるとGPIO側の割り込みを設定したりだるいので・・・)
マイコンの端子名で書かれているのでこれをArduino Dueのピン番号に変換します。
Arduinoのピン配置を確認するとマイコンが搭載されているすぐ下のコネクタに配置されていることが分かります。NPCS0(PA28)は10pinのことでMaster設定の時と同じです。
設定するためにはこれまでの記事にも書きましたがPIO Controllerの設定を行います。
大きな変更点として、GPIOの時は汎用入出力のため、PIO_ABSRの設定を行っていませんでしたが、今回はSPIで周辺機能を使うため、設定が必要となります。あとは、PIO_PSR[0]もPIO_PERではなく、PIO_PDRの方に値を書き込む必要があります。
③SPI関連レジスタの設定
SPIのモードとか通信速度をはじめ、通信に必要なレジスタは最低限設定は必要です。Masterの時と異なるのは、SPIのフレームを受信したときの割り込みを有効化したり、チップセレクト関係のレジスタの値の書き込みが必須となることです。
ソースコード内のコメントを参照ください。
④割り込み設定(NVICの設定)
GPIOの割り込みの時にも行いましたが、
NVICにてSPI0の割り込みを有効化する必要があります。
具体的にはSPI0はPID24なので、PID24の割り込みを有効化します。
NVIC->ISERで割り込み許可します。
後は、割り込みが入ったときの処理をSPI0のHandler関数の中に書いていくことになります。
・GPIO割り込みの記事
ソースコード例
//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の同時送信 or 受信データ数の設定。(最小8bit,最大16bitで1bit刻みで変更可能)
#define SPI_READ_BYTE 50 //SPI読み出し時に読み出す数
//////////////////////////////////////////////////////////////////////////////////////////////
//SPI信確認用のフラグとデータ格納用の変数////////////////////////////////////////////////////////////////////////////
volatile bool gSPI_Received = false;//SPI受信フラグ
volatile int gReceivedData = 0;//受信データの格納
//////////////////////////////////////////////////////////////////////////////////////////////
int scbr;//SPIの周波数を決めるための変数
//SPIモード設定用の変数
int cpol;
int ncpha;
int data_filter;
//以下、SPIのSlave設定//////////////////////////////////////////////////////////////////////////////
void spi_slave_resister_setting(){
PMC->PMC_PCER0 = 1<<24 (SPI0, PID24) にクロック供給
//PIOA25:MISO, PIOA26:MOSI, PIOA27:SPCK, PIOA28:NPCS0
//PIOA25,26,27,28はGPIOではなくPeripheralを使用する。
PIOA->PIO_PDR = 0b1111<<25;
//PIOA25,26,27,28のPeripheral A/B選択はAとする。
PIOA->PIO_ABSR = PIOA->PIO_ABSR&(~(0b1111<<25));
//PIOA25,26,27,28の出力はオープンドレインにしない。
PIOA->PIO_MDDR = 0b1111<<25;
// SPI0を使用不可にする。(この状態でレジスタ設定)
SPI0->SPI_CR = 2;
//スレーブモード時は、0bit目を0にする以外の他設定は不要。
SPI0->SPI_MR = 0;
//選択された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;
}
data_filter = 0b1111111111111111 >> (16 - SPI_BITS);
//周波数の設定をレジスタに書き込むための演算
//SPIの周波数計算
//(84MHz)/SCBR = SPI周波数
scbr = 1000*((double)84 / SPI_FREQUENCY);
SPI0->SPI_CSR[0] = (3<<24)+(scbr<<16)+(scbr<<8)+((SPI_BITS - 8)<<4)+ (ncpha << 1) + cpol;
//いったん全ての割り込みを禁止にする。
SPI0->SPI_IDR = 0xffffffff;
SPI0->SPI_IER = 1;//フレーム受信時に割り込みを発生させる。
NVIC->ISER[0] = 1<<24;//SPI0(Peripheral ID:24)の割り込み発生を許可。
// チップセレクトがアクティブの場合は起動を待つ。
// (フレームの途中から通信し始めてしまうのを防ぐ)
while (((PIOA->PIO_PDSR >> 28)&1) == LOW){}
SPI0->SPI_CR = 1;//SPI0を起動する。
}
//SPIデータ受信時の割り込み処理(Master側のCSがLoの場合、データを受信するたびに割り込みが入る)
void SPI0_Handler(){
gSPI_Received = true;//SPI受信フラグを立てる。
gReceivedData = (SPI0->SPI_RDR & data_filter);
}
//データの受信がある場合、受信したデータを読み出す
void SPI_receive(bool *spi_read_flag){
static int spi_read_counter = 0;
if(gSPI_Received == true){
if (*spi_read_flag == true){
spi_read_counter+=1;
// 受信した文字を出力する。
Serial.print(gReceivedData,HEX);
if(spi_read_counter < SPI_READ_BYTE){
Serial.println(",");
}
if(spi_read_counter == SPI_READ_BYTE){
*spi_read_flag = false;
spi_read_counter = 0;
}
}
gSPI_Received = false;
}
else{
//Serial.println("SPI_No_message");
}
}
Masterの時と同様にspi_slave_resister_setting()はsetup()で読み出し、SPI_receive(bool *spi_read_flag)をmain()から呼び出してもらうと呼び出したタイミングで受信データレジスタの値が読み出されるプログラムになっていると思います。