有機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 Input |
P1_0に接続 |
| 2 SCL |
Serial Clock |
P1_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 Input |
P1_0に接続 |
| 2 SCL |
Serial Clock |
P1_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倍表示させています。描画速度が遅いのが気になります。

メニューに戻る