SPIの実験 (基礎編)

〔PIC12F1822と16F819で通信〕 〔SPIデバイスを複数個接続する〕 〔マイコンのトップに戻る〕


このページでSPIに関する事を記載して行きます。
SPI:シリアル・ペリフェラル・インタフェース(Serial Peripheral Interface)

 RS232C(USART)は1対1の非同期式シリアル通信方式で伝送距離も比較的長く(15m程)取れますが、
SPIインターフェースは1対nの同期式のシリアル通信で伝送距離は基板と基板間などの装置内での短い距離です。 SPIは1対nですが、1がマスターでnがスレーブとなっています、もちろん1対1もOK。

 信号はPICならSDO(データアウト)、SDI(データイン)、SCK(シリアルクロック)です。
arduinoならMODI(マスタアウト/スレーブイン)、MISO(マスタイン/スレーブアウト)、SCKです。

 送受信2本と、マスターが生成出力する同期クロック信号(SCK)の1本、計3本で通信を行います。
また、どのスレーブと通信を行うか選択するSS信号が1本、スレーブ機器が3個有るならマスターは3本配線します。 SS信号は通常マスターからHIGHを出力して、通信したいスレーブにはLOWを出力して通信します。 また、1対1の場合でスレーブがSS信号を使わないなら省く事も可能です。
それと、クロック周期(SCK)の1クロックで1ビット送信するので通信速度はかなり早いです。

その他にPICのシリアル通信方式としてI2CとUSARTが有ります。
USARTについてはこちらを参照下さい。
I2Cについてはこちらを参照下さい。

《SPIバスの概要》

概略図1

この図の様に各機器はシフトレジスターにてリング状に接続されています。

 送信時、マスターはシフトレジスターの上位1ビット(MSB:7)を送出(シフト)します、
スレーブはその1ビットをシフトレジスターの下位(LSB:0)に入れると、上位(MSB:7)がシフトされマスター側に送り出されます、 マスター側はスレーブから送られてきた1ビットをシフトレジスータの下位(LSB:0)に入れます。 これを8回繰り返して1バイトを送信する訳です。
だから、送信と受信は同時に起こり、お互いのシフトレジスター内容が入れ替わる事になります。

PICは1バイト送信が完了するとSSPBUFレジスターにデータを格納して割込みを発生させます。
SSPBUFに送信データをセット→送信完了(割込み発生)→SSPBUFから受信データ読む

《SPIバスの接続》

接続図1
1対1の接続図
接続図2
1対nの接続図
マスター側のSSピンはデジタル出力ピンであればどれでもOKです。

《SPIの通信テスト》

 最近arduinoIDE0022でSPIライブラリが追加された様なのでSPIの実験を行ってみることにしました。
手持ちでSPI対応はarduinoとPIC16F819(SPI(SSP)機能有り)だけなので、これで通信テストを行います。
arduinoのSPIライブラリはマスターしか出来ない様で、arduinoをマスター、PICをスレーブとします。

配線

接続図3
 今回接続は上図の(接続2)を使用します。
 SSは接続しません。
 arduinoからデータを1バイトずつ1秒おきに送信
 します。
 PICはデータを受信したら1バイトおきにLCDの
 モニターに送ります。
 モニター写真
 マイコンのデバッグ用にこんなのを作っています。
 1ピン接続で送られてきたデータを表示します。
 結構便利ですよぉ。
 モニターの作成はこちらを参照して下さい。


尚、今回の実験ではPICに外部クロック(セラロック)を取付けて行っています、
16F819は内部クロックでモニター出力するとデータが化けるので外部クロック(20MHz)で行っています。
PS. *3)
現在、モニタープログラムをTimer2のタイミングで送る様に変更しました、これで化ける事は無いです。

SPI通信時に確認する事

  ※マスターはスレーブのクロック極性/位相に合わせてデータを送り出します。

PICでの操作

HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode)V9.80コンパイラでの説明です。
次の順番で行って行きます。(スレーブ設定です

@SSPCONレジスタの設定
 SSPCON=0b00100101 ;
       緑数字部分:クロック極性 0:LOW(アイドルは0V、アクティブは5V)
                        1:HIGH(アイドルは5V、アクティブは0V)
       赤数字部分:SCKの生成クロック選択とマスター・スレーブの選択
              '0000' = Fosc/4 (Fosc:クロック用発振周波数)
              '0001' = Fosc/16
              '0010' = Fosc/64
              '0011' = TMR2の出力の1/2
              '0100' = スレーブモードでSS使う
              '0101' = スレーブモードでSS使わない
              (尚、'0100'と'0101'以外はマスター選択です)
       それ以外の数字部分はこのまま('001')使用します。

ASSPSTATレジスタの設定
 SSPSTAT=0b00000000 ;
       緑数字部分:スレーブなら 0
               マスターなら 0:入力データの中央でサンプリング
                        1:入力データの末尾の縁でサンプリング
       赤数字部分:クロック位相 0:アイドルからアクティブへ変化した時データ送信
                         極性LOW( ___| ̄|___ )   極性HIGH(  ̄|___| ̄ )
                        1:アクティブからアイドルへ変化した時データ送信
                         極性LOW( ___| ̄|___ )   極性HIGH(  ̄|___| ̄ )

               ※ クロック位相の0/1はPICとArduinoでは逆です注意。 *4)

       青数字部分:SSPBUFの受信状態 1:受信完了 0:受信未完了

B割込みの許可
 SSPIF= 0 ; // SPIの割込みフラグを初期化する
 SSPIE= 1 ; // SPIの割込みを許可する
 PEIE = 1 ;  // 周辺装置割込みを許可する
 GIE = 1 ;  // 全割込み処理を許可する

Cデータを受信すれば、割込み関数が動き出すのでSSPBUFから読み込みます。

PIC(16F819)側のプログラム例

このプログラムにはデバッグモニタープログラム「skMonitorLCD.c」「skMonitorLCD.h」が必要です。
デバッグモニターについてはこちらを参照して下さい。

尚、LCDモニターの出力はRA1から行っているので「skMonitorLCD.h」を下記の様に変更します。
#define MONITOR_PIN RA1        // モニタ出力するピンの番号を設定する

また、CPUクロックはここでは20MHzで行っているので次の様に変更します。 *3)
#define _XTAL_FREQ 20000000  // 使用するPIC等により動作周波数値を設定する
#define BAUDRATE 129      // 9600bps(8MHz=51)(4MHz=25)(16MHz=103)(20MHz=129)

MPLABにてプロジェクトを作成して新規ファイルにコピーペーストして貼り付けて下さい。
次にコンパイルPIC書き込みを実行して下さい。
HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode)V9.80コンパイラを使用しています。 *3)
MPLAB(R) XC8 C Compiler Version 1.00コンパイラを使用しています。
---------------------------------------------------------------------
#include <xc.h>
#include "skMonitorLCD.h"      // これはLCDモニター用です

#define _XTAL_FREQ  20000000   // delay用(クロック20MHzで動作時)

unsigned int RCV_Buff ;
int Flag ;

// デバッグしない(DEBUGDIS):低電圧プログラミング機能使用しない(LVPDIS)
// メモリを保護しない(UNPROTECT):外部リセット信号は使用せずにデジタル入力(RA5)ピンとする(MCLRDIS)
// 電源電圧降下常時監視機能ON(BOREN):電源ONから72ms後にプログラムを開始する(PWRTEN)
// ウオッチドックタイマ無し(WDTDIS):外部クロックを使用する(HS)
__CONFIG(DEBUGDIS & LVPDIS & UNPROTECT & MCLRDIS & BOREN & PWRTEN & WDTDIS & HS) ;

// SPIの割込み関数
void interrupt InterSPI( void )
{
     if (SSPIF == 1) {         // SPI:データを受信
          RCV_Buff = SSPBUF ;
          Flag = 1 ;
          SSPIF = 0 ;          // 受信フラグをリセット
     }
}
// メインの処理
void main()
{
     ADCON1 = 0b00000110 ;     // アナログは使用しない、RA0-RA4をデジタルI/Oに割当
     TRISA  = 0b00000000 ;     // 1で入力 0で出力 RA0-RA7全て出力に設定(RA5は入力専用)
     TRISB  = 0b00010010 ;     // 1:in 0:out SDI(RB1:in) SDO(RB2:out) SCK(RB4:in) SS(RB5:未)
     PORTA  = 0b00000000 ;     // 出力ピンの初期化(全てLOWにする)
     PORTB  = 0b00000000 ;     // 出力ピンの初期化(全てLOWにする)

     // SPIモードの設定と初期化
     SSPCON = 0b00100101 ;     // クロック極性はLOW スレーブモードでSS使わない
     SSPSTAT= 0b00000000 ;     // クロック位相は0(立下りエッジで読込む) *4)
     SSPIF= 0 ;                // SPIの割込みフラグを初期化する
     SSPIE= 1 ;                // SPIの割込みを許可する
     PEIE = 1 ;                // 周辺装置割込み有効
     GIE  = 1 ;                // 全割込み処理を許可する
     Flag = 0 ;

     // LCDモニターを使用する為の初期化処理 *3)
     MonitorInit() ;

     while(1) {
	     if (Flag == 1) {
               // 受信したらモニターに出力(1バイト受信)
               MonitorPutc(0x11) ;
               MonitorPuts("        ") ;
               MonitorPutc(0x11) ;
               MonitorPutc(RCV_Buff) ;
               Flag = 0 ;
          }
     }
}
---------------------------------------------------------------------

Arduinoでの操作

arduino IDE 0022 での説明です。
次の順番で行って行きます。(マスター設定です

@SPI.begin() : SPIを行う為の初期化です。
 13番ピン(SCK)出力 12番ピン(MISO)入力 11番ピン(MOSI)出力 10番ピン(SS)出力
 と設定されます。尚、SSピンは使わない場合でも出力状態のままにしておく必要があります。

ASPI.setBitOrder(MSBFIRST) : ビットオーダーの設定
 MSBFIRSTとLSBFIRSTが有ります。

BSPI.setClockDivider(SPI_CLOCK_DIV8) : シリアルクロック(SCK)の設定
 SPI_CLOCK_DIV2/SPI_CLOCK_DIV4/SPI_CLOCK_DIV8/SPI_CLOCK_DIV16/SPI_CLOCK_DIV32/
 SPI_CLOCK_DIV64/SPI_CLOCK_DIV128(SPI_CLOCK_DIV8ならシステムクロック/8です)

CSPI.setDataMode(SPI_MODE1) : クロック極性 クロック位相 の設定  *1) *2)
Mode クロック極性 クロック位相 説明
0(LOW:0V) アイドル0Vで5V→0Vの変化で送信する
0(LOW:0V) アイドル0Vで0V→5Vの変化で送信する
1(HIGH:5V) アイドル5Vで0V→5Vの変化で送信する
1(HIGH:5V) アイドル5Vで5V→0Vの変化で送信する

SPI_MODE0
波形図(MODE0)
SPI_MODE1
波形図(MODE1)
SPI_MODE2
波形図(MODE2)
SPI_MODE3
波形図(MODE3)

Dデータの送信と受信を行います。
 ans = SPI.transfer(value) ;
 value:送信する1バイトデータ
 ans :送信後、受信される1バイトデータ

Arduino側のスケッチプログラム例

IDEに下記のスケッチプログラムをコピーペーストして貼り付けて下さい。
次にIDEツールバーの「Upload」ボタンをクリックしてコンパイルとarduinoボードに書込みを行います。
---------------------------------------------------------------------
#include <SPI.h>

void setup()
{
     SPI.begin() ;                        // SPIを行う為の初期化
     SPI.setBitOrder(MSBFIRST) ;          // ビットオーダー
     SPI.setClockDivider(SPI_CLOCK_DIV8) ;// クロック(CLK)をシステムクロックの1/8で使用(16MHz/8)
     SPI.setDataMode(SPI_MODE1) ;         // クロック極性0(LOW) クロック位相1(0V→5Vの変化で送信)

     delay(5000) ;  // 5Sしたら開始
}
void loop()
{
     int i ;
     byte ans ;

    // LCDに表示しやすい用にキャラクター文字を送っています。
    for (i = 0x30 ; i < 0x7b ; i++) {
          ans = (byte)i ;
          ans = SPI.transfer(ans) ;
          delay(1000) ;
    }
}
---------------------------------------------------------------------
こんな感じでテストしました。
実験風景

まとめ

 意外と比較的簡単に送受信が行えました。
 スレーブからは自由に送信が行えなさそう、
 マスターからのアクションが無いと送れないのかな?

 解せない点1
 arduinoにUSB接続で送信していると、しばらく
 (1分程)するまでは送信データ内容がおかしい、
 しばらくした後は正常にデータが送られている。
 USB接続でなく、外部電源の電池で行うと最初から
 正常なデータを送っている。
 どぉ〜いうことやねん! わからん!

 解せない点2
 PIC側の設定がクロック極性0(LOW)で、
           クロック位相0(立上り)に設定
 arduino側をSPI.setDataMode(SPI_MODE0)で
 クロック極性0・クロック位相0だとデータ内容が変
 (SPI_MODE1)のクロック極性0・クロック位相1なら
 正常にデータが送れる。
 なぜだぁ!、設定は合わせるのではないんかい!
 PS.  *2)
 ロジアナで見たらSPI_MODE1で良さそうですね。
 (アイドル0Vで0V→5Vの変化でPICは待つので)


 PS.  *4)
 PICとArduinoのクロック位相0と1の設定は逆でした、PICが0ならArduinoは1って感じになります。


解せない点が解決したらHP更新します。

PIC12F1822(マスター)とPIC16F819(スレーブ)でのPIC間SPI実験2はこちらの記事を参照下さい。
ArduinoのSPIでEEPROM(AT93C86)と接続して読み書きを行って見ますの記事はこちらを参照下さい。
ArduinoのSPIで大気圧センサー(MPL115A1)を使用した記事はこちらを参照下さい。



記事一部変更(*4) 2016/06/13
記事見直し(*3) 2014/09/23
追記(*2) 2014/06/07
追記(*1) 2013/02/12


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2016 Shigehiro Kimura All Rights Reserved.