Windows IoT Core(Raspberry Pi/Arduino Wiring)とPICマイコンでSPI通信を行うテクニック
SPI communication between Windows IoT Core and PIC microcomputer
PICマイコン関連で多数の解説書を執筆している後閑哲也氏が
PICと楽しむRaspberry Pi活用ガイドブックという本を2017年4月11日に出されるということなので、パクリだといわれないように、管理人自身が勉強した成果を、取り急ぎひとつ紹介したいと思います。
今回紹介するのは見出し通り、PICマイコンに搭載されているシリアル通信モジュール・MSSPと、Windows IoT Coreを搭載したRaspberry PIもしくはArduinoとSPI形式による通信をする方法です。
以前にも紹介したように、Windows 10 IoT Coreには低レベルGPIOドライバーが搭載されており、これにはSPIの制御ルーチンも含まれています。このドライバーが最も効果を発揮するのはArduino Wiring Project(※1)なので、今回はArduino言語を用いてプログラミングしています。逆に言えば、このプログラムは簡単な修正でArduino Unoなどでも応用が可能です。
今回使用したPICマイコンは、国内の実店舗でも比較的手に入りやすい「PIC16F1827」を使用しています。ただ、PIC16F1シリーズであれば基本的な機能は同じなので、移植の手間はそこまでかからないでしょう。
Raspberry PiとPICとの通信の主従関係(マスター/スレイブ)は常にRaspberry Piが主となっています。そのため、主従関係を明確にするためSS信号を含めた4本の配線をする必要があります。この配線図ではArduinoの方式に従って、SS信号の線はCS0ではなくGPIO(5)に繋いでいます。また、PICのMISOはRaspberry Pi(Arduino)のMISOに、MOSIもMOSI同士で繋がないと動作しない点に気をつけてください。
今回使用するPIC16F1827におけるピンの役割は下図のようになっています(データシートより)。赤色で示しているのはプログラムライターの接続先です。型番の違うPICを使用するときの参考にしてください。写真では、プログラムライターとRaspberry Piの電源とを共有していますが、このような配線にする場合はMPLABプロジェクトのプロパティで「Power target circit from PICKit3」のチェックを外して、プログラムライターからの電源供給を無効にするようにしてください。
まずはPICマイコンにプログラム(MPLAB X IDE/XC8 Compiler Freeを使用)を書き込みます。今回はマスターから送られてきた1byte数値(8bitPICマイコンは1回につき8ビットのデータを送受信し、SSPBUFに格納する)を受け取り、マイコンに格納している値を1ずつ引いた値を返すというシンプルな内容にしました。main内ループでの処理だと同じデータを二重に取得するなどの問題があったため、割り込み処理で信号を処理するようにしています。SPI通信を有効にするにはPICピンの入出力(ここではRB1,RB4)も明確に定義するのが肝です。
初期設定の解説はコメントのみで割愛しますが、命令の意味をより詳しく知りたいのであれば、同じく後閑哲也氏の著書
PIC16F1ファミリ活用ガイドブックをご一読ください。
spi.c
#include <xc.h>
#pragma config FOSC = INTOSC // 内部クロックを使用
#pragma config WDTE = OFF // ウォッチドッグタイマ
#pragma config PWRTE = OFF // パワーアップタイマー
#pragma config MCLRE = ON // MCLRピンの有効・無効
#pragma config CP = OFF // コードプロテクト
#pragma config CPD = OFF // メモリーの保護
#pragma config BOREN = ON // 電圧降下監視
#pragma config CLKOUTEN = OFF // CLKOUTの有効・無効
#pragma config IESO = OFF // 外部クロックへの切り替え
#pragma config FCMEN = OFF // 外部クロックの監視
#pragma config WRT = OFF // 4KWのフラッシュメモリ自己書き込み保護
#pragma config PLLEN = ON // 4xPLLを動作
#pragma config STVREN = OFF // スタックエラー時のリセット
#pragma config LVP = OFF // 低電圧プログラミング
#define _XTAL_FREQ 32000000 // スリープ処理のための周波数定義
#define LOW 0
#define HIGH 1
unsigned char return_data = 0, read_data;
void interrupt OnInterSpi()
{
if (SSP1IF == HIGH){ // SPIデータがあるか
SSP1IF = LOW; // SPIデータをリセット
read_data = SSP1BUF;
if(return_data == 0) return_data = 255; else return_data--;
SSP1BUF = return_data;
}
}
void main(void)
{
OSCCON = 0b00110100; // 内部クロック4Mhz(4xPLLにより32MHzで動作)
ANSELA = 0b00000000; // RAはすべてデジタル
TRISA = 0b00000001; // RA0を入力端子に
PORTA = 0b00000000; // RA初期化
// SPIモードの設定と初期化
ANSELB = 0b00000000;
TRISB = 0b00010010; // RB1(7/SDI1)とRB4(10/SCK1)は入力
PORTB = 0b00000000; // RB初期化
SDO1SEL = 0; // RB2をSDOピンに設定
SSP1CON1 = 0b00100100; // スレーブでSS使用
SSP1STAT = 0b01000000; // スレーブ, クロック位相はLOW
SSP1IF = 0; // SPIの割込みフラグを初期化する
SSP1IE = 1; // SPIの割込みを許可する
PEIE = 1; // 周辺装置割込みを許可する
GIE = 1; // 全割込み処理を許可する
while(1) ;
}
Pickit3(*2)などでプログラムを書き込んだら、次はVisual Studioでのプログラミングに移りましょう。Arduino Wiring Appプロジェクトを作ったら、inoファイルに次のように記述します。
spi.ino
#include <spi.h>
#define SS_PIN 5
void setup()
{
Serial.begin(9600);
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
pinMode(SS_PIN, OUTPUT);
digitalWrite(SS_PIN, HIGH);
delay(500);
}
char msg[21];
byte i = 0, n;
void loop()
{
digitalWrite(SS_PIN, LOW);
n = SPI.transfer(i);
digitalWrite(SS_PIN, HIGH);
#ifdef _WIN_IOT
// 文字列のあふれを防ぐためのマイクロソフトの独自拡張関数
sprintf_s(msg, 20, "%d, %d\n", i, n);
#else
sprintf(msg, "%d\n", n);
#endif
Serial.print(msg);
if (i == 255) i = 0; else i++;
}
双方のプログラムが正常に動作すれば、Visual Studioのデバッガーに受信記録が列挙されていきます。今回は数値の増減が一致していますが、PICへの電源が入るタイミングによっては結果が異なることがあると思います。
また、SPI通信を使った電子パーツを扱った方はご存じかと思いますが、データの扱いが非常にややこしくなるため、1対1でデータをリアルタイムにやりとりするケースはほとんどありません。だいたいはマスターから命令番号と追加データを送り、スレーブで命令番号に対応するデータを作成して返送する(この間はダミーのデータを受信する)のが一般的です。
※1:20MHz~30MHzで動作するといわれていますが、ドライバーが未対応のためSPI1ポートが使えないというデメリットもあります。ただ、マイクロソフトのArduino関連のヘッダーファイルにはSPI1ピンの定義があるため、将来的に対応する可能性はあるでしょう。
※2:1ヶ月もたたずにコネクターがイカれて使い物にならなくなり、安物買いの銭失いになった互換品の例。電子パーツ専門店・マルツで売っている純正品と比べてもいろいろと構造が違うことがわかります。
2017/03/29