Windowsアプリケーションから Arduino の OLED ディスプレイを操作する(HSP - 画像送信編)
todo: draft
目標
- シリアル通信を介してUSBで接続された ArduinoMicro+OLED に画像を表示する.
環境
- Arduino IDE (Windows Store Version)
- Arduino Micro 5V (ATmega32u4)
- ディアイワイモール (DIYmall) iic i2c OLEDモジュール ディスプレイ 0.96インチ 51マイクロコントローラ12864 for Arduino
前提
- "Adafruit SSD1306" と "Adafruit GFX Library" を使用したサンプルが動作すること.
次の記事を参考にしてください.
特にI2Cアドレスの罠は注意.
PC上で読み込んだ画像をArduinoに転送・表示
- Adafruitのサンプルによれば,
display.drawPixel
を使って,特定のピクセルに対して色を設定することが出来る. - つまり,1ピクセルずつ丁寧に色を決めていけば,画像は書ける.
- 画像の転送はどうすればよいか?
- 転送する画像の横幅は128pxと決め打ちしておくとする.
- 左上から順に「白黒白白・・・」というデータをシリアル通信を介して送ればよい.
ここでは,末尾コードを
0
,黒を表すコードを1
,白を表すコードを2
とした.というわけで,とりあえず動くコードを示します.
Arduino側のコード
#include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 4 Adafruit_SSD1306 display(OLED_RESET); #if (SSD1306_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SSD1306.h!"); #endif void setup() { Serial.begin(9600); // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64) // init done // Show image buffer on the display hardware. // Since the buffer is intialized with an Adafruit splashscreen // internally, this will display the splashscreen. display.display(); delay(2000); display.clearDisplay(); display.display(); } int buffcount = 0; void loop() { while (Serial.available() > 0){ int c = Serial.read(); if (c == 0){ display.display(); buffcount = 0; } else{ int y = buffcount/128; int x = buffcount%128; display.drawPixel(x, y, c == 1 ? BLACK : WHITE); buffcount++; } } }
HSP側のコード.2値画像変換パートはこの記事のメインでは無いので適当に流してください.
#include "hspext.as" #const COMPORT 3 // 256色モード screen 0, 320, 240, 1 // ダイアログ表示 dialog "jpg;*.jpeg;*.png;*.bmp;*.gif",16 if stat == 0 : end // 白と黒の2色のみから成るパレットに書き換え repeat 256 : palette cnt,0,0,0,0 : loop palette 0,255,255,255,1 // 画像をスクリーン1に読み込み buffer 1 : picload refstr wx = ginfo_winx wy = ginfo_winy // スクリーン1から0へ縮小コピー gsel 0 pos 0,0 : gzoom 128,128*wy/wx,1,0,0,wx,wy // 画像処理ここまで // ================================ // シリアルで画像を送信する onexit *exit // シリアル通信に接続 comopen COMPORT, "baud=9600 parity=N data=8 stop=1" if stat : dialog "error: comopen" : end repeat 64 y = cnt repeat 128 x = cnt pget x,y if ginfo_r == 0 : c = 1 : else : c = 2 // c=1:黒 , c=2:白 // シリアル通信にピクセルデータを送信 computc c loop loop // 末尾データ computc 0 dialog "complete" *exit comclose end
動いた
- 試してみていかがでしたか?
- 上で示したコードは無駄な事をたくさんしている.
- 無駄な要素をすべて列挙し始めたらキリが無いので,『通信』に焦点を当てて改善しよう.
通信の高速化をしよう
computc 10000回は遅い
- HSP側のプログラムが画像を転送し始めてから完了するまで6.95秒掛かった.
computc
を大量に呼び出すのはあまり効率的ではない.- バッファに送信するデータを蓄えてから,まとめて送信するべき.
- というわけで,この改善を行ったコードを次に示す.
- 以下のコードでは,送信するデータを文字列型変数buffに蓄えている.
- 実行時間は0.5秒まで短縮した.
正直がっかりした.
#include "hspext.as" #const COMPORT 3 screen 0, 320, 240, 1 dialog "jpg;*.jpeg;*.png;*.bmp;*.gif",16 if stat == 0 : end repeat 256 : palette cnt,0,0,0,0 : loop palette 0,255,255,255,1 buffer 1 : picload refstr wx = ginfo_winx wy = ginfo_winy gsel 0 pos 0,0 : gzoom 128,128*wy/wx,1,0,0,wx,wy // ================================ // シリアルで画像を送信する onexit *exit comopen COMPORT, "baud=9600 parity=N data=8 stop=1" if stat : dialog "error: comopen" : end sdim buff,10000 p = 0 repeat 64 y = cnt repeat 128 x = cnt pget x,y if ginfo_r == 0 : c = 1 : else : c = 2 // c=1:黒 , c=2:白 poke buff,p,c // computc c p += 1 loop loop comput buff computc 0 dialog "complete" *exit comclose end
データ圧縮アイデア
- ※ここから先はアルゴリズムな話になります.よろしくお願いします.
- データの転送に使用する文字は3種類.
- しかし,256種類(8bit)使えるはずである.
- もっと効率化できるはず.
データ圧縮その1
int posy = 0, posx = 0; void loop() { while (Serial.available() > 0){ int c = Serial.read(); for (int i = 0; i < 8; ++i){ display.drawPixel(posx+i, posy, c & (1 << (7-i)) ? WHITE : BLACK); } posx += 8; if (posx == 128){ posx = 0; ++posy; } if (posy == 64){ posy = 0; display.display(); } } }
- HSP側のコードを示す前に,HSP特有の融通の利かなさについて.
- C言語等多くの言語では文字列の末尾に
\0
を付け加えて終端を表すことが多い. - HSPの
comput
命令でも同様で,\0
をデータの終端として認識する. - よって,バッファの途中に
\0
を含む場合,その\0
がデータの終端として認識されてしまう. computc
を使えば解決するが,先程の通り,なるべく使いたくはない.- そこで,バッファの途中に
\0
を含む場合,0にならないようランダムにビットを立てている.
sdim buff,10000 p = 0 i = 0 bit = 0 repeat 64 y = cnt repeat 128 x = cnt pget x,y if ginfo_r == 0 : c = 0 : else : c = 1 bit = (bit << 1) | c i += 1 if i == 8 { poke buff,p,bit p += 1 bit = 0 i = 0 } loop loop // 文字コード0を含む文字列はcomputでは送信できない… // 無理やり0以外の数値に書き換えている repeat p if peek(buff,cnt)==0 : poke buff,cnt,1<<rnd(8) loop comput buff dialog "complete"
- この改善で,効率は8倍になるはず.
データ圧縮その2
- 色の連続数をデータとする手法.一般に連長圧縮と呼ばれる.
- 「0が20個」「1が12個」「0が40個」…というデータを送る
- 問題は,同じ色が256個以上連続するケース.
- この場合,「0が255個」「1が0個」「0がX個」とすれば良い.
- 前述の通り,hspextの都合から
0
は送信出来ないので,0の代わりに255を使用する. - すなわち,同じ色が260個の場合は
254,255,6
となる.
int pos = 0; int col = 0; void loop() { while (Serial.available() > 0){ int c = Serial.read(); if (c == 0){ display.display(); pos = 0; continue; }else if (c != 255){ for (; c; --c, ++pos) display.drawPixel(pos&127, (pos>>7)&63, col ? WHITE : BLACK); } col ^= 1; } }
sdim buff,10000 p = 0 count = 0 last = -1 repeat 64 y = cnt repeat 128 x = cnt pget x,y if ginfo_r == 0 : col = 0 : else : col = 1 if last == -1 { last = col if col == 1 { poke buff,p,255 : p += 1 } } if col != last { poke buff,p,count : p += 1 count = 0 last = col } if count == 254{ poke buff,p,count : p += 1 poke buff,p,255 : p += 1 count = 0 } count += 1 loop loop if 0 < count{ poke buff,p,count : p += 1 last = last ^ 1 } if last == 1 { poke buff,p,255 : p += 1 } comput buff computc 0 dialog "complete"
- 効率は 約2倍~254倍.