開発のきっかけ
M5STACKは非常に便利で気軽に開発できるデバイスです。これを用いて丁度必要であった鉄道模型用のパワーパックを作成することにしました。
要件定義 機能要件は次のようにしました。 1. PWM,及びPFM制御とする 2. LCDを最大限生かせるGUIを用意する。 3. FET基盤は内臓しない。(M5Stackを今後別の用途に使用する予定があるため) 4. 保護回路を実装する 5. モーターから励磁音を鳴らす。 6. 本体からも音を鳴らす。
事前調査 以前PWMコントローラーをArduinoUNOで制作しているため、前提技術はそのまま流用。M5Stackの良さが生きています
開発環境について PCに環境を整えると、ArduinoIDEが変わっているではありませんか....(諸事情により1年以上ArduinoIDEを触っていなかった。)知らない操作感とESP32がコンパイル出来ないエラーに悩まされ結局元のバージョン(Arduino1)に戻しました。この展開、ROSと同じですね また、M5stackへのコンパイルがとても、とても、とても遅いため、Linuxに開発環境を移行。体感10倍まで早くなりました。
プログラムについて 癖が強い書き方で、非効率的なプログラムで恐縮ですが、このようになっています。
M5STACK is an incredibly convenient and user-friendly development device. I decided to use it to create a power pack for model trains, which was exactly what I needed.
Requirements Definition The functional requirements were defined as follows:
Preliminary Investigation Since I had previously created a PWM controller with Arduino UNO, I reused the underlying technology. This allowed me to leverage the advantages of M5Stack.
Development Environment Setting up the environment on my PC, I noticed that Arduino IDE had changed... (I hadn't used Arduino IDE for over a year due to various reasons.) I struggled with an unfamiliar interface and compile errors related to ESP32, so I ended up reverting to the original version (Arduino 1). This development process reminded me of experiences with ROS....lol Additionally, compiling for M5Stack was incredibly, incredibly, incredibly slow, so I migrated the development environment to Linux, which made the process up to ten times faster.
Programming The code is written in a somewhat idiosyncratic and inefficient manner, for which I apologize. However, this is how it turned out.
メインルーチン
#include <M5Stack.h> //26pin out
#include <M5GFX.h>
M5GFX lcd;
M5Canvas canvas(&lcd);
int val=0;
float valpwm;//pwmvalue
float prevalpwm=0;
int Mmode=0;
int mascon=0;
int premascon=0;
float prespd=0;
float spd=0;//Scalespeed
int setspeed=120;//Scalespeedlmit
String tex1;
String tex2;
int mdata=0;
int sound =0;
void setup() {
M5.begin();
M5.Power.begin();
lcd.begin();
canvas.setColorDepth(8);
canvas.createSprite(lcd.width(), lcd.height());
M5.Lcd.clear(BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(RED);
int batt=M5.Power.getBatteryLevel();
M5.Lcd.setCursor(280, 10);
M5.Lcd.printf("%d",batt);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setCursor(65, 10);
M5.Lcd.println("Running");
M5.Lcd.setCursor(190, 10);
M5.Lcd.println("Battery");
M5.Lcd.setCursor(135, 90);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.print("Speed");
M5.Lcd.setCursor(3,90);
M5.Lcd.print("OP");
M5.Lcd.setCursor(3,110);
M5.Lcd.print("PWM:");
M5.Lcd.setCursor(100, 110);
M5.Lcd.print("%");
M5.Lcd.setCursor(135, 110);
M5.Lcd.print("Carrier");
M5.Lcd.setCursor(270, 110);
M5.Lcd.print("Hz");
M5.Lcd.drawRect(286,143,34,79,DARKGREY);
M5.Lcd.drawRect(248,138,34,84,DARKGREY);
M5.Lcd.drawRoundRect(78,33, 120, 29,3, DARKGREY);
M5.Lcd.drawLine(10, 180, 230, 180, NAVY);//scale 200ms
//M5.Speaker.setVolume(1);
M5.Speaker.mute();
pinMode(26,OUTPUT);
ledcSetup(0,100,8);
ledcAttachPin(26,0) ;
}
void loop() {
M5.update();
if (M5.BtnA.wasReleased()){
tex1="MENU";
subtext(tex1,tex2);
tex2=tex1;
mdata=1;
}else if ( M5.BtnA.pressedFor(2000, 2000)) {
tex1="SETING";
subtext(tex1,tex2);
tex2=tex1;
}else if (M5.BtnB.wasReleased() || M5.BtnB.pressedFor(300, 100)) {
if(mascon>-8){
premascon=mascon;
mascon=mascon-1;
masconLCDs(mascon,premascon);
Gmas(mascon,premascon);
}else{
premascon=mascon;
mascon=-8;
masconLCDs(mascon,premascon);
Gmas(mascon,premascon);
}
} else if(M5.BtnC.wasReleased() || M5.BtnC.pressedFor(300, 100)) {
if(mascon<5){
premascon=mascon;
mascon=mascon+1;
masconLCDs(mascon,premascon);
Gmas(mascon,premascon);
}else{ M5.Lcd.fillRect(250, 200, 30,20, BLACK);
premascon=mascon;
mascon=5;
masconLCDs(mascon,premascon);
Gmas(mascon,premascon);
}
}
val=val+mascon;
if(val<0){
val=0;
}else if(val>1275){
val=1275;
}
prespd=spd;
prevalpwm=valpwm;
valpwm=val/5;
spd= SPDdata(valpwm,setspeed);
SpeedLCDs(spd,prespd);
pwmLCDs(valpwm,prevalpwm);
sound=Hzmake(mapping(valpwm));
if(sound>0){
//M5.Speaker.setVolume(1);
ledcSetup(0,sound,8);
ledcWrite(0,valpwm);
//M5.Speaker.tone(sound, 100);
}else{
ledcWrite(0,0);
}
HzLCDs(sound,Hzmake(mapping(prevalpwm)));
delay(10);
HzdataLCD(sound);
}
LCD駆動用
LCD駆動用
void masconLCDs(int mascon1,int premascon1){
M5.Lcd.setCursor(3, 60);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.printf(": %d", premascon);
M5.Lcd.setCursor(3, 60);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.printf(": %d", mascon);
delay(10);
}
void subtext(String maintex,String pretex){
M5.Lcd.setCursor(3, 35);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.printf("%s",pretex);
M5.Lcd.setCursor(3, 35);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.printf("%s",maintex);
delay(10);
}
void SpeedLCDs(float SPD1,float preSPD1){
M5.Lcd.setCursor(200, 90);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.printf("%.1f km/h", preSPD1);
M5.Lcd.setCursor(200, 90);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.printf("%.1f km/h", SPD1);
canvas.pushSprite(&lcd, 0, lcd.height());
delay(10);
}
void pwmLCDs(float SPD1,float preSPD1){
int pass=mapping(SPD1);
int repass=mapping(preSPD1);
M5.Lcd.setCursor(30, 90);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.printf(":%.1f:", preSPD1);
M5.Lcd.setCursor(30, 90);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.printf(":%.1f:", SPD1);
M5.Lcd.setCursor(50, 110);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.printf(":%d", repass);
M5.Lcd.setCursor(50, 110);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.printf(":%d", pass);
if(repass>pass){
M5.Lcd.fillRect(80+pass, 35,repass-pass+1,25, BLACK);
}else if(pass>repass){
M5.Lcd.fillRect(80+pass, 35,pass-repass,25 , BLUE);
}else{
M5.Lcd.fillRect(80, 35,2,25 , GREEN);
}
canvas.pushSprite(&lcd, 0, lcd.height());
}
void HzLCDs(int SPD1,int preSPD1){
M5.Lcd.setCursor(225, 110);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.printf("%d", preSPD1);
M5.Lcd.setCursor(225, 110);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.printf("%d", SPD1);
canvas.pushSprite(&lcd, 0, lcd.height());
delay(10);
}
}
void Gmas(int mascon1,int premascon1){
int xval=mascon1*10;
int pxval=premascon1*10;
if(mascon>0){
M5.Lcd.fillRect(288, 220-pxval*1.5, 30,pxval*1.5, BLACK);
M5.Lcd.fillRect(288, 220-xval*1.5, 30,xval*1.5 , GREEN);
delay(10);
}else if(mascon<0){
M5.Lcd.fillRect(250, 220+pxval, 30,-pxval, BLACK);
M5.Lcd.fillRect(250, 220+xval, 30,-xval , RED);
delay(10);
}else{
M5.Lcd.fillRect(250, 200, 30,20, BLACK);
M5.Lcd.fillRect(288, 200, 30,20, BLACK);
delay(10);
}
canvas.pushSprite(&lcd, 0, lcd.height());
}
void HzdataLCD(int Hz){
if(Hz>0){
M5.Lcd.fillRect(20, 140,220,220, BLACK);
int ms=4000/Hz;
int kaisuu=200/ms;
for(int i=0;i<=kaisuu;i++){
int base=20+i*ms;
M5.Lcd.drawLine(base, 180, base,180-40, DARKGREEN);
M5.Lcd.drawLine(ms+base, 180, ms+base,180+40, DARKGREEN);
M5.Lcd.drawLine(ms/2+base, 180+40, ms/2+base,180-40, DARKGREEN);
M5.Lcd.drawLine(base, 180-40, ms/2+base,180-40, DARKGREEN);
M5.Lcd.drawLine(ms/2+base, 180+40, ms+base,180+40, DARKGREEN);
}
M5.Lcd.drawLine(10, 180, 230, 180, NAVY);//scale 200ms
canvas.pushSprite(&lcd, 0, lcd.height());
}else{
M5.Lcd.fillRect(20, 140,220,220, BLACK);
}
}
その他演算、出力
周波数情報
int Hzmake(int PWs){
int valhz=0;
if(PWs==0){
valhz=0;
}else if(PWs<=6){
valhz=0;
}else if(PWs<=9){
valhz=0;
}else if(PWs<=12){
valhz=0;
}else if(PWs<=14){
valhz=6;
}else if(PWs<=16){
valhz=0;
}else if(PWs<=18){
valhz=0;
}else if(PWs<=20){
valhz=0;
}else if(PWs<=22){
valhz=0;
}else if(PWs<=45){
valhz=0;
}else if(PWs<=55){
valhz=0+PWs*3;
}else {
valhz=0+PWs*2;
}
return valhz;
}
//周波数をvalhzに入力。時間はパーセンテージに応じて割り当てる
int SPDdata(int vol, float sspeed){
float respd=0;
respd=map(vol,0,255,0,sspeed);
return respd;
}
int mapping(int map1){
int Va1=map(map1,0,255,0,100);
return VaI;
}
meinにはなるべく書かないように意識しています。また、周波数の出力部分(valhz)は時間をPWMのデューティ比に応じた関数となっているため、実車の相関性を元に作りやすいようにしています。また、加速度も調整しやすくしているため、車両に応じて変更するとより雰囲気が出るかと思います。 LCDはちらつきが生じにくいように黒部分塗りつぶしを行い、スプライト実装を行っていましたが、なぜかエラーが生じたり波形出力が上手く行かなかったり重かったりしたので、結局力業で行きました。
余談ですが、私は墜落インバーターことSC59A 日立製作所製造IGBT2レベルインバータとシーメンスのVVVF(501風)にしています。
I consciously try to avoid writing too much in 'mein'. Additionally, the frequency output part (valhz) is designed to be a function based on the duty cycle of PWM over time, making it easier to create based on the correlation of real vehicles. Furthermore, I've made adjustments to the acceleration to make it easier to change according to the vehicle, which I think adds more authenticity.
Regarding the LCD, I initially tried to reduce flickering by filling in the black areas and implementing sprites. However, I encountered errors, had trouble with waveform output, and experienced performance issues. So, in the end, I had to resort to manual work.
On a side note, I've used the SC59A Hitachi IGBT 2-level inverter, also known as the crash inverter, and Siemens' VVVF (501-style) for this project.
性能について PWMは8bit、周波数は可聴域対応でです。表示データはESP32の性能を活かすことを目標に描画タスクを多めに設定しています。 M5Stackの左ボタンは拡張用の予約として実装。中央がマスコン下げ、右がマスコン上げになっています。 本体からは僅かに音が鳴るように26pinで出力していますが、25pin指定にすれば本体から音を鳴らせます。
Performance PWM is 8-bit, and the frequency is compatible with the audible range. The display data is set with a higher number of drawing tasks as a goal to leverage the performance of the ESP32. The left button on the M5Stack is implemented as a reserved button for expansion. The center button is for reducing throttle, and the right button is for increasing throttle. A slight sound is output from pin 26 from the main unit, but if you specify pin 25, can generate sound directly from the main unit."
まとめ M5Stackの特徴である開発の気軽さと汎用性、Arduino言語が使用できるメリットを十分に感じることが出来ました。また、モータからの音が小さいため、本体からも音を流していましたが、両者から聞こえる音に差があったため、ここの調整もしていきたいと考えています。そして何より波形ですね...フリッカーが酷すぎますね... 最後に、SticCもあるため、こちらで何か出来るといいなと考えていますが何をしようか...
Summary I was able to fully appreciate the ease of development and versatility, as well as the benefits of being able to use the Arduino language, which are features of M5Stack. With M5SticC available, thinking about what can do here, but what should do...
走行動画では少し分かりにくかったので、より音の聞き取りやすいブラシレスDCを駆動させた動画を添付します。
https://youtube.com/shorts/ptZY0kZ62zQ?feature=share
同じ12Vですので、何も手を加えていません。
システム構成について
このような構成となっています。
System Configuration
1.Various LCD drawing functions 2.Calculation routines 3.Main loop
The configuration is as described above.