I2C極小OLED(有機EL)SSD1306をスクロールさせたり、もうちょっと深堀りしてみました

Arduino
※OLED SSD1306 の I2C ライブラリプログラミング方法について、勘違いしていたところがありました。
以下の記事で新たに再検証しましたので、そちらをご覧ください。
有機EL ( OLED ) SSD1306 を再検証してみました ( I2C 通信用 )
(2017/10/16)

こんばんは。
前回に引き続き、専用ライブラリを使わないI2C極小OLED(有機EL)SSD1306の動作がだんだん分かってきたので、もうちょっと解明していきたいと思います。
ちなみに、OLEDはamazonさんで購入したものを使ってます。

前回の記事でのサンプルコードも、ちょっと修正(2015/7/2時点)しましたので、改めてご覧ください。
では、まず、表示方向について、こんな感じということを突き止めました。


スポンサーリンク


Adafruit さんのページの下の方にSSD1306のデータシートをダウンロードできますが、それを見ると、
始点が左上になってるんですよね。
でも、これは右下。
ということは、これは実は上下逆を想定して作ってあるのかな???
それにしては、VCC GND SCL SDA の文字の方向がこの写真の方向なんですが・・・
ま、これが分ればなんとかなります。
あと、PAGE という概念で垂直方向を表示させねばなりません。
例えば、最初のバイトのB00100001を書き込む場合、
Wire.write(B00100001);
というコードを使うとビットの最下位(LSB)が縦の一番下になり、上方が最上位ビット(MSB)になります。
縦方向かいな!!
と突っ込みたいところですが、本来こういうものなのでしょう・・・
また、連続で書き込むと1つのPageの中で、その場所で上書きされずに、自動でその次のcolumn位置に書き込まれます。
Wire.write(B11111111); というコードを連続で128回繰り返すと、1ページ分が白色で埋まります。
ただ、8bit以上垂直に表示させるためには、ページを切り替えなければなりません。
そして、水平のスタート位置を指定してやらねばなりません。 カーソルを移動するようなもんですね。
こんな感じで、縦方向に下がLSBなので、見た目のビット列が-90度ローテーションした形で出力されます。
あと、I2Cアドレスに関してもお知らせしておきます。
このように、裏面にアドレスが表示されてますが・・・


このアドレスをプログラムで指定しても動きません。
I2C機器のお約束ごとのようなもので、8ビットとした場合、左寄せのアドレスなのです。
これを1ビット右にずらした値を指定してやらないといけないんです。
何でこうなのかは、よく調べてないので分かりませんが・・・
また、これは、ビットをRAMに書き込むので、一度書き込んだら、延々と表示し続けます。
ですから、消す場合にはゼロを上書きするか、ディスプレイをOFFにすることです。
あと、ちょっと面白い機能がありました。
これには内蔵マイコンで、あるI2Cコマンドを指定してやれば画面全体がスクロールしたりできます。
動画はこんな感じです。
これは、8バイトひらがな3文字のビットマップを画面全体でスクロールさせてます。
最初の5秒は右から左にスクロール。
5秒後に斜めスクロールです。

あと、もう一つ、
スクロール範囲を変えると、こんなこともできます。
しかし、自分も使い方が良く分かってないです。

ちなみに、このOLEDはamazonさんで購入できます。
とりあえず、サンプルコードを載せておきます。
参考程度にどうぞ・・・

※OLED SSD1306 の I2C ライブラリプログラミング方法について、勘違いしていたところがありました。
以下の記事で新たに再検証しましたので、そちらをご覧ください。
有機EL ( OLED ) SSD1306 を再検証してみました ( I2C 通信用 )
下のコードは昔の私の解釈です。誤動作するかもしれませんのでご了承ください。
(2017/10/16)

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include <Wire.h>
#define OLED_ADDR (0x3C) //OLED address 製品に記載の数値は7bitなので、8bitに変換して1bit右へずらした値(78>>3c)
 
byte FontB[3][8]={{
  B00000000, //あ
  B00001100,
  B01010010,
  B01011000,
  B01010100,
  B11111110,
  B01001010,
  B00000100
  },{
  B00000000, //い
  B00011000,
  B00100000,
  B01000000,
  B00000100,
  B00000010,
  B00000100,
  B01111000
  },{
  B00000000, //う
  B00011000,
  B10100100,
  B10100010,
  B10100010,
  B10100010,
  B00010000,
  B00000000
}};
 
void setup(){
  delay(1000); //ESP-WROOM-02 ( ESP8266 )が起動するまで待つ
  Wire.begin();
  
  Initial_Display();
  Display_Black();
  Display_FontMap();
  H_Scroll_display();
  
  delay(5000);
  
  Initial_Display();
  Display_Black();
  Display_FontMap();
  VH_Scroll_display();
}
 
void loop(){
}
 
void Initial_Display(){
  Wire.beginTransmission(OLED_ADDR);
  Wire.write(B00000000); //control byte, Co bit = 0 (continue), D/C# = 0 (command)
    Wire.write(0xAE); //display off
    Wire.write(0xA8); //Set Multiplex Ratio  0xA8, 0x3F
      Wire.write(B00111111); //64MUX
    Wire.write(0xD3); //Set Display Offset 0xD3, 0x00
      Wire.write(0x00);
    Wire.write(0x40); //Set Display Start Line 0x40
    Wire.write(0xA0); //Set Segment re-map 0xA0/0xA1
    Wire.write(0xC0); //Set COM Output Scan Direction 0xC0,/0xC8
    Wire.write(0xDA); //Set COM Pins hardware configuration 0xDA, 0x02
      Wire.write(B00010010);
    Wire.write(0x81); //Set Contrast Control 0x81, 0x7F(明るさ設定)
      Wire.write(255); //0-255
    Wire.write(0xA4); //Disable Entire Display On(ディスプレイ全体ON)
    Wire.write(0xA6); //Set Normal Display 0xA6, Inverse display 0xA7
    Wire.write(0xD5); //Set Display Clock Divide Ratio/Oscillator Frequency 0xD5, 0x80
      Wire.write(B10000000);
    Wire.write(0x2E); //Deactivate scrollスクロール表示解除
    Wire.write(0x20); //Set Memory Addressing Mode
      Wire.write(0x10); //Page addressing mode
    Wire.write(0x21); //set Column Address
      Wire.write(0); //Column Start Address←水平開始位置はここで決める(0~127)
      Wire.write(127); //Column Stop Address 画面をフルに使う
    Wire.write(0x22); //Set Page Address
      Wire.write(0); //垂直開始位置(ページ)
      Wire.write(7); //垂直終了位置(ページ)
    Wire.write(0x8D); //Set Enable charge pump regulator 0x8D, 0x14
      Wire.write(0x14);
    Wire.write(0xAF); //Display On 0xAF
  Wire.endTransmission();
}
 
void Display_Black(){
  byte i, j, k;

  for(i = 0; i < 8; i++){//8ページ分塗りつぶす
    Wire.beginTransmission(OLED_ADDR);
      Wire.write(B10000000); //control byte, Co bit = 1 (One command only), D/C# = 0 (command)
      Wire.write(0xB0 | i); //set page start address←垂直開始位置はここで決める8bitで1ページ(B0~B7)
    Wire.endTransmission();
    
    for(j = 0; j < 16; j++){//column = 8byte x 16 ←8バイト毎に水平に連続で16個表示
      Wire.beginTransmission(OLED_ADDR);
      Wire.write(B01000000); //control byte, Co bit = 0 (continue), D/C# = 1 (data)
      for(k = 0; k < 8; k++){ //なぜか一気に128列まで出力できなかったので、8byte毎にした
        Wire.write(0x00);
      }
      Wire.endTransmission();
    }
    yield();
  }
}

void Display_FontMap(){
  byte i, j;

  Wire.beginTransmission(OLED_ADDR);
  Wire.write(B00000000); //control byte, Co bit = 0 (continue), D/C# = 0 (command)
    Wire.write(0xB0 | 4); //set page start address←垂直開始位置はここで決める(B0~B7)
    Wire.write(0x21);//set column addres
      Wire.write(50);//start column addres←水平開始位置はここで決める(0~127)
      Wire.write(127);//stop column addres
  Wire.endTransmission();

  Wire.beginTransmission(OLED_ADDR);
  Wire.write(B01000000); //control byte, Co bit = 0 (continue), D/C# = 1 (data)
    for(i = 0; i < 3; i++){//column = 8bit X 16 ←上記の8バイト1文字を水平に3個表示
      for(j = 0; j < 8; j++){
        Wire.write(FontB[i][j]);
      }
    }
  Wire.endTransmission();
}

void H_Scroll_display(){//Horizontal Scroll 水平スクロールのみ
  Wire.beginTransmission(OLED_ADDR);
  Wire.write(B00000000); //control byte, Co bit = 0 (continue), D/C# = 0 (command)
    Wire.write(0x2E); //For configuration, once off the scroll. 一旦スクロールをオフにしてから設定するみたい
    Wire.write(0x26); //Horizontal scroll set. 0x27=Reverse direction. 水平スクロールセット 0x27は逆方向
      Wire.write(0x00); //Dummy byte ダミーバイト
      Wire.write(0x00 | 0); //Define start page address. 0-7 スクロールするページ範囲の最初の位置0~7
      Wire.write(0x00 | 7); //Set time interval pattern. 0-7 スクロールスピード(フレームレート)選択0~7
      Wire.write(0x00 | 7); //Define end page address. 0-7 スクロールするページ範囲の最後の位置0~7
      Wire.write(0x00); //Dummy byte ダミーバイト
      Wire.write(0xff); //Dummy byte ダミーバイト
    Wire.write(0x2F); //Activate scroll スクロールをオン
  Wire.endTransmission();
}
 
void VH_Scroll_display(){//Vertical and Horizontal Scroll 水平垂直同時スクロール
  Wire.beginTransmission(OLED_ADDR);
  Wire.write(B00000000); //control byte, Co bit = 0 (continue), D/C# = 0 (command)
    Wire.write(0x2E); //For configuration, once off the scroll. 一旦スクロールをオフにしてから設定するみたい
    Wire.write(0x2A); //V and H Scroll Setup.0x29=Reverse.上下左右同時スクロール(斜めスクロール)0x29は逆方向
      Wire.write(0x00); //Dummy byte ダミーバイト
      Wire.write(0x00 | 0); //Define start page address.  0-7 スクロールするページ範囲の最初の位置0~7
      Wire.write(0x00 | 7); //Set time interval pattern. 0-7 スクロールスピード選択(フレームレート)0~7
      Wire.write(0x00 | 7); //Define end page address. 0-7 スクロールするページ範囲の最後の位置0~7
      Wire.write(0x00 | 63); //Vertical scrolling offset. 0-63 ここの数値を変えるといろいろ変化します0~63
    Wire.write(0x2F); //Activate scroll スクロールをオン
  Wire.endTransmission();
}

以上です。
誤り等ありましたら、ご連絡いただけると幸いです。
では、また・・・

最新記事では超小型の有機EL(OLED)SSD1306 を使ったNTP時計付き電光掲示板も作りました。
こちらをご覧ください
→Web News記事自動取得 OLED ( 有機EL )ミニ電光掲示板に16×16フリー日本語フォント( 東雲 ) を使う

Amazon.co.jp 当ブログのおすすめ

スイッチサイエンス ESPr Developer 32 Type-C SSCI-063647
スイッチサイエンス
¥2,420(2024/03/29 13:47時点)
ZEROPLUS ロジックアナライザ LAP-C(16032)
ZEROPLUS
¥22,504(2024/03/28 20:55時点)
Excelでわかるディープラーニング超入門
技術評論社
¥1,700(2024/03/29 10:12時点)

コメント

  1. kj より:

    このOLEDにグラフを表示させようとさまよっていたらたどり着きました。
    縦は1カラム単位でできるようなことがデータシートに書いてありましたが、表示内容を1列分横にずらすようなことはできるのでしょうか?

  2. mgo-tec より:

    kjさん
    こんばんは。管理人の MGO-tec です。
    このブログを見ていただいき、ありがとうございます。
    どういうグラフをどの様に水平にずらすのかが分からないのですが、
    例えば、このページのプログラムでいうと、103行目の
    (まだソースコードに行番号表示ができておらず、すみません)
    Wire.write(0x00 | 50); 
    の 50 の数値を0~126の範囲で変えると水平位置を1ドットの単位で文字をずらすことができます。
    このページのソースコードについてはページ全体の無条件リピートスクロールがありますが、この機能はグラフ表示には使えないと思います。
    あとは8bit単位でずらしていくか、またはビット中で1bitづつずらしていくかでしょうか。
    例えば、ビットが
    00000000
    11111111
    00000000
    00000000
    00000000
    00000000
    00000000
    00000000
    ならば、縦方向になりますから、直線が垂直に8ドット分表示されます。
    それをプログラム上でB11111111ビットを下の方にずらすと、画面では左方向にずれます。
    あと、ただ単に四角形や線を描いて、スピードを問わないのであれば、
    http://mgo-tec.com/blog-entry-29.html
    で紹介したライブラリを使った方が遙かに簡単にグラフや図形を描けます。しかし、メモリーを食うし、速度も遅いです。
    そのライブラリのサンプルプログラムを使うと使い方がある程度分かると思います。
    ただ、ちょっと特殊な使い方ですけど・・・。
    これで解決になったでしょうか?
    また分からなければ分かる範囲でお答えしますので、どうぞご連絡ください。

  3. kj より:

    回答ありがとうございます。
    すでに表示済みのものを動かしたいので
    Wire.write(0x00 | 50);を変えるの方法は無理・・ですよね?(多分)
    ライブラリのほうは、元が遅く・重く、さらに全画面書き換えしないと無理ということで諦めていたところで、こちらを見つけた次第です。
    速度優先であればスクロールは諦めて片方から端に行くまで縦に8バイト書いたほうが早いということですね。
    直接命令を送れば前に書いたものを残したまま次が書けるということが分かっただけでも大いに助かりました。ありがとうございます。

  4. mgo-tec より:

    kjさん
    お返事遅くなりすみません。
    > すでに表示済みのものを動かしたいので
    なるほど、OLEDのRAMコマンドでは今のとこを私はその方法を知りません。
    ビットを操作してプログラムを書き換えた方が良いかと思います。
    > 速度優先であればスクロールは諦めて片方から端に行くまで縦に8バイト書いたほうが早いということですね。
    なるほど、表示したものをスクロールさせるのであれば、プログラム上で電光掲示板のようにビット操作するしかないと思います。
    このOLEDのRAMコマンドでは一度出した画面をスクロールさせるしかないので、刻々変わるデータをスクロールさせる場合はビット操作プログラムを組むしかないかと思います。
    ですが、以前、画面の一番右端に1列のビット棒グラフメーターを表示させ、それを画面全体スクロールさせると、データロガー的なグラフができます。あまりキレイな表示ではなかったのですが、127×64画面一杯の1データグラフならできました。
    OLEDのRAMコマンドでスクロールよりも、プログラムでビット操作してずらした方が自由度が効いて良いと思います。
    これでも、ライブラリを使うよりは早いと思います。
    以上、あまりお役に立てず、すみません。

  5. kj より:

    回答ありがとうございます。
    >ビット操作プログラムを組むしかないかと思います。
    その通りだと思います。ですが、フレームバッファで1kバイト使ってしまうのでハードウェアで出来ないものかと考えた次第ですが中々うまくはいかないものですね。
    スクロールは諦めて、byte[8][8]分データを書いて9回目には次のカラムへ、端までいったら最初に戻る形にしようと思います。

  6. mgo-tec より:

    そうですね。
    私は早めにUNOに見切りをつけて、MEGAに移行しました。SRAM 2KBは少なすぎます。
    また、何かありましたらご連絡ください。

  7. Ryogen より:

    はじめまして、SSD1306の日本語解説のページがなかなかなくてこちらにたどり着きました、とても参考になっております。
    自分は128*32のSPI版を使用しております、ライブラリをDUEで作成してRX62×のボードに使う予定です。
    32ドット版ですのでRAMのページは4ページになるはずなのですが、どういうわけかあたかも8ページあるような動作をしています、書き込みデータにFFを送っても4ドット分だけ塗りつぶされ、次のページの同じカラムにFFを送ってようやく8ドット表示されます、仮想的に64ドットになっており50%に縮小されるようなイメージです、実際には偶数ビットのデータだけがGDRAMに書かれるようです、SSDのコマンドに0xA8(MulitiplexRatio)と言うのがあり、これに0x1f(32Line)を送って見ましたが、単に表示ページが制限されるだけでした、どこか設定の見落としガあるのでしょうか?何かお心当たりがあればお願いします。

  8. mgo-tec より:

    Ryogenさん
    記事をお読み頂きありがとうございます。
    残念ながら私は32ドット版のSSD1306は持っておりませんので、全く分からない状態です。
    Adafruitさんのホームページでデータシートを見てみましたが、64ドット版でしたね。
    実機で確かめてみなければ何とも言えない状態です。
    Adafruitさんのホームページで、UG-2832HSWEG04のデータシートはありましたね。
    これは32ドット版のようですが、これを参考に試してみるしかないのでしょうか・・・。
    私もSSD1306は正直言って殆ど使い方が分っていない状態で、とりあえず試しては修正して・・・の繰り返しで、ひたすら試行錯誤してみただけです。何も手がかりがありませんでした。
    ですから、そのデバイスは当ブログ記事でアップしていること以上のことは分からない状態です。
    今の状態ではお力になれず、申し訳ございません・・・m_ _m

  9. Ryogen より:

    お返事ありがとうございます。
    ビットマップデータを問題に対応した形で拡張すれば表示出来ている状態です、こちらこそお忙しいところ、お手を煩わせてしまって申し訳ありませんでしたm(_ _)m。
    また、返信の仕方がわからず新しくスレ立てちゃってすみません^^;

  10. 内緒です より:

    WEMOS D1 mini OLED Shield の動作確認ができず困っていましたが
    ここのサンプルプログラムがそのまま動作しました。
    表示は、64×48なので設定値を試行錯誤してみるつもりです

    • mgo-tec mgo-tec より:

      内緒です さん

      コメントありがとうございます。
      名前だけ「内緒です」と解釈しましたので、コメントは公開させていただきます。

      かなり前に作ったプログラムですが、動いて何よりです。

      WEMOS のOLED イイですね。
      パラレル端子がありそうですね。
      もし、パラレルがあるのなら、高速表示できそうです。

  11. MK より:

    このOLEDモジュールを使おうとしていたところで、とても参考になりました。ありがとうございます。
    ところが、いろいろ応用していくとうまくいかないところが多く、データシートも見直していると、勘違いに気付きました。
    それは、I2Cの場合、startTransmission()の直後の1バイトは”Control byte”として、後に続くデータの種類を判別するのに使われるという点です。
    Control byte:
    0x00-0x3F: 次からendTransmisson()までのデータはコマンドデータ。
    0x40-0x7F:次からendTransmisson()までのデータはGDRAMデータ。
    0x80-0xBF:次はコマンドデータ、その次は”Control byte”。
    0xC0-0xFF:次はGDRAMデータ、その次は”Control byte”。
    なので、データ書き込み前の「 Wire.write(0x40);」は、
    Set Display Start Lineコマンドではなく、次からGDRAMデータであることを示すControl byteです。
    また、コマンドも、startTransmission()の直後の1バイトはコマンドとしては機能していないと思われます。

    • mgo-tec mgo-tec より:

      MKさん

      コメントありがとうございます。

      しばらく、I2C 通信をつかっておらず、SSD1306 も使っていなかったので、こういうご指摘をいただけると助かります。
      何しろ1年半も前に作ったもので、SSD1306 もまだ良く分からない時で、試行錯誤していたものでした。
      それで、とりあえず意図した動作ができたので、記事に掲載した状態です。

      今は別の取り込み中作業があり、すぐに検証できませんので、確かめられたらまたこのコメント欄でお知らせしたいと思います。
      ありがとうございました。

    • mgo-tec mgo-tec より:

      MKさん

      検証が遅くなり申し訳ございません。
      この記事を上げた当時は良く分からないままでも意図した動作が出来たので放っておいたのですが、改めて見直すと間違いだらけでした。
      当時は、「動いたからいいや」みたいなノリで深くデータシートを読み込むことはしませんでした。
      ゴメンナサイ。m(_ _)m

      久々にSSD1306 のデータシートを読み込んでいくと、確かにコントロールバイトが必要と明記してありました。
      だだ、検証に時間がかかったのは、control byte の説明文で、Co bit の解釈が難しかったのです。

      If the Co bit is set as logic “0”, the transmission of the following information will contain
      data bytes only.

      となっているので、Co bit をゼロにしたら、後はデータバイトのみと読み取れたので、コマンドにできないのではないかと思って、散々試行錯誤を繰り返しハマってしまいました。
      でも、よくよくデータシートを見てみると、コマンドもデータの分類にされている感じだということが分かってきました。
      よって、コントロールバイトを0x00 にすると、後は続けてWire.endTransmission();までコマンドを置けるということが分かって解決しました。
      なかなか英語の解釈が難しいですね。

      私が解釈した、コントロール(control)バイトの意味はこんな感じです。

      0x00(B00000000) コマンドを続けて連続で指定できる
      0x80(B10000000) コマンドを1回(1byte)だけ指定できる。
      0x40(B01000000) GDRAMにデータを連続で32byteまで書き込める。
      0xC0(B11000000) GDRAMに1回(1byte)だけ指定できる。
      

      これで、beginからendTransmission までにCoビットを0と1の混在で使うことはできませんでした。
      また、Coビットが1の場合、コマンドが2byte以上あるものは使えませんでした。
      でも、Coビットを0にすれば、何も問題ないので良いと思います。

      あと、もう一つ。
      GDRAM にデータを書き込む場合に、128バイト連続でデータを書き込めるかと思いきや、32byte までしか受け付けませんでした。
      私が何か間違えているんでしょうか?

      ということで、他の記事でもアップしたSSD1306 I2C のソースコードを全て訂正することにします。
      MKさん、ホントにご指摘ありがとうございました。
      m(_ _)m

  12. MK より:

    mgo-tecさん
    ご確認ありがとうございました。

    私も、SSD1306 のデータシートを何度も読みましたが、なかなか理解するのに時間がかかりました。Co(Continuation bit)が”0″の説明はあってもCoが”1”の説明は無いのですから。
    理解できるきっかけになったのは、I2Cのデータシーケンスの図の下にある
    “Slave Address”、”m≧0 words”、”1 byte”、”n≧0 bytes”
    という記述でした。
    これを見て、”m≧0 words” というのは、
    「control byte(Co=1) + 1 byte data(Command/Data) 」のペアがm回繰り返すことであり、
    “1 byte”+”n≧0 bytes” というのは、
    「control byte(Co=0) + n byte data(Command/Data) 」がStopCondition まで1回続くということだと考えれば理解できるとわかりました。

    「連続で32byteまで」というのは、ArduinoのWireライブラリのせいだと思います、Wire.hの中に「#define BUFFER_LENGTH 32 」という定義があって、I2Cのデータバッファのサイズを決めています。
    startTransmission の後、write されたデータはこのバッファに貯められ、endTransmission で一気に送信されるので、BUFFER_LENGTHを超えたデータをwriteすると、無視されてしまいます。
    なので、control byte が1バイトあるので、GDRAMに連続で書けるデータは正確には31バイトだと思います。
    また、BUFFER_LENGTHを増やせばその分連続で書けるデータ数は増やせると思います。

    • mgo-tec mgo-tec より:

      MKさん
      お返事ありがとうございます。

      なるほど、言われてみれば、”m≧0 words” のところをよく見ると、そう読めますね。
      私はそこだけ見てても全然分かりませんでした。

      連続データ32byte については、確かに Wire.h に記述されていましたね。
      そこまでは調べてませんでした。
      よくぞ、そこまで調査されたと思って、感心してしまいました。
      スバラシイの一言です。

      これで、おかげ様で、当時は謎だらけだったSSD1306については大分解明できたと思います。
      重ねてお礼申し上げます。
      本当にありがとうございました。
      m(_ _)m

タイトルとURLをコピーしました