M5Stackで久々に何か作りたい欲がむくむくと出てきた。
私は本当に波があるので良くない。波が来るまでは全く触らなくなってしまうので……
前回触ったのは1年前くらいだろうか?と思っていたら、7ヶ月前くらいっぽい。めちゃくちゃだなぁ。
まぁでも気が向かないと何も作れないし何も取り組みもしないので……。自分の性格だから仕方ないなぁと諦めている。
今回やりたくなったこと
以前までは温度などの情報をとる媒体としてM5Stackを使っていたが、そろそろモーターを使って何かを動かしたいなという気持ちになってきた。
とはいえ以前一度やってみた時には、全然うまく行かなくて、なんやこれもういいやって放り出してしまったのであった。学ぶモチベが低かったというか……。
取り敢えず動いたからそれで満足して放置してしまったので、今回はちゃんと制御ができるようにしたいと思った。
それで最終的に、
「ボタンを押すとモーターが動いたり止まったりするやつ」を作ってみた後に
「光センサーを用いて、光が当たると動いて当たらないと止まるやつ」を作り、
それを「光走性で動く虫」か「運動野の神経細胞に光受容体を発現させられているせいで光が当たるとめちゃくちゃに動くマウス」を作ってみようと思った。
動機がいまいちよくわからないかもしれないが、まぁそういうことで……作りたいんだからしょうがない。
取り敢えず色々弄ってみる
取り敢えずまずは適当に触ってみようと思って、手元にあったFS90Rのコードや光センサー用のコードをぽちぽちして何かを作り上げた。
本当に雑に触っただけでかなり頭がよろしくないコードだが、こうすると光が当たった時にモーターが回転を始めることが分かった。
#include <M5Stack.h> int led1 = 16; //PWMの設定 const double PWM_Hz = 50; //PWM周波数 const uint8_t PWM_level = 16; //PWM 16bit(0~65535) void setup() { Serial.begin(115200); M5.begin(); pinMode(led1, OUTPUT); //モータのPWMのチャンネル、周波数の設定 ledcSetup((uint8_t)1, PWM_Hz, PWM_level); //モータのピンとチャンネルの設定 ledcAttachPin(led1, 1); //disable the speak noise dacWrite(25, 0); } uint16_t analogRead_value = 0; uint16_t digitalRead_value = 0; void loop() { M5.update(); M5.Lcd.setCursor(175, 80); M5.Lcd.setTextColor(BLACK); M5.Lcd.printf("%d\n", analogRead_value); M5.Lcd.setCursor(175, 100); M5.Lcd.printf("%d\n", digitalRead_value); analogRead_value = analogRead(36); digitalRead_value = digitalRead(26); M5.update(); if(analogRead_value<1000){ for(int i = 3000; i <= 4000; i+=50){ ledcWrite(1, i); delay(100); Serial.printf("%d\n", i); } }else{ for(int i = 0; i <= 10; i+=10){ ledcWrite(1, i); delay(10); Serial.printf("%d\n", i); } } }
何が問題かというと、
- なんで回るかどれだけ回るかどうしたら止まるかetcが何も分かってない(大問題)
- コードのパーツの意味がほとんど分かってない
- ボタンを押した時の制御がうまくやりきれてない、特に画面表示の変え方がうまくできない
- そもそも待機状態の時を作ることができてない
などなど、問題だらけの継ぎ接ぎコードである。
ということでまずはモーターの勉強をしなければならないぞ。
FS90Rを勉強する
さくっと調べてみると以下のブログに当たった。
このブログによると、
動作としては、回る方向(時計回り、反時計回り)と速度をPWMを使って制御する
のだそうだ。PWMってなんだ。
PWMとは、Pulse Width Modulationの略で、パルス波のデューティー比を変化させて変調する変調方法なのだそうな。
ちなみに デューティー比 = パルス幅 / 周期 のこと。
今回のFS90Rのパルス幅は、データシートによると、
700~1500μ秒で時計回り、1500μ秒で停止、1500~2300μ秒で反時計回り となるのだそう。
よって、例えば周波数50Hz、16bit PWMにおいて停止させたいなら
周期(μsec)=1/50*10^6 = 2.0×10^4 より
デューティー比 = 1500μsec / (2.0 × 10^4) μsec = 0.075
16bitより 0.075×2^16 ≒ 4915 という感じに算出ができる。
プログラムにおいては
ledcSetup((uint8_t)1, PWM_Hz, PWM_level);
でHz(PWM_Hz)とbit(PWM_level)を、
ledcWrite(1, PWM);
でPWMを決めて指示を出す感じだと思えばいいっぽい?
実際PWMの値を4915にしてみると、こう……なんとも言えない速度で微妙に回転を続ける。
ので、周辺を探った結果、ledcWrite(1,4800);で止まることを見つけ出した!やったぜ。
回したいときにはPWMの値をパルス波700~2300μ秒になるように決めてやればええんやね。とはいえ今回みたいに微妙なズレはあるのだろうが。
結局色々弄り倒した結果、ボタンAを押している間か、光が当たっている間だけ動き続ける何かができた。
回転速度とか数とかは結局ストップ時以外あんまりいじれていない。
「コード全然違くね?」と思うかも知れないが、これは夫のテコ入れがある。でも頑張って理解したから使わせてくれよ。
#include <M5Stack.h> int led1 = 16; //PWMの設定 const double PWM_Hz = 50; //PWM周波数 const uint8_t PWM_level = 16; //PWM 16bit(0~65535) void setup() { Serial.begin(115200); M5.begin(); pinMode(led1, OUTPUT); //モータのPWMのチャンネル、周波数の設定 ledcSetup((uint8_t)1, PWM_Hz, PWM_level); //モータのピンとチャンネルの設定 ledcAttachPin(led1, 1); //disable the speak noise dacWrite(25, 0); } uint16_t analogRead_value = 0; uint16_t digitalRead_value = 0; enum RunningMode { MOTOR_CLOCKWISE = 0, MOTOR_STOP = 1, MOTOR_ANTICLOCKWISE = 2 }; struct Context { RunningMode current_mode = MOTOR_STOP; RunningMode previous_mode = MOTOR_STOP; int motor_position = 0; }; void control_motor_stop_mode(struct Context* ctx) { //とくに毎サイクルでやることなし } void control_motor_clockwise_mode(struct Context* ctx) { M5.Lcd.setCursor(0, 100); M5.Lcd.setTextSize(2); M5.Lcd.printf("Now i is : %4d\n", ctx->motor_position); Serial.printf("%d\n", ctx->motor_position); ledcWrite(1, ctx->motor_position); ctx->motor_position += 100; } struct Context context; void loop() { // static void (*control_fn_ptr)(void) = &control_motor_stop_mode; M5.update(); analogRead_value = analogRead(36); digitalRead_value = digitalRead(26); if(M5.BtnA.isPressed()){ // switch to MOTOR_CLOSEWISE // here is a transition procedure from "other mode" to "MOTOR_CLOCKWISE" if (context.previous_mode != MOTOR_CLOCKWISE) { context.previous_mode = context.current_mode; context.current_mode = MOTOR_CLOCKWISE; M5.Lcd.clear(); M5.Lcd.setCursor(0, 50); M5.Lcd.setTextSize(2); M5.Lcd.printf("btnA waspressed"); } control_motor_clockwise_mode(&context); }else if(analogRead_value<1000){ if (context.previous_mode != MOTOR_CLOCKWISE) { context.previous_mode = context.current_mode; context.current_mode = MOTOR_CLOCKWISE; M5.Lcd.clear(); M5.Lcd.setCursor(0, 50); M5.Lcd.setTextSize(2); M5.Lcd.printf("Light!!"); } control_motor_clockwise_mode(&context); }else{ // switch to MOTOR_STOP // transition from previous mode to MOTOR_STOP if (context.previous_mode != MOTOR_STOP) { context.previous_mode = context.current_mode; context.current_mode = MOTOR_STOP; M5.Lcd.clear(); M5.Lcd.setCursor(0, 0); M5.Lcd.setTextSize(2); M5.Lcd.printf("Now base status"); context.motor_position = 4800; ledcWrite(1, context.motor_position); // stop motor Serial.printf("%d\n", context.motor_position); } control_motor_stop_mode(&context); } delay(100); }
さて、これでiの値をとって観察することもできるようになったし、次は回転の制御をして「ばたばた」させたり「動き回」ったりするようなクリーチャーを生み出せばいいかな?
緊急停止ボタンも作りたいところだが。
手元に赤外線センサもあるのでそれを使って、何かが接近してきたら逃げ出すやつとか作ってもいいな。それってゴキb