SPI通信


  T.SPI通信とは

  U.SPI接続LCDの制御



T.SPI通信とは

(1)SPIとは
 Serial Peripheral Interfaceの略で、モトローラ社(現 フリースケール社)が提唱した3線式の同期式シリアル通信インターフェースのことです。SPIはI2Cと同じようなシリアル通信方式の1つです。I2Cと比較すると、配線数は増えますが通信速度は速くなります。基本的には、

  ・データ出力信号SDO(MOSIとも表現します Master OUT  Slave IN)

  ・データ入力信号SDI(MISOとも表現します Maser IN  Slave OUT)

  ・クロック信号SCK(Serial Clock)

  ・スレーブ選択信号CS(Chip Select)

  の4本の信号線で通信を行います。SPI通信ではスレーブはアドレスを持たず、個別の物理的なアドレスピンを介して、マスターが通信する相手となるスレーブ選択-CSピンを用いて指定します。スレーブが複数ある場合は、SDO、SDI、SCKは共通に使いますが、CSはスレーブ毎の本数が必要になります。まれに複数のスレーブ選択ピンが一つに(カスケード)接続される場合もあります。クロック信号は常にマスタ側が制御します。SPIは全二重インターフェースなので、データの送信と受信を同時に行うことが可能です。



(2)SPIの動作手順

 動作は以下のような手順でおこなわれます。

1) SPI通信を開始するには、マスタからクロック信号送ると共に、選択するスレーブのCS(チップセレクト)を有効(LOW)にすることによってデータの送受信が可能になります。通信が終了したら、クロックを停止して、選択したスレーブのCSを無効(HIGH)にする必要があります。

2)SPIでは、クロックの極性(CPOL)とクロックの位相(CPHA)により、4つの動作モードがあります。

 ◎CPOL(Clock Polarity)・・・CPOL=0(クロックの正極性)、CPOL=1(クロックの負極性)を表します。

  CPOL=0(クロックの正極性)・・・ 正極性とは、通信を行わない待機時にCLK信号をLに固定し、通信時にHのクロックパルスを発生させます。

  CPOL=1(クロックの負極性)・・・負極性とは、通信を行わない待機時にCLK信号をHに固定し、通信時にLのクロックパルスを発生させます。

 ◎CPHA(Clock Phase=CPHA)・・・クロックは1クロックで0から1、1から0に切り替わるがその切り替わりのタイミングのことをいう。
  クロックパルスの最初でサンプリングする(CPHA=0)か最後でサンプリングする(CPHA=1)かを表します。
  CPHA=0の場合、クロックの奇数番目の変化でデータを取り込み、偶数番目の変化でシフト。
  CPHA=1の場合、クロックの奇数番目の変化でシフトし、偶数番目の変化でデータを取り込む。

以上の組み合わせから下記表のように4通りの動作モードがあります。
動作モード CPOL CPHA 備   考
 モード0  0  0 クロックは正極性、立ち上がりエッジ(クロックパルスの最初)でデータ取り組み
 モード1  0  1 クロックは正極性、立ち下がりエッジ(クロックパルスの最後)でデータ取り組み
 モード2  1  0 クロックは負極性、立ち下がりエッジ(クロックパルスの最初)でデータ取り組み
 モード3  1  1 クロックは負極性、立ち上がりエッジ(クロックパルスの最後)でデータ取り組み

 どの動作モードで通信するかは、スレーブ側デバイスの仕様に左右されます。マスタ側の通信プログラムは、スレーブ側の仕様に合わせて変更されます。

3)SPI接続LCD AQM1284RNの概要
 秋月電子で購入できるSPI接続LCD AQM1284RNを取り扱ってみます。マスタからの受信のみでデバイスからの読み込みはできません。LCDの制御は、SPI標準のSCLK(クロック)、SDI(データ信号)、/CS(チップセレクト)の3本と+RS(データとコマンドの選択)合計4本で行います。RSはコマンドの場合は「0」、データの場合は「1」です。SPIはSCLKアイドル時はH、SCLK立ち上がりで読み込みます(モード3)。


4)SPI接続LCD AQM1284は、SCLKアイドル時Hで、SCLK立ち上がりで読み込みですので、SPIスレーブへの書き込み基本関数は以下のようになっています。

 void WriteSPI(unsigned char data)
 {
   unsigned char i;
   C_CS;        ・・・ チップセレクトを0にしてデータ送信を可能にする
   for(i=0;i<8;i++){ ・・・1バイトのデータを1ビット毎に8回クロックと共に送信
    if(data & 0x80){ ・・・最上位ビットから順に送信する
     S_SDI;      ・・・data送信ビットが1ならデータ入力端子を1にする
    }
    else{
     C_SDI;      ・・・data送信ビットが0ならデータ入力端子を0にする
    }
    delay;        ・・・6usの遅延
    L_CLK();      ・・・クロック端子を0にする
    H_CLK();      ・・・クロック端子を1にした後data送信ビットが送信される
    data<<=1;      ・・・data送信ビットを最上位にシフトする
   }
   S_CS;         ・・・チップセレクトを1にして1バイト送信を終了する
 }


 送るものがデータか、コマンドかの区別はRSの値で判別します。H(=1)でデータ、L(=0)でコマンドを意味します。液晶の初期化手順、コントラスト設定等は付属のマニュアルに従って行っています。動作原理はI2Cと同様です。受信と送信が同時にできますので、I2Cより高速にデータのやり取りができると思いますが、今回はデータの送信しか行っていません。プログラムはI2Cで使ったプログラムを変更して使用しています。 SPI接続のLCDを使うためにはフォントを自前で用意する必要があります。マイコンカーラリーのR8CM12のサイトで紹介されている「美咲フォント」を使用しました。フォントの使用方法等は、このサイトで詳しく説明されています。 




U.SPI接続LCDの制御(Ver.2 ピン配置変更 2020.8.30)

 多くのSPI接続のグラフィック液晶が各社から販売されています。 マイコンカーラリーのホームページにR8C38で秋月電子のSPI−LCD、AQM1248-RNを扱ったプログラムが紹介されています。

 AQM1248-RNはCS(チップセレクト)、SCLK(クロック)、SDI(データ入力)の3線の他、RS端子(データ、コマンド識別端子)の4線を使って制御しています。


(1)接続ピン配置・・・ピッチ変換基板に接続した出力ピン配置(液晶側からの出力はないのでSDOは使いません)
 (Ver.2では、E8Aエミュレータを使えるようにピン配置を変更しています)

AQM1248−RN側              R8C/M12側
         
1 VDD 今回は3.6Vを使用
2 CS  チップセレクト端子    p1_7に接続
3 RESET リセット端子 今回は無接続
4 RS データ、コマンド識別端子 p1_3に接続
5 SCLK クロック端子       p1_6に接続
6 SDI データ入力端子      p1_4に接続
7 GND グランドに接続


AQM1248-RN R8C/M12A 配線図(半田面)




(3)プログラム

1)メインプログラム EM_SPI_LCD.c

01:#include "sfr_r8m12a.h" // R8C/M12A SFRの定義ファイル
02:#include "spi_lcd_lib.h" // 共通のプロトタイプ宣言をヘッダーファイル化
03:void main(void)
04:{
05: unsigned char x,y;
06: init(); // R8CM12の初期化
07: SPI_LCD_init(); // LCD AQM1248A-RNの初期化
08: SPI_LCD_clr(); // 画面を消去しないとゴミが表示されてしまう
09: SPI_LCD_posxy(20,0);
10: SPI_LCD_data('S');SPI_LCD_data(' ');
11: SPI_LCD_data('P');SPI_LCD_data(' ');
12: SPI_LCD_data('I');
13: SPI_LCD_str(" ジッケン");
14: x=20;y=2;
15: x=SPI_LCD_data2('S',x,y);
16: x=SPI_LCD_data2('P',x+10,y);
17: SPI_LCD_data2('I',x+10,y);
18: x=0;y=4;
19: x=SPI_LCD_str2("2Xヒョウジ",x,y);
20: while(1);
21:}


2)spi_lib_lcd.h ・・・共通のプロトタイプ宣言をヘッダーファイル化

01:#define S_RS  p1_3=1 // 液晶のRS端子(データ,コマンド識別端子) p1_3を1にする
02:#define C_RS  p1_3=0 // 液晶のRS端子(データ,コマンド識別端子) p1_3を0にする
03:#define RS_DDR pd1_3 // 液晶のRS端子の入出力設定レジスタ
04:#define S_CS  p1_7=1 // 液晶のCS端子(チップセレクト端子) p1_7を1にする
05:#define C_CS  p1_7=0 // 液晶のCS端子(チップセレクト端子) p1_7を0にする
06:#define CS_DDR pd1_7 // 液晶のCS端子の入出力設定レジスタ
07:#define S_SDI  p1_4=1 // 液晶のSDI端子(データ入力端子) p1_4を1にする
08:#define C_SDI  p1_4=0 // 液晶のSDI端子(データ入力端子) p1_4を0にする
09:#define SDI_IN  pd1_4=0 // p1_4を入力ポートに設定
10:#define SDI_OUT pd1_4=1 // p1_4を出力ポートに設定
11:#define SDIDDR pd1_4 // 液晶のSDI端子の入出力設定レジスタ
12:#define S_SCL p1_6=1 // 液晶のクロック端子 p1_6を1にする
13:#define C_SCL p1_6=0 // 液晶のクロック端子 p1_6を0にする
14:#define SCL_DDR pd1_6 // 液晶のSCLK端子の入出力設定レジスタ
15:#define CLOCK 20000

16:void init(void);
17:void wait_us(unsigned long usec);// us単位のタイマ
18:void wait_ms(unsigned long msec);// ms単位のタイマ
19:void H_CLK(void);// クロックをHighにする
20:void L_CLK(void);// クロックをLowにする
21:void delay(void);// 6usの遅延関数
22:void SPI_LCD_init(void);// lcd初期化関数
23:void WriteSPI(unsigned char data);// 書き込みの基本関数
24:void SPI_LCD_cmd(unsigned char cmd);// 液晶へのコマンド出力関数
25:void SPI_LCD_clr(void);// LCDの表示RAMを消去する関数
26:void SPI_LCD_ram(unsigned char wc);// G-LCD RAMにデータを1バイト送信する関数
27:void SPI_LCD_data(unsigned char dat);// G-LCDに1文字表示する関数 通常表示
28:void SPI_LCD_posxy(unsigned char x,unsigned char y);// LCDの表示RAMのカーソル位置指定する関数
29:void SPI_LCD_str(char *str);// LCDに文字列を出力する関数 通常表示
30:// G-LCDに縦横2倍で1文字表示する関数
31:unsigned char SPI_LCD_data2(unsigned char dat,unsigned char px,unsigned char py);
32:// G-LCDに縦横2倍で文字列を表示する関数
33:unsigned char SPI_LCD_str2(unsigned char *str,unsigned char px,unsigned char py);


3)spi_lcd_ctrl.c ・・・SPI LCD関連命令関数の集まり

01:#include "sfr_r8m12a.h"// R8C/M12A SFRの定義ファイル
02:#include "font_8x8.h" // 8x8dotフォント
03:#include "spi_lcd_lib.h"// 共通のプロトタイプ宣言をヘッダーファイル化
04:
  /***************************************/
  /* I/O 初期化関数 */
  /***************************************/
  void init(void)
  {
    int i;
    prc0 = 1; // プロテクトレジスタ解除
    hocoe = 1; // 高速オンチップオシレータ発振開始
    for(i=0;i<50;i++); // 発振安定まで待つ
    hscksel = 1; // 高速オンチップオシレータ選択ビットを1にする
    scksel = 1; // システム基準クロックを高速にする
    prc0 = 0; // プロテクトレジスタ禁止
    pd1 = 0xff; // p1_0〜p1_7 出力設定
  }
  /*****************************************/
  /* 液晶初期化関数 */
  /*****************************************/
  void SPI_LCD_init(void)
  {
    RS_DDR = 1; // 液晶のRS端子の入出力設定レジスタ
    CS_DDR = 1; // 液晶のCS端子の入出力設定レジスタ 出力に設定
    SCL_DDR= 1; // 液晶のSCLK端子の入出力設定レジスタ出力に設定
    SDI_OUT; // 液晶のSDI端子の入出力設定レジスタ 出力に設定
 
    SPI_LCD_cmd(0xae); // LCD display off
    SPI_LCD_cmd(0xa0); // ADC select = normal
    SPI_LCD_cmd(0xc8); // Common output = revers
    SPI_LCD_cmd(0xa3); // Bias = 1/7
    // 内部レギュレータを順番にONする
    SPI_LCD_cmd(0x2c); // 内部レギュレータを順番にONする ON 1
    wait_us(30);
    SPI_LCD_cmd(0x2e); // 内部レギュレータを順番にONする ON 2
    wait_ms(2);
    SPI_LCD_cmd(0x2f); // 内部レギュレータを順番にONする ON 3
    wait_ms(2);
    // コントラスト設定
    SPI_LCD_cmd(0x23); // Vo voltage resister ratio set
    SPI_LCD_cmd(0x81); // Electronic volume mode set
    SPI_LCD_cmd(0x1c); // Electronic volume value set
    // 表示設定
    SPI_LCD_cmd(0xa4); // display all point = normal
    SPI_LCD_cmd(0x40); // display start line=0
    SPI_LCD_cmd(0xa6); // display normal/revers = normal
    SPI_LCD_cmd(0xaf); // display ON
  }
  /********************************************/
  /* クロックをHighにする */
  /********************************************/
  void H_CLK(void)
  {
    S_SCL;
    delay();
  }
  /********************************************/
  /* クロックをLowにする */
  /********************************************/
  void L_CLK(void)
  {
    C_SCL;
    delay();
  }
  /********************************************/
  /* 書き込みの基本関数 */
  /********************************************/
  void WriteSPI(unsigned char data)
  {
    unsigned char i;
    C_CS;
    for(i=0;i<8;i++){ // 1ビト毎にクロックと共に送信
      if(data & 0x80){
        S_SDI;
      }
      else{
        C_SDI;
      }
      delay();
      H_CLK();
      L_CLK();
      data<<=1;
    }
    S_CS;
  }
  /********************************************/
  /* コマンド書き込み関数 */
  /********************************************/
  void SPI_LCD_cmd(unsigned char cmd)
  {
    C_CS; // Chip select = 0 有効
    C_RS; // コマンドを指定
    WriteSPI(cmd); // SPIで書き込み
    S_CS; // Chip select =1 終了
  }

  /******************************************/
  /* データ書き込み関数 */
  /******************************************/
  void SPI_LCD_ram(unsigned char wc)
  {
    C_CS; // Chip select = 0 有効
    S_RS; // データを指定
    WriteSPI(wc); // SPIで書き込み
    S_CS; // Chip select =1 終了
  }
  /********************************************/
  /* 1文字表示関数 通常表示 */
  /********************************************/
  void SPI_LCD_data(unsigned char dat)
  {
    unsigned char i,p;
    dat -= 0x20; // 配列アドレスを計算
    for(i=0;i<8;i++){
      p=font[dat][i];
      SPI_LCD_ram(p); // データ転送
    }
    SPI_LCD_ram(0); // 文字間隔を空ける
  }
  /********************************************/
  /* 文字列出力 通常表示 */
  /********************************************/
  void SPI_LCD_str(char *str)
  {
    while(*str){
      SPI_LCD_data(*str++);
    }
  }
  /********************************************/
  /* 1文字表示関数 縦横2倍で表示する */
  /********************************************/
  unsigned char SPI_LCD_data2(unsigned char dat,unsigned char px,unsigned char py)
  {
    unsigned char i,p,cnt;
    dat -= 0x20; // 配列アドレスを計算

    // 拡大フォント表示(上半分)
    SPI_LCD_posxy(px,py); // 上半分の表示位置を指定する
    for(i=0;i<8;i++){
      p=font[dat][i]; // フォントデータの下位4ビット
      cnt = 0;
      if(p & 0b00000001) cnt = 0b00000011;// 縦方向2倍
      if(p & 0b00000010) cnt |=0b00001100;// 〃
      if(p & 0b00000100) cnt |=0b00110000;// 〃
      if(p & 0b00001000) cnt |=0b11000000;// 〃
      SPI_LCD_ram(cnt);SPI_LCD_ram(cnt); // 横を2倍に拡げる
    }
    SPI_LCD_ram(0);SPI_LCD_ram(0); // 空白部分も2倍

    // 拡大フォント表示(下半分)
    SPI_LCD_posxy(px,py+1); // 下半分の表示位置を指定する
    for(i=0;i<8;i++){
      p=font[dat][i]; // フォントデータの上位4ビット
      cnt = 0;
      if(p & 0b00010000) cnt = 0b00000011;// 縦方向2倍
      if(p & 0b00100000) cnt |=0b00001100;// 〃
      if(p & 0b01000000) cnt |=0b00110000;// 〃
      if(p & 0b10000000) cnt |=0b11000000;// 〃
      SPI_LCD_ram(cnt);SPI_LCD_ram(cnt); // 横に2倍に拡げる
    }
    SPI_LCD_ram(0);SPI_LCD_ram(0); // 空白部分も2倍
    return (i+1)*2+px; // 次に隣に表示する位置を返す
  }
  /********************************************/
  /* 文字列を縦横2倍で表示する関数 */
  /********************************************/
  unsigned char SPI_LCD_str2(unsigned char *str,unsigned char px,unsigned char py)
  {
    while(*str){
      px=SPI_LCD_data2(*str++,px,py);
    }
    return px; // 次の文字位置のx座標を返す
  }
  /********************************************/
  /* データ消去 */
  /********************************************/
  void SPI_LCD_clr(void)
  {
    unsigned char i,j;
    for(j=0;j<8;j++){
      SPI_LCD_posxy(0,j);
      for(i=0;i<128;i++){
        SPI_LCD_ram(0);
      }
    }
  }
  /********************************************/
  /* カーソル位置指定 */
  /********************************************/
  void SPI_LCD_posxy(unsigned char x,unsigned char y)
  {
    SPI_LCD_cmd(0xb0 | y); // ページ指定
    SPI_LCD_cmd(0x10 | (x >> 4)); // ページ内ROMアドレス指定
    SPI_LCD_cmd(x & 0xf); // 〃
  }
  /********************************************/
  /* タイマ本体 us単位のwait関数 */
  /********************************************/
  void wait_us(unsigned long usec)
  {
    usec = (usec * CLOCK) / 45000;
    while(usec --);{}
  }
  /********************************************/
  /* タイマ本体 ms単位のwait関数 */
  /********************************************/
  void wait_ms(unsigned long msec)
  {
    unsigned int i;
    for(i=0;i<msec;i++){
      wait_us(1000);
    }
  }
  /********************************************/
  /* 6usの遅れ関数 */
  /********************************************/
  void delay(void)
  {
    unsigned char i;
    for(i=0;i<5;i++){}
  }





4)font_8x8.h ・・・フォントファイル

  /**********************************************/
  /* 対象マイコン 特になし */
  /* ファイル内容 グラフィック液晶制御ライブラリ用フォントデータ */
  /* バージョン Ver.1.00 */
  /* Date 2012.04.27 */
  /* Copyright ルネサスマイコンカーラリー事務局 */
  /**********************************************/

  /*
  本キャラクタデータは、
  8×8 ドット日本語フォント「美咲フォント」
  http://www.geocities.jp/littlimi/font.htm
  を16進数データに変換して使用しています。
  フリーで提供されている門真なむ様にはお礼申し上げます。
  */

  // キャラクタデータ 0x20〜0xfd
  const char signed font[222][8] = {
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x20 ' '
  {0x00,0x00,0x00,0x5f,0x00,0x00,0x00,0x00}, // 0x21 '!'
  {0x05,0x03,0x00,0x05,0x03,0x00,0x00,0x00}, // 0x22 '”'

      ・
      ・
      ・

    途中省略

      ・
      ・
      ・

  {0x00,0x0f,0x42,0x41,0x21,0x1e,0x00,0x00}, // 0xf8 'り'(独自)
  {0x00,0x10,0x69,0x6d,0x4b,0x49,0x30,0x00}, // 0xf9 'る'(独自)
  {0x24,0x14,0x7f,0x04,0x02,0x3e,0x40,0x00}, // 0xfa 'れ'(独自)
  {0x00,0x10,0x49,0x4d,0x4b,0x49,0x30,0x00}, // 0xfb 'ろ'(独自)
  {0x24,0x14,0x7f,0x04,0x42,0x42,0x3c,0x00}, // 0xfc 'わ'(独自)
  {0x40,0x30,0x0c,0x33,0x40,0x40,0x20,0x00} // 0xfd 'ん'(独自)
  };





プロジェクトファイルのダウンロード

メニューに戻る