Arduino歴3ヶ月の初心者が安価で高機能なマイコンESP32を使ってスマホから操作できるラジコンを作った時に躓いた点などを備忘録兼共有用に時系列順で書き残す
経緯
とある所属しているロボコン団体のメンバー募集兼遊びとしてラジコンを作る企画が浮上した
そのときはESP32っていうwifi飛ばせるマイコンがあるらしいよという話だけを聞いのだが個人的にも興味が湧いたので企画に参加することにした
初期の最終目標
- スマホで操作
- すぐ動かせる
- 電源は1つ 電池
- 簡単に組み立てられる
- 予算は一機¥3000
ハード
一般的なホビー用DCモーターx2の二輪駆動のラジコン
チームにいた有識者はESPはノイズに弱いと繰り返して別のマイコンを使うか電源をモーターとESPで分けることを薦めてきた
ブラシ付きモーターは回る時に接点の切り替えからノイズが発生する
なお私はなんらかのフィルターをつけてるもしくは何かしらで絶縁して対処すればいいと考えていたのでその程度で引き返すことは考えていなかった
最初の目標はESPはUSBから電源を供給した状態で別電源を使ってモーターを回すこと
「とりあえず動く」までに購入した部品
ESP32-DevKitC-32E
主役
- ESP32-DevKitC-32E ¥1230 秋月電子
ブレッドボード 301 (没)
チームの手元にあった
27列
- EIC-301 ¥200 秋月電子
モーター タミヤダブルギアボックス (没)
チームの手元にあった
調べたところ¥780
モーター電圧は3V以下
二輪ロボットプラットフォーム (高すぎ没)
高すぎ
- 二輪ロボットプラットフォーム ¥1480 秋月電子
モータードライバー TB67H450 (没)
チームで最初に買いに行ったモータードライバーがこれ
- TB67H450 ¥320 秋月電子
一般的な1回路のモータードライバーだ
選んだ理由はよく分かっていない
端子は8本
IN端子x2
OUT端子x2
モーター電源 VM
モーター電流設定 Vref
モーター電流検出 RS
GND
IN端子2本はESPに繋ぎ電流関連は使わないので適宜GND等に接続した
回転速度はIN端子から直接PWMで制御すれば良い
私はタミヤのギアボックスとESP32Eで試作した
電源を3Vに設定して使用
結果動かなかった
モータードライバーのIN端子を直接ESPの電源に繋げるなどして確かめたが動かない
故障等疑ってもう一つ買ってきたのだがここで原因に気づいた
「・モータ電源電圧(VM):4.5V~44V」
完全に敗北した
そもそもこのドライバーではタミヤのギアボックスのモーターを直接制御することなどできなかったのである
チームの別のメンバーはこのドライバーで二輪ロボットプラットフォームのモーターを動かすことに成功していた
定格4.5VのモーターだったようでUSBの電源をそのまま流してやったところ元気に回ったそうだ
モータードライバー DRV8832 (没)
モーター電圧範囲が3Vを含むようなモータードライバーを個人で探して見つけたのがこれ
- DRV8832 ¥230 秋月電子
今度こそ行けるだろ&前のより¥90安いと作る前から勝手に舞い上がっていたわけだがこのドライバーが割と曲者だった
端子は10本
IN端子x2
OUT端子x2
モーター電源 VCC
モーター電圧設定 Vset
モーター電圧設定用定電圧出力 Vref
モーター電流設定 ISENSE
障害通知 FAULTn
IN端子があって何故モーターの電圧設定の端子があるのか
それはこのドライバーがIN端子からのPWM入力に対応していないからである
どうやら中にPWM生成のロジックが入っているようでVsetを参照してパルスを生成するとのこと
このVsetもPWMに対応していないと書いてあったがこれはESPのアナログ出力でなんとかできるだろう
テスターで確認したところVrefからは常に1.28Vほどが出ていた
これを抵抗等で分圧してVsetに入れるとその4倍の電圧がOUT端子から出てくるということだそう
乾電池直接繋げて即動かすと言う使い方には適していそうだが少なくとも今回の目的には合わない
かなり謎な仕様だなと思いつつ安いしこれでどうにかしたいところ
電流設定等いらない端子は適宜GND等にVsetをそのままVrefに繋げて
IN端子からESPのIO出力3.3Vをそのまま流したところモーターがぴくっと動いた
障害通知端子を確認したところGNDと通電していた
動いたのは大きかったが結局原因は分からず私のやる気は一度ここで尽きた
モータードライバー DRV8835 (可)
その後のチームの集まりで買い出しに行ったときにメンバーがノリで手に取った一品
ホームページで確認したところどうやら2回路入でモーター電圧等も用途に合っている
変な仕様のドライバー2個使うより安いしとりあえず一個買ってみるかと買った理由もほぼノリ
- DRV8835 ¥450 秋月電子
端子は12本
IN端子x2x2
OUT端子x2x2
ドライバー電源 Vcc
モーター電源 VM
モード設定 MODE
GND
モード設定というのはこのドライバーはDCモーターモードとステッピングモーターモードがあって
ステッピングモーターモードにすると位相制御が直でできるようだ
一番最初のドライバーで作った回路を少し変えてESPのプログラムもそのまま試したところなんとあっさり動いてしまった
二輪ロボットプラットフォームのモーターも動いた
ブレッドボード 801
新しく購入したドライバーDIPであるためブレッドボードの中央の溝を跨ぐ形でしか配置できず手元の27列ブレッドボード1枚では足りない
同じ¥200で購入できる30列のブレッドボードがあったので購入した
- BB-801 ¥200 秋月電子
モーター ROB-13302
DRV8835を買いに行った足で隣の千石電商に寄ったところ
二輪ロボットプラットフォームのギアボックス付きモーターに似たものが2個セットで¥600で売っていた
タミヤのダブルギアボックスが¥780だったのでこれは安いということで購入した
定格4.5V
- ROB-13302 ¥600 千石電商
電池BOX
- スイッチ付き電池BOX単3x3 ¥90 秋月電子
モータードライバー TC78H653FTG
これは私が秋月電子のページをぶらぶらしていたところ見つけたもの
DRV8835でもいいのだが2回路入りで驚きの¥200
即買った
- TC78H653FTG ¥200 秋月電子
端子は16本
IN端子x2x2
OUT端子x2x2
モーター電源 VM x2
モード設定 LARGE MODE STBY
GND x3
ドライバの電源はモーター電源から供給される
LARGEは電流を多く消費するモーターを使う場合にIN,OUT2組を並列にして使う場合の設定
MODEはDRV8835と同じくステッピングモーターのためのもの
STBYはGNDに落とすことでドライバーをスリープ状態にすることができる
これだけ詰め込んで¥200である
今までのドライバーを買った金はなんだったのだろうか
モード設定の端子を適宜落とすなり上げるなりして
他はDRV8835からほぼ配線を変更することなく置き換えることができた
ただしこのドライバー基盤の上の方に大きめのチップコンデンサが載っていて
ESPにギリギリまで近づけて配置した場合ESPのアンテナにコンデンサが干渉してしまう
さらに端子の識別文字が裏面に印刷されていて一度ブレッドボードに刺すと非常に配線が分かりにくくなる
この二つの問題点は基盤に足をつける際に表裏返すことで解決した
ピンヘッダーのプラスチック部の高さがチップコンデンサよりも高くブレッドボードに刺さりきらないこともないうえ識別文字も見えるため配線がわかりやすい
ランド部はスルーホール加工のため表面よりもハンダ付けの難易度は多少上がるが十分に機能する
モータードライバーはこれで確定
電源の統一
この時点でモーターのノイズが原因でESPが不安定になることは確認できなかったので電源の統一に取り組んだ
ESPの電源供給は3通り
- USB給電
- 5V端子から5+α~12V給電
- 3.3V端子からきっかり3.3V給電
USB給電は今回電池BOXを用意してしまったうえコストカットの面からもよろしくないので除外する
電池切れの定義が電池一本の電圧が1Vになった時点として電池BOXからの電圧は3.0~4.5V
5V給電にせよ3.3V給電にせよ電源を昇圧もしくは昇降圧しなければならない
5V端子については個人で確認したところ給電してもESPの電源LEDが光らず動作しているのかわからなかった
よって3.3V給電にすることにした
ESPの3.3Vでの消費電流が調べたところ様々な情報があったが平均したところ最高で500mAほどだった
選んだDCDCコンバーターがこちら
- XC9306使用3.3VDCDCコンバーター ¥350 秋月電子
入力5Vで最大900mAまで供給できるらしい
ただこれは基盤に直に半田付けした際の数値でありピンヘッダーを使うと精々500mAというところ
要件は満たしているのでこれでなんとかなりそうだ
部品確定
以上ESPとモーターは別電源でモーターを動かすところまでたどり着いた
- ESP32-DevKitC-32E ¥1230 秋月電子
- モータードライバー TC78H653FTG ¥200 秋月電子
- ギアボックス付きモーター ROB-13302 ¥600 千石電商
- ブレッドボード BB-801 ¥200 秋月電子
- スイッチ付き電池BOX単3x3 ¥90 秋月電子
- XC9306使用3.3VDCDCコンバーター ¥350 秋月電子
計¥2670で収まった
ブレッドボードの配線はこんな感じ
左の電源ラインを4.5Vに接続すると動く
ソフト
メンバーにもコードを書いていた人がいるようだが個人で完成まで漕ぎ着けたのでそれを説明する
モーター速度制御
ledcWriteで普通にPWMする
クライアントと通信
ESP32は無線インターフェイスとしてWifiとBluetoothを備えている
ESPとクライアントで通信する一般的な方法は2通り
Blynkというアプリを使う方法とESPとクライアントをWifiで接続してブラウザを利用して情報をやり取りする方法である
Blynkはアプリのインストールやトークンの取得などその場ですぐ使うことは難しいので今回はWifi接続を選択する
Wifi接続もまた2通り
ESPとクライアントを同じWifiに接続して通信する方法とESPをサーバーにして直接Wifiを飛ばしそれにクライアントが接続する方法(softAP)である
すぐ使えるという観点から後者の方が今回の目的に合っているので後者のsoftAPを選択する
下記リンクを参考にsoftAPとhttpRequestでラジコンを作った
これでも遊んでいて十分楽しいのだがリアルタイムで制御ができない
折角だからもっと操作性の良いものを作りたい
WebSocket
さてここからが本題である
WebSocketを使ってリアルタイム通信すればだいぶ良くなるのではないか
ESP側
有志による優秀なライブラリがあるのでそれを使わせていただく
Async Web Serverと名前がついているがWebSocketを扱うプラグインが同梱されている
まずはこれとその依存ライブラリ(AsyncTCP)をインストールする
ライブラリの使用方法はドキュメントに記載されている
WebSocketサンプル
コンソールからWebSocket飛ばすとつながる
const IPAddress ip(192,168,1,1);
const IPAddress subnet(255,255,255,0);
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len){
switch(type){
case WS_EVT_CONNECT:
Serial.printf("ws[%u] connect: %s\n", client->id(), client->remoteIP().toString().c_str());
client->printf("id: %u\nip: %s\n", client->id(), client->remoteIP().toString().c_str());
client->ping();
break;
case WS_EVT_DISCONNECT:
Serial.printf("ws[%u] disconnect\n", client->id());
break;
case WS_EVT_ERROR:
Serial.printf("ws[%u] error(%u): %s\n", client->id(), *((uint16_t*)arg), (char*)data);
break;
case WS_EVT_PONG:
Serial.printf("ws[%u] pong\n", client->id());
break;
case WS_EVT_DATA:
{
AwsFrameInfo *info=(AwsFrameInfo*)arg;
if(info->final && info->index==0 && info->len==len && info->opcode==WS_TEXT){
data[len]=0;
String str=(char*)data;
Serial.printf("ws[%u] text-msg[%llu]: %s\n", client->id(), info->len, str.c_str());
ws.printfAll("%s\n",str.c_str());
}
}
break;
}
}
void setup(){
Serial.begin(115200);
delay(1000);
WiFi.mode(WIFI_AP_STA);
WiFi.softAP(ssid,pass);
delay(100);
WiFi.softAPConfig(ip,ip,subnet);
Serial.printf("SSID: %s\nPASS: %s\nAPIP: %s\n",ssid,pass,WiFi.softAPIP().toString().c_str());
ws.onEvent(onEvent);
server.addHandler(&ws);
server.begin();
Serial.println("server started");
}
void loop(){
ws.cleanupClients();
}
クライアント用HTML
こっちは一般的なWebSocket
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
</head>
<body>
<pre id="log">Connecting…</pre>
<script>
'use strict';
let ws,ws_send=()=>console.log('uninitialized'),recieved={};
const ws_init=()=>{
console.log('ws_init');
ws=new WebSocket(`ws://${window.location.hostname}/ws`);
ws.onopen=e=>{
log.textContent='Opened :)';console.log('opened');
ws.send('Hello');
};
ws.onclose=e=>{
log.textContent='Closed :(';console.log('closed');
setTimeout(ws_init,2000);
};
ws.onmessage=e=>{
log.textContent+=`${e.data}\n`;
};
ws.onerror=e=>{
console.log(e);
};
};
window.onload=ws_init;
</script>
</body>
</html>
ジョイスティック操作にした
こちらを参考にさせていただいた
/*
stick=HTMLElement
stick.children[0]=HTMLElement
stick_stat=pressed?true:false
*/
window.onpointermove=e=>{
if(!stick_stat)return;
const stick_style=stick.getBoundingClientRect();
let pos=[
(e.clientX-stick_style.left-stick_style.width*.5)||0,
(e.clientY-stick_style.top-stick_style.height*.5)||0
],l=Math.sqrt(pos[0]*pos[0]+pos[1]*pos[1]);
pos=pos.map(x=>x*Math.min(stick_style.width*.5,l)/l);
stick.children[0].style.transform=`translate(-50%,-50%)translate(${pos.join('px,')}px)`;
if(timer)return;
//https://openrtm.org/openrtm/sites/default/files/6357/171108-04.pdf
pos=[Math.atan2(...pos)-Math.PI*.75,Math.min(1,l/stick_style.width*2)];
ws_send([Math.cos(pos[0])*pos[1],Math.sin(pos[0])*pos[1]]);
timer=setTimeout(()=>timer=0,100);
};
その他
ESPのString.toInt()が負の数に対応していないらしく送信時はオフセットをつけた
文字列で送信しているため切り出しを楽にするため0paddingをした
課題
websocketの実装を始めたのが8/9ジョイスティックまで完成したのが8/11なので動作確認があまりできていない
電池電圧が3.6Vあたりまで低下してくるとESPの電源供給が怪しくなってくる
ESPが発振してDCDCコンバーターが熱くなる
やはりコンバーターの許容電流が足りないのかもしれない
8/13追記
ESPの起動時の大食いが原因であることがわかった
電池と並列に100μFの電解コンデンサを挟んだところだいぶマシになった
補助電源としての役割の方が大きいので単純に容量が大きい方がいいとは思う
今手元に100μF以外ないので今後検証する
→470μFで安定した
おわり
今回のプロジェクトでできたものはここに置いてある
今後も改良を続けたい