【パワーメーター】アナログ針式メーター作り<ステップモーターいじる>

※自転車関係のテーマ掲載停止しました。
2020年夏以降、信州MAKERSのテーマとしてロードバイク関連の記事は、連載停止しました。

理由は、読者がついてこない点で、労力の割に効果がないテーマである点です。自転車関連テーマの来訪率がサイト全体の1%未満で、誰も見に来ないので、他のテーマに重点をおくことにしました。本記事は、YOUTUBEからのご来訪が多いですが、本記事を見てステップモーターをいじる読者はほとんど居ません。更に、
私もそうでしたが、自転車趣味では、暇な時間があれば、ロングライドしたり、ヒルクライムしたりして、電子工作に割く時間がほとんどない点があります。電子工作と自転車乗り趣味は両立しないというのが私の結論です。ですので、本記事を見て、電子工作しようとする自転車乗りは、皆無であるという結果です、自転車乗りを対象とした電子工作記事は停止しました。

右踏みのパワー値の補正方式と精度校正もほぼ終わりました。もう、MFT2017用に必死でモノ作り開始しないと間に合わないので、左踏み、スタンディング、実走行での校正未実施で精度あやふやのままになります。残りの校正は、9月以降から再開します。さて、MFT2017への展示ですが、苦労して得たパワー値を液晶パネルで表示するのも味気ないので、アナログ針式メーターを作ります。
※本メーターは、MFT2017の会場デモ用で、実際にロードバイク走行には使える代物では、ありませんので、
もし、アナログメーターを欲しい方は、海外で発表された製品がありますので探してみてください。
実用的なアナログメーターを制作するのは、電池寿命と振動・雨などの実装信頼性など難題が多いので、電子機器実装技術のプロ級の腕自慢の方ならできると思いますが、私のような素人だと無理だと思います。
スマホとか携帯電子機器の実装設計開発経験がないと実使用に耐えるパワーメーターは作れません。
=>今回のアナログメーター構想から工作で、正味で12時間x20日=240時間くらいかかってますので、
作れるのは余程暇な人です。

=>DIYでアナログメーターを作るトライをすることは、メカトロの基礎技術習得には非常に良い学習となります。
マイコン、モーター、プログラム作りで、自信がつきますので、数か月かけてトライしていただければ幸いです。

=>毎日が日曜日の私の経験でも、電子工作DIYとロードバイク乗り趣味を両立するのは、無理であることを体験しております。
DIYを優先するために、私のロードバイク活動は、ローラー台しか乗らない趣味になってます。

壁にぶち当たったら、この記事下部のコメント覧に記入するか、信州MAKERS宛てにメールください。
ich48297@wd5.so-net.ne.jp です。
完成動画

●アナログ針式メーターの動機
①市販パワーメーターもそうですが、力を入れた直後にパワー値が上がらないと精度が悪いのではと感じてしまいます。パワーの精度感は、サンプリング周期と表示タイミングで、精度感があるかどうかが決まるように感じてます。しかし、レスポンスが良すぎると、数値がコロコロ変わるので煩い感じになるので、アナログ針式で±10W程度のフレは気にならないようにすれば心地よいメーターになるのではないかと思って、試作してみることにしました。
②本シートチューブひずみゲージ貼り式(MyPowerMeter)は、多分、10%前後の誤差が発生する場合があるので精度的にも丁度いいのかと考えてます。

③市販パワーメーターは、GARMIN EDGEにパワー値を表示する場合が多いと思いますが、MyPowerMeterは、ANT+プログラムが未だ作れないでいます。そこで、逆転の発想で、
パワー値をエンジン回転数にみたててアナログ的に表示したほうがDIY MAKER作品としてオリジナリティが発揮できると思った点も大きいです。

●文字盤デザイン

バイク趣味でアナログ針式メーターをDIYしている方がたくさんいらっしゃいます。デザインを提供してくださる方もいらしてその中から、選びました。
https://blogs.yahoo.co.jp/xj750e2/GALLERY/show_image.html?id=29291423&no=6

これをPhotoshopで加工してMyPowerMeter用にしました。

●アナログ針式パワーメーター<MyPowerMeter>概仕様検討
1:表示範囲
0W~500Wまでセンター250W
角度範囲0W~500W=約250度
W/角度=2W/deg
最小目盛り:12.5W
数値:50W単位
3:回転速度
0~100Wを0.3秒(50deg/0.2sec=250deg/sec)以内
100W~400Wを0.5秒(150deg/0.5sec=300deg/sec)以内
4:電池耐久時間
3時間以上

●ステップモーター選定
3種類のモーターを試してきて、最終的には、
コパル SPG27-1101に落ち着きました。

http://akizukidenshi.com/download/ds/copal/SPG27-1601.pdf
メリット1:分解能と速度のバランスが良い
=>1周120パルス(3度/パルス)
=>最小目盛り10.25度=3~4パルスで表現
=>1step5msec周期で自起動可能なので加減速不要

メリット2:安い・早い  秋月で350円
http://akizukidenshi.com/catalog/g/gP-11839/
デメリット1:サイズが大きい φ29x29 60g
デメリット2:電圧高く(7V以上)電力を食う7V0.2A=1.4W
デメリット3:ユニポーラ駆動なので、効率が悪い、線が多い

●モーター駆動テスト
ステップモーターを回転させるのに駆動電圧可変が必須なので超小型スイッチング電源仕入れました。LMR62421便利です。
http://akizukidenshi.com/catalog/g/gK-07608/
ドライバーは、ULN2003というダーリントンアレイIC
http://www.tij.co.jp/product/jp/ULN2003A
基板とモーターセットでアマゾンで格安で売ってたものを流用しました。電圧は50V以下電流500mA以下です。

この基板は電子工作で多く使われてるので検索たくさんあります。
http://dd647.blogspot.jp/2016/02/arduino-5v-28byj-48.html

●結線(備忘録)

http://www.instructables.com/id/BYJ48-Stepper-Motor/

 

●動画の動作サンプルプログラム 参考にしてください
相切り替えと累積誤差の処理でバグ取りに数日かけた貴重なプログラムです。mbed NucleoL432KCで動作
ライブラリーはなしですので、下記TEXTをコピペすれば動作します。
パワーメーターからシリアルでパワー値が入ってきたら動作する単純な仕様です。
GoWB(paraW);//W数入力で針移動 関数でW数動作させてます。
初期化でHOMESEEKが面倒でした。
バックラッシュをとる工程がある点でややこしいです。
メカトロの学習にはちょうどいい教材ですが、私は40年メカトロやってきたのでもういじりたくないです。

#include “mbed.h”
Serial pc(USBTX, USBRX);
Serial nucleo(PB_6,PB_7);
DigitalOut myled(LED1);
DigitalOut step_a(PA_0);
DigitalOut step_b(PA_1);
DigitalOut step_c(PA_3);
DigitalOut step_d(PA_4);
//AnalogOut monitor(PB_4);
InterruptIn Homeken(PB_4);
//Ticker timer1;
//———-hari ——-
int step50w=9;
int tStep=0;
int parasu;
int W_Home=0;
float SpW=0.18;//step50w/W_Home;
float WpS=50/9;
//—————-
float step_deg=0.3333333; //3deg/w
float target_pw;
int zeroten=20;
int i=0;
int j=0;
int k=0;
int n=0;
int x;
int mode =0;
int mode_1=0;//1個前のmode
int phNoT=0;
int Edge=0;
int Edge_1=0;
int flagW_CW=0;
int flagW_CCW=0;
int RedgeNo_CW=0;
int FedgeNo_CW=0;
int RedgeNo_CCW=0;
int FedgeNo_CCW=0;
int BackLush=0;
int RedgeM=0;
int FedgeM=0;
int ndir;//現在
int rdir_1;//1回前
int stepNoT=0;
int tWatt;
int nWatt;
int bWatt;
int stepsuuGo;
int slow_pw;
int flagw;
int ini;//ini=1 初期化中ini=0初期化終了
// serial parameters
char buf[10]={};//10文字x10string
float para[10];//**********************Watt間移動*******************
int wattGo(int targetW,int nowW,int beforeW){
int direction0,direction1;
int bucklush;
int stepW;
int stepGo;
if (nowW-beforeW>=0){direction0=-1;}//CW
if (nowW-beforeW<0){direction0=1;}//CCW
if(nowW-targetW>0){direction1=1;}//CCW
if(nowW-targetW<=0){direction1=-1;}//CW
if(direction0==direction1){bucklush=0;}
if(direction0!=direction1){bucklush=1;}
stepW=int(0.5+(0.1867*abs(nowW-targetW)-0.1667));
stepGo=int((stepW+bucklush)*-direction1);
return stepGo;
}//*********相切関数 rdir=1 CW rdir=-1 CCW rdir>1 Rush時間 rdir=0電源オフ*********
void add_step(int rdir) {//modeは、次のtarget modeになっている
__disable_irq(); // 禁止
//static int mode=0;
if (rdir==1){phNoT–;}
if (rdir==-1){phNoT++;}
//逆転時補正
//rdir:-1=>1だと(0=>3=>2=>1)減算を加算に変更targetモードmodeを0=>1,3=>0,2=>3,1=>2 (mode+1)%4
//rdir:1=>-1だと(0=>1=>2=>3)減算を加算に変更targetモードmodeを0=>3,3=>2,2=>1,1=>0 (mode+3)%4
if (rdir!=rdir_1 && rdir_1==-1){mode=(mode_1+1)%4;}//ridri:CW-1=>CCW1の場合に進めたmodeを1個戻す
if (rdir!=rdir_1 && rdir_1==1){mode=(mode_1+3)%4;}//ridri:CCW1=>CW-1の場合に進めたmodeを1個戻す
if (rdir!=0){
rdir_1=rdir;
//pc.printf(“>>add_step:rdir_1=%d,rdir=%d,mode_1=%d,mode=%d,phNoT=%d\n\r”,rdir_1,rdir,mode_1,mode,phNoT);
switch (mode) {
case 0 :
mode_1=0;
step_a=1;
step_b=1;
step_c=0;
step_d=0;
if(rdir==1){mode=1;}
if (rdir==-1){mode=3;}
if (rdir>1){wait_ms(rdir);}
//mode=1;
break;case 1 :
mode_1=1;
step_a=0;
step_b=1;
step_c=1;
step_d=0;
if(rdir==1){mode=2;}
if (rdir==-1){mode=0;}
if (rdir>1){wait_ms(rdir);}
//mode=2;
break;
case 2:
mode_1=2;
step_a=0;
step_b=0;
step_c=1;
step_d=1;
if(rdir==1){mode=3;}
if (rdir==-1){mode=1;}
if (rdir>1){wait_ms(rdir);}
//mode=3;
break;case 3:
mode_1=3;
step_a=1;
step_b=0;
step_c=0;
step_d=1;
if(rdir==1){mode=0;}
if (rdir==-1){mode=2;}
if (rdir>1){wait_ms(rdir);}
//mode=0;
break;
}//switch
}//if
else { step_a=0;
step_b=0;
step_c=0;
step_d=0;
}// else
myled=!myled;
__enable_irq(); // 許可
}
void Go(int ndir,int steps,int pw){
for (i=1;i<=steps;i++){
add_step(ndir);
wait_us(pw);
}
}
void GoB(int ndir,int steps,int pw){//BackLush自動補正
__disable_irq(); // 禁止
int steps_1=steps;
if(rdir_1!=ndir){
steps=steps+BackLush;}
for (i=1;i<=steps;i++){
add_step(ndir);
wait_us(pw);
}
pc.printf(“GBnd=%d,st=%d\n\r”,ndir,steps);
__enable_irq(); // 許可
}void flip(){//Rise
if (Homeken.read()==1){//CC(-1)回転で立ち上がりエッジが出た=>左エッジ検出
Edge=1;
//pc.printf(“Rise:Edge=%d\n\r”,Edge);
}
}
void flipd(){//flipは HomeSeek専用にするInitialize時にしか使わない,Run時は、flipがかからない
if (Homeken.read()==0){//CW(-1)回転で立ち上がりエッジが出た=>右エッジ検出
Edge=0;
//pc.printf(“Fall:Edge=%d\n\r”,Edge);
}
}
void HomeSeek_R(){
int n=0;
Edge_1=Edge;
while(1){
Go(1,1,slow_pw);
__disable_irq(); // 禁止
if (Edge!=Edge_1 && Edge==1){//CWでrise
Edge_1=Edge;
RedgeNo_CCW=phNoT;
RedgeM=mode;
pc.printf(“CCW<Reverse:Redge>RedgeNo_CCW=%d,phNoT=%d,RedgeM=%d mode_1=%d\n\r”,RedgeNo_CCW,phNoT,RedgeM,mode_1);
}
//pc.printf(“Center:Edge=%d,Edge_1=%d\n\r”,Edge,Edge_1);
if (Edge!=Edge_1 && Edge==0){//CWでFall
FedgeNo_CCW=phNoT;
FedgeM=mode;
flagW_CCW=FedgeNo_CCW-RedgeNo_CCW;
pc.printf(“CCW<Reverse:Fedge>FedgeNo_CCW=%d,phNoT=%d,FedgeM=%d,flagW_CCW=%d,mode_1=%d\n\r”,FedgeNo_CCW,phNoT,FedgeM,flagW_CCW,mode_1);
break;
}
//pc.printf(“Bottom:Edge=%d,Edge_1=%d\n\r”,Edge,Edge_1);
__enable_irq(); // 許可
Edge_1=Edge;
n++;
//if(i>120){break;}
}
x=0;
}//HomeSeek開始
void HomeSeek(){
int n=0;
Edge_1=Edge;
while(1){
Go(-1,1,slow_pw);
__disable_irq(); // 禁止
if (Edge!=Edge_1 && Edge==1){//CWでrise
Edge_1=Edge;
phNoT=0;
n=0;
RedgeNo_CW=n;
RedgeM=mode;
pc.printf(“************************CW:<Redge>RedgeNo_CW=%d,Reset>>phNoT=stepNoT=%d,RedgeM=%d,mode_1=%d\n\r”,RedgeNo_CW,phNoT,RedgeM,mode_1);
}
//pc.printf(“Center:Edge=%d,Edge_1=%d\n\r”,Edge,Edge_1);
if (Edge!=Edge_1 && Edge==0){//CWでFall
FedgeNo_CW=n;
FedgeM=mode;
flagW_CW=FedgeNo_CW-RedgeNo_CW;
pc.printf(“CW:<Fedge>FedgeNo_CW=%d,phNoT=%d,FedgeM=%d,flagW=%d,mode_1=%d\n\r”,FedgeNo_CW,phNoT,FedgeM,flagW_CW,mode_1);
HomeSeek_R();//CCW方向でHOMESEEKしてフラグエッジphNOを比較
BackLush=FedgeNo_CW-RedgeNo_CCW;//逆転直後のエッジ位置でバックラッシュ量が判る
pc.printf(“BackLush=%d\n\r”,BackLush);
Go(1,zeroten+BackLush,slow_pw);
Go(-1,BackLush+2,target_pw);//ゼロ点の微調整をここで
pc.printf(“<<<<<Stanby:zeroten=%d,phNoT=%d\n\r”,zeroten,phNoT);
break;
}
//pc.printf(“Bottom:Edge=%d,Edge_1=%d\n\r”,Edge,Edge_1);
__enable_irq(); // 許可
Edge_1=Edge;
n++;//if(i>120){break;}
}}
void GoW(int W){//6W以上の差がないと動かない
//bWatt=nWatt;
//nWatt=tWatt;
tWatt=W;
//tWatt=getsuu();
pc.printf(“getsuu tWatt=%d\n\r”,tWatt);
stepsuuGo=wattGo(tWatt,nWatt,bWatt);//wattGo(wsuuGo,nowW,beforeW)
pc.printf(“stepsuuGo=%d,tWatt=%d,nWatt=%d,bWatt=%d\n\r”,stepsuuGo,tWatt,nWatt,bWatt);
if (stepsuuGo<0){ndir=1;}//CCW
if (stepsuuGo>=0){ndir=-1;}//CW
pc.printf(“GoW:W=%d,nWatt=%d,bWatt=%d,ndir=%d,stepsuuGo=%d\n\r”,tWatt,nWatt,bWatt,ndir,stepsuuGo);
Go(ndir,abs(stepsuuGo),target_pw);
}
void GoWB(int goW){//バックラッシュ付GoBを使う
__disable_irq(); // 禁止
int dstep=0;
int ndir=0;
// bWatt=nWatt;
dstep=int(goW*SpW+0.5)-int(nWatt*SpW+0.5);//shishagonyu
if (dstep<0){ndir=1;}//CCW
if (dstep>=0){ndir=-1;}//CW
//pc.printf(“>>>GoWB>>:ndir=%d,dstep=%d,goW=%d,nWatt=%d,phNoT=%d\n\r”,ndir,dstep,goW,nWatt,phNoT);
GoB(ndir,abs(dstep),target_pw);
nWatt=goW;
__enable_irq(); // 許可
}
//******Getstep from Key Board ***********************
int getsuu(){//カンマ区切りのデータをCRがくるまで最大10文字x10パラメータ受信
int i=0;//カンマかCRがくるまで記憶
int j=0;//カンマ区切り
int k=0;
pc.printf(“*******Input Watts X/X/X/CR********** \n\r”);
while(1){
buf[i]=pc.getc();//monitor用
//buf[i]=nucleo.getc();//1789通信用
//pc.putc(buf[i]);
if (buf[i]>=45 && buf[i]<=57){
i++;
for (k=0;k<=i;k++){pc.printf(“%c”,buf[k]);}
pc.printf(“\n\r”);
}
if (buf[i]==72){HomeSeek();}//’H’=HOME SEEK
if (buf[i]==82){HomeSeek_R();}//’R’HOME SEEK 逆転
if (buf[i]==13){ //CR
//pc.printf(“<<CR(13)>>:buf char=%d,%d,%d\n\r”,buf[0],buf[1],buf[2]);
para[j]=atof(&buf[0]);
for (k=0;k<10;k++){buf[k]=0;}
pc.printf(“<<CR(13)>>:j=%d,para=%4.1f\n\r”,j,para[j]);
break;//CR(13)でWhile終わり
}
}
return j;
}//<<<<<<<<<<<<<getsuu 高速化>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void getsuuH(){//カンマ区切りのデータをCRがくるまで最大10文字x10パラメータ受信
int i;//カンマかCRがくるまで記憶
int j;//カンマ区切り
i=0;
j=0;//pc.printf(“*******Input Watts X/X/X/CR********** \n\r”);
while(1){
buf[i]=pc.getc();
//buf[i]=nucleo.getc();
pc.putc(buf[i]);
if (buf[i]==13){break;//CR(13)でWhile終わり
}
i++;
if(i>=1000){i=0;}
}
para[0]=atof(&buf[0]);
}void memoriTest(){
int i=0;
int n=0;
int steps=9;
int paraW=0;while(1){
n++;
if(n>=100){break;}
for (i=1;i<=10;i++){
GoB(-1,steps,target_pw);
pc.printf(“CW:W=%d\n\r”,steps*50*i);
wait(0.2);
}
for (i=1;i<=10;i++){
GoB(1,steps,target_pw);
pc.printf(“CCW:W=%d\n\r”,steps*50*i);
wait(0.2);
}
}
}
int main() {//stepNoTで針座標を表現する
pc.baud(115200);
nucleo.baud(19200);
Homeken.rise(&flip);
Homeken.fall(&flipd);target_pw=4000;
slow_pw=8000;
ndir=1;
ini=1;
int paraW=0;
int paraW_1=0;
//setting

// action
HomeSeek();
pc.printf(“HomeSeek Finished!!\n\r”);

wait(0.5);
bWatt=0;
nWatt=0;
tWatt=0;
n=0;

//int memori[]={50,100,150,200,250,300,350,400,450};
//memoriTest();
//nucleo.attach(&getsuuH,Serial::RxIrq);//シリアル転送割り込み
while(1){
paraW=int(para[0]);
pc.printf(“main>>>>>>>Wait for Serial W Input<<<<<<<<<<<<<<<<<<para=%d\n\r”,paraW);
int roundW=int((float)paraW/1+0.5)*1;//round(paraW/WpS,0)
//pc.printf(“ifmae==Round:para=%4.1f,roundW=%d\n\r”,paraW,roundW);
paraW=roundW;
if (paraW!=paraW_1){
pc.printf(“ifato==Round:para=%4.1f,roundW=%d\n\r”,para[0],paraW);
GoWB(paraW);//W数入力で針移動
//if (abs(paraW-paraW_1)>250){
//wait(0.2);
//pc.printf(“+++wait0.5\n\r”);
// }
paraW_1=paraW;
//pc.printf(“main:Afer GoWB:paraW=%d,phNoT=%d,Edge=%d\n\r”,paraW,phNoT,Homeken.read());
}
}

}

 

 

●ステップモータを回転させるための管理項目
相切り替えのプログラムだけなら簡単にできてしまいますが、
ステップモータは、動作させる条件の組み合わせで回転しますので条件を管理しないとまともに回転しません。ポイントは
①相切り替え順序がきちんとしているか
②結線がきちんとしているか
③4本の信号がきちんと入っているか
④モーターのコイル抵抗値を確認してあるか
=>数オームか数十オームかで扱いがぜんぜん違う
⑤プルイン(自起動)周波数でまわすことができるか
=>ステップモーターをいじるには、電圧を自由に調整できる電源が必要です。自起動周期と電圧が密接な関係があるので、その周期で回転しなかったら電圧をあげてみて何Vで回転するかを記録していくことが重要です。
⑥目標の速度トルクが明確なこと
=>どこまで回さないといけないのかで、速いと加速減速のプログラムを開発しなければなりません。遅ければ加速減速なしで普通のプログラムでできます。

●以後
パワーメーターのケースと針を設計して3Dプリントします。
針の運針動作が実際見ながら修正しないとまともにならないので早めに針とケースを試作しておきます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です