OLED displayをarduinoに取り付けたい.
中華通販から買った@¥150ぐらいのがこれ.128x32dotらしい.IFはI2Cである.
繋げば動くArduinoであるから、どこかのスケッチをDLすれば動くのだろう.しかし疑問はいろいろある.I2Cアドレスは何番なのか? キャラクタROMを搭載しているのか? などなど....
以下ではこいつをArduino+I2Cで動かす方法を解説する.
-----
netを徘徊すると、SSD1306というOLED driver ICが検索にひっかかるのだが、どうやらSSD1306とは下写真の「表示部+IC」をひっくるめてSSD1306であるらしい.
そして、最初の写真にあるブルーのプリント基板はSSD1306とは無関係だという点も知っておく必要があるだろう.ブルー基板の役割は何か?
・SSD1306にはI2C以外にもIF形式が用意されているのだが、ブルー基板によってI2C IFに固定されている
・ブルー基板の回路図はこれらしい.3.3V REGが描かれている.
てなことを考えつつ、SSD1306 datasheetを読んでみた.
datasheetはこれみたい. → こちら
ただしこのリンク先は128x64dotを解説しているのでGraphic RAMのアロケーションを読み替える努力を要するかもしれない.RAMの下半分には書けるけど表示されないとかなんとか.
それでは、DDS1306 datasheetから気になったところを抜き出してゆく.
【電源】
IC電源 VDD = 1.65~3.3V
OLED電源 VCC = 7~15V
いきなりなんだこりゃ? OLEDは3.3Vじゃないんだ? OLEDの高電圧をどうやって生成しているのだろう? SSD1306には、キャパシタを使った電圧ダブラが内蔵されているらしいよ.
【IF】
詳述はしないが、こんなにたくさんある.6800なんか懐かしいよ.作ってたの高校生の時だもん.
IFの選択は外部ピンBS[2:0]で行う.BS設定はブルー基板上で固定されている.
6800 BS[2:0]=100
8080 BS[2:0]=110
3線SPI BS[2:0]=001
4線SPI BS[2:0]=000
I2C BS[2:0]=010 ←今回のはこれ
【I2Cアドレス】
011110x
xの部分は外部ピンのSA0で決まる.
すなわちこのどっちか.
0111101 = 3D
0111100 = 3C
ブルー基板にSA0ピンが在るのかどうか? だが回路図を見ても、現物を見ても、SA0と思しき信号線は見えない.だとすると、このOLEDは決め打ちで3Dか3Cのどちらかなのだろう.それでどっちなのかは、動かしてみなくちゃ判らないのだろう.動かしてみたところ、3Cであることが確認された.
【I2C Write/Read】
I2Cの1アクションはこんなバイト並びでアクセスするんだってさ.
まず知るべきなのは2byte目であろう.
2byte目のbit構成は、{ Co, D/C#, 000000 } である.
Co=0 → 後続はdataだけである意味
D/C#=0 → 後続はcommandである意味
D/C#=1 → 後続はGDRAM dataである意味(アドレスauto increment)
やや意味不明.SSD1306をいろいと動かしてみても、Coがどう働いているのかは不明だ.
飽くまでもやってみた結果なのでわたしの勘違いかもしれないが、I2Cでcommandを与えるやり方と、GDRAMへdataを書くやり方には違いがあるようだ.両者の例をまず示す.
<commandを与えるやり方>
Wire.beginTransmission(0x3c); I2C address
Wire.write(0x00); Co=0 C/D=0の意味
Wire.write(0xd5); 複数個のcommands
Wire.write(0x80); ↓
Wire.write(0x8d); ↓
Wire.write(0x14); ↓
Wire.write(0xaf); ↓
Wire.endTransmission();
<GDRAM dataを書くやり方>
Wire.beginTransmission(0x3c); I2C address
Wire.write(0x40); Co=0 C/D=1の意味
Wire.write(0x33); GDRAMへ1byte書く、GDRAMアドレスはインクリされる
Wire.endTransmission();
違いの一つ目は、コントロールbyteが00か40かである.これはおまじないみたいなものなので無問題である.
違いの二つ目は、commandは連続した複数bytesを連続送信できたが、GDRAMはI2Cの1動作毎に1byteしか送信できないみたいだ.やってみてそうだった.Coを変えてみても結果は同じであった.なんか腑に落ちない.
【VRAMなのか?】
8bitマイコン時代に在ったようなVRAM+キャラクタROM構成ではない.
128x64dotのベタなグラフィックRAMがあるのみ.
したがって文字を表示させたければユーザーがdot絵を描く必要がある.
【GDRAM構成】
SSD1306は128x64dotのGDRAMを内蔵している.
縦横の呼び名は、横をSEG0-SEG127、縦をCOM0-COM63 と称する.
それに加えて、8個のCOMを束ねてPAGEと称する.PAGE0~PAGE7がある.
1つのPAGEは画面上で128x8dotの横長区画になるのは想像がつくだろう.
↓ホストCPUからGDRAMに1byteを書き込んだとき、その8bitが画面上にどのように表示されるのかには注意が必要だ.その8bitはPAGEの縦一列に対応するのである.なんだか扱いがめんどくさい気がしてくる.この図はPAGE単体の拡大図だ.
↓SSD1306は128x64dot GDRAMを内蔵しているが、ここで扱う128x32dot OLEDにはその半分しか表示されない.この図のように.
もっとも、必ずしもこの図のように上半分だけとは限らない.GDRAMの表示開始lineを指定する機能があるので表示エリアを上下に移動させることは可能だ.(コマンド40-7f)
↓まとめるとこのようになる.縦8bit画素に相当する1byteがGDRAMアクセスの最小単位である.書き込みアドレスは自動インクリされるので、0byte目~511byte目まで書き込めば128x32dotの表示エリアを埋め尽くせるという仕組みだ.
ただし、SSD1306はアドレスインクリメント範囲を任意に設定できるので、上図のベタな展開はそういった機能を使わないケースにおいてである.
【コマンド】
使いそうなコマンドとして以下をリストしておくなり.
表示開始行
byte数:1
command: 40h ~ 7Fh 40hならCOM0、7FhならCOM63
縦ライン数
byte数:2
command:{ A8h, 31 }
128x32dot OLEDなので32lineなので31をセットしておく.この数がアンマッチだとscrollしたときにおかしくなってしまうのだと思うんだ.
表示反転
byte数:1
command:A0h 通常
command:A1h 左右反転
command:C0h 通常
command:C8h 上下反転
COM config
byte数:2
command:{ DAh, 00 }
リセット値が01なのでなんか嫌なので00にしておく.機能はよくわからない.
scrollする/しない
byte数:1
command:2Eh scrollしない
command:2Fh scrollする
GDRAM write address increment range
byte数:8
command:{ 20h, 00, 21h, 00, 127, 22h, 0, 3 }
GDRAMへ書き込む際のアドレスは自動インクリメントされる.そのアドレス範囲を指定するコマンドである.3つのコマンドの合体である.
下図のように、GDRAMの0番地→511番地まで自動インクリされたくば上のコマンドを発すればいい.このコマンドによって、513個目のdataは0番地に戻る.結果としてGDRAMの下半分には書き込めなくなる.OLEDが128x32dotなのでそれでいいのだ.
コントラスト
byte数:2
command:{ 81H, N }
N=0~255 (reset value 127)
内蔵RC発振器
byte数:2
command:{ D5h, 80h } この設定だと370kHzで発振するらしい
内蔵チャージポンプ
byte数:2
command:{ 8Dh, 14h } OLED用高電圧発生回路をオンにする
SLEEP/ACTIVE
byte数:1
command:AEH OFF、sleep
command:AFH ON、active
コマンドはこれらの他にもあるけど、reset valueで放置しとけばいい感じ.
また、scroll commandがいろいろあるが、興味ないので割愛する.
-----
以上の知見をもとに、べたーっとcodingしてみた. →こちら
追記:asciiで文字列表示できるようにしたのもつくってみた.こっちの方が参考になるかもしれない →こちら
トラブルシュート: 立ち上がりの遅い電源をつかったらトラブル発生.Arduinoの起動時間がOLEDよりも早くて、OLEDがまだ寝ぼけているうちにsetup()が済んでしまってOLEDがクルクルパーになってしまった.対策のため、setup()の先頭にdelay(1000)を入れとくのがオススメよ.こんなことってよくあるよね.(リンク先のsourceにdelayは入ってない)
↓動かした環境はこんなもの.いろんな基板がついてるけど、本件に関係するのはarduino nanoとOLEDだけである.
↓プログラムを動かすとOLEDに数字が表示される.それだけ.8x8dot文字だと老眼にはきつい文字サイズである.もっと大きな表示にしなくちゃいけないね.
以下はsource code.
#include <Wire.h> I2Cを動かすライブラリ
#define OLED_ADRS 0x3c OLED基板のI2Cアドレスは3Ch
自作した0~9のキャラクタ
byte font[] = {
0x3e,0x41,0x41,0x41,0x41,0x3e,0x00,0x00, // 0
0x00,0x00,0x42,0x7f,0x40,0x00,0x00,0x00, // 1
0x42,0x61,0x51,0x49,0x49,0x46,0x00,0x00, // 2
0x22,0x41,0x49,0x49,0x49,0x36,0x00,0x00, // 3
0x18,0x14,0x12,0x7f,0x10,0x10,0x00,0x00, // 4
0x2f,0x49,0x49,0x49,0x49,0x30,0x00,0x00, // 5
0x3e,0x49,0x49,0x49,0x49,0x32,0x00,0x00, // 6
0x03,0x41,0x21,0x11,0x09,0x07,0x00,0x00, // 7
0x36,0x49,0x49,0x49,0x49,0x36,0x00,0x00, // 8
0x26,0x49,0x49,0x49,0x49,0x3e,0x00,0x00 }; // 9
ーーー中略ーーー
数字をOLEDに表示するルーチン
1文字で8x8dotなので8bytesを送信する.
GDRAMへ1byite送信するためにI2Cへ3bytes送信している.
void oled_numeric(int n)
{
for(int i=0;i<8;i++){
Wire.beginTransmission(OLED_ADRS); アドレス送信
Wire.write(0x40); GDRAMデータである
Wire.write(font[n*8+i]); font送信
Wire.endTransmission();
}
}
OLEDをクリアする.ゼロをGDRAM全域に書き込む.
void oled_clear()
{
for(int i=0;i<512;i++){
Wire.beginTransmission(OLED_ADRS);
Wire.write(0x40);
Wire.write(0);
Wire.endTransmission();
}
}
起動時に一度呼ばれるルーチン
各種初期化
void setup() {
Wire.begin(); I2C初期化
SSD1306の初期設定.コマンドは上で説明したとおり.
Wire.beginTransmission(OLED_ADRS); // transmit to device 3d
Wire.write(0x00); // below are commands
Wire.write(0x40); // display start line (40-7f)
Wire.write(0xa8); // line number
Wire.write(0x1f); // line number 32
Wire.write(0xa0); // left-right non inverting
Wire.write(0xc0); // up-down non inverting
Wire.write(0xda); // COM config
Wire.write(0x00); // COM config normal
Wire.write(0x2e); // stop scroll
Wire.write(0x20); // addressing mode
Wire.write(0x00); // addressing mode horizontal
Wire.write(0x21); // addressing range SEG0-SEG127
Wire.write(0);
Wire.write(127);
Wire.write(0x22); // addressing range PAGE0-PAGE3
Wire.write(0);
Wire.write(3);
Wire.write(0x81); // contrast
Wire.write(0xff); // contrast max
Wire.write(0xd5); // set OSC
Wire.write(0x80); // 370kHz
Wire.write(0x8d); // enable charge pump
Wire.write(0x14); // charge pump
Wire.write(0xaf); // OLED ON
Wire.endTransmission(); // stop transmitting
oled_clear();
}
ループルーチン
OLEDに0~9を0.5秒毎に表示する
void loop() {
if(oled_number==9) oled_number=0;
else oled_number++;
oled_numeric(oled_number);
delay(500);
}
かしこ
0 件のコメント:
コメントを投稿