造形中に例のガタガタ音(フィラメント送りモーターの脱調)、そのうち治るかと思っていても悪化して全くノズルから出てこない状態になってしまった。
ノズル清掃して、その後の送りでもダメだから寿命と思ったほうが良いだろう。
写真は取り外したのもの、Flashforgeのサイトで購入(Adventure3用)したけど、正月明けになるよね。定格400時間と言ってるけど、そこまでの使用時間は無いけども。
外観見る限りは、この中にヒーターと温度センサーが仕込まれているようです。
admin
la vie libre
P制御だけだと、どうしてもハンチング(オーバーシュート)が発生してしまいますが、I制御を追加することでそれを抑止できます。
D係数は外乱対応とのことですが、調整方法を理解していないので後日触りますが、とりあえずの目標の静止状態は作ることができました。
スピーカーから盛大にノイズが出てますが、これはスピーカー制御端子(25)をモーター制御に使っているから。逆にこういうノイズが出ているときには静止状態であると言えます。
設計データは以下のリンクへ、
https://github.com/chateight/reversed_pendulum
パラメータ(PID)調整はリコンパイルでは時間の無駄なので、Processingを使って動作中に書き換えできるようにしています。
admin
倒立振子の製作を始めてますが、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