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)
かなりぼ~っとしてますね~(-_-;)

2 件のコメント:

昔青年 さんのコメント...

おはようございます!
回転数計こってますね。デジタル表示全盛の今日この頃、ソフトでアナログ回帰もいいかも(笑)無線機のメーターも高級機は、デジタルでアナログメーター表示になってますね。当局のおんぼろ自作機のメーターに応用できるかも

マーティーの工房日誌 さんのコメント...

その昔、昔青年さんからいただいた宿題がやっとできそうです。
センサーを幾つかポチっておりますので、もう暫くお待ち下さいm(_ _)m
そういえば、Arduino ProMiniをポチりました^^;
NANOとほぼ同価格ですが、小さい分重宝しそうです。