有機ELディスプレィ(OLED)


 I2C通信の極小OLED(Organic Light Emitting Diode)、コントローラSSD1306とSSD1331の制御を行ってみます。有機ELと呼ばれているものです。Arduinoでも同じ内容でおこなっていますので、そちらの記事を参照してください。

T.ssd1306


U.ssd1331




T.ssd1306


  (1)文字を表示する


  (2)数値を表示する





(1)文字を表示する





結 線 図(半田面)SCL、SDA端子は10KΩでプルアップ


               
 OLED側  
R8C/M12A側
 1 SDA Serial Data InputP1_0に接続
 2 SCL Serial ClockP1_1に接続
 3 VCC 今回は3.6Vを使用VCCに接続
 4 GND  GNDに接続




◎プログラム構成
 基本的にはArduinoで使用したプログラムをR8C/M12A用に移植したものです。Arduinoの時と異なるのは、デバイスアドレスの指定です。Arduinoの時には、0x3C(011 1100・・・7ビット形式)を指定していましたが、R8C/M12Aでは、R/Wビットを追加した8ビット形式0x78(0111 1000)で指定する必要があります。

 プログラムは以下の4つの部分から構成されています

 構成ファイル   
EM_OLED_I2C0.c ・・・ メイン関数
i2c_ctrl.c ・・・ 制御関数
i2c_ctrl.h ・・・ ヘッダーファイル
font_8x8.h ・・・ フォントファイル


1)メイン関数 EM_OLED_I2C0.c

 01:#include ”sfr_r8m12a.h”
 02:#include ”i2c_ctrl.h”
 03:int px,py;

 04:void main(void)
 05:{
 06: unsigned char i;
 07: init();
 08: init_OLED();
 09: OLED_CLR();
 10: px=0;
 11: py=2;
 12: for(i=0x41;i<=0x48;i++){// 2倍表示
 13:  Locate_xy(px,py);
 14:  px=DISP_DAT2(i,px,py);
 15:  py=2;//
 16:  wait_ms(100);
 17: }

 18: px=0;
 19: py=0;
 20: for(i=0x41;i<=0x48;i++){// 1倍表示
 21:  Locate_xy(px,py);
 22:  px=DISP_DAT(i);
 23:  px +=8;
 24: }
 25:}

 26:void init(void)
 27:{
 28: int i;
 29: prc0= 1;// プロテクトレジスタ解除
 30: hocoe= 1;// 高速オンチップオシレータ発振開始
 31: for(i=0;i<50;i++);// 発振安定まで待つ
 32: hscksel= 1;// 高速オンチップオシレータ選択ビットを1にする
 33: scksel= 1;// システム基準クロックを高速にする
 34: prc0= 0;// プロテクトレジスタ禁止
 35: pd1= 0xff;// p1_0〜p1_7 出力設定 1011 1000
 36: pd3= 0x18;// p3_3,p3_4 出力設定 0001 1000
 37:}





2)制御関数 i2c_ctrl.c

 01:#include ”sfr_r8m12a.h”
 02:#include ”i2c_ctrl.h”
 03:#include ”font_8x8.h”
    ・
    ・  途中省略
    ・
   int I2C_write(unsigned char data) // 書き込みの基本関数
   {
    int i;
    int ack;
    I2C_EDA_OUT;
    for(i=0;i<8;i++){// 上位から1ビト毎にクロックと共に送信
     if(data & 0x80){// ビットの値がHigh(=1)だったら
      I2C_S_EDA;// データラインをHigh(=1)にする
     }
     else{
      I2C_C_EDA;// ビットの値がLow(=0)データラインをLow
     }
     delay();
     I2C_H_CLK();// クロックを作る
     I2C_L_CLK();// 送信
     data<<=1;// 次のビットへシフト
    }// 8ビット繰り返す
    I2C_EDA_IN;// データラインを入力モードにする
    I2C_H_CLK();// クロック信号を作る
    ack=I2C_EDA_data;// クロックHighの状態でP3_3の値をackに入れる
    I2C_L_CLK();// クロックLow
    delay();
    I2C_EDA_OUT;// P3_3を出力に戻す
    return ack;// ACK信号を戻り値
   }
     ・
     ・  途中省略
     ・
   void IO_write(unsigned char data)// GDDRAMデータを出力する関数
   {
    unsigned char idt;
    I2C_start();// I2Cスタートコンディション
    I2C_write(ADD);// スレーブアドレスを送る
    I2C_write(0x40);//
    I2C_write(data);// 出力データを送る
    I2C_stop();
   }

   // OLEDの初期化
   void init_OLED()
   {
    I2C_start();
    I2C_write(ADD);// スレーブアドレスを送る
    I2C_write(0x00);// Co=0 D/C#=0 Commandを送る
    I2C_write(0x81);// Set Contrast Control 2byte command
    I2C_write(0xFF);// Contrast max
    I2C_write(0x40);// Display start line 1行目
    I2C_write(0xA8);// Set line number 2byte command
    I2C_write(0x1F);// Line number 4行=32(0x1F)
    I2C_write(0xA1);// Set Segment Re-map 画面配置左右反転(A0/A1)
    I2C_write(0xA6);// 白黒反転 Set Normal(A6)/Inverse Display(A7)
    I2C_write(0xC8);// Set COM Output Scan Direction(C0/C8)
    I2C_write(0xDA);// COM config
    I2C_write(0x00);// COM config normal 00
    I2C_write(0x2E);// Deactivate Scroll
    I2C_write(0x20);// Set Memory Addressing Mode
    I2C_write(0x00);// Horizontal Addressing Mode
    I2C_write(0x21);// Set Column Address SEG0-SEG127
    I2C_write(0x00);// SEG 0
    I2C_write(0xFF);// SEG 127
    I2C_write(0x22);// Set Page Address PAGE0-PAGE3
    I2C_write(0x00);// PAGE0
    I2C_write(0x03);// PAGE3 03
    I2C_write(0xD5);// Set Display Clock Divide Ratio/Oscillator Frequency
    I2C_write(0x80);// 370kHz
    I2C_write(0x8D);// Enable charge pump regulator 2byte command
    I2C_write(0x14);// Charge pump
    I2C_write(0xAF);// Display ON
    I2C_stop();
   }

   void Locate_xy(unsigned int px,unsigned int py) // 表示位置指定関数
   {
    I2C_start();
    I2C_write(ADD);// スレーブアドレスを送る
    I2C_write(0x00);//
    I2C_write(0x21);//
    I2C_write(px);//
    I2C_write(0x7f);//
    I2C_write(0x22);//
    I2C_write(py);//
    I2C_write(0x03);//
    I2C_stop();
   }

   void OLED_CLR(void) // OLEDの画面クリャー関数
   {
    int i;
    I2C_start();
    I2C_write(ADD); // スレーブアドレスを送る
    I2C_write(0x00);
    I2C_write(0x20);
    I2C_write(0x00);
    I2C_write(0x21);
    I2C_write(0x00);
    I2C_write(0x7F);
    I2C_write(0x22);
    I2C_write(0x00);
    I2C_write(0x03);
    I2C_stop();
    I2C_start();
    I2C_write(ADD); // スレーブアドレスを送る
    I2C_write(0x40);
    for(i=0;i<512;i++){
     I2C_write(0x00);
    }
    I2C_stop();
   }

   // 1倍フォント表示関数
   void DISP_DAT(unsigned char x) 
   {
    int i;
    x -= 0x20; // 配列アドレス計算
    for(i=0;i<8;i++){
     IO_write(font[x][i]);
    }
   }

   //  2倍フォント表示関数
   unsigned char DISP_DAT2(unsigned char dat,unsigned int px,unsigned int py)
   {
    unsigned char i,p,cnt;
    dat -= 0x20;// 配列アドレス計算
    // 拡大フォント(upper half)
    Locate_xy(px,py);// Upper half表示位置指定
    for(i=0;i<8;i++){
     p=font[dat][i];// upper half 4bit of font data
     cnt = 0;
     if( p & 0b00000001 ) cnt = 0b00000011;// 縦方向2倍
     if( p & 0b00000010 ) cnt |= 0b00001100;//  〃
     if( p & 0b00000100 ) cnt |= 0b00110000;//  〃
     if( p & 0b00001000 ) cnt |= 0b11000000;//  〃
     IO_write(cnt);
     IO_write(cnt);
    }

    // 拡大フォント(under half)
    Locate_xy(px,py+1);// Under half表示位置指定
    for(i=0;i<8;i++){
     p=font[dat][i];// under half 4bit of font data
     cnt = 0;
     if( p & 0b00010000) cnt = 0b00000011;// 縦方向2倍
     if( p & 0b00100000) cnt |= 0b00001100;//  〃
     if( p & 0b01000000) cnt |= 0b00110000;//  〃
     if( p & 0b10000000) cnt |= 0b11000000;//  〃
     IO_write(cnt);
     IO_write(cnt);
    }
    return px+16;// 次に隣に表示する位置を返す(縦倍:8 縦横倍:16)
   }


3)ヘッダーファイル i2c_ctrl.h 

   /* p1_1・・・CL I2Cクロック */
   /* p1_0・・・DA I2Cデータ */
   #define I2C_S_SCL  p1_1=1// I2Cクロック p1_1を1にする
   #define I2C_C_SCL  p1_1=0// I2Cクロック p1_1を0にする
   #define I2C_S_EDA  p1_0=1// I2Cデータ p1_0を1にする
   #define I2C_C_EDA  p1_0=0// I2Cデータ p1_0を0にする
   #define I2C_EDA_IN  pd1_0=0// p1_0を入力ポートに設定
   #define I2C_EDA_OUT  pd1_0=1// p1_0を出力ポートに設定
   #define I2C_EDA_data p1_0// データポートをp1_0に設定
   #define CLOCK  20000

   #define ADD 0x78 // device address 3c(011 1100)→0x78(0111 1000)

   /*======================================*/
   /* プロトタイプ宣言 */
   /*======================================*/
   void init(void);
   void wait_us(unsigned long usec);// us単位のタイマ
   void wait_ms(unsigned long msec);// ms単位のタイマ
   void delay(void); // 6usの遅延関数
   void I2C_start(void); // I2C開始
   void I2C_stop(void); // I2Cストップ
   void ACK(void); // ACKNOWLEDGE信号送信
   void I2C_H_CLK(void); // クロックHigh
   void I2C_L_CLK(void); // クロックLow

   int I2C_write(unsigned char data);// 書き込みの基本関数
   unsigned char I2C_read(void);// 読み込みの基本関数

   void init_OLED(void); // OLED初期化関数
   void Locate_xy(unsigned int px,unsigned int py);// 表示位置指定関数
   void DISP_DAT(unsigned char x);// フォント表示関数
   unsigned char DISP_DAT2(unsigned char dat,unsigned int px,unsigned int py);// 2倍フォント表示関数
   void OLED_CLR(void);// OLEDクリャー関数

   void IO_write(unsigned char data);// GDDRAMにデータを出力する関数


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




(2) 数値を表示する

 P1_7に半固定抵抗を接続し、AD変換した値をOLEDに表示してみます。OLED接続端子をP1_0,P1_1に変更し、プログラムを文字表示関数で文字列も表示できるように変更しています。

結 線 図(半田面)半固定抵抗は10KΩ


                   
 OLED側  
R8C/M12A側
 1 SDA Serial Data InputP1_0に接続
 2 SCL Serial ClockP1_1に接続
 3 VCC 今回は3.6Vを使用VCCに接続
 4 GND  GNDに接続
  P1_7に半固定抵抗接続





◎プログラムは以下の4つの部分から構成されています

 EM_OLED_1306.c ・・・メイン関数
 i2c_ctrl.c     ・・・制御関数
 i2c_ctrl.h     ・・・ヘッダーファイル
 font_8x8.h    ・・・フォントファイル



1)メイン関数 EM_OLED_1306.c

 01:#include ”sfr_r8m12a.h”
 02:#include ”i2c_ctrl.h”

 03:unsigned int px,py;
 04:unsigned int ad;
 05:unsigned char rd[3];
 06:unsigned char str[8];

 07:void main(void)
 08:{
 09: unsigned int x,y;
 10: unsigned int ad;
 11: init();
 12: init_OLED();
 13: OLED_CLR();
 14: x=0;
 15: y=2;
 16: Locate_xy(x,y);
 17: I2C_OLED_str2("AD=",x,y);
 18: adcon0 = 0x01;// AD変換スタート
 19: while(1){
 20:  ad=Get_AD();
 21:  ad=ad>>2;
 22:  ad=Get_AD();
 23:  ad=ad>>2;
 24:  y=2;
 25:  x=48;
 26:  I2C_OLED_num2(ad,x,y);// 10進数2倍角で表示
 27:  wait_ms(1000);
 28: }
 29:}

 30:void init(void)
 31:{
 32: int i;
 33: prc0 = 1;// プロテクトレジスタ解除
 34: hocoe = 1;// 高速オンチップオシレータ発振開始
 35: for(i=0;i<50;i++);// 発振安定まで待つ
 36: hscksel = 1;// 高速オンチップオシレータ選択ビットを1にする
 37: scksel = 1;// システム基準クロックを高速にする
 38: prc0 = 0;// プロテクトレジスタ禁止
 39: pd1 = 0x7f;// ポートディレクションレジスタ p0〜p6出力ポート設定
 40: p1 = 0x00;
 41: mstcr= 0x00;/* モジュールスタンバイ制御レジスタをアクティブ*/
 42: admod= 0x08;/* A/D動作 0x08:繰り返しモード 0x00:単発モード */
 43: adinsel= 0x81;/* A/D入選択レジスタ AN7*/
 44: pd3 = 0x18; // p3_3,p3_4 出力設定 0001 1000
 45: pd4 =0xff; // ポート4全て入力に設定
 46: pur4 =0b11100100;// ポート4 pull-up ON
 47:}



2)制御関数 i2c_ctrl.c
    ・
    ・  途中省略
    ・
      変更部分

   /****************************************/
   /* OLEDへの文字列表示関数 1倍表示関数 */
   /****************************************/
   void I2C_OLED_str(unsigned char* ptr)
   {
    while(*ptr !=0){
     DISP_DAT(*ptr++);
    }
   }

   /****************************************/
   /* OLEDへの文字列表示関数 2倍表示関数 */
   /****************************************/
   void I2C_OLED_str2(unsigned char* ptr,unsigned int px,unsigned int py)
   {
    while(*ptr !=0){
     px=DISP_DAT2(*ptr++,px,py);
    }
   }

   /****************************************/
   /* AD変換する関数            */
   /****************************************/
   unsigned int Get_AD(void)
   {
    int i;
    i=ad1;
    return i;
   }

   /****************************************/
   /*数値を10進数2倍角で表する関数(0〜255) */
   /****************************************/
   void I2C_OLED_num2(unsigned char num,unsigned int x,unsigned int y)
   {
    unsigned char i;
    unsigned char rd[3];
    unsigned char str[8];
    unsigned int px;
    for(i=0;i<3;i++){ // 0〜255を各桁に分解する
     rd[i] = num % 10;
     num = num/10;
    }
    str[1]=rd[2]+0x30;// ASCIIコードに変換
    str[2]=rd[1]+0x30;//   〃
    str[3]=rd[0]+0x30;//   〃
    Locate_xy(x,y);
    px=DISP_DAT2(str[1],x,y);
    px=DISP_DAT2(str[2],px,y);
    px=DISP_DAT2(str[3],px,y);
   }


3)ヘッダーファイル i2c_ctrl.h 

  /********************************************/
  /* i2c_ctrl.h              */
  /********************************************/
  #define I2C_S_SCL p1_1=1 // I2Cクロック p1_1を1にする
  #define I2C_C_SCL p1_1=0 // I2Cクロック p1_1を0にする
  #define I2C_S_EDA p1_0=1 // I2Cデータ p1_0を1にする
  #define I2C_C_EDA p1_0=0 // I2Cデータ p1_0を0にする
  #define I2C_EDA_IN pd1_0=0 // p1_0を入力ポートに設定
  #define I2C_EDA_OUT pd1_0=1 // p1_0を出力ポートに設定
  #define I2C_EDA_data p1_0  // データポートをp1_0に設定
  #define CLOCK 20000
  #define ADD 0x78    // アドレス 0B 0111 110+0(c/w)
     ・
     ・  途中省略
     ・
        追加部分

  // AD変換関数
  unsigned int Get_AD(void);

  // OLEDへの文字列表示関数 1倍表示関数
  void I2C_OLED_str(unsigned char* ptr);

  // OLEDへの文字列表示関数 2倍表示関数
  void I2C_OLED_str2(unsigned char* ptr,unsigned int px,unsigned int py);

  // 数値を10進数2倍角で表示する関数(0〜255)
  void I2C_OLED_num2(unsigned char num,unsigned int x,unsigned int y);



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


メニューに戻る









U.ssd1331


 SSD1331制御のOLEDはArduinoの項でも扱っています。SPI通信の手順は基本的にはArduino と同じなのですが、R8C/M12Aで扱う場合はいささか異なるクセがあります。こちらの知識が不足のせいもあるのでしょうが、いささか取り扱いに手間取りました。

(1)結線図

SSD1331側  
R8C/M12A側
1 GND      グランドに接続
2 VCC       今回は3.6Vを使用
3 SCL クロック端子     P3_4
4 SDA データ入力端子 P3_7
5 RESET リセット端子 P4_7
6 DC データ、コマンド識別端子 P3_5
7 CS Chip Select端子 P3_3
    
 FT231X
TXD0 P1_4
RXD0 P1_5
AD変換 半固定抵抗 P1_7


 配線図(半田面)






(2)SPIの通信仕様

 SSD1331のSPI通信はモード3仕様になっていますので、最初に書き込みの基本関数の仕様をモード3に合わせてあるか確認修正しておきます。SPIでは、クロックの極性(CPOL)とクロックの位相(CPHA)により、4つの動作モードがあります。モード3は通信を行わない待機時にCLK信号をHに固定し、通信時にLのクロックパルスを発生させ、クロックの立ち上がりエッジでデータを送信します。


  void WriteSPI(unsigned char data)
  {
    unsigned char i;
    RS_DDR = 1;// RS端子の入出力設定レジスタ
    CS_DDR = 1;// CS端子の入出力設定レジスタ 出力に設定
    SCL_DDR= 1;// SCLK端子の入出力設定レジスタ出力に設定
    SD_DDR = 1;// SD端子の入出力設定レジスタ 出力に設定
    H_CLK();・・・ Mode3 Clock Highで待機する
    for(i=0;i<8;i++){ // 1ビト毎にクロックと共に送信
     if(data & 0x80){// MSBビットから送信
      S_SDA;
     }
     else{
      C_SDA;
     }
     L_CLK();・・・Mode 3 立ち上がりエッジでデータ送信
     H_CLK();
     data<<=1;
    }
  }


(3)複数バイトコマンド書き込み関数

 コマンド命令に関して、Arduinoで扱った時は1つのコマンド毎にCS(Chip select = LOW 有効)→DC(データ、コマンド識別命令=LOW)→Command命令→CS(Chip select = HIGH 無効)の流れで構成したComannd_Write関数を使用してコマンド命令を書いてました。Adafruitライブラリーでもそのように使用していましたのでそれを真似て使用して支障は無かったのですが、R8C/M12Aではそのようなやり方では、2バイト〜4バイト連続命令が有効に解釈されていないようで戸惑いました。2バイトコマンドに関しては、以下のような2バイトコマンド連続書き込み関数を多用しています。それ以外の連続バイトコマンドについては、関数化しないで並べて記述して、CS(LOW)→DC(LOW)→コマンド命令、・・・、・・・、→CS(HIGH)のように記述しています。

  void Command_Write2(unsigned char address,unsigned char data)
  {
    C_CS;       ・・・チップセレクトをLow
    C_DC;       ・・・コマンドデータ識別端子をLow(コマンド)
    WriteSPI(address);・・・コマンドアドレス
    WriteSPI(data);  ・・・コマンド内容命令
    S_CS;       ・・・チップセレクトをHigh
  }


(4)OLED初期化関数

 OLED初期化関数はArduinoと内容は同じものを使用していますが、以下のように2バイト書き込み命令 Command_Write2()関数を多用していますので、見た目はかなり異なるものになっています。


  void OLED_Init(void)
  {
   Command_Write(0xAE);// Display OFF
   wait_ms(110);// 100ms以上必要

   Command_Write2(0xA0,0x32); // Set Remap RGB Color 0x32 0b00110010(256)
   Command_Write2(0xA1,0);// Set Display Start Line 0~163
   Command_Write2(0xA2,0);// Display Offset 0

   Command_Write(0xA4);// Normal Display

   Command_Write2(0xA8,0x3F);// Set Multiplex Ratio
   Command_Write2(0xAD,0x8E);// Set master Confuguration
   Command_Write2(0xB0,0x0B);// Power Save Mode,No Save Mode
   Command_Write2(0xB1,0x31);// Phase 1 and 2 period adjustment
   Command_Write2(0xB3,0xF0);// Display ClockDivider/Oscillator Frequency
   Command_Write2(0x8A,0x64);// Precharge A
   Command_Write2(0x8B,0x78);// Precharge B
   Command_Write2(0x8C,0x64);// Precharge C
   Command_Write2(0xBB,0x3A);// Precharge Level
   Command_Write2(0xBE,0x3E);// Command VcomH
   Command_Write2(0x87,0x06);// Mastercurrent
   Command_Write2(0x81,0x91);// Contrast A 00-FF
   Command_Write2(0x82,0x50);// Contrast B,00-FF
   Command_Write2(0x83,0x7D);// Contrast C,00-FF

   Command_Write(0xAF);// Display On
   wait_ms(120);
  }


(5)プログラム構成

 プログラムは以下の4つの部分から構成されています

 OLED_SSD1331.c ・・・メイン関数
 spi_ctrl.c     ・・・制御関数
 ctrl.h       ・・・ヘッダーファイル
 font_8x8.h    ・・・フォントファイル

 中身はArduinoで作成したものと基本的な部分は同じですので、記述することはしませんが、詳しくは下のリンクからプログラムのプロジェクトファイルをダウンロードしてみてください。


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




(6)その他のプログラム

 下の写真は可変抵抗値をAD変換した値をOLEDに表示する例ですが、縦横2倍表示させています。描画速度が遅いのが気になります。







メニューに戻る