チューニングで問題だったのは、M5Stackの画面にログ用に表示させている数値の表示がやたらに遅いこと。ジャイロ・加速度・pitch/role/yawを画面表示させているとloop()の処理時間がおよそ50msも掛かっていました。これではフィードバックが間に合わないので、ログ表示を削除すると10ms以下に収まってます。
P値も大きすぎるとリンギングは盛大になるので、適当なところで止めてみたのが以下の動画です。まあ、振動はしてるし、外乱にも対応できない状態ですが。
admin
la vie libre
倒立振子の製作を始めてますが、PID制御のパラメータ設定を簡単に行えるような準備も整ったのであとはコード全体を書いて調整に取り掛かるだけ。
PIDパラメータは調整のおおまかな方針は決まっているようですが、最後の微調整はカットアンドトライになるので、いちいちソースコンパイル、ダウンロードでは効率が悪いので、ダイナミック(on the fly)でパラメータ書き換えの手段が必須になります。
やり方はM5Stack側はUDP受信で、送信側はProcessingを使って更新します。
<Processingのコード>
/*
set PID parametor to the m5stack using UDP
*/
import hypermedia.net.*;
import controlP5.*;
UDP udp;
ControlP5 cp5;
final String IP = "192.168.1.15";
final int PORT = 3002;
String msg = "10, 3, 1"; // P, I, D
void setup() {
size(200, 200);
cp5 = new ControlP5(this);
udp = new UDP( this, 3002 );
ControlFont cf = new ControlFont(createFont("Serif",20));
cp5.addButton("UDP_Msg")
.setFont(cf)
.setLabel("send")
.setPosition(50,50)
.setSize(100,100);
}
void draw() {
background(200);
}
void UDP_Msg(){
udp.send(msg, IP, PORT);
}
SENDボタンでUDP送信、パラメータの書き換えはソースコードで行いますがコンパイル時間は見えないので、実質瞬時に対応できます。
<M5StackのUDP受信処理抜き出し>
M5StackのRegExp処理はいまいちよく分からなかったので、受信テキストは文字列分割で対応。カンマ区切りの3個のパラメータだけなので文字分割でも対応できますが、その分コードは美しくない。このコードをloop()処理中に記述して、ダイナミックにPIDパラメータ変更を実現します。
UDP通信とパラメータ格納の該当部分だけ抜き出したコードです。
#define N 1024
// PID variables
float P_val = 50;
float I_val = 2;
float D_val = 10;
WiFiUDP udp;
char packetBuffer[N];
int packetSize = udp.parsePacket();
if (packetSize){
int len = udp.read(packetBuffer, packetSize);
String pid_data = String(packetBuffer);
//Serial.println(pid_data);
int index = pid_data.indexOf(",");
P_val = (pid_data.substring(0, index)).toFloat(); // P
pid_data = pid_data.substring(index + 1, pid_data.length());
index = pid_data.indexOf(",");
I_val = (pid_data.substring(0, index)).toFloat(); // I
pid_data = pid_data.substring(index + 1, pid_data.length());
D_val = pid_data.toFloat(); // D
admin
M5StackでのPWM制御はArduinoとは違うようなので記録。
① PWMのセットアップ(ledcSetup()
)を行う
② PWMの制御ピンを論理チャネルとコンバイン(ledcAttachPin()
)する
③ PWM制御信号を出力(ledcWrite()
)する
DCモーターの回転方向とブレーキを決める信号は通常のdigitalWrite()で実行。
/#include <m5stack.h>
const uint32_t PWM_Hz = 2000; // PWM freq.
const uint8_t PWM_level = 8; // PWM resolution 8bit(1~256)
const uint8_t PWM_CH = 1; // PWM channel
int pwm_a = 2;
int fw_a = 16;
int rv_a = 17;
int pwm_b = 26;
int fw_b = 5;
int rv_b = 25;
void setup() {
M5.begin();
M5.Power.begin();
pinMode(pwm_b, OUTPUT);
pinMode(fw_b, OUTPUT);
pinMode(rv_b, OUTPUT);
// set PWM_CH & resolution
ledcSetup(PWM_CH, PWM_Hz, PWM_level);
// combine PWM control pin to PWM_CH
ledcAttachPin(pwm_b, PWM_CH);
}
void loop() {
M5.update();
if (M5.BtnA.wasPressed())
{
// PWM duty 0.25(64/256)
ledcWrite(PWM_CH,128);
digitalWrite(fw_b, LOW);
digitalWrite(rv_b, HIGH);
}
else if (M5.BtnC.wasPressed())
{
// stop
ledcWrite(PWM_CH,0);
}
delay(10);
}
DIOのピンアサインは以下の通りです。M5StackはArduinoと違ってDIOの本数が限定させるので、他の信号と重複するのは致し方なし。
admin
引き続いて、M5Stackのpitch/roll/yaw情報をUDPで送って、Processing上のモデルに反映してみます。
M5Stackからはpitch/roll/yaw情報をカンマ区切りのテキストで送ります。
このテキストをProcessingで受信して、直方体のモデルのposeに反映します。正負が逆になったx/z軸は値を反転。座標系の取り方でどうにでもなるような気もしますが。
Processingのソースは見ての通り単純です。最初3Dの描画できなくて調べたらベータ版では動かないようで4.1.1にアップデートしたらきちんと動作しました。
codeは、
https://github.com/chateight/processing/blob/main/sketch_221220b.pde
/*
read m5stack udp data(IMU data ; pitch, roll, yaw) and move the imaginary object
*/
import hypermedia.net.*;
UDP udp;
final String IP = "0.0.0.0";
PFont myFont;
float[] pry = new float[3]; // pitch, roll, yaw store work
void setup() {
udp = new UDP(this, 3002);
udp.listen( true );
size(500,500,P3D);
frameRate(30);
loop();
}
void draw() {
background(0);
translate(width/2, height/2);
rotateX(radians(-pry[1]));
rotateY(radians(pry[0]));
rotateZ(radians(-pry[2]));
box(150, 150, 50);
}
void receive( byte[] data, String ip, int port ) {
String message = new String( data );
println( "received : \""+message+"\" from "+ip+" on port "+port );
String[][] matchedTexts = matchAll(message, "[0-9|-]+.[0-9]+");
if (matchedTexts != null) {
int index = 0;
for (String[] matchedText : matchedTexts){
pry[index] = float(matchedText[0]);
index++;
}
}
println(pry[0], pry[1], pry[2]); // pitch, roll, yaw
}
動かして見たのは、今のYouTube画面で参照ください。yawは時間経過とともにずれていくのでいくらか補正はしていますが、十分じゃ無いですね。
admin
M5Stackからのデータを視覚化するのに、一番手軽に使えそうなツールだと思うProcessingでM5StackからUDPで送られてくる温度データを時系列でグラフ化してみました。Bluetoothで送るのは接続不安定でストレスになるから、簡単に送れるUDPを使います。電池寿命とか関係ないし、
<M5Stackから送られるメッセージ形式>
日時(秒まで)と温度データ
<Processingのコード>
メッセージから、温度部分を抜き出して表示しています。Processingの画像処理は専用のAPIになっています。正規化処理もJavaとは別物ですが、
receive()メソッドは、UDPリッスンで受信データがあれば起動されるでしょうが、drawの読み出し(val)と書き込みが競合すれば値は不定になる可能性はありそうですが、視覚的なものなので排他制御をするまでもないでしょう。
import hypermedia.net.*;
import controlP5.*;
UDP udp;
final String IP = "0.0.0.0";
ControlP5 cp5;
Chart chart_tmp;
final int NUM_GRAPH_DATA = 200;
PFont myFont;
float val;
void setup() {
size(650, 700);
myFont = createFont("Arial", 30);
udp = new UDP(this, 3002);
udp.listen( true );
frameRate(10);
cp5 = new ControlP5(this);
chart_tmp = cp5.addChart("tmp sensor");
chart_tmp.setView(Chart.LINE)
.setRange(20, 22)
.setSize(600, 200)
.setPosition(10, 250)
.setColorCaptionLabel(color(0,0,255))
.setStrokeWeight(1.5)
.getColor().setBackground(color(224, 224, 224))
;
chart_tmp.addDataSet("temp");
chart_tmp.setData("temp", new float[NUM_GRAPH_DATA]);
chart_tmp.setColors("temp", color(0, 0, 192), color(255,255,128));
}
void draw() {
background(255);
fill(0);
chart_tmp.unshift("temp", val);
}
void receive( byte[] data, String ip, int port ) {
String message = new String( data );
println( "received : \""+message+"\" from "+ip+" on port "+port );
String[] matchedTexts = match(message, "[0-9|-]*.[0-9]*$");
if (matchedTexts != null) {
println(matchedTexts[0]);
val = float(matchedTexts[0]);
}
}
動画はこちらを参照ください、グラフ下端が20℃、上端が22℃で範囲を拡大して見やすくしています。
admin
以前にやった時もそうですが、接続が安定しない原因は何なのかの単純化ですが、相変わらずロジックは分かりません。
Arduino IDEで以下のようなソースを動作、
#include <M5Stack.h>
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
int cnt = 0;
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32");
}
void loop() {
SerialBT.println("Hello World : " + String(cnt));
cnt += 1;
delay(1000);
}
ここで、ArduinoIDEのシリアルをBluetoothに選択、
すると、コンソールに結果出力されますが、この状態でMacのコンソールを開く(つまりコンソールが2台ある状態)だと、両方のコンソールにランダム風に結果が出力されます。これはまともでしょう、
いずれの状態でもMac本体のBluetoothはペアリングはしていない。
これだけシンプルでも、再コンパイルとかで期待したように動かなくなります。回避方法はMacの再起動のみ。
再起動すると、以下のようにBluetooth接続も復活しますが。
この後でArduinoIDE側のシリアルをUSBに切り替えて、Macコンソールを起動、
ここまでは正常ですが、ここでM5Stackをリセットすると接続が切れて復旧しない。これはArduinoIDE側のシリアルをBluetoothにしていても同じだから、Macを立ち上げて最初にBluetoothシリアル使うまではいいけれども、その後なんらかのアクションをすると必ずMacをリブートしなければいけないという不条理な状態になっています。
admin
二輪で、本体の傾きをセンスしてバランスをとりながら倒れないように制御するロボット(倒立振子というらしい)ですが、イメージとしては秋田の竿燈みたいなものか。竿燈は二次元の制御だけど、二輪ロボットは一軸制御だから制御は相対的には楽そうに見えます。
回転モーメントが大きいほど、つまり高さ方向が長いほどフィードバック時間に余裕がありそうに思えますね。
原理は、
https://matome.eternalcollegest.com/post-2136401209031725401
完成品としては、
https://docs.m5stack.com/en/app/bala2fire
や、
https://www.switch-science.com/products/8297
あたりがありそうですが、M5stack使って自作するというのもありそうです。
例えばこれとか、
https://maker.pro/arduino/projects/build-arduino-self-balancing-robot
PID制御はカットアンドトライでパラメータを決めるのが現実らしい。PIDライブラリというのがArduinoには用意されていて、Arduino IDEのライブラリで検索するとたくさん出てきます。
admin
M5stackでUDPが使えるから、Telloのコマンド制御もできるよねと思いましが、既に先人のコードができてます。
https://qiita.com/Mitsu-Murakita/items/b86ad79d3590adb3b5b9
TelloのSSIDだけを追加すれば動きましたが、動作がイマイチもっさり、というのはスイッチの反応が遅くて多少長押ししないとtake offもしない。
で時間待ちの500msを150msにするとほぼイメージ通りに動きますが、疑問点が一つ。
wasPressed()関数って、ボタン押されたことを記憶できる関数のはずなのになぜ長押ししないといけないか?
loop()の中でボタン機能の確認コードを作って確認してみると、どうもdelay()中にボタンを押してもwasPressed()には反映されない模様です、だからdelay()で設定した時間以上にボタンを押していないといけないようです、仕様として変だと思いますが。
~~~
if ( M5.BtnA.wasPressed() ) {
Serial.println("BtnA.wasPressed() == TRUE");
}
//Serial.println("loop");
delay(300);
~~~
あとフリップ動作は、イマイチスイッチを押すタイミングとM5stackを傾けるタイミングが掴めない。
admin
Nodeに関連して検索したサイトと自分用のメモです。
○ WebSocketのURL形式
通信時に指定するURLは、
ws://www.sample.com、あるいはセキュア接続ならwas://www.sample.com
明に使うことはあまりないだろうけども
○ Node.jsでソケット通信
<socket.ioのインストール>(アプリを作成するディレクトリで)
% npm install socket.io
もはやdgramはインストール不要らしい
○ websocket動かすには
Node.jsとExpressの他にwsライブラリのインストールも必要、本当?
<HTTP serverとclientサンプル>
socket.ioを使った、
以下の二つはGitHubのサンプル動かしているのでセット
https://qiita.com/kouji0705/items/cf16044c7d825d09d707
https://github.com/neroneroffy/webSocketDemo
https://ai-soldier.work/websocket-node-ws/#
これ動かしてみたけど、ラズパイでも軽快に動く
○ Express
<documents>
<解説>
https://qiita.com/mml/items/3cc90479df033c0998e4
https://qiita.com/ganyariya/items/85e51e718e56e7d128b8
<起動>
$ npm start
○ pug(HTMLテンプレート)について
<pugの概要>
https://and-ha.com/coding/what-is-pug/
こんなこともできる(markdownをincludeしてHTML出力)
https://ytyaru.hatenablog.com/entry/2018/05/01/000000
<pugの拡張>
https://qiita.com/zenno04/items/d16f881170170b567b16
Template Inheritance
https://pugjs.org/language/inheritance.html
○ wsサーバーの起動
Express serverとWebSocket severそれぞれ別に起動で良い
$ node ws_server.js
実際の運用は起動スクリプトに書くから個別起動で問題ない
○ Javascript(chart.js)でグラフ作成
json使いましょう
https://www.web-development-kb-ja.site/ja/javascript/chartjsでjsonデータを表示する/833307193/
○ JSからEJSへ
use strict : ESM(ES module)有効なら指定不要だったよね
admin