ぼくがかんがえたさいきょうのもたどら

超お久しぶりです。さばねっこです。

突然ですが皆さん,最強のモタドラってなんだと思いますか??

古今東西様々な回路系ロボコニストが多種多様なモータードライバを作ってきました。
かく言う私もいろんなロボコン向けモタドラを作ってきたわけですが…

クアッドモタドラ(高専ロボコン2022@豊田A)
縦刺しつよもたどら(学生ロボコン2023@豊田高専)

今はBLDCの時代,ロボマスモータをはじめとする様々な新世代のモータがロボコン界隈を賑わせるようになりました。
こういったモータはブラシ付きモータと比較して圧倒的な性能(サイズ,重量,耐久性)を実現していますが,一方その運用には専用のドライバ,あるいは高機能かつ高価なドライバを用意する必要があります。

高機能ドライバの有名どころとしてはOdriveとかVESCでしょうか。
ただ,どちらも大抵1万円程度はするのが一般的なようですね。

まあ結構作りもいいのでそれぐらいはするよなーという感じ。

でも,冷静に考えて,そんなに高性能なモタドラが必要な用途ってあんまりないですよね??

ロボコンにおける大抵のモータ用途はアームの駆動やアイテムの保持等,連続100W程度で十分な程度のものも多いです。
また,大出力が必要な機構の代名詞である足回りについても,実は超高速・超高加速度を狙わなければそこまでの出力は必要ありません。

つまり,多少余裕をもって考えても400W程度のモータで大抵の需要は満たせるわけです。
そのためにいちいち超高性能なドライバを使うのはちょっともったいないですよね。

そんな現状を踏まえたうえで,私が考えるさいきょうのロボコン用モタドラというのは…

高い汎用性があり,性能はそこそこ,それでめっちゃ安いやつ。

具体的には…
・ 高い汎用性
→どんなモータ(BLDC,ブラシ付き問わず)でも回せるようにしたい
→BLDCはもちろんFOCする
→いろんなエンコーダで制御できるとうれしい
・とにかく安く
→他ドライバの価格を考えると,2000円ぐらいで収まると素晴らしい
・電流は連続20A,ピーク30A程度流せれば十分
→M3508+α程度の出力のモータを回せたら十分
・機械の邪魔にならないそれなりのコンパクトさ

になります。

というわけで作ったのが冒頭にも貼ったこちらのモタドラでございます。 かわいいですね

設計データとかファームはこちら

GitHub - sabanekko3/sMDUv3: 小型汎用BLDCモタドラ · GitHub

基本スペックはこんな感じ。

性能
電源電圧 10~24V
電流 各相最大30A
連続電流 未検証(計算上ヒートシンクつけて25A程度)
エンコーダ sin/cosエンコーダ(ロボマス),AS5048,AS5600,AMT102(多分動く。動作未確認),センサレスは未実装
インターフェイス CAN(ロボマスプロトコル,TGT系プロトコル),USB(SLCAN)
安全機能 過電流・過電圧・エンコーダ断線・基板過熱検知

※TGT系プロトコル=豊田高専の学ロボ2024で使ってたやつの系譜

結構「ちょうどいい」性能なのではないでしょうか。
M3508程度のモーターであれば十分ドライブできますし,その他の普通のBLDCであってもエンコーダさえつければ動作できます。

センサレスについては単純に面倒だったのもありますが,ロボコンにおいてセンサレスFOCをする需要ってそんなに無いような気がしたので未実装。
どうせゼロ速付近の制御はまともにできないし,それでいいなら普通のラジコン用ESC使えばいいと思う。

基板設計

基板は4層で,表層に大電流が流れる設計のため,2oz銅箔を採用します。
熱設計的にはヒートシンクつければ連続20Aは普通にいけるはず。

サイズは57x26で,C620より一回り大きくなってしまいました。
まあ片面に部品集めてるし大きいMOSFET採用してるからしょうがないね。
あとJLC特有の激安8層基板は使いません。たくさんPCBAするなら8層基板をちまちま作るより4層基板で銅箔厚くした方が安いです。

さて問題の値段ですが,JLCPCBで20枚まとめてPCBA発注すると240ドルぐらい。
一枚あたりに換算すると1800円ぐらいでしょうか。
贅沢に2oz基板を使っているのでそんなもんですかねー。もうちょっと安くしたかった。
というか昔試算したときは1000円ちょっとぐらいだったと思うんですけどね。物価上昇コワイ。

あと複数種類のエンコーダで使えるようにSPIやらI2CやらADCやらを一つのエンコーダコネクタにつなげるキチガイ構成で実装しています。

ゴミエンコーダ
別に問題なく動くからヨシ!
使わないピンは無効化しとけばよいのです。

制御ソフト開発

BLDCモタドラ開発においてはこちらの方がメイン。

えらいので制御器の設計はまじめにシミュレーションで確認しながらやります。

研究室でPLECS使い放題なの激アツすぎる。

まあとはいっても実装する制御は普通のベクトル制御なんですけどね。
変調も三角波重畳なSVMなのでマジで普通です。

一応アンチワインドアップ周りには気を使って設計しています。ロボコン用途で雑に扱えるようにするなら必須ですよね。
2自由度の制御系でのアンチワインドアップの仕方がよくわからなかったのですが,とりあえずd軸電圧を優先して吐き出せるようにクランプしています。

伝わる気のしないブロック図(6/17修正

d軸電流発生すると効率落ちちゃうからね。
まあこれに関してはもっといいやり方がある気がするので勉強ですね~

で,それをマイコンに実装して動作確認。
波形撮り忘れましたが,電流制御器の応答はだいたい1500[rad/s](250Hz)ぐらいにしました。
なんか基板設計がゴミゆえにノイズマシマシでこれ以上は無理でしたね~。かなしい。

モータのキャリブレーション

このモタドラはM3508とM2006の両方に対応しています。
ですが自分がどっちのモータを回しているかわからないと困るため,キャリブレーションするときに自動で判別してくれるようにしておきました。
まあやってることはモータの温度センサがつながってるかを見てるだけですが…。

ちなみに,電圧ステップに対する電流変動を読むことでモータのRとかLとかも自動で測定しています。
自動で電流制御ゲイン調整(零極相殺型)してくれるので楽。
ただこの辺の性能はVESCとかの方が高いですよねー。LdとLqは分離できていないのでSPMSMじゃないと動かせません。
というかLdとLqの同定の話はこの本に全部書いてありました。もっとちゃんと読み込んでおけば…

インターフェイスまわりの開発

モタドラへの指令値の送信はCANで行うわけですが,せっかくなのでC610・C620互換にしておきました。
というわけで上流レイヤーから見たらMADやらT-Motorやらの普通のBLDCもM2006やM3508とまったく同じように使えます。
こんな感じで,いろんな組み合わせを混在させることもできます。

もちろん(?)自作のオリジナルプロトコルを使えば直接位置指令や速度指令を送ることも可能です。

あと基板上に搭載されたUSBからもSLCANプロトコルで通信できるので,CANを経由せずにデバッグしたり,こんな感じのGUIを作って(というかAIに作らせて)PCから設定できるようにもして遊べます。

ゴミカスGUI(なぜかクソ重い)

そういえばブラシ付きDCにも対応しています。ほぼおまけ機能なので電流制御とかはできないけど。

反省点

まず,なぜかゲート電源生成にリニアレギュレータを採用してしまった点。計算上は熱も問題ないんだけど割と熱くて不安になります。

まあ今設計しろと言われたら間違いなくSTSPINつかってスイッチングレギュレータ式にしますよね。
なんかいつの間にか結構値段も入手性もよくなった印象。
もうロボコンに関わることはない気がしますが,機会があったら作ってみてもいいかもね~。

そういえばスペックシートにはAS5600対応って書いてますけどなんかいまいち安定しません。AS5048推奨です。これも改善したかったですね。面倒なのでやらないけど。

おわりに

こういうモタドラの構想は高専時代からずっとしてたんですが,今回こうやって実現する機会に恵まれて本当に良かったです!
そもそも大学でロボコンなんてやるつもりはなかったのになんやかんや楽しかったなー。
公開はしない予定ですけどこれ以外にも電源基板とかいろいろ作ってました。
長岡のチームメンバーのみんな本当にありがとう。


ということで以上になります。
私は今後はパワエレ系研究室でゴリゴリ研究していく予定なので(何故か5年一貫博士課程とやらに入ってしまった),浮上頻度とかは下がるかもしれませんが,気が向いたらまた趣味の電子工作をすると思うので,そのときまで気長にお待ちいただければなーと思います。

それでは~~

そうだ,マカロンを作ろう

おひさしぶりです。さばねっこです。

突然ですが,異常独身男性必須のスキルといえばなんでしょう。 そうだよね,マカロンを焼く能力だよね。

というわけで修行していきます。

Day1(9/28)

まずはメレンゲ,やうやう白く成りゆくまで,混ぜる。

メレンゲを気合で作ったら,粉類を入れて生地を作ります。 今回はとりあえずセリアで売ってるマカロンの素(?)を使って作ります。 いろんな色のやつがあったけどとりあえず今回はかわいいピンク色のやつを採用。

背景がきたない

ちなみに,この粉一袋当たり卵白が20g必要なのですが,これは大体卵0.7個分ぐらいの分量です。 卵一個で結構遊べるんですね~。

続いてクッキングシートに生地を広げます。 セリアにマカロン用の枠線が書いてあるシートがあったので採用。 今回は直径3cmのミニサイズで作ります。

あとは焼くだけ,というわけにはいきません。 一時間しっかり乾燥させてください(1敗)。

で,乾燥させたら魚焼きグリルで頑張って焼きます。 オーブン?なんですかそれ。 焼くときはこんな感じでアルミホイルを上からかぶせてます。直火が当たると焦げるからね。

で,焼きあがったのがこちら。 ちょっと焼きすぎた…。

完成品
はい。本日の作戦は失敗です。 奥のやつは表面が割れまくってしまってますねー。 右下のやつはぱっと見は綺麗ですが,実際は焼き時間が短すぎて半生です。 上のやつについて,一応ピエは出ている(?)のですが,一周きれいに入っているわけではないです。

ちなみに間のクリームは普通のホイップクリームです。 わざわざ追加で仕込むのも面倒なので。

考察

表面が割れた点,これに関しては乾燥不足が主な要因と考えられます。 あらかじめ乾燥させて表面を固め,メレンゲが膨らもうとする力に耐えられるようにしなければなりません。

また,今回はほとんどマカロナージュせずに作りました。 なんかメレンゲと粉類を混ぜるとき,メレンゲの泡を多少潰すようにしながら混ぜることで,メレンゲを柔らかくするらしいです。 これをしないとメレンゲが膨らみすぎて割れる,らしい。

Day2

前回の反省を踏まえ,事前にしっかり乾燥させることにします。 というわけで天気が良くて暇な日が訪れるまで待機。

Day27(10/25)

なんか一か月経ってた。

さて,この日の天気は快晴。事前にラーメンも食べて気合は十分。

0辛のはずなのにめっちゃ辛い

https://maps.app.goo.gl/2AZAMCnMZxc4oz78A

また,今回は秘密兵器も導入します。

Innovation
文明開化の音がする。

というわけでまずはメレンゲを作って…

やっぱり背景が汚い

生地を広げる。 なんか形が汚いしちょっと飛び散ってますが気にしてはいけません。 今回は形云々というより最適な焼き加減を調べたいだけなので。ハイ。 あ,もちろんマカロナージュちゃんとしてます。

で,しっかり乾燥させます。 この日は 新潟にしては珍しく しっかり太陽が出ていたので,直射日光に当てながら乾燥させるグループと日陰で乾燥させるグループに分けました。

焼きあがったのがこちら。 めっちゃきれいにできてないか????? 一周きれいにピエ(と言っていいのかわからない割れ)が出ております。 理想的には本来はこの割れから生地が出てきて横に少しあふれる感じになるのですが,いったん今回は良しとしましょう。

ちなみにもちろん失敗もしておりまして…

見た目はよさそうだけど均一にピエが入らなかった

温度高杉+乾燥不足?

考察

とりあえずある程度はうまくいきましたね。 この要因として,やはりしっかり乾燥させた点が大きいと思います。特に,直射日光が当たってた生地に感じては表面が割れることもなくうまく焼けた印象。天気は大事ですね。 また,ピエから横に膨らまない問題ですが,これは単純にマカロンの直径が小さい⇒中の生地が少ない,という点が考えられます。今度はもうちょっと大きめに作ってみようかな。

まとめとコツ

  • よく乾燥させる。とてもよく乾燥させる。
  • 魚焼きグリルの最低火力ぐらいで焼く。温度が高いとすぐ割れる。
  • 形がいい感じに焼きあがるかは焼き始めた最初の3分ぐらいで決まる印象。火加減調整がんばる。
  • そもそも魚焼きグリルで焼くものではない。

いかがでしたか!?!? 皆さんもおうちでマカロンを作って 「質」を追い求めていきましょう!

Brainf**kでモーター制御したい(その3)なんちゃってPI制御

こんにちは。さばねっこです。

今回も前回に引き続きBrainfuckマイコンで遊んでいきます。

これまでの記事はこちら

sabanekko2.hatenablog.com

sabanekko2.hatenablog.com

相変わらずコードとかはこの辺に上がってます。

github.com

実行環境とか

前回まででマイコンとして動作させるのに必要になる最低限の機能は実装できました。

現状のSFRマップはこんな感じ。

ID 名称 r/w? 機能
0x00 NOP r/w -
0x01 TMR0_DIVR r/w timer0の分周比
0x02 TMR0L r timer0の0~7bit
0x03 TMR0H r timer0の8~15bit
0x04 PWM_DT r/w PWMのデッドタイム
0x05 PWM_PERIDO r/w PWMの周期
0x06 PWM1_DUTY r/w PWM1のON期間
0x07 PWM2_DUTY r/w PWM2のON期間
0x08 PWM3_DUTY r/w PWM3のON期間
0x09 ENC_DIVR r/w 直交エンコーダーカウンタの分周比
0x0A ENC r/w 直交エンコーダーのカウント値
0x0B~ STDIO r/w 標準入出力(シリアル通信)

ペリフェラルのブロック図的なのを書くとこんな感じ。

ペリフェラルブロック図

まあ細分は違いますが各レジスタの役割的にはこんな感じです。

前回と比較して、相補PWMを生成できるようになったので、デッドタイム調整用の PWM_DT 、エンコーダ入力を分周するパラメータの ENC_DIVR 、タイマー0の分周比を設定する TMR0_DIVR を追加しました。

モーター制御専用マイコンとして考えればこれくらいで十分ですね。

つぎにモータを回すためのドライバを設計していきます。

現在使用しているTangPrimer 25KにはPmod規格のピンソケットが搭載されていますが、これといい感じに適合するモータードライバが手元にないので設計していきます。

kicadで適当に設計して…

ゴミドライバ

適当に実装。

時間とやる気がなかったので本当に適当です。まあせいぜい1Aぐらいしか流れないのでいいかなって。

今回のモタドラはPWM1とPWM2で制御しているので、そのdutyレジスタを制御することでモーターを回すことができます。
やってみるとこんな感じ。

このときのコードはこんな感じ。

>>>>+. dead time 1
>
+++++ +++++
+++++ +++++
+++++ +++++
+++++ +++++
+++++ +++++
+++++ +++++. period 60

[
   //正転
   >
   +++++ +++++
   +++++ +++++. //pwm1 duty 20

   ----- -----
   ----- -----. //pwm1 duty 0
   
   //逆転
   >
   +++++ +++++
   +++++ +++++.//pwm2 duty 20

   ----- -----
   ----- -----.//pwm2 duty 0
   <<
]

ちゃんと正転・逆転できていてよさそうですね。

ちなみにこの実験の時にはCPUのクロック周波数をめっちゃ下げています。
duty比をインクリメント/デクリメントしてる間の時間をdelay代わりにするわけですね。

とまあこんな感じでモーター制御を実装するのに必要な環境は整いました。
続いて、実際に位置制御を実装してみましょう。

位置P制御の実装

PID制御の説明は省略させていただきます。

早速ですがコードを。

>>>>+. dead time 1

>
-.+ //PWM period:255(8bit系なので マイナス1 = 255)

+++++ +++++
[
   >+++++ +++++ ++
   >+++++ +++++ ++
   <<-
]
>.>. //pwm1 2 duty = 120(10x12)

>>++++. //エンコーダー分周比5
<<<

//PWM1は固定、PWM2のduty比を動かし制御
//無限ループ起点はPWM1_DUTY

[
   >>>>, //エンコーダー読み込み
   [
       -
       <<+<+
       >>> //エンコーダーの値をPWM2とPWM3に加算
   ]
   <<<.
   >
   [-<->] //PWM3の値をもとにPWM2を120にリセット
   <<
]

ループ部分をC言語で書き直してみるとこんな感じ。

//初期設定等は省略

int pwm2_duty_value = 120;
int pwm3_duty_value = 0;   //PWM3は使用しないが値の一時保存で利用

while(1){
    while(TMR0L != 0){}    //タイマー0の下位8ビットが0になるまで待機

    pwm3_duty_value += ENC;
    pwm2_duty_value += ENC;   //エンコーダーのPWM3_DUTYとPWM2DUTYに加算

    PWM2_DUTY = pwm2_duty_value; //PWM2のdutyを適用(.命令)

    pwm2_duty_value -= pwm3_duty_value; //Pゲインで加算された分を引き算
    pwm3_duty_value = 0;
}

要はエンコーダーの現在位置の値をPWM2のdutyに加算してるだけですね。

エンコーダーの正の回転方向とPWM2のduty比を大きくしたときの回転方向は逆向きなので、これだけで負のフィードバックとなり位置制御が実現できます。

実際の様子がこんな感じ。

いい感じに位置制御できてそうですね。

まあアンチワインドアップとかエンコーダー値がオーバーフローしたときの処理などは無いので回し過ぎると暴走するんですけどね。
その辺の処理はめんどくさ過ぎるので実装したくないです。

位置PI制御(概念)

P制御ができたら次はPI制御、と行きたいところですが、これが意外とめんどくさい。
I制御の実装には誤差の時間積分値が必要なわけですが、この計算をするためには、前回の演算処理をしてからどれだけ時間が経過したかを把握する必要があります。

今回は一定時間間隔でPI制御処理をすることでその辺をいい感じ()に実現しようと思います。

今回のBrainfuckマイコンには、現在時刻を知るために16bitのtimer0を実装しているので、無限ループの最初に、timer0の下位8bitが0になるまで待機、という処理を挟みます。

これにより一定時間間隔で計算処理を実行するわけですね。

実装はこんな感じ。

+++++
[
   >+++++ +++++ ++
   <-
]
>.  //timer0 の分周比は61(5*12 + 1)


>>>+. dead time

>
-.+ //period:255

+++++ +++++
[
   > +++++ +++++ ++
   > +++++ +++++ ++
   >>+++++
   <<<<-
]
>.
>.   //pwm1 2 duty = 120(10*12)
>>.  //エンコーダ分周比 = 51(10*5 + 1)

<<<


//PWM1は固定、PWM2のduty比を動かし制御
//無限ループ起点はPWM1_DUTY

[
   <<<<,[,] //TMR0の下位8bitが0になるまで待機
   >>>>
   
   >>>>,
   [
       -
       <<+<+++++ +  +  //Pゲイン=6   Iゲイン=1
       >>> //エンコーダーの値を7倍してPWM2に加算
   ]
   <<<.
   >
   [-<----- ->] //P要素で加算した分をPWM2_DUTYから減算
   <<
]

コードの概形はP制御の時と同じですが、ちょっと処理が追加されています。
今回はタイマー0の分周比を61としているので、制御周期は

$$ T_{\text{PERIOD}} = \frac{1}{500\text{kHz}} * (\text{TMR0_DIVR+1}) * 256 $$ $$ = \frac{1}{500\text{kHz}} * 61 * 256=31.232[ms] $$

となりますね。ちょっと低いですがまあ概念実証できればいいので。

ちょっとわかりずらすぎるのでループ部分をC言語で書き直してみるとこんな感じ。

//初期設定等は省略

int pwm2_duty_value = 120;
int pwm3_duty_value = 0;   //PWM3は使用しないが値の一時保存で利用
while(1){
    while(TMR0L != 0){}    //タイマー0の下位8ビットが0になるまで待機

    pwm3_duty_value += ENC;       //エンコーダーのPWM3_DUTYに保存
    pwm2_duty_value += ENC*(6+1); //Pゲイン=6,Iゲイン=1

    PWM2_DUTY = pwm2_duty_value; //PWM2のdutyを適用(.命令)

    pwm2_duty_value -= pwm3_duty_value * 6; //Pゲインで加算された分を引き算
    pwm3_duty_value = 0;
}

P制御の時はループ毎に現在のエンコーダーの値をデフォルトのduty比(120)に加算し、ループ終了時にまたデフォルト値に戻す、という処理を行っていました。

今回のPI制御では、エンコーダーの値に6+1倍したものをduty比に加算し、ループ終了時にエンコーダー値の6倍を減算しています。
これにより、加算した分と減算した差分の1倍分が積算され(つまり誤差の積分値となる)I制御が実現されます。

実際に動かしてみるとこんな感じ。

赤LEDは制御対象であるPWM2のduty値を表しています。

分かりずらいですが、軸を目標位置(原点)からずらした状態で固定していると、誤差が積算され、PWM2のduty比がどんどん増えていきます。
ちゃんとPI制御できてそうですね。


というわけで無事brainfuckでモーターを回し、PI制御を実装することができました。
ゲインとかは超適当ですが、処理としてはちゃんとできてそうなのでいいかな。

まあ本当はPID制御するべきなんでしょうけど、めんどくさいので今回はやりません。
一定時間間隔での制御は実装できているのであとはbrainfuckのコードを書くだけでできますしね。技術的面白さはそんなにないかーって…。

今回のbrainfuckマイコンの実装で初めてFPGAを触ってみましたが、結構面白ですねこれ。
もっと難しいものかと思っていたのですが、想像していたよりは敷居は低そうです。

ただ、まだ不安定な回路が生成されてしまったりするのでもっと勉強しないとですね…。

Brainf**kでモーター制御したい(その2)SFRの実装など

こんにちは。さばねっこです。

前回に引き続きBrainfuckに標準入出力の外の世界を見せるべくBrainfuckマイコンの実装をしていきます。

今回はマイコンの機能の要も言えるSFR(特殊機能レジスタ)の実装をしてみてたのでその紹介です。

※本記事でいうSFRは、マイコンのペリフェラルのパラメータが入るレジスタのことです。マイコンによって呼び方が違うかも。

前回の記事はこちら。

sabanekko2.hatenablog.com

SFRとは

さて、まずは前提の話を少々。

この記事を読む皆さんなら一度はマイコンでLチカぐらいはしたことがあるかと思いますが、Arduinoをはじめとする抽象度の高い開発環境を使っている方はあまりSFRの存在を意識したことがないかもしれません。

基本的に、マイコンにはSFRと呼ばれる特別なメモリが存在します。 例えばArduinoでLチカするときは以下のようなコードを書くと思います。

pinMode(LED_PIN,OUTPUT);
while(1){
    digitalWrite(LED_PIN,HIGH);
    delay(500);
    digitalWrite(LED_PIN,LOW);
    delay(500);
}

この pinMode とか digitalWrite って関数、中身どうなってるんでしょうね?

実はこの関数の中では、 DDRxレジスタとPORTxレジスタという、GPIOのモードや出力を設定するSFRに値を書き込んでいます(AVRの場合)。

SFRを直接いじるように書き直してみるとこんな感じ。
レジスタを叩く、とも言いますよね。

//今回はポートDの2番ピンでLチカするコードとする

DDRD = 0b00000100; //2番ピンを出力モードに設定
while(1){
    //
    PORTD = 0b00000100; //2番ピンをHIGH
    delay(500);
    PORTD = 0b00000000; //2番ピンをLOW
    delay(500);
}

GPIO以外にもタイマーやADCなどの各種ペリフェラルも、実はSFRに設定値を書き込むことで動作するようになっています。

SFRの実装方法

閑話休題、今回はBrainfuckでモーター制御するべく開発しているわけですが、モーターを回すためにはPWMの生成やエンコーダの読み込みが必要なわけです。
早速SFRの出番になりますね。
PWMのDUTY比を設定したりやエンコーダの値を読みだすようなSFRが欲しくなってきます。

しかし、Brainfuckには現在選択しているメモリに対してインクリメント、デクリメントする機能しかありません。 先ほどのコードで言うと

//本当はこう書きたい
while(1){
    PORTD = 0b00000100; //2番ピンをHIGH
    delay(500);
    PORTD = 0b00000000; //2番ピンをLOW
    delay(500);
}

//Brainfuckならこんな感じのことしかできない!! FUCK!!!!!
while(1){
    PORTD ++; //無関係な0,1番ピンもHIGHになる
    PORTD ++;
    PORTD ++;
    PORTD ++; //ここでようやく2番ピンがHIGH
    delay(500);

    PORTD --;
    PORTD --;
    PORTD --;
    PORTD --;
    delay(500);
}

というような形の実装しかできないわけです。うんちですね。

まあ処理が無駄に多くなるだけなら別に構わないのですが、目的の値にたどり着くまでに別の無関係な値を経由しなければならないのが極めてうんちなわけです。
上の例であれば全然関係ない0番ピン、1番ピンも一瞬HIGHになってしまいますよね。

それは困るのでBrainfuckの命令を拡大解釈してSFRを実装することにします。

Brainfuckには外部と情報をやり取りするための.と,という命令があります。

意味
. 現在選択されているアドレスの値を出力
, 現在選択されているアドレスに入力から読み込んで代入

この入出力先は一般的に標準入出力です。

この命令をこのように拡大解釈します。

意味
. 現在のアドレスと同じアドレスのSFRに現在選択されているアドレスの値を書き込み
, 現在のアドレスと同じアドレスのSFRから現在選択されているアドレスの値を書き込み

言葉だとよくわからない感じになってしまったので図にしますね。

通常のBrainfuckはこう。

ふつうのBrainfuck

どのアドレスであっても、.,命令は標準入出力とやり取りすることになります。

で、今回はこう。

超解釈Brainfuck

特定のアドレスは標準入出力ではなくSFRと紐づけされるわけですね。

このような実装をすることにより、先ほどのコードのように途中で0番ピンや1番ピンがHIGHになってしまうことはありません。
C言語風に書くとこんな感じでしょうか。

int ram[RAM_SIZE] = {0};

while(1){
    ram[0] ++;
    ram[0] ++;
    ram[0] ++;
    ram[0] ++;
    PORTD = ram[0]; //直接SFRは触らず.命令でメモリの値をSFRに書き込み

    delay(500);

    ram[0] --;
    ram[0] --;
    ram[0] --;
    ram[0] --;
    PORTD = ram[0];
    delay(500);
}

というわけで実際に実装してみました(唐突)。

今回のレジスタマップはこんな感じ。
モーター制御するうえで必要になりそうな基本的な機能を適当に乗せてみた感じです。

名称 機能
0 NOP -
1 TMR0L timer0の0~7bit
2 TMR0H timer0の8~15bit
3 PWM_PERIDO PWMの周期
4 PWM1_DUTY PWM1のON期間
5 PWM2_DUTY PWM2のON期間
6 PWM3_DUTY PWM3のON期間
7 ENC 直交エンコーダーのカウント値
8~ STDIO 標準入出力(シリアル通信)

まあこのレジスタマップはオレオレ仕様なので必要になり次第機能はどんどん追加できてしまいますね。
チート感はありますがこれがないと無理ゲーなのでゆるして。

PWM機能を使って電子ボタルをやってみるとこんな感じ。

わかりづらいですが、ボード上の緑のLEDがゆっくり点滅しています。
Brainfuckのコードはこんな感じ。

//3番地はPWM周期レジスタ
//4番地はPWMのdutyレジスタ

>>> //3番地まで移動
+++++ +++++ +++++ ++++++.  //周期を20に設定
>>

//以下無限ループ
+
[ 
    //だんだん明るくなる部分
    >
    +++++ +++++ +++++ ++++++
    [
        <<
        +. //一度加算するごとにdutyレジスタに書き込み
        >>-
    ]
    <<

    //だんだん暗くなる部分
    [
        -. //一度減算するごとにdutyレジスタに書き込み
    ]
    >
]

なんとなく増やしてみた。

コードはクッソ長いのでこちらにおいておきます。

github.com

このコード書くのクッソ大変だった。リアルに4時間ぐらいかかってしまったのでもう二度とやりたくない。

エンコーダの読み込みもできます。
PICでいうQEIモジュールみたいなものを実装した感じですね。

コードはこんな感じ。

https://github.com/sabanekko3/brainfuck_cpu_tang25k/blob/sabane/dev/brainfuck_decode/enc_test.bfgithub.com

まあエンコーダカウンタの値を読み込んでそれをDUTYレジスタに移動させているだけですね。

とまあこんな感じで無事SFRの実装ができました。
あとはP制御(できればPID)のコードを書いてモーターを回すだけですね。まあドライバがまだ届かないんだけど。

なんか今回の記事滅茶苦茶fuckfuck言ってますね。お目汚し失礼しました。 いや本当はBrainf**kって書きたいんだけどmarkdownの都合上 Brainf\*\*k みたいに書かないといけないのがめんどくさくって…。

続編はこちら

sabanekko2.hatenablog.com

Brainf**kでモーター制御したい(その1) CPU設計

こんにちは。さばねっこです。

突然ですが皆さん、次のコードをご覧ください。

+++++++++[>++++++++>+++++++++++>+++>+<<<<-]>.>++.+++++++..+++.
>+++++.<<+++++++++++++++.>.+++.------.--------.>+.>+.

実はこの記号の羅列はBrainfuckと呼ばれる、 + - > < [ ] . , のたった8つの命令で構成される難解プログラミング言語で記述された Hello World! を出力するコードです。

意味不明ですね。

とりあえずこちらのエミュレータに上のコードを貼り付けて実行してみてください。ちゃんとHello Worldできたでしょ?

naruyoko.github.io

まあ基本的なことはこのあたりの記事を読んでいただくのが速いと思います。 qiita.com

一見何にもできなさそうな言語ですが、チューリング完全なので理論上は何でもできるそうです。すごい。

さて、 ここまででBrainfuckのことは完ぺきに理解していただけたと思いますが(?)、残念ながら実用言語ではないため、先ほどのようにちょっとした文字列を表示する+α程度のことにしか使われていません。
かわいそう…。

というわけで(?)Brainfuckでモーターを制御してみようと思います。
皆さんも標準入出力に囚われ続けるBrainfuck君に翼を授けてあげたいですよね!そうだよね!!

ということでやっていきます。

あ、もちろんブラシ付きモーターですよ?ベクトル制御は流石にちょっと…。

FPGA上に実装する

さて、Brainfuck君に翼を授けるべく活動を始めたわけですが、当然生のBrainfuckで動かせるマイコンなんて存在しません。
まずはBrainfuckを動かすことのできるハードウェアを用意しなければなりませんね。

というわけでFPGA(Tang Primer 25K)上に生のBrainfuckをそのまま実行できるCPUを作りましょう。

実はBrainfuck CPUを実装している方はすでに幾人かいらっしゃるのでその方々のブログを参考にしつつ実装していきます。

というわけで適当に実装。

github.com

(verilogはつい最近始めたばかりで何もわからないので、コードレビューしていただける方がいたら本当にうれしいです。)

生のBrainfuckを動かす、とか言いましたが、さすがにソースコードの文字コードをそのまま入れているわけではなく、3bit幅の機械語に変換して書き込んでいます。

実際に動かしてみるとこんな感じ。

uartから出力バッファを垂れ流しているのでteraterm等でPCから確認できます。

動画では

--[----->+<]>----.[--->+<]>----.+++[->+++<]>++.
++++++++.+++++.-[->+++<]>-.>-[--->+<]>.-[----->+<]>-.++++++++.-[-->+<]>----..

という感じのコードを実行しており、 brainFUCK!! と表示されています。

コードはこちらのサイトで生成しました。

copy.sh

すごく便利。

ちなみに、CPU自作するならFPGAなんかに甘えずロジックICで作れ!、という派閥の皆さんもいらっしゃるかと思います。
が、一応このプロジェクトは最終的にモーターを制御することを目的としています。
Brainfuckとかいうバカ効率の悪い言語を使うとなると、それなりの周波数で動かさなければなりません。
ロジックICで全て作るのはいろいろ難しそう…と判断しFPGAを採用しました(本当はせっかく買ったFPGAをタンスの肥やしにしたくないだけ)。

ここからの展望

ひとまずBrainfuckの実行環境は実装できたのでここからはPWM等を出力したりエンコーダーのフィードバックを受け入れるSFRを実装しなければなりませんね。
また、そもそも手元にFPGAから使えるモタドラがないのでそれも用意しないといけません。めんどくさぁ。
そういえば、ループ命令を抜ける際にいちいち ] を探索しており滅茶苦茶効率が悪いのでそれもどうにかしたいです。

というかいい加減真面目に卒論書き始めないとやばいんじゃないですかね。やばいですね。

まあこんな感じでダラダラやっていく予定なので気長にお待ちいただけると幸いです。

参考文献

偉大なる先人の皆さん

primenumber.hatenadiary.jp

uzusayuu.hatenadiary.jp

その他

uartの送信部分はこのコードを丸パクリしました。

github.com

--

全然関係ないですがはてなブログのMarkdown編集モード便利ですね。なんで今まで使ってなかったんだろう。

続編はこちら

sabanekko2.hatenablog.com

sabanekko2.hatenablog.com

リニアモーターの改良とペンプロッタ化

 こんにちは。さばねっこです。

 今回は最近制作しているリニアモーターを使用したペンプロッタの詳細を書いていきたいと思います。

 リニアモーター本体に関する記事はこれ。

sabanekko2.hatenablog.com

 いわゆる Tubular linear motor と呼ばれるタイプのモーターで、名前の通りチューブの中に磁石が詰め込まれており、その外周部分に電機子コイルが設置されているタイプのモーターですね。構造が単純で自作徒にやさしいモーターです。

 

 今回はこのリニアモーターを利用してXYテーブルを作成し、ペンプロッタに応用してみました。

 

 

 設計データはこのリンクから見れるはずなのでよかったらグリグリして遊んで見てみてください。

 Autodesk Viewerというビューワーソフトにつながっています。スマホとかからでも見れるはず。

autode.sk

 

 まあリニアモーターユニットを2つ用意して接続しただけのシンプル構造なのでそんなに語ることは無いんですけどね。

 とはいえ前回の記事で紹介したバージョンのリニアモーターからかなりいろいろアップデートしているので、その辺も込みで紹介したいと思います。

 というかその辺の紹介が今回の記事を書く主なモチベーションだったりします。

リニアモーター本体の変更点

 前回とはほとんど別物と言っていいぐらい設計変更してあります。

駆動ユニット

 磁気的な構造は前回紹介したときと同じなのですが、このように小型のボビンを用意し、それらを嵌め合わせる形で三相分のコイルを設置する形に変更しました。

 こんな感じで一相ごとにコイルを巻いてあります。ボビンの右手側に小さなでっぱりがあると思いますが、このでっぱりが隣のボビン、もしくは固定ブロックにかみ合うことで固定されます。

 貧弱そうな固定法ですが、用途的にスラスト方向にしか大きな加重はかからないため、強度的な問題は発生しませんでした。

 このようにコイルを一相ごとに制作するとコイルを巻くのが結構楽になるんですよね。万一コイルが焼けてしまったときの交換も簡単です。

 

 そういえば前回の記事で書き忘れていたのですが、摺動部分にはテフロンチューブを使っています。

テフロンチューブによるリニアブッシュ

 強度や耐久性的には微妙ですが、簡単に作れて非磁性なのが魅力ですね。今回の用途であれば強度は十分でした。もっとハードな用途であればリニアガイドとかを使うべきですね。

 

 ちなみに、コイル周辺のねじはプラねじを使用しています。鉄製のねじだと界磁磁石に引き寄せられてしまい、コギングトルクが発生してしまいます。これだと制御がいろいろめんどくさくなってしまうので、偶々大量に持っていたプラねじを採用しました。まあ強度的にステンレスねじとかのほうが良いとは思います。

 

ペンの上下機構

 この記事唯一のペンプロッタ要素です(?)。

 ペンプロッタなので当然筆記具を搭載しないといけないわけですので、サーボとバネを使って簡単な筆記機構を作ってみました。

 

 紙にペンを押し付ける方向にバネがついてるので、多少紙面がデコボコしていても適切な筆圧で筆記できます。そして筆記しないときはサーボでペン持ち上げるわけです。

 

制御について

位置センサー

 モーターの制御に関してもいろいろ改良しました。

 一番大きいのはホールセンサ式エンコーダの採用ですね。

センサー部分(きたない)

 二つのリニアホールセンサにより界磁を読み取り、その値から自己位置を計測します。このホールセンサは電気角でちょうど90度ずれるように配置してあるため、測定値をarctanすれば角度を得られるというわけですね。

 まあロボマスモーターとやっていることは同じですね。

 前回の記事を書いた段階ではワイヤー式のエンコーダーを使用していましたが、時々滑って誤差が出るので微妙でした。その点この方式は滑りとは無縁なので素晴らしいですね。

 

 ちなみにホールセンサはこちらを使用しました。

https://www.mouser.jp/ProductDetail/Diodes-Incorporated/AH49HZ3-G1?qs=5V6w%252Be2aIqad55YB4NdxpA%3D%3D&srsltid=AfmBOornpWSNJvHpn3LRJx_qWyDtEYnPKrJ6AuIyxgV8OBXB1GKhfsfP

 かなり感度の低いセンサーですが、強力なネオジム磁石の界磁を読むのにはちょうど良い感じでした。リニアホールセンサの中では比較的安い部類なのもgoodですね。というかほかの素子が高すぎる。

 

 ちなみにセンサー部分のGHコネクタはアロンアルファで固定しています。GHコネクタは固定の爪がコネクタ内に完全に隠れるのでこういう用途で非常に便利。

 

ドライバの制御

 実際にモーターを駆動するドライバには前回記事で紹介したように改造したB-G431B-ESC1を使用しています。

sabanekko2.hatenablog.com

 ファームはこちらのリポジトリに上げてあります。

github.com

 僕が書いてる部分はUserLibフォルダに入ってます。

 制御している内容は去年ロボマスモーターの制御をしていたころと同じですが、いろいろ無駄な部分を削っているのでだいぶマシ(当社比)になってるはずです。

 PWM周波数は18kHzで、電流・位置制御も同じく18kHzとなっています。ただ計算時間的にはまだまだ余裕(計算時間は1サイクル4.5usぐらい)なのでもっと上げてもいいかもしれませんね。

 位置指令はCAN通信を通じて受け取ります。ゲイン調整とかも外部からできるようになっています。やる気が足りていないので機能はめっちゃシンプルです。

 

 というか僕のクソコードでもこの計算時間納めてくれるcordic君マジ優秀。今回は自己位置を取得するためにatan2を毎サイクル計算する設計になっているのですが、CPUが別の処理をしている間にatan2の計算をcordicモジュールがやってくれます。cmsis dspライブラリとかの力もあると思いますが、演算が速すぎて書いた本人もびっくりしてる。

PCからの位置指令送信

 今回のペンプロッタはPCから位置指令を送信し、その位置にリニアモータが追従する方式になっています。というわけで位置指令を送る用のペイント風ソフトもPython(tkinter)で作ってみました。

github.com

 pythonはガチ初心者なので勘弁してください。

 

 ドラッグで線を引けるわけですが、その間はペン先が下がり、実際に線を筆記することができます。

 位置指令はUSB-CAN変換器を通してCANに変換され、リニアモータードライバやサーボドライバに指令が行きます。

 

 ちなみにUSB-CAN変換基板やサーボドライバは学生ロボコンで制作したものをそのまま流用しています。汎用性高めに作っておいてよかった。

学ロボと同じ基板モジュール

 もちろんレールユニットも流用しているのでワンタッチで基板交換できます。

お知らせ

 ここまで紹介してきたペンプロッタですが、今度11月09日、10日に開催されるNT名古屋に出展する予定ですので、良かったら遊びに来てください!!

wiki.nicotech.jp

 ペンプロッタの筆記体験(?)とかできたらなと思っています。また昨年制作したPCBアキシャルフラックスモーターも持っていく予定(壊れなければ)そちらも見ていただければ嬉しいなと…。

 工作趣味人の素晴らしい作品がたくさん見られる素晴らしいイベントなので、僕に興味がなくてもぜひお立ち寄りいただければ嬉しいなと思います…!


 

ADCできる端子の生えたB-G431B-ESC、あったらいいな~~

 どうもこんにちは。さばねっこです。

 今回は完全に備忘録記事です。まあ生存報告もかねて…。

 

 現在私はリニアモーターを制作しており、そのドライバとしてB-G431B-ESCを使用しています。

https://www.mouser.jp/ProductDetail/STMicroelectronics/B-G431B-ESC1?qs=%252B6g0mu59x7KUfhaFDGurZQ%3D%3D

 3000円強でこれは破格すぎる。

 このドライバはこのようにとても小型で最大40A(強制空冷時)、そしてさらにデバッガも搭載されている非常に優秀なモータードライバです。

 サイズはデバッガ込みで41x31mm程度。クッソ小さい。

 しかもデバッガ部分はmouse bitesでつながっているため手軽に分離することができ、その場合は驚異の41x18mm程度になります。

 マイコンはおなじみSTM32G431ですので、STM32マイコンの開発を行ったことがある方であれば手軽に開発を始められるのではないでしょうか。もちろん公式が公開しているファームを利用すればすぐにモーターを回すことができますし、simpleFOCライブラリによる開発もできるので非常に便利です。まあ僕は使ったことないのでよくわかりませんが…。pizac氏の解説がかなり分かりやすいので参考になるのではないでしょうか。

pizac.hatenablog.com

 

 なんかこういうのを見るとわざわざドライバを自作するのが馬鹿らしくなってきてしまいますね…。

 

 

 このように、このドライバはBLDCを回したいだけの人から制御の勉強をしっかりやりたい人までおすすめできる素晴らしいデバイスなのですが、一つ残念な点(オタク並感)があります。

 なんとADCが使える端子が出ていません!ロボマスモーター回せないじゃないか!!!

 みなさんご存じ(?)のようにM2006、M3508といったいわゆるロボマスモーターのエンコーダーはリニアホールセンサを用いたアナログ出力のものなので、それを制御するためにはADC(2ch分)の存在が不可欠となります。

 というかロボマスモーター云々以前に、私が現在制作しているリニアモーターでもリニアホールセンサを用いたエンコーダーを利用しようとしているので使えないと普通に困るんですよね。

我がリニアモータのリニアホールセンサユニット(笑)



 というわけでADCできるようにプチ改造しますよ、というわけです。

 

 まずこのへんの回路図や現物を見ながらどこに接続するか考えます。

https://www.digikey.jp/reference-designs/ja/motor-control/2840

 TP3とマイコン横の謎の長方形空パッドがちょうど使えそうですね。

 字が見づらすぎますね。すみません。

 それぞれ回路図上では検流用オペアンプの出力となっていますが、設定次第では普通のADCピンとして割り当てられるのでこちらを使用します。

 まあマイコンの足に直接繋いでも良いのですがさすがにめんどくさい、というか家にあるUEWでは太すぎて少々厳しいです。

 

完成の図

 というわけであとは適当にコネクタを付けてそこまで配線して完成。

 コネクタがフラックスなどでめっちゃ汚れてますが気にしてはいけません。

 いろいろ考えるのがめんどくさかったのでGHコネクタを基板上にアロンアルファで接着しました。

 

 こんな具合で改造したB-G431B-ESCを使用して無事リニアモーターを動かすことができました。めでたしめでたし。

 

  さすがに誰得記事すぎるので参考になるかもしれない画像を投下。

 CUBE MXはこんな感じで設定しました(回路図読んだだけ)。

CUBE MXの設定例

 ADC2_IN3のところは一応設定してあるだけで未使用です。

オペアンプの設定例

 上の設定だとオペアンプの出力をマイコン内部で直接ADCに接続することができます。

 ちなみにADCの値を電流に換算する計算はこんな感じ。


float adc_to_current(uint16_t adc_val){
	constexpr float rl = (2.2f*1.5f)/(2.2f+1.5f); //基板上の並列接続された抵抗の合成抵抗
	constexpr float v_bias = rl/(rl+22.0f)*3.3f;  //バイアス電圧
	constexpr float amp_gain_inv = 1.0f/7.0f; //cube mxで設定するオペアンプのゲインの逆数
	constexpr float shant_r_inv = 1.0f/0.005f;         //シャント抵抗の値
	float v = adc_val*3.3/static_cast<float>(1<<12);

	return ((amp_gain_inv + 1.0f)*v_bias - amp_gain_inv*v)*shant_r_inv;
}
  

 ※コメントに大嘘が書いてありましたので訂正しました。すみません。(基板上の分圧抵抗の比→基板上の並列接続された抵抗の合成抵抗)

どうやって導出したのか一切記憶がありません。まあでも基本的には差動増幅回路のなので、それをベースにバイアス部分をいい感じにしたら上のような計算になると思います。間違ってるかもしれんけど。

 

 以上です。誰得記事にお付き合いいただきありがとうございました。