2020年5月27日水曜日

OLED(SH1106 128x64 I2C)でメーター遊び

前回の「OLED(SH1106 128x64 I2C)を噛じる」で
SH1106のライブラリを探してた時に目に止まってたものです。
Analog VU Meter - I2C OLED SH1106 - OLEDMeter Animation
OLEDは、マーティーが入手してるのと同じ
128 x 64、ドライバ SH1106なのです。
それと、Arduino UNOです。

では、作者stievenart氏のサイトの下の方から
スケッチ「OLEDMeter.ino」をダウンロードさせていただきますm(_ _)m
開いて眺めると、何と!
21~22行目に
「Adafruit_SH1106.h」を使ってるじゃないですか!
てっきり、Adafruitライブラリは、SSD1306用だけかと思ってました。
*********
#include <Adafruit_GFX.h>        // https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SH1106.h>  // https://github.com/wonho-maker/Adafruit_SH1106
*********

早速、Adafruitの2つのライブラリーをGitHubからダウンロードします。
Adafruit-GFX-Libraryは、ここから
[Tags]を見てみると、執筆時点では、「v.1.2.3」になってました。
Clone or download ]から最新のmasterをダウンロードします。
「Adafruit-GFX-Library-master.zip」
Adafruit_SH1106は、こっちから
[Tags]を見てみると、執筆時点では、「Nothing to show」でした。
最後のUpdate「9 Jan 2017」なので随分昔からあったんですね~
これも[ Clone or download ]からmasterをダウンロードします。
「Adafruit_SH1106-master.zip」
マーティーは、いまだ、Arduino IDE Ver.1.8.3 です。
[スケッチ]-[ライブラリをインクルード]
-[.ZIP形式のライブラリをインストール]で
ダウンロードしたZIPファイル
「Adafruit_SH1106-master.zip」
を解凍せずに選択します。
画面は、何も反応ないように見えますが、一瞬で追加されます。
夫々下に「ライブラリが追加されました。...」と出ます。
再度、[.ZIP形式のライブラリをインストール]で
「Adafruit-GFX-Library-master.zip」を選択します。
[ライブラリをインクルード]の一覧の下の方に
[Adafruit GFX Library]と[Adafruit_SH1106-master]が追加されてます。
では、作者stievenart氏のサイトの下の方からダウンロードしてた
スケッチ「OLEDMeter.ino」を開きます。
ざっと眺めると
Display TypeとI2Cアドレス設定は、107行目だけですね~
マーティーのOLEDは、
前回」の最初に「i2c_scanner」ってのでI2Cアドレスを調べて
「0x3C」なので変更不要です。
では、まずは、[検証・コンパイル]
前のU8g2ライブラリの時よりコンパイル時間が半分以下のようです。
メモリ使用量も少ないですね~
取り敢えず、オーディオ入力なしでやってみます。
Arduino UNOとの結線図です。
 OLED    Arduino UNO
  Vcc  ・・・  5V 
  GND ・・・ GND
  SDA ・・・  A4
  SCL  ・・・  A5
書き込むと~
お~~っ!
これは、カッコいい!
拡大!
では、A0に
2.5mmのヘッドホンジャックをつけて
と思ったら、オスのモノラルジャックが先に見つかったので
即席でケーブル作って
PCのHeadphone出力とArduino UNOのA0をAMPなしで直結です。
ピンジャックの中に抵抗でLRをMIXしているケーブルです。
お~っ!デジタルでアナログライクなのは、いいですね~
動画で!
PCからは音が出ないので、映像の音は、スマホ+Bluetooth Speakerです。
PCとスマホの再生ボタンをタイミングを何度か練習して、ほぼズレなしですが、
Arduinoでは、50msecでPeak to Peakの計算してるので
針の動きは、かなり平均化された動きです。

所で、作者stievenart氏は、メーターの針以外の目盛画像を
Windowsのペイントソフトで作成して、
ここのImage2GLCDというツールでHEXコードに変換されています。
ダウンロードしようとすると
登録しないとダウンロードできませんね~(T_T)
他のを探してみると、
GitHubのここによさそうなのがありました。

試しにやってみます。
まずは、「Analog VU Meter - I2C OLED SH1106」のサイトから
目盛りの背景画像をコピーしてきます。
その画像からメーター部分だけを切り出すと、240 x 128 pixelになったので
両サイドを黒塗りして、256 x 128 pixelの画像サイズにします。
OLED 128x64 の丁度倍です。
で、GitHubのimage2cppサイトに行って
[参照]ボタンから
256x128のJPEGファイルを開くと
[Canvas size]の所が、オリジナルサイズの256 x 128になってます。
[3. Preview]に入力画像と同じものが表示されます。
[Canvas size]を出力サイズの128 x 64 にします。
[Background color]:Black
そのままでは、Previewが、入力画像の一部になるので、
[Scaling]を[scal to fit, keeping proportions]にします。
PreviewがScalingされた正しい画像になりました。
[4. Output]で
[Code output format]plain bytes
[Draw mode]Horizontal - 1 bit per pixel
にして[Generate code]を押すと、下の欄にHEXコードが生成されます。
そのコードを全部コピーして
スケッチ「OLEDMeter.ino」を開いて
「 VUMeter[] = { 」の後ろ
38~101行目のデータと入れ替えます。
image2cppで生成したデータと差し替えました。
書き込みます!
単純に間引きされただけのようで、やけに汚いですね~(T_T)
オリジナルの目盛りするには、
最初から128 x 64のキャンパスで描かないといけなさそうです。
描くの大変だったろうな~と思います。
ちょっとVolt Meterを描いてみました^^;
パワポで描いて、スクリーンショットしてトリミングしたので
1105x554
GIMPで補間無しで128x64にリサイズ
ん~ん、まあまあかな~
GitHubのimage2cppサイトに行って
サイズ 128x64 の画像を読み込んでるので
[Scaling]:orignal size
にして、[Generate code]します。
一応、Previewが正しく表示されてることを確認して
細かい所は置いといて(-_-;)
一応できました!

折角なので、電圧計にしたいですね~^^;

元のスケッチを理解する必要があります。
なかなかややこしそうです。
頑張って、絵を描いてみました^^;


やっと、元のスケッチの式の意味が理解できたので
電圧計用に変更します。
それにしても、sin、cos関数を使ってるのに
#include <math.h> なくてもできてますね~ なぜだろう?

***元のOLEDMeter.inoのvoid loop()内のみ変更******
void loop(){
    sample = analogRead(analogInput);

// convert volts to arrow information
// MeterValue: 針(半径rMeter=80)の先端が振れる円弧長
// 針がADCmax:1023で86°振れる円弧長、2*PI*80*86/360=120.079
  float MeterValue = sample * 120.079 / 1023;

// shifts needle to zero position
// 針先の円弧上の針中央位置にシフト
// 半径rMeter=80で86°の円弧長の1/2・・・120.079/2=60.039
  MeterValue = MeterValue - 60.039;

  display.clearDisplay();                                // refresh display for next step
  display.drawBitmap(0, 0, VUMeter, 128, 64, WHITE); // draws background

// 針先端のX座標=針底部X座標+針のX方向の振れ量
// sin内、半径rMeter=80の円周長で除算して、2PI乗算してラジアンにする
  int a1 = (hMeter + (sin(MeterValue / 502.64 * 6.283) * rMeter)); 

// 針先端のY座標=針底部Y座標+針のY方向の振れ量
// cos内、半径rMeter=80の円周長で除算して、2PI乗算してラジアンにする
  int a2 = (vMeter - (cos(MeterValue / 502.64 * 6.283) * rMeter)); 

  display.drawLine(a1, a2, hMeter, vMeter, WHITE);         // draws needle
  display.display();
}
*********
一応、完成品の180°版アナログ Volt Meter のスケッチを
Google Driveのここに入れてます。
「Volt_Meter_86deg-A_OLED_SH1106.ino」です。


ADC(A0)入力に3.3Vを印加すると
いい感じですね~
5Vを入力!
まあ、いいでしょう!
入力GNDでは、これ
ちなみにA0にリード線つけてOPEN状態で指でつまむと
商用電源の誘導を拾ってピロピロ動きます。
なかなか面白くなってきました^^;
で、振れ角180°で、こんなのを作ってみました。
GIMPで128x64にリサイズして...

・針の中心:( hMeter , vMeter )= ( 65 , 64)
・針の長さ:rMeter=62
・針の最大振れ角:  180°(±90°)
・針の振れ半径: rMeterの円周長 = 2PI*62 = 389.557
・針先端の最大振れ円弧長= 2PI*rMeter*(180/360)= 194.779= ±97.389
・sin( MeterValue / 389.557 * 6.283) * rMeter )
・cos( MeterValue / 389.557 * 6.283) * rMeter )
針も根本が太い長三角形にしました。

***前のスケッチから変更部分(青文字)のみ******
int hMeter = 65;
int vMeter = 64;
int rMeter = 62
~~~
void loop(){
~~~
// MeterValue: 針(半径rMeter=62)の先端が振れる円弧長
// 針がADCmax:1023で180°振れる円弧長、2*PI*62*180/360=194.779
  float MeterValue = sample * 194.779 / 1023;  //★1
~~~
// 針先の円弧上の針中央位置にシフト
// 半径rMeter=62180°の円弧長の1/2・・・194.779/2=97.389
  MeterValue = MeterValue - 97.389;  //★1
~~~
// sin内、半径rMeter=62の円周長で除算して、2PI乗算してラジアンにする
  int a1 = (hMeter + (sin(MeterValue / 389.557 * 6.283) * rMeter)); //★1
// cos内、半径rMeter=62の円周長で除算して、2PI乗算してラジアンにする
  int a2 = (vMeter - (cos(MeterValue / 389.557 * 6.283) * rMeter)); //★1
~~~

  display.drawLine(a1, a2, hMeter, vMeter, WHITE);     // draws needle
  display.drawLine(a1, a2, hMeter-1, vMeter, WHITE); //★1 draws needle
  display.drawLine(a1, a2, hMeter+1, vMeter, WHITE); //★1 draws needle  
*********
完成品のVolt Meter 180°版スケッチを
Google Driveのここに入れてます。
「Volt_Meter_180deg-A_OLED_SH1106.ino」です。

3.3Vを入力中の全景。
3.3Vを指しています。
針も微妙な三角形になってます。

Adafruit GFX Graphics Libraryのこのコマンド解説を眺めてると、
BitMapデータを使わず、Line、Circle、Textの描画コマンドだけで
描いてみたくなりました。

座標は、EXCELで計算してま~す。
左上にデジタル電圧計も追加^^;
5V 10bitだと 1bit ±0.005V なので小数2位まで表示しました。
まあ、基準電圧はVccですが(-_-;)
この手の小さいアナログメーターは売ってないので、重宝しそうです。

デジアナ電圧計のスケッチです。
見易さのためオリジナルのコメントはほぼ外してますm(_ _)m
FullスケッチをGoogle Driveのここに入れてます。
「Volt_Meter_180deg-E_OLED_SH1106.ino」です。
*********
#include <Wire.h>                    // requried to run I2C SH1106
#include <SPI.h>                      // requried to run I2C SH1106
#include <Adafruit_GFX.h>        // https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SH1106.h>  // https://github.com/wonho-maker/Adafruit_SH1106

#define OLED_RESET 4             // reset required for SH1106

Adafruit_SH1106 display(OLED_RESET);  // reset required for SH1106

int analogInput = A0;    // ADC入力:A0
int hMeter = 64;           // 針センターのX座標
int vMeter = 63;           // 針センターのY座標
int rMeter = 62;            // 針の長さ

unsigned int sample ; // ADC生値
float volt ;  // 入力を電圧に変換、5V max
unsigned int volt_i ;  // voltの整数部
unsigned int volt_d1 ;  // voltの小数1位部
unsigned int volt_d2 ;  // voltの小数2位部

void setup(){

  pinMode(analogInput, INPUT);
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);   // SH1106、I2C Address:0x3C
  display.clearDisplay();                  // clears display from any library info displayed
}

void loop(){
  sample = analogRead(analogInput);

// MeterValue: 針(半径rMeter=62)の先端が振れる円弧長
// 針がADCmax:1023で180°振れる円弧長、2*PI*62*180/360=194.779
  float MeterValue = sample * 194.779 / 1023;

  float volt = sample * 5.0 / 1023 + 0.005 ; // 電圧値に変換、小数3位で四捨五入
  volt_i = volt ;  //voltの整数部をvolt_iに格納
  volt_d1 = ((volt - volt_i) * 100) / 10 ; //voltの整数1位をvolt_d1に格納
  volt_d2 = (volt * 100) - (volt_i * 100) - (volt_d1 * 10) ; //voltの整数2位をvolt_d2に格納

// 針先の円弧上の針中央位置にシフト
// 半径rMeter=62で180°の円弧長の1/2・・・194.779/2=97.389
  MeterValue = MeterValue - 97.389 ;
  display.clearDisplay() ;       // refresh display for next step

// 電圧デジタル表示
  // display.printf(%f1.1, volt); // Adafruit_SH1106では、printf 未対応
  display.fillRect(0, 0, 33, 11, WHITE) ; // 表示領域の白ベタ枠
  display.setTextSize(1); // FONT Size x1
  display.setTextColor(BLACK);
  display.setCursor(2, 2);  
  display.print(volt_i) ; // 電圧整数部
  display.print(".") ;
  display.print(volt_d1) ; // 電圧小数1位部
  display.print(volt_d2) ; // 電圧小数2位部
  display.print("V");

// 目盛り描画
  display.drawLine( 2, 63, 12, 63, WHITE ) ;
  display.drawLine( 6, 59, 12, 60, WHITE ) ;
  display.drawLine( 6, 56, 12, 56, WHITE ) ;
  display.drawLine( 7, 52, 13, 53, WHITE ) ;
  display.drawLine( 8, 49, 14, 50, WHITE ) ;
  display.drawLine( 7, 44, 15, 47, WHITE ) ;
  display.drawLine( 10, 42, 16, 44, WHITE ) ;
  display.drawLine( 12, 38, 17, 41, WHITE ) ;
  display.drawLine( 13, 35, 18, 38, WHITE ) ;
  display.drawLine( 15, 32, 20, 35, WHITE ) ;
  display.drawLine( 14, 27, 22, 32, WHITE ) ;
  display.drawLine( 19, 26, 24, 30, WHITE ) ;
  display.drawLine( 22, 23, 26, 27, WHITE ) ;
  display.drawLine( 24, 21, 28, 25, WHITE ) ;
  display.drawLine( 27, 18, 31, 23, WHITE ) ;
  display.drawLine( 29, 14, 33, 21, WHITE ) ;
  display.drawLine( 33, 14, 36, 19, WHITE ) ;
  display.drawLine( 36, 12, 39, 17, WHITE ) ;
  display.drawLine( 39, 11, 42, 16, WHITE ) ;
  display.drawLine( 43, 9, 45, 15, WHITE ) ;
  display.drawLine( 45, 4, 48, 14, WHITE ) ;
  display.drawLine( 50, 7, 51, 13, WHITE ) ;
  display.drawLine( 53, 6, 54, 12, WHITE ) ;
  display.drawLine( 57, 5, 57, 11, WHITE ) ;
  display.drawLine( 60, 5, 61, 11, WHITE ) ;
  display.drawLine( 64, 3, 64, 11, WHITE ) ;
  display.drawLine( 68, 5, 67, 11, WHITE ) ;
  display.drawLine( 71, 5, 71, 11, WHITE ) ;
  display.drawLine( 75, 6, 74, 12, WHITE ) ;
  display.drawLine( 78, 7, 77, 13, WHITE ) ;
  display.drawLine( 83, 4, 80, 14, WHITE ) ;
  display.drawLine( 85, 9, 83, 15, WHITE ) ;
  display.drawLine( 89, 11, 86, 16, WHITE ) ;
  display.drawLine( 92, 12, 89, 17, WHITE ) ;
  display.drawLine( 95, 14, 92, 19, WHITE ) ;
  display.drawLine( 99, 14, 95, 21, WHITE ) ;
  display.drawLine( 101, 18, 97, 23, WHITE ) ;
  display.drawLine( 104, 21, 100, 25, WHITE ) ;
  display.drawLine( 106, 23, 102, 27, WHITE ) ;
  display.drawLine( 109, 26, 104, 30, WHITE ) ;
  display.drawLine( 114, 27, 106, 32, WHITE ) ;
  display.drawLine( 113, 32, 108, 35, WHITE ) ;
  display.drawLine( 115, 35, 110, 38, WHITE ) ;
  display.drawLine( 116, 38, 111, 41, WHITE ) ;
  display.drawLine( 118, 42, 112, 44, WHITE ) ;
  display.drawLine( 121, 44, 113, 47, WHITE ) ;
  display.drawLine( 120, 49, 114, 50, WHITE ) ;
  display.drawLine( 121, 52, 115, 53, WHITE ) ;
  display.drawLine( 122, 56, 116, 56, WHITE ) ;
  display.drawLine( 122, 59, 116, 60, WHITE ) ;
  display.drawLine( 126, 63, 116, 63, WHITE ) ;

// 目盛り飾り Circle
  display.fillCircle( hMeter, vMeter, 8, WHITE);
  display.drawCircle( hMeter, vMeter, 38, WHITE);
  display.drawCircle( hMeter, vMeter, 51, WHITE);

// 目盛り数値
  display.drawChar( 17, 57, 0x30, WHITE, BLACK, 1 ) ;
  display.drawChar( 27, 32, 0x31, WHITE, BLACK, 1 ) ;
  display.drawChar( 48, 18, 0x32, WHITE, BLACK, 1 ) ;
  display.drawChar( 75, 18, 0x33, WHITE, BLACK, 1 ) ;
  display.drawChar( 97, 32, 0x34, WHITE, BLACK, 1 ) ;
  display.drawChar( 107, 57, 0x35, WHITE, BLACK, 1 ) ;

// 直流電圧計マークV表示
  display.drawChar( 60, 31, 0x56, WHITE, BLACK, 2);
  display.drawLine( 60, 47, 70, 47, WHITE ) ;
  display.drawLine( 60, 48, 70, 48, WHITE ) ;

// 針先端のX座標=針底部X座標+針のX方向の振れ量
// sin内、半径rMeter=62の円周長で除算して、2PI乗算してラジアンにする
  int a1 = (hMeter + (sin(MeterValue / 389.557 * 6.283) * rMeter)); //
  
// 針先端のY座標=針底部Y座標+針のY方向の振れ量
// cos内、半径rMeter=62の円周長で除算して、2PI乗算してラジアンにする
  int a2 = (vMeter - (cos(MeterValue / 389.557 * 6.283) * rMeter)); //
  
  display.drawLine(a1, a2, hMeter, vMeter, WHITE);      // 針の描画
  display.drawLine(a1, a2, hMeter-1, vMeter-1, WHITE);  // 針の肉盛り
  display.drawLine(a1, a2, hMeter+1, vMeter+1, WHITE);  // 針の肉盛り  
  display.display(); // 描画バッファの内容を画面に表示
}
*********

BitMap使ってないので、変数が減るかと思いましたが
グローバル変数は、1331(64%)
最初のオリジナルのVU Meter・・・1321 Byte(64%)
だったので、ほとんど変わりませんね~(T_T)
128x64分の描画バッファが必ず使われるんですね。
スタンドアローンのBitMap⇒コード変換ソフト「LCD Assistant
ってのを見つけたので、次は使ってみようかな。

モーターの回転数計や電流計や色んなアナデジメーターが作れそうです^^;
Arduino UNOはでかいので
こここっちで使ったSparkFun Pro Microがいいかな?
いや、Arduino NANOで十分ですね~