マスターにSPIデバイスを複数個接続して見ます

〔ArduinoとPIC16F819で通信と基礎編〕 [PIC12F1822と16F819で通信] 〔マイコンのトップに戻る〕


前回の記事までは、マイコン(マスター)とデバイス(スレーブ)の1対1での通信でした、
ですのでSPIの基本は実験1実験2の記事を参照下さい。
このページではマイコン(マスター)と複数のデバイス(スレーブ)の1対nでの通信記事を書きます。

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

マスターがスレーブと通信する場合は、該当スレーブのSS(CS)信号をLOWにして通信します、
なので例えば、スレーブ2と通信する場合は、SS1=HIGH SS2=LOW として通信する事になります。
通信しない他のスレーブは必ずSS=HIGHとして下さい、
 又、マスターの電源が投入された直後は全てのSSをSS=HIGHとします。


下図はSPI通信の処理概略フロー図です。
SPIの処理流れ図

今回はスレーブ1はSDカードで、スレーブ2はMCP3208(12-Bit A/D Converters)での実験ですが、
MCP3208は初期化処理は必要ないので有りません。

《PIC》

Arduino実験風景  先ずは実験風景から
 [構成]
 電源は3.3Vで統一
 マスター:
  PIC18F25K22(32MHz)
  SDI(SSP1):RC4
  SDO(SSP1):RC5
  SCK(SSP1):RC3
 スレーブ1:MicroSD
  SS1(CS):RC2
  SPIモード:SPI_MODE3 *2)
  SPI速度は4MHz

 スレーブ2:MCP3208(ADC)
  SS2(CS):RC1
  SPIモード:SPI_MODE1 *2)
  SPI速度は2MHz
 表示用にI2C接続のLCD
  SDA(SSP2):RB2
  SCL(SSP2):RB1

動作

SS1(RC2)とSS2(RC1)ピンをHIGHにします。
LCD(I2C)の初期化を行いLCDに"Start"を表示させます。
次にSPIの初期化を行い、SDカードの初期化をします。
3秒後に開始します。
while(1) {
 SPIの設定をMCP3208用(SPI_MODE1/2MHz)にし、 *2)
 MCP3208のチャンネル0から半固定抵抗の値を読み出してLCDの1行目に表示します。
 1秒待つ
 SPIの設定をSDカード用(SPI_MODE3/4MHz)にし、 *2)
 "TEST.TXT"ファイルの1行目を読出してLCDの2行目に表示します。
 1秒待つ
 "TEST.TXT"ファイルの2行目を読出してLCDの2行目に表示します。
 1秒待つ
}

サンプルプログラム

下記のサンプルはメインファイルの部分のみです。
---------------------------------------------------------------------
// 18F25K22
// SDとMCP3208(ADC)をSPIの同一バス上で接続し動作させるテスト
#include <xc.h>
#include <stdlib.h>
#include <string.h>
#include "skSPIlib.h"
#include "skSDlib.h"
#include "skI2Clib.h"
#include "skI2CLCDlib.h"

#define _XTAL_FREQ       32000000  // delay用に必要(クロック32MHzを指定)
#define ADC_CS           LATC1     // MCP3208のCSピン
//#define CS             LATC2     // SDカード選択信号(skSDlib.hで宣言されている)

//  プロトタイプの宣言
void Wait(unsigned int num) ;
int ADC_analogRead(int channel) ;

// コンフィギュレーションの設定(ここの記述に無い設定はデフォルト値での動作)
// 外部クロックは使用しない(PRICLKEN_OFF)
// 動作クロックを4倍では動作させない(PLLCFG_OFF):内部クロックを使用する(INTIO7)
#pragma config PRICLKEN=OFF,PLLCFG=OFF,FOSC=INTIO7     // CONFIG1H
// 電源電圧降下常時監視機能ON(BOREN_NOSLP):監視電圧は(2.85V)に設定
// 電源ONから後65.6msにプログラムを開始する(PWRTEN_ON)
#pragma config BOREN=NOSLP,BORV=285,PWRTEN=ON          // CONFIG2L
// ウオッチドッグタイマー無し(WDTEN_OFF)
#pragma config WDTEN=OFF                               // CONFIG2H
// 外部リセット信号は使用せずにデジタル入力(RE3)ピンとする(MCLRE_INTMCLR)
// オシレータが安定するのを待ってシステムクロックを供給する(HFOFST_OFF)
#pragma config MCLRE=INTMCLR,HFOFST=OFF                // CONFIG3H
// 低電圧プログラミング機能使用しない(LVP_OFF)
#pragma config LVP=OFF                                 // CONFIG4L

// 割り込みの処理
void interrupt InterFunction( void )
{
     // I2C関連の割り込み(LCDで利用)
     InterI2C() ;
}
// メインの処理
void main()
{
     struct SDFILE_OBJECT fp ;// *3)
     int  ans ;
     char dt[32] ;

     PLLEN  = 1;              // 内部クロックを4x倍で利用する
     OSCCON = 0b01100000;     // 内部クロックとする(8MHzx4=32MHz)
     INTCON2bits.RBPU = 0 ;   // 内部プルアップを行う
     WPUB   = 0b00000110 ;    // RB1/RB2をプルアップする
     ANSELA = 0b00000000 ;    // AN0-4アナログは使用しない、デジタルI/Oに割当
     ANSELB = 0b00000000 ;    // AN8-13アナログは使用しない、デジタルI/Oに割当
     ANSELC = 0b00000000 ;    // AN14-19アナログは使用しない、デジタルI/Oに割当
     TRISA  = 0b00000000 ;    // RA0-RA7全て出力に設定
     TRISB  = 0b00000110 ;    // RB1(SCL)RB2(SDA)は入力、他は全て出力、1で入力 0で出力
     TRISC  = 0b00010000 ;    // RC4(SDI)は入力、他は全て出力、1で入力 0で出力
     PORTA  = 0b00000000 ;    // 出力ピンの初期化(全てLOWにする)
     PORTB  = 0b00000000 ;    // 出力ピンの初期化(全てLOWにする)
     // 使用するSSピンをHIGHに設定する
     PORTC  = 0b00000110 ;    // 出力ピンの初期化(SPIのCS以外はLOW)

     // LCDモジュールの初期化処理
     // アイコン未使用,コントラストは中位,VDD3.3V,16文字
     InitI2C_Master() ;
     LCD_Init(LCD_NOT_ICON,32,LCD_VDD3V,16) ;
     LCD_Puts("Start") ;
     // SPIの初期化を行う処理
     // CLK極性:1 CLK位相:0 (アイドル5Vで、5V -> 0Vに変化で転送) 通信速度(32MHz/16)
     SPI_Init(SPI_MODE3,SPI_CLOCK_DIV16,16) ;          // SD用にて初期化 *2)

     // MMC/SDCの初期化を行う
     ans = SD_Init() ;
     if (ans != 0) {
          // SDカード初期化エラー(カード未挿入かも?)
          LCD_SetCursor(0,1) ;
          LCD_Puts("Error SD_Init") ;
          while(1) ;     // 終了
     }

     Wait(300) ;         // 3秒後に開始

     while(1) {
          // MCP3208のCH0から読み込んで表示を行う
          SPI_setDataMode(SPI_MODE1) ;                 // *2)
          SPI_setClockDivider(SPI_CLOCK_DIV16,0) ;     // 2MHz
          ans = ADC_analogRead(0) ;
          itoa(dt,ans,10) ;
          LCD_SetCursor(0,0) ;
          LCD_Puts("      ") ;
          LCD_SetCursor(0,0) ;
          LCD_Puts(dt) ;
          Wait(100) ;
          // SDから読み込んで表示を行う
          SPI_setDataMode(SPI_MODE3) ;                 // *2)
          SPI_setClockDivider(SPI_CLOCK_DIVADD,1) ;    // 4MHz
          ans = SD_Open(&fp,"TEST.TXT",O_READ) ;           // ファイルのオープン  *3)
          if (ans == 0) {
               memset(dt,0x00,sizeof(dt)) ;
               ans = SD_fGets(&fp,dt,18) ;                 // ファイルから1行読込む  *3)
               LCD_SetCursor(0,1) ;
               LCD_Puts("                ") ;
               LCD_SetCursor(0,1) ;
               LCD_Puts(dt) ;
               Wait(100) ;
               memset(dt,0x00,sizeof(dt)) ;
               ans = SD_fGets(&fp,dt,18) ;                 // ファイルから1行読込む  *3)
               LCD_SetCursor(0,1) ;
               LCD_Puts("                ") ;
               LCD_SetCursor(0,1) ;
               LCD_Puts(dt) ;
               SD_Close(&fp) ;                            // ファイルのクローズ  *3)
          } else {
               LCD_SetCursor(0,1) ;
               LCD_Puts("Error SD_Open") ;
          }
          Wait(100) ;
     }
}
// ADC_analogRead(channel)   MCP3208からアナログ値を読み取る処理
//  channel : 読み取るチャンネルを指定する(0-7ch)
int ADC_analogRead(int channel)
{
     int d1 , d2 ;

     // ADCから指定チャンネルのデータを読み出す
     ADC_CS = 0 ;              // SS(CS)ラインをLOWにする
     d1 = SPI_transfer( 0x06 | (channel >> 2) ) ;
     d1 = SPI_transfer( channel << 6 ) ;
     d2 = SPI_transfer(0x00) ;
     ADC_CS = 1 ;             // SS(CS)ラインをHIGHにする

     return (d1 & 0x0F)*256 + d2 ;
}
//  Wait(num)   時間待ちの処理を行う
//   num : 10ms単位で指定する
void Wait(unsigned int num)
{
     int i ;

     // numで指定した回数だけ繰り返す
     for (i=0 ; i<num ; i++) {
          __delay_ms(10) ;    // 10msプログラムの一時停止
     }
}
---------------------------------------------------------------------

《Arduino》

PIC実験風景  次はArduinoでの実験風景です。

 MCP3208とLPS25H(気圧センサ)での実験ですが
 LPS25Hが電源3.3Vなので、Arduino Zero Proで
 動作確認を行っています。
 (Arduino Unoの場合は、電圧レベル変換が必要です)
 LPS25Hの話はこちらを参考にして下さい。

 [構成]
 マスター:
  Arduino Zero Pro(48MHz)
  MISO:ICSP-1番ピン
  MOSI:ICSP-4番ピン
  SCK :ICSP-3番ピン
 スレーブ1:LPS25H
  SS1(CS):デジタル9番ピン
  SPIモードはSPI_MODE3
  SPI速度は1MHz

 スレーブ2:MCP3208(ADC)
  SS2(CS):デジタル10番ピン
  SPIモードはSPI_MODE1
  SPI速度は1MHz

 表示はArduinoIDEのシリアルモニターです。
 LPS25H/MCP3208ともにSPI速度は1MHz統一です。

動作

動作的には各1秒毎にMCP3208のアナログ値とLPS25Hの大気圧・温度・高度を読み込んで
シリアルモニターに表示をさせています。

サンプルスケッチ

下記のサンプルはメインファイルの部分のみです。
---------------------------------------------------------------------
// 大気圧センサLPS25H(SPI)とMCP3208(ADC-SPI)の同時読み込みテスト
#include <SPI.h>
#include "skLPSxxSPI.h"

#define LPS25H_CS   9
#define MCP3208_CS 10

skLPSxxx LPS(LPS25H,LPS25H_CS) ;        // デバイスはLPS25Hを指定

void setup()
{
     int ans ;

     // シリアルモニターの設定
     Serial5.begin(9600) ;
     // 使用するCS(SS)ピンをHIGHに設定する
     pinMode(MCP3208_CS,OUTPUT) ;       // MCP3208のCS信号のピン設定
     digitalWrite(MCP3208_CS,HIGH) ;
     pinMode(LPS25H_CS,OUTPUT) ;        // LPS25HのCS信号のピン設定
     digitalWrite(LPS25H_CS,HIGH) ;
     // SPIの初期化
     SPI.begin() ;                      // SPIを行う為の初期化
     SPI.setBitOrder(MSBFIRST) ;        // ビットオーダー
     SPI.setClockDivider(48) ;          // SPI通信クロック(CLK)は1MHz

     delay(5000) ;                      // 5Sしたら開始

     // 気圧センサの初期化を行う(更新速度は1Hz)
     SPI.setDataMode(SPI_MODE3) ;       // CLK極性 1(idle=HIGH) CLK位相 1(HIGH->LOW)
     ans = LPS.PressureInit() ;
     if (ans == 0) Serial5.println("Initialization normal") ;
     else {
          Serial5.print("Initialization abnormal ans=") ;
          Serial5.println(ans) ;
     }
     delay(1000) ;
}
void loop()
{
     int dt ;

     // MCP3208のCH0からアナログ値を読み込む
     SPI.setDataMode(SPI_MODE1) ;       // CLK極性 0(idle=LOW) CLK位相 1(LOW->HIGH)
     dt = ADC_analogRead(MCP3208_CS,0) ;
     Serial5.print("analog:") ;
     Serial5.println(dt) ;
     delay(1000) ;
     // LPS25Hから気圧値と温度値を読み込む
     SPI.setDataMode(SPI_MODE3) ;       // CLK極性 1(idle=HIGH) CLK位相 1(HIGH->LOW)
     LPS.PressureRead() ;               // 圧力と温度を読み出す
     Serial5.print("[LPS25H]") ;
     Serial5.print(Press) ;             // 気圧値の表示を行う
     Serial5.print(" hPa  ") ;
     Serial5.print(Temp) ;              // 温度の表示を行う
     Serial5.print(" 'C  ") ;
     // 気圧値から高度を計算し、表示を行う
     Serial5.print(LPS.AltitudeCalc(Press,-10)) ;
     Serial5.println(" m") ;

     delay(1000) ;                      // 1秒後に繰り返す
}
// ADC_analogRead(channel)   MCP3208からアナログ値を読み取る処理
//  ss      : SPIのSS(CS)ピン番号を指定する
//  channel : 読み取るチャンネルを指定する(0-7ch)
int ADC_analogRead(int ss,int channel)
{
     static char f ;
     int d1 , d2 ;

     // 指定されたSSピンを出力に設定する(但し最初コールの1回のみ)
     if (f != 1) {
          pinMode(ss,OUTPUT) ;
          digitalWrite(ss,HIGH) ;
          f = 1 ;
          delay(1) ;
     }
     // ADCから指定チャンネルのデータを読み出す
     digitalWrite(ss,LOW) ;              // SS(CS)ラインをLOWにする
     d1 = SPI.transfer( 0x06 | (channel >> 2) ) ;
     d1 = SPI.transfer( channel << 6 ) ;
     d2 = SPI.transfer(0x00) ;
     digitalWrite(ss,HIGH) ;             // SS(CS)ラインをHIGHにする

     return (d1 & 0x0F)*256 + d2 ;
}
---------------------------------------------------------------------

《その他》

SPIのSDO/SDI接続ピンにプルアップ抵抗(10KΩ)を接続しています。
LPS25Hからの読出し時に、有るビットが化ける現象が発生したので、プルアップ抵抗を取付けた所、
上手く読み出せました、又、LPS25HをLPS331APにそのまま取り替えても動作しました。
ですのでプルアップ抵抗の取り付けを推奨します。

MCP3208からアナログ値を読み込む場合は、各サンプルに有るADC_analogRead( )関数を利用して
下さい。(Arduinoで"MCP3208(SPI)を利用しA/D変換を行う"の記事はこちらに有ります)

Arduinoの場合は、標準のSPIライブラリを使っています。
PICの場合は、"skSPIlib.h"/"skSPIlib.c"を使っています、
尚、今回のこの実験で"skSPIlib.h"/"skSPIlib.c"を更新しています、
↓からダウンロードして下さい。

12F1822/16F18xx/18F25K22の場合は、"skSPIlib1.lzh"をダウンロード
16F1938/19xxの場合は、"skSPIlib2.lzh"をダウンロード
"skSPIlib.lzh"、 12F1822/16F182x/16F193x/18F25K22 共用ライブラリに変更(統合)しました。 *1)
          ライブラリの”SPI_MODEのCKE(クロック位相)を変更”しました。 *2)

《skSPIlib関数説明》

コンパイラ:MPLAB(R) XC8 C Compiler Version 1.00/1.32

skSPIlib.h

SPI通信を行う関数のヘッダファイルです。
"skSPIlib.c"を利用する場合に
#include "skSPIlib.h" をプログラムの先頭で記述して下さい。

デフォルトではMSSP1を使う様になりますが、PICによってはMSSP2が有ります。
MSSP2側を使う場合は、"#define SPI_MSSP2_USE"を記述して下さい。
このライブラリは、MSSP1/MSSP2の何方か一方のみ使用出来ます。

skSPIlib.c

このライブラリはSPI通信を行う為の関数集です。
この関数集は、PIC12F1822/PIC16F18xx/193x/PIC18F2xK22で使用出来ます。

SPI通信を行う関数の使い方を説明します。

SPI_Init(mode,divider,sdo)
 SPIモードの設定と初期化を行う処理
  mode   :SPIの転送モードを設定します(クロック極性とクロック位相の組み合わせ) *11)
        SPI_MODE1 = クロック極性(0:LOW) クロック位相(0:アイドル0Vで、0V->5Vに変化で転送)
        SPI_MODE0 = クロック極性(0:LOW) クロック位相(1:アイドル0Vで、5V->0Vに変化で転送)
        SPI_MODE3 = クロック極性(1:HIGH) クロック位相(0:アイドル5Vで、5V->0Vに変化で転送)
        SPI_MODE2 = クロック極性(1:HIGH) クロック位相(1:アイドル5Vで、0V->5Vに変化で転送)
  divider :SPIの通信速度を設定します
        SPI_CLOCK_DIV4   = Fosc/4 (PIC動作周波数の4分の1で動作)
        SPI_CLOCK_DIV16  = Fosc/16 (PIC動作周波数の16分の1で動作)
        SPI_CLOCK_DIV64  = Fosc/64 (PIC動作周波数の64分の1で動作)
        SPI_CLOCK_DIVT2  = TMR2の出力の1/2 (タイマー2の速度で動作)
        SPI_CLOCK_DIVADD = FOSC / ((SSPxADD + 1) * 4)
  sdo     :使用するSDO送信のピン番号を指定する
             現状は12F1822/16F1823/1825/1826/1827/1829以外は指定しなくてもOK

SPI_setDataMode(mode)
 SPIの転送モード設定を行う処理
  mode   :SPIの転送モードを設定します(上記参照)

SPI_setClockDivider(divider,rate)
 SPIの通信速度設定を行う処理
  divider :SPIの通信速度を設定します(上記参照)
  rate    :SSPxADDに設定するクロック分周値(SPI_CLOCK_DIVADDを指定する場合に設定する値)
       SPI_CLOCK_DIVADD = FOSC / ((SSPxADD + 1) * 4)
       rate=0は指定不可です、”SPI_CLOCK_DIV4”を使いましょう。

ans = SPI_transfer(dt)
 SPI通信でのデータ送信とデータ受信を行う処理
  dt  : 8ビットの送信するデータを指定します
  ans: 8ビットの受信したデータを返します



記事一部変更(*3) 2017/02/26
記事一部変更(*2) 2016/06/13
変更(*1) 2015/04/24


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