OLEDモジュールを衝動買い
まったく時が経つのは実に早いもので、前回のブログ記事はお正月でしたね(笑)。
共立電子でArduino用のパーツを物色していて、表示に美しさに惹かれてWinstarのWEH001602A を衝動買い。あまり説明を良く読まずにかったのが悪かったのですが、このパーツArduinoのLCDライブラリ(LiquidCrystal)では使えません。
デジットblogのここ にもさらっと書かれていますが、
☆コントローラ:WS0010
●LCD標準コントローラHD44780系コントローラと同じ操作性
●コマンドセットは基本的に同じ
●イニシャライズ等プログラム的に一部変更が必要
WS0010 DATASHEET
HD44780系コントローラと同じ操作性
と書かれているところが味噌で、残念ながらHD44780互換では無かったです。
あとは手がかりとなるのはデータシートらしいのですが、そもそも組込み屋でもない私にはデータシートの読み方が分からず、どうやってこれをソースコードに落としこめばいいのかさっぱりです。
ちなみに上記のデジットBlog内のデータシートのリンク先よりも、WINSTAR本家のこのページ のデータシート(WS0010を選択)のほうが新しいです。
とりあずググってみた
とりあえずデータシートは置いといて、誰かサンプルコードぐらいで書いてるんじゃないかと検索してみると、参考になりそうなNicolas Electronics のページや、そのままずばりArduino用のライブラリ(Adafruit )などいくつか見つかりました。
やはり先人たちがやってくれていた、私がわざわざコードを書く必要は無かったのだと安心して、サンハヤトのArduino用ユニバーサル基板 に目をしょぼしょぼさせながらWHE001602Aをハンダ付けして自作シールドを作成。
手元のジャパニーノに接続しAdafruitのライブラリのサンプルをコンパイルしてみると、あっさり「Hello OLED World」が表示されました。うーん、思ったより簡単で拍子抜け。
4bitモードだと不安定?
メッセージを変えてコンパイルしてArduinoをリセットすると、時々文字が化けて全く読めない状態になった。しかもUSBを抜き差ししてもなかなか元に戻らない。
いろいろ原因を探していてこのページ を見つけた。それによるとこのOLEDの電源に問題があってノイズが乗りやすく、4bitモードで下位ビットのデータを送出する時にノイズが乗ると誤動作してしまうらしい。おまけにOLEDの利点でもある低消費電力が災いして、USBから抜いた後もしばらく、その状態を維持したままになってしまうとのこと。なるほど納得な説明だ。
これを避けるには素直に8ビットモードで使うか、ノイズ対策しろということなんだろうけど、単に表示するだけなら8ビットモードで動かすのが簡単だけど、Arduinoのピンを余分に4つも使うのは勿体ないし、かとハード的に対策するのも面倒。
あらっ?!スペックシートに解決策が!
何度も実験してみて一度正常に表示されれば、その後は安定して表示されているので、初期化ルーチン工夫すればなんとかなりそうってことで、ここでいよいよさきほどのスペックシート を読んでみたら、最終ページにそのものずばりな事が書かれてる!
Notes
Repeated procedures for an 4-bit bus interface
Noise causing transfer mismatch between the four upper and lower bits can be corrected by a reset triggered by consecutively writing a “0000” instruction five times. The next transfer starts from the lower four bits and then first i nstruction “Function set” can be executed normally.
Please insert the synchronization function in the head of procedures. The repeated procedures are show as follows
要するに4ビットモードでおかしくなっちゃったら”0000”を5回送ってリセットしろという事ですね。
Adafruitのコードを眺めて、なんとなくスペックシートとソースコードとの関連も見えてきたので、トライアンドエラーの末なんとか動くようになったのが以下のコードです。
/* ASM Defines */
#define NOP "nop\n\t" //62.5ns
#define NOP_1us NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP
#define NOP_625ns NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP
#define tAS68 NOP //62.5ns at 16MHz (SPEC min 20ns)
#define tCY68 NOP NOP NOP NOP NOP NOP NOP NOP //Min 500 ns
#define tPW68 NOP NOP NOP NOP // Min 250 ns
#define tDS68 NOP //Min 40 ns
#define tDH68 NOP //Min 20 ns
#define tOD68 NOP //Min 10 ns
/* CURSOR/DISPLAY SHIFT INSTRUCTION */
#define CUR_LEFT 0x00 //Shifts the cursor position to the left. (AC is decremented by 1).
#define CUR_RIGHT 0x10 //Shifts cursor position to the right. (AC incremented by 1).
#define DSP_LEFT 0x20 //Shifts entire display to the left. The cursor follows the display shift.
#define DSP_RIGHT 0x30 //Shifts the entire display to the right. The cursor follows the display shift
#define OLED_WHITE_SHIELD 1
/* Pin Settings (4bit Only) */
#ifdef OLED_WHITE_SHIELD
unsigned char _rs_pin = 5 ; // LOW: command. HIGH: character.
unsigned char _rw_pin = 6 ; // LOW: write to LCD. HIGH: read from LCD.
unsigned char _enable_pin = 7 ; // activated by a HIGH pulse.
unsigned char _busy_pin = 10 ; // HIGH means not ready for next command
unsigned char _data_pins [ 4 ] = { 8 , 12 , 11 , 10 };
#endif
#ifdef OLED_GREEN_SHIELD
unsigned char _rs_pin = 2 ; // LOW: command. HIGH: character.
unsigned char _rw_pin = 3 ; // LOW: write to LCD. HIGH: read from LCD.
unsigned char _enable_pin = 4 ; // activated by a HIGH pulse.
unsigned char _busy_pin = 9 ; // HIGH means not ready for next command
unsigned char _data_pins [ 4 ] = { 12 , 11 , 10 , 9 };
#endif
unsigned char _led_pin = 13 ;
char moji = '1' ;
unsigned char line = 0 ;
unsigned char cur = 0 ;
void write4bit ( unsigned char data )
{
int i = 0 ;
for ( i = 0 ; i < 4 ; i ++ ) {
pinMode ( _data_pins [ i ], OUTPUT );
digitalWrite ( _data_pins [ i ], ( data >> i ) & 0x01 ? HIGH : LOW );
}
digitalWrite ( _enable_pin , HIGH );
__asm__ ( tCY68 );
digitalWrite ( _enable_pin , LOW );
}
void checkBusy ()
{
unsigned char busy = 1 ;
int i = 0 ;
for ( i = 0 ; i < 4 ; i ++ )
{
pinMode ( _data_pins [ i ], OUTPUT );
digitalWrite ( _data_pins [ i ], HIGH );
}
busyMode ();
do {
digitalWrite ( _enable_pin , LOW );
digitalWrite ( _enable_pin , HIGH );
busy = digitalRead ( _busy_pin );
digitalWrite ( _enable_pin , LOW );
digitalWrite ( _enable_pin , HIGH );
digitalWrite ( _enable_pin , LOW );
__asm__ ( NOP_1us );
} while ( busy );
}
void busyMode ()
{
pinMode ( _busy_pin , INPUT );
__asm__ ( tAS68 );
digitalWrite ( _rs_pin , LOW );
__asm__ ( tAS68 );
digitalWrite ( _rw_pin , HIGH );
}
void commandMode ()
{
digitalWrite ( _enable_pin , LOW );
__asm__ ( tAS68 );
digitalWrite ( _rs_pin , LOW );
__asm__ ( tAS68 );
digitalWrite ( _rw_pin , LOW );
}
void writeMode ()
{
digitalWrite ( _enable_pin , LOW );
__asm__ ( tAS68 );
digitalWrite ( _rs_pin , HIGH );
__asm__ ( tAS68 );
digitalWrite ( _rw_pin , LOW );
}
void readMode ()
{
digitalWrite ( _enable_pin , LOW );
__asm__ ( tAS68 );
digitalWrite ( _rs_pin , HIGH );
__asm__ ( tAS68 );
digitalWrite ( _rw_pin , HIGH );
}
void syncFunc ()
{
int i = 0 ;
// Synchronization function for an 4-bit bus
for ( i = 0 ; i < 5 ; i ++ ) {
write4bit ( 0x00 );
}
}
void functionSet ()
{
// Function Set
write4bit ( 0x02 ); //Set 4 bit Mode
write4bit ( 0x02 ); //Font Setting HIGH Bit
write4bit ( 0x08 ); //Font Setting LOW Bit (2LINES and 5x8 and ENGLISH_JAPANESE
checkBusy ();
}
void initCmd ()
{
// Initial Command Setting
write4bit ( 0x00 ); //Display ON HIGH
write4bit ( 0x01 ); //Display ON LOW
checkBusy ();
}
void writeCmd ( unsigned char command )
{
int i = 0 ;
unsigned char hIns = command >> 4 , lIns = command ;
commandMode ();
write4bit ( hIns );
write4bit ( lIns );
checkBusy ();
}
void writeData ( unsigned char data )
{
int i ;
unsigned char hIns = data >> 4 , lIns = data ;
//commandMode();
//syncFunc();
//functionSet();
writeMode ();
write4bit ( hIns );
write4bit ( lIns );
checkBusy ();
}
void displayOn ( unsigned char d , unsigned char c , unsigned b )
{
writeCmd ( 0x08 | ( d << 2 ) | ( c << 1 ) | b ); //entry mode
}
void clearDisplay ()
{
writeCmd ( 0x01 ); //clear display
}
void returnHome ()
{
writeCmd ( 0x02 ); //Return Home
}
void entryMode ( unsigned char increment , unsigned char shift )
{
writeCmd ( 0x04 | ( increment << 1 ) | shift ); //entry mode
}
void cursorDisplay ( unsigned char shift )
{
writeCmd ( 0x10 | shift ); //Shift & Cursor
}
void setCGRAMAddr ( unsigned char x , unsigned char y )
{
writeCmd ( 0x80 | ( y << 6 ) | x );
}
void initOLED ()
{
pinMode ( _rs_pin , OUTPUT );
pinMode ( _rw_pin , OUTPUT );
pinMode ( _enable_pin , OUTPUT );
pinMode ( _led_pin , OUTPUT );
/* Wait For Power Stabilization 500 ms */
delay ( 500 );
commandMode ();
syncFunc ();
functionSet ();
displayOn ( 1 , 0 , 0 ); //display on
clearDisplay (); //clear display
returnHome ();; //Return Home
entryMode ( 1 , 0 ); //entry mode
cursorDisplay ( CUR_RIGHT );
}
void setup ()
{
initOLED ();
setCGRAMAddr ( line , cur ++ );
writeData ( moji ++ );
pinMode ( 13 , OUTPUT );
}
void loop ()
{
digitalWrite ( 13 , HIGH );
if ( cur > 15 ) {
line = line ? 0 : 1 ;
cur = 0 ;
setCGRAMAddr ( line , cur );
} else {
cur ++ ;
}
writeData ( moji ++ );
delay ( 500 );
digitalWrite ( 13 , LOW );
delay ( 500 );
if ( moji > 'z' ) moji = 'a' ;
}
LiquidCrystal互換なライブラリにしろよって声が聞こえて来そうな、ぜんぜん違うコードなのだけど、まあスペックシートから起こしたらこうなったって感じなので、これを参考にもっと高機能でいかすライブラリをどなたか書いてくださる事を希望します。