2020年6月13日土曜日

OLED(SH1106 128x64 I2C)でスピンドルモーターのタコメーターⅠ

OLEDシリーズ第4弾!
これが最も作りたかったものであります^^;

この時にいただいたCNC用の強力な300Wスピンドルモーターに
回転数計(タコメーター)をつけたいのであります。
やっぱタコメータは、アナログじゃないと雰囲気でないよな~
と思いつつ、この辺りの5V電圧計で、
目盛りだけ2Dプリンタで自作かな~とポチっておいたのですが
US$ 5.17(5% OFF)


前々回、OLEDでこのデジアナ電圧計を作った時に
タコメーターにぴったしだと思い立ったわけであります^^;
CNC2418の回転数を測定したのが、
ずっと前の2017年4月、古~いPS2マウスのボードの
フォトインタラプタ部を使ってやりました。
今回も同じもの使うつもりで、ここでもやってます。

Arduinoで周波数カウンタを探しましたが
一定の期間のパルスをカウントするゲート方式だと、
1回の測定に100msecほど必要なのでちと長過ぎます。

で、Arduinoのパルス幅測定「pulseIn」命令を使うことにします。
入力パルスのHIGH or LOWの時間を測定するもので、
HIGH期間とLOW期間を足せば1周期の時間が判ります。
ただ、分解能 1μsecなので、±1%精度とすると
1μsec100カウントの10KHz辺りが限界ということになります。

スピンドルモーターの回転数は、Max 12000rpmなので
12000 ÷ 60 = 200Hz
pluseIn向きの低周波ですね~^^;

スピンドルモーターのファンの回りに
センサー用のスリット付きの輪っかを嵌めようと考えてて
回転するのでスリット1つだとバランスが心配なので3つにすることにして
rpmに対する周波数とpluseInでのカウント数は、
3000rpm・・・50 x 3 = 150Hz・・・6667カウント(μsec)
12000rpm・・・200 x 3 = 600Hz・・・1667カウント(μsec)
となるので
pluseInで十分な精度が得られそうです^^;
では、スケッチから!
まずは、Arduino UNOで文字ベースの周波数カウンタ&タコメーター
3スリットとしてrpmを算出しています。
スケッチは、Google Driveのここに入れてます。
*****FreqCounter_Tachometer.ino*****
// Freqency Counter & Tachometer
// Arduino UNO & OLED I2C SH1106(128x64)
// Marty Vessel June, 2020

#include <Wire.h>              // for I2C6
//#include <SPI.h>             // If will use SPI
#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                  // PA4 for RESET SH1106 PDA
Adafruit_SH1106 display(OLED_RESET);  // RESET SH1106

volatile unsigned long LowTime    = 0; // INPUT PulseのHIGH期間[μsec]
volatile unsigned long HighTime   = 0; // INPUT PulseのLOW期間[μsec]
volatile unsigned long TotalTime  = 0; // INPUT Pulseの1周期[μsec]

int SensorIn = 7; // Arduino UNO PD7

void setup() {
  pinMode(SensorIn, INPUT);                 // Tachometer Sensor Input
  display.begin(SH1106_SWITCHCAPVCC, 0x3C); // SH1106、I2C Address:0x3C

  display.setTextSize(1); // FONT Size x1
  display.setTextColor(WHITE);
}

void loop() {
// pluseIn usage
// unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout));
// pin: 入力ピン
// state: カウントするレベル型、HIGH or LOW
// timeout: カウントのタイムアウト時間(μsec)、Default 1000000μsec(1sec)

  HighTime = pulseIn(SensorIn, HIGH,500000) ; // timeout:0.5sec μsec
  LowTime = pulseIn(SensorIn, LOW,500000) ; // timeout:0.5sec μsec
  TotalTime = HighTime + LowTime ;

  display.clearDisplay();             // clears display from any library info displayed 
  display.setCursor(2, 2); 
  display.print(HighTime) ;
  display.print(" usec") ;
  display.setCursor(2, 10); 
  display.print(LowTime) ;
  display.print(" usec") ;
  display.setCursor(2, 18); 
  display.print(TotalTime) ;
  display.print(" usec") ;
  unsigned long Freq = 1000000 / TotalTime ;
  display.setCursor(2, 26); 
  display.print(Freq) ;
  display.print(" Hz") ;
  // 3スリット、100Hz位で四捨五入してRPM値を計算
  unsigned long RPM = ( Freq * 20 +50) /100 *100 ;
  display.setCursor(2, 34); 
  display.print(RPM) ;
  display.print(" rpm") ;

  // 描画バッファの内容を画面に表示
  display.display();

  delay(200) ;
}
*********

ダイニングテーブルにシリコンマット敷いてテスト中!^^;
左は周波数とDUTYをVR可変できるATtiny85のパルスジェネレータです。
Min 60Hz ~ 600Hz辺りまでを可変します。
「Generator_FreqDutyControl_min61Hz.ino」
一応スケッチは、Google Driveのここです。
A0にリード線がついてますが、ここでは未使用です。
テストなので簡素な表示です。
上からHIGH時間、LOW時間、1周期、周波数
一番下が、3スリット想定での回転数です。
439Hz x 20 = 8780rpm ≒ 8800rpm というわけです。
あれっ?綴りが間違ってますね~ rmsじゃなくてrpmです(-_-;)
執筆中に気づいて_| ̄|○ この先全部間違ってました(T_T)
スケッチは、修正したのですが、写真の撮り直しが面倒でして~
以降の rms ⇒ rpm で読み替えてください(-_-;)
ちなみに、rms・・・実効値のことですね^^;
誤:rms  正:rpm
さて、メーターのデザインですが、こんなイメージで
水色部の様にメーターを円周上のバーグラフにして、
針とバーレベルで2重表示したいのです。
誤:rms  正:rpm
ひとまず、0~12000rpmの目盛りだけできました!
全てdrawLine命令で描いてます。
針は、まだ、この時の最後の電圧計のままです。
ここでも気づかず、rmsと勘違いしてるのであります(-_-;)
誤:rms  正:rpm
バーレベルは、半径方向のLine・・・120本分の始点と終点座標を
EXCELで計算したデータを配列に入れて
drawLine命令で呼び出していきます。
所が、ここで地雷を踏んでしまうことに...(-_-;)
*********
const uint16_t ScaleMap[121][4] PROGMEM = {
{ 11, 63, 18, 63 },
{ 11, 62, 18, 62 },
{ 11, 60, 18, 61 },
~~~
{ 117, 62, 110, 62 },
{ 117, 63, 110, 63 }
};
*********

これは、デバッグ中で目盛りは外してますが、
左下にレベル0~2まで正しくバーレベルが表示されてます。
上記の配列ScaleMapの 0~2までの3本を
for文でdrawLine命令を回してます。
*********
  for(s=0; s<=2; s++) {
    display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE);
  }
*********
左上の数字は、s=3の時の座標データ
誤:rms  正:rpm

所が、forを4回以上回すと、グチャグチャになるのです(T_T)
*********
  for(s=0; s<=10; s++) {
    display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE);
  }
*********
左上は、s=11の時の座標データで正しく呼び出されてるんですがね。
誤:rms  正:rpm
上で、for(s=0; s<=3; s++) 以上回すとダメなのです。
whileで回してもダメ(T_T)
挙句の果ては禁断のgotoで回してもダメなのです。
とにかく、drawLineの中を変数にして4回以上回すと描画異常になります。
drawLineの中のスタックか何かが2bit分しかないような...
adafruit_sh1106ライブラリのソースを見るも、よ~わからんし(-_-;)
マーティーのスケッチミスではないと思うのですが...

座標データ配列を1次元に
 const uint16_t ScaleMap[484] PROGMEM = {
してもダメ

s開始、s間隔には関係なく
 for(s=10; s<=12; s++) ・・・OK
 for(s=10; s<=13; s++) ・・・NG
ループ4回目から異常なラインになるのです。
座標データは正しく読み出されてるのにな~(T_T)

drawLineの引数は、uint16_tなので、配列定義は、
 const uint16_t ScaleMap[121][4] PROGMEM = {
ですが
 const uint8_t ScaleMap[121][4] PROGMEM = {
とかもやってみましたが、変わらずダメなのです。

スケッチの上方に回さず、単純に10回コードに分割すると
*********
s = 0;
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE); 
s+=1;
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE); 
s+=1;
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE); 
s+=1;
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE);
~~~10回続ける~~~
*********
この様に正しく表示されるのです。
左下の0から10個のバーレベルが表示されてます。
狐につままれた感じです(-_-;)
誤:rms  正:rpm
とにかく前に戻すルーチン(for、while、goto)全滅なので、
禁断のgotoで後ろに飛ばすしかありません(T_T)
放射状のバーレベルは、120目盛りなので
入力ADC max1023でlevel=120にして
s値のカウントがlevelと同じになったらgotoで最後に飛ばす。
とってもアホなコードですが、whileもforも使えないので
これしか方法がないのです^^;
*********
int s=0;
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE);
// ★
s+=1; 
if(s > level) goto END; 
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE);
// ★
~~~
★~★の3行を120個繰り返して記述
~~~
s+=1;
if(s > level) goto END;
display.drawLine(ScaleMap[s][0], ScaleMap[s][1], ScaleMap[s][2], ScaleMap[s][3], WHITE);
END:
*********
で、やっと予定通りの動作になりました!
描画スピードもそこそこです。
左上は、s、levelの値(max 120)です。
誤:rms  正:rpm
スケッチをここまで分解すると、
座標データを配列にする意味がありませんね~(-_-;)
drawLine内に座標データを直接入れます。
ちなみに、この長~いスケッチはEXCELで生成しております^^;
*********
int s=0;
display.drawLine( 11, 63, 18, 63, WHITE ) ;
s += 1; 
if( s > level ) goto END;
display.drawLine( 11, 62, 18, 62, WHITE ) ;
~~~
120本全てのバーレベル用ラインを記述
~~~
s += 1; 
if( s > level ) goto END;
display.drawLine( 117, 63, 110, 63, WHITE ) ;
END:
*********
バーレベルがrpm(回転数)
中央下にrpm値を表示することにしました。相変わらずrmsになってますが(-_-;)
針は、まだ電圧計のままで、左上は電圧表示に戻してます。
誤:rms  正:rpm

ようやく回転数表示にしました。
この状態は、約500Hz DUTY90%のパルスを入力しています。
実は、針とバーレベルの二重表示にしたのは、
針でPWMのDUTYから比例計算した回転数を表示したいのです。
負荷がかかると、DUTY比例の回転数から下がるのが見たいな~
というわけです。
左上:DUTY、針:DUTYから比例計算した回転数 で、
この時やった方式でADCにPWMを直接入力してDUTYを計測してます。
誤:rms  正:rpm

ひとまず完成かな^^;
これは、両方に同じパルス(520Hz、DUTY 89%)を入力中です。
周波数でrpmバーレベルが動き、DUTYで針が動きます。
バーレベルは、100rpm刻み(全体の120分割)なので、
真っ白に塗りつぶされず、黒ドットが残ってますが
50rpm刻みで240分割にすれば、バーがキレイに塗りつぶされると思います。
スケッチはGoogle Driveのここです(rms ⇒ rpmに修正済です)
「Tachometer-BarLevel_Duty_type180.ino」
誤:rms  正:rpm
接続図というほどのものではないですが
PD7:回転数測定用のセンサーからのパルス入力
A0:PWMのDUTY測定用のADC入力
です。
では、やっとできたので
実際のモーターでやってみます。

まず、テストでは、この6枚羽根を付けるので
スケッチを3スロット用から6スロット用に変更しておきます。
上のスケッチ
「Tachometer-BarLevel_Duty_type180.ino」から
ここを、20⇒10に変えるだけです。
*********
  unsigned long Freq = 1000000 / TotalTime ;
//  unsigned long RPM = ( Freq * 20 +50) /100 *100 ; // 3スリット
  unsigned long RPM = ( Freq * 10 +50) /100 *100 ; // 6スリット
*********
フォトインタラプタ・センサーは、概略こんな回路になっているはずです。
LEDは赤外線です。
元は、マウス用なので位相差を見て回転方向を判定のため2個入ですが、
片方の出力だけ使ってます。
全景です^^;
0~48V可変電源は、まだ0.02Vです。
上の写真の中央付近のこれは、モーターのPWM制御ボード
この時登場した
48V 20AまでモーターをPWM制御できるもので、最大定格:60Vです。
US$ 3.23(34% OFF)

ボードのPower MOS-FETのGateから220Ωで引っ張り出して
ATtiny85でドライブします。
ATtiny85の中身は、上と同じ時に作った
Soft Start/Stop制御回路です。
左上は、最初の方のパルスジェネレータで
Prescale 1/8にして、7.8KHzにしています。
その右で、 ATtiny85のSoft Start/Stop制御しています。
では、動画で
DUTY値は、Soft Start/Stop制御する前のPWMのDUTYです。
針は、そのDUTY値から比例計算したRPM値で振れています。
パルスジェネレータのVRでDUTYを可変しています。
DUTY(針)の動きに対して
Soft Start/Stop制御で追従がゆっくりになってます。

で、この時測った無負荷時のグラフは、これで
電圧 vs 回転数の直線性は、とても良かったのです。
(電流は各3回測定で、測定精度が悪いので数十mA程振らついてます)
今回、DUTY vs 回転数のグラフを取ってみると、
ん~ん、かなり直線から外れてますね~
EXCEL近似式いれてみると3次関数(右下青文字)になっちゃいました(T_T)
48V 無負荷です。
針は、DUTYから100% 12000rpmとして1次関数で回転数を算出してます。
これは、上のグラフでDUTY 50%の時、
針とバーレベルがこんなにズレるんですよね~(T_T)
ん~ん、針の意味がなくなる~?(T_T)
モーター後ろのファンを布で軽く押さえて負荷を掛けると
DUTY 90%以上では回転数はさほど落ちませんが
それより低いDUTYでは、負荷を掛けるほど回転数は落ちます。
まあ、針位置より低いと負荷が掛かり過ぎという目安にするかな?^^;
(さすがに最後だけrpmに修正して、写真撮り直しております)
DUTYが高い時は、
針:1次関数で算出した回転数 と バー:実回転数 の差はこうなります。
この辺では、ファンを布で押さえて少々負荷を掛けても回転数落ちません。
メーターデザインとしては気に入ったものができたと思います^^;
折角、バーレベルと針の両方できるようにしたので、採用したいけど
この辺は、実際に切削してみて考えることにしよう(-_-;)

それにしても、rpmとrmsを間違えるとは...(T_T)
かなりぼ~っとしてますね~(-_-;)

2020年6月5日金曜日

ATtiny85でOLED(SH1106 128x64 I2C)が使えてる(・o・)

OLEDシリーズも第3弾になりました!
すっかりハマっております^^;

目に止まったのが、このサイト
この8pinのIC、ひょっとしたらと思ったら
何と!
ATtiny85でOLED(SH1106 128x64 I2C)を使えてるのです。
OLEDは、RAMを大量消費するので無理かと思ってました。
「Tiny Graphics Library」ってのがあるようですね~
特別にライブラリをインストールするわけではなさそうです。
詳しい英文の解説が続いて、
最後の方に
「Tiny Graphic Library Program」部にスケッチへのリンクがあります。
このリンクはちと分かり難かった~
そのリンクのスケッチを開いて、全てコピーして
Arduino IDE Ver.1.8.3に貼り付けます。
取り敢えず、そのまま[検証・コンパイル]してみます。
設定は、これです。
お~っ!何事もなく無事コンパイルできました!
懸念してたグローバル変数の使用量は、10%です!
さて、作者David Johnson-Daviesさん使用の温度センサーは、
TMP37 20mV/℃、0mV at 0°C

マーティー手持ちの温度センサーも同じ様な TO-92 3pin
NSのLM35DZ、10mV/℃、0mV @0℃
温度センサーの計算式だけ変更すればいいハズです。
LM35DZは、0℃で0mV、100℃で1000mV

analogReference(INTERNAL1V1);
なので、基準電圧:1.1V
入力:1.1V の時にADC:1023になるので
100℃、1000mVの時、
・温度[℃]=(ADC値 ÷ 1023 x 1100mV)÷ 10mV/℃
                = ADC値 x 110 ÷ 1023
更に1目盛 0.5℃刻みなので
・温度[0.5℃刻み]= ADC値 x 110 ÷ 1023 x 2
                       = ADC値 x 220 ÷ 1023

ということで、スケッチを
*********
int Temperature = (analogRead(A2)*25)/233;

int Temperature = (analogRead(A2)*220)/1023;
*********
に変更します。

ATtiny85は、すっかりお気に入りになり
新品の生チップを10個、ここで調達しております。
US$ 12.31/10個(送料無料)

この設定にして、Arduino UNOの書込機でやるので
[書込装置:”Arduino as ISP”]です
新品の生チップは、
Clock:8MHz、Prescale Resister:1/8になってるので
最初に[ブートローダを書き込む]して
Fuse bitをセットする必要があります。
Fuse bitの書換が終ったら
スケッチを書き込みます。
書き込みも無事終わりました。
この時作ったArduino UNOの書込装置です。
ちなみに、14pinのソケットを新調しております^^;
US$ 1.01/2個(30% OFF)

配線して~
電源ON!
表示には数秒かかるという遅さで、
半世紀前のPC8001のBASICインタプリタを思いだしました^^;
ちゃんと表示されたと思いきや
ん?温度が表示されません(T_T)
あれっ? 15分経つと画面が真っ暗になってしまいました(-_-;)
電源入れ直すと、目盛りは、また表示されます。
スケッチを見直すと~
「int Temperature = (analogRead(A2)*220)/1023;」
の「analogRead(A2)*220」の最大時
intの最大 32767 < 1023 x 220 でオーバーフローしてました~(-_-;)
Y軸のTemperature値がオーバーフローすると止まるようです。
ということで、
オリジナルのセンサー 20mV/℃ から 10mV/℃への変更だけなので
元の式を単純に2倍することにして
int ⇒ unsigned int にして
65535 > 1023 x 25 x 2
なので、今度はオーバーフローしないハズです^^;
*********
int Temperature = (analogRead(A2)*25)/233;

unsigned int Temperature = (analogRead(A2)*25*2)/233;
*********

それと、15分間隔で24時間は、確認が大変なので
ひとまず、1.5秒間隔にします。
*********
while ((unsigned long) ((StartMins + millis()/60000)/15)%96 == SampleNo);
 ⇒
while ((unsigned long) ((StartMins + millis()/100)/15)%96 == SampleNo); 
*********

横軸 2.4分になりました。
途中、センサーを手で温めて山を作ってみました^^;
無事動作したので
横軸を24時間に戻して~
*********
while ((unsigned long) ((StartMins + millis()/100)/15)%96 == SampleNo);
 ⇒
while ((unsigned long) ((StartMins + millis()/60000)/15)%96 == SampleNo); 
*********

夜中の0時に始めるので
*********
const int Now = 1547;     // To set the time; eg 15:47

const int Now = 0000;
*********
にして、書き込んで開始です。
一晩様子を見たいと思います。
・・・朝8時、夜中の室温は、もう初夏なのであまり下がってないですね~
小型のリチウムイオン電池のモバイルバッテリーで放置してます。
12時間後
20時辺りの跳ね上がりは、夕食のために位置を移動してます。
こういうスタンドアローンのデータロガーもなかなか面白いですね~
結局、変更したのは、温度センサーだけですが、
一応、Google Driveのここに全スケッチを入れてます。
ここにLICENSE FREEのある免責事項が書いてあります。

ちょっとPlotの描画速度を測りたいと思います。
PB1(6pin)が空いてるので
setupに1行追加して
*********
void setup() {
  // initialize
 
pinMode(1, OUTPUT);  // PB1(pin6) for PlotDot時間確認用

*********

最高速にするため、while行をコメントアウトします。
*********
// Now start plotting the temperature every 15 mins
// while ((unsigned long) ((StartMins + millis()/60000)/15)%96 == SampleNo);

*********

PlotPointコードの後に「PINB |= 0b10;」を追加して
は、ADC完了毎にPB1をトグルしてます。
つまり、1dotプロットする毎にPB1を反転させます。
PB1のパルスの1/2が、ADCしてPlotPointする処理時間になります。
*********
PlotPoint(SampleNo+x1, Temperature-10+y1);
PINB |= 0b10;  // PB1反転

*********

CPU Clock 8MHz(internal)
PB1の波形を見ます。
1周期の1/2(HIGH or LOW期間)が1dotプロットする時間なので
2.1msec/dotってとこですね~
かなり遅いな~
1msecを軽く切ると思ってました。
では、CPU Clock 16MHz(PLL)
ちなみに、ATtiny85のCPU Clockを変更するには、
[16MHz(PLL)]にして
で、忘れるのはマーティーだけだと思うのですが
この[ブートローダを書き込む]を忘れないように実行してから
スケッチを書き込みます。
CPU Clock 16MHz(PLL)
1.9msec
ちょっとしか速くなりませんね~
ADC Clock 50KHzでも高々0.26msecのはずなので、
analogReadの時間が支配的だとも考え難いし~?
ATtiny85のDatasheetには、
ADC Clockは、CPU Clockに応じて決まると書いてあるだけですが
たぶん、
Clock   8MHzではPrescale   1/64でADC Clock 125KHz
Clock 16MHzではPrescale 1/128でADC Clock 125KHz
で13cycleだと0.104msecだと思われます。

では、CPU Clock 16MHzのまま
更に、ADCもコメントアウトして殺します。
forルーチン内はこれだけです。
*********
for (;;) {
    SampleNo = (SampleNo+1)%96;
// unsigned int Temperature = (analogRead(A2)*25*2)/223;
    unsigned int Temperature = 20;
    PlotPoint(SampleNo+x1, Temperature-10+y1);
    PINB |= 0b10;   // PB1反転
  }
*********

1.75msec(Clock 16MHz)
ADC有り1.9msecから0.15msec減ってるので
上のADC時間はほぼ正しいことになります。
ん~ん、IICの通信レートで決まるのかな~?
これをCPU Clock 8MHzでは、2msec
ADC有りでは、2.1msecだったので
やはりADC処理部は、0.1~0.15msecということになります。
整理すると
この差、つまりADCの有無の処理時間を波形で見ます。
*********
unsigned int Temperature = (analogRead(A2)*25*2)/223;

unsigned int Temperature = 20;
*********

PB1のパルスの1/2が、ADCとPlotPointする処理時間になります。
CPU Clock 8MHzのADC処理部の有り
CPU Clock 8MHzのADC処理部の無し

CPU Clock 16MHzのADC処理部の有り
CPU Clock 16MHzのADC処理部の無し
8MHz、16MHzともADC Clockは、自動的に125KHzだと考えられます。

I2C Clock Default 100KHzらしいので、400KHzにしてみます。
  Wire.begin();
の後に追加
  Wire.setClock(400000);
を追加して変更できるのですが、このATtiny85では変化なし(T_T)
  Wire.setClock(100000L); 
でも変化なし
  #define I2C_FASTMODE  1
も変化なし

これは、CPU Clock 16MHzのADC処理部の無し
I2C 400KHzにしたつもりですが、一つ上のショットと全く変わりありません。
どうやらATtiny85では、固定されてるようです。
ATmega328Pでは、TWI ModuleのBit Rate Generatorをいじってますね~
ATtiny85には、TWI Moduleがないので変化しないんですね。
で、どこだったか、こんな文章が!
「the default Arduino Wire() library does not support changes in bus speed or feature proper clock stretching support.」
・・・I2C speedは、変更できないのか~(T_T)

このSoftI2CMasterライブラリを使えばできるようですが、
またの機会にします^^;

ということで、
1dot 2msecとして、横100dotだと、200msecになるので
5Hzの1周期がReal Timeでサンプリングして表示できる速度です。
何に応用しようかな~?

最後に、Y軸の温度範囲を変更しようと思いましたが
画面ドットの座標は、この図の仕様で
これを8x8pixelに分割してProgram Memoryに格納して
Graphic Fontとして表示しています。
だからバッファ領域が不要なのですが、
データ作るのは、とても大変ですね~
汎用のツールがないので、自力でソフト組まないといけないのです。
Pythonで作れそうですが、マーティーにはちと重たいな~(-_-;)

もう一つ、同じ作者David Johnson-Davies氏の
これ、面白そうなんですが
ATtiny85のRAMでは、OLED 64x48が限界だそうで、
I/Fは、IICではなく、描画データ転送速度が速いSPIが必要らしいので
今回はできませんね~
さて、OLEDシリーズ第4弾に取り掛かるとします^^;