過去の記事でPS3コントローラとESP32でBLE通信をやりました。
最近になって3Dプリンタを使える機会があったので、N番煎じになりますが「一回ミニ四駆ラジコン作ってみるか」と思い作ってみました。
割と試行錯誤しながら作った結果、いくつか失敗したところもあったのでそれも含めて記事にしようと思った次第です。
では、始めます。
1:設計コンセプト
まずは作る上でどのようなコンセプトにするかを決めました。ブレッドボード上での簡単な回路実験などから、今回は以下のようにコンセプトを決めました。
1:車体自体(ミニ四駆で言うシャーシ部分)を3Dプリンタで自作する
2:できる限り汎用的な設計にする
3:電池で動くようにする
これらのコンセプトを元に作っていくようにしました。
2:回路設計とプログラム
コンセプトは決まったので、まずは電池とマイコンでモータを制御する回路を実験しつつ設計していきました。
当初はモータを1つにしサーボモータを動かして前輪をステアリングさせることを考えていましたが、色々と難しそうだったので「モータ2つでそれぞれ左右のタイヤを制御する」という方式を採用することにしました。
ブレッドボード上で色々と実験した結果、使用する部品と回路、プログラムは以下のようになりました。
- 回路部品一覧
部品名 | 個数 | 備考 |
---|---|---|
ATOM Lite | 1 | マイコン |
AE-XCL102D503CR-G | 1 | 5V出力昇圧DCDCコンバータ |
DRV8835 モータドライバモジュール | 1 | モータドライバ |
ユニバーサル基板AE-D1 | 1 | ユニバーサル基板 |
スライドスイッチ SS-12D00G3 | 1 | 電源スイッチ |
ピンヘッダ 1×40 | 1 | ATOM Lite固定用 |
DCモータ | 2 | ノーマルモータ |
単4電池ボックス(2本) | 1 | 電池ボックス |
XHコネクタ2Pセット | 1 | 電源用コネクタ |
EHコネクタ2Pセット | 2 | モータ用コネクタ |
- 回路図
- プログラム
・M5Atom_ps3_rc.ino
/** * PS3コントローラでモータ制御 */ #include "M5Atom.h" #include <Servo.h> #include <Ps3Controller.h> #include "Drv8835.h" /* ピン定義 */ const int A_IN_1 = 22; const int A_IN_2 = 19; const int B_IN_1 = 33; const int B_IN_2 = 23; /* チャンネルの定義 */ const int A_IN_1_CHANNEL = 1; const int A_IN_2_CHANNEL = 2; const int B_IN_1_CHANNEL = 3; const int B_IN_2_CHANNEL = 4; const int SPEED_VALUE = 255; // PWMで入力する値(MAXは255) const int LED_BRIGHTNESS = 10; // LEDの明るさ const int DELAY_TIME = 200; // 待ち時間 /* 状態 */ const int CAR_COAST = 0; // 空転 const int CAR_FORWARD = 1; // 前進 const int CAR_REVERSE = 2; // 後進 const int CAR_RIGHT_TURN = 3; // 右回り const int CAR_LEFT_TURN = 4; // 左回り const int CAR_BRAKE = 5; // ブレーキ Drv8835 motor = Drv8835(); // モータドライバ void setup() { // 本体初期化(UART有効, I2C無効, LED有効) M5.begin(true, false, true); Ps3.begin("xx:xx:xx:xx:xx:xx"); // M5AtomのMacアドレス Serial.begin(115200); showMsgLed("Pairing Ready...", 255, 0, 0); // LED赤色 // ペアリング待ち while (!Ps3.isConnected()){ delay(1000); } // モータドライバの準備 motor.setAin(A_IN_1, A_IN_2, A_IN_1_CHANNEL, A_IN_2_CHANNEL); motor.setBin(B_IN_1, B_IN_2, B_IN_1_CHANNEL, B_IN_2_CHANNEL); // ペアリングできたらLEDを青に showMsgLed("Pairing OK", 0, 0, 255); Serial.println("--- setup end ---"); } void loop() { M5.update(); //本体のボタン状態更新 // 車の状態を取得 int car_status = getCarStatus(); if (Ps3.data.button.circle) { // 〇ボタン押下時(前進) Serial.println("push Circle"); if (car_status == CAR_REVERSE || car_status == CAR_RIGHT_TURN || car_status == CAR_LEFT_TURN) { // 前進以外でモーターが動いている時は一度全部のモータを止める motor.brakeA(); motor.brakeB(); delay(DELAY_TIME); } // 前進 motor.forwardA(SPEED_VALUE); motor.forwardB(SPEED_VALUE); } else if (Ps3.data.button.r1) { // R1ボタン押下時(右回り) Serial.println("push R1"); if (car_status == CAR_FORWARD || car_status == CAR_REVERSE || car_status == CAR_LEFT_TURN) { // 右回り以外でモーターが動いている時は一度全部のモータを止める motor.brakeA(); motor.brakeB(); delay(DELAY_TIME); } // 右回り motor.forwardA(SPEED_VALUE); motor.coastB(); } else if (Ps3.data.button.l1) { // L1ボタン押下時(左回り) Serial.println("push L1"); if (car_status == CAR_FORWARD || car_status == CAR_REVERSE || car_status == CAR_RIGHT_TURN) { // 左回り以外でモーターが動いている時は一度全部のモータを止める motor.brakeA(); motor.brakeB(); delay(DELAY_TIME); } // 左回り motor.coastA(); motor.forwardB(SPEED_VALUE); } else if (Ps3.data.button.triangle){ // △ボタン押下時(逆転) Serial.println("push Triangle"); if (car_status == CAR_FORWARD || car_status == CAR_RIGHT_TURN || car_status == CAR_LEFT_TURN) { // 逆転以外でモーターが動いている時は一度全部のモータを止める motor.brakeA(); motor.brakeB(); delay(DELAY_TIME); } // 逆転 motor.reverseA(SPEED_VALUE); motor.reverseB(SPEED_VALUE); } else if (Ps3.data.button.cross) { // ×ボタン押下時(ブレーキ) Serial.println("push Cross"); motor.brakeA(); motor.brakeB(); } else { // ボタンを押していない時は空転 motor.coastA(); motor.coastB(); } delay(100); } // Serial文字列の送信とLEDの色を変更する関数 void showMsgLed(String s, uint8_t r, uint8_t g, uint8_t b) { M5.dis.setBrightness(LED_BRIGHTNESS); // LEDの光量(0-255) M5.dis.drawpix(0, getDispColor(r, g, b)); Serial.println(s); } // FastLEDに設定する色を取得する関数 CRGB getDispColor(uint8_t r, uint8_t g, uint8_t b) { return (CRGB)((r << 16) | (g << 8) | b); } // 車の状態を取得する関数 int getCarStatus() { int statusA = motor.getStatusA(); int statusB = motor.getStatusB(); if (statusA == Drv8835::FORWARD && statusB == Drv8835::FORWARD) { // A:正転、B:正転=前進 return CAR_FORWARD; } else if (statusA == Drv8835::REVERSE && statusB == Drv8835::REVERSE) { // A:逆転、B:逆転=逆転 return CAR_REVERSE; } else if (statusA == Drv8835::FORWARD && statusB == Drv8835::COAST) { // A:正転、B:逆転=右回り return CAR_RIGHT_TURN; } else if (statusA == Drv8835::COAST && statusB == Drv8835::FORWARD) { // A:逆転、B:正転=左回り return CAR_LEFT_TURN; } else if (statusA == Drv8835::BRAKE && statusB == Drv8835::BRAKE) { // A:ブレーキ、B:ブレーキ=ブレーキ return CAR_BRAKE; } else { // 上記以外:空転 return CAR_COAST; } }
・Drv8835.h
/** * DRV8835モータドライバのクラス */ class Drv8835 { public: static const int COAST = 0; static const int FORWARD = 1; static const int REVERSE = 2; static const int BRAKE = 3; Drv8835(); void setAin(int in1, int in2, int c_in1, int c_in2); void setBin(int in1, int in2, int c_in1, int c_in2); void forwardA(uint32_t pwm); void forwardB(uint32_t pwm); void reverseA(uint32_t pwm); void reverseB(uint32_t pwm); void brakeA(); void brakeB(); void coastA(); void coastB(); int getStatusA(); int getStatusB(); private: const int LEDC_TIMER_BIT = 8; // PWMの範囲(8bitなら0〜255、10bitなら0〜1023) const int LEDC_BASE_FREQ = 490; // 周波数(Hz) const int VALUE_MAX = 255; // PWMの最大値 int a_status = COAST; int a_in1 = 0, a_in2 = 0; int a_c_in1 = 0, a_c_in2 = 0; int b_status = COAST; int b_in1 = 0, b_in2 = 0; int b_c_in1 = 0, b_c_in2 = 0; }; Drv8835::Drv8835() { // コンストラクタ } void Drv8835::setAin(int in1, int in2, int c_in1, int c_in2) { // AINのピン番号とチャンネルを設定 a_in1 = in1; a_in2 = in2; a_c_in1 = c_in1; a_c_in2 = c_in2; pinMode(a_in1, OUTPUT); // IN1 pinMode(a_in2, OUTPUT); // IN2 // ピンのセットアップ ledcSetup(a_c_in1, LEDC_BASE_FREQ, LEDC_TIMER_BIT); ledcSetup(a_c_in2, LEDC_BASE_FREQ, LEDC_TIMER_BIT); // ピンのチャンネルをセット ledcAttachPin(a_in1, a_c_in1); ledcAttachPin(a_in2, a_c_in2); } void Drv8835::setBin(int in1, int in2, int c_in1, int c_in2) { // BINのピン番号とチャンネルを設定 b_in1 = in1; b_in2 = in2; b_c_in1 = c_in1; b_c_in2 = c_in2; pinMode(b_in1, OUTPUT); // IN1 pinMode(b_in2, OUTPUT); // IN2 // ピンのセットアップ ledcSetup(b_c_in1, LEDC_BASE_FREQ, LEDC_TIMER_BIT); ledcSetup(b_c_in2, LEDC_BASE_FREQ, LEDC_TIMER_BIT); // ピンのチャンネルをセット ledcAttachPin(b_in1, b_c_in1); ledcAttachPin(b_in2, b_c_in2); } void Drv8835::forwardA(uint32_t pwm) { // AOUTを正転 if (pwm > VALUE_MAX) { pwm = VALUE_MAX; } ledcWrite(a_c_in1, pwm); ledcWrite(a_c_in2, 0); a_status = FORWARD; } void Drv8835::forwardB(uint32_t pwm) { // BOUTを正転 if (pwm > VALUE_MAX) { pwm = VALUE_MAX; } ledcWrite(b_c_in1, pwm); ledcWrite(b_c_in2, 0); b_status = FORWARD; } void Drv8835::reverseA(uint32_t pwm) { // AOUTを逆転 if (pwm > VALUE_MAX) { pwm = VALUE_MAX; } ledcWrite(a_c_in1, 0); ledcWrite(a_c_in2, pwm); a_status = REVERSE; } void Drv8835::reverseB(uint32_t pwm) { // BOUTを逆転 if (pwm > VALUE_MAX) { pwm = VALUE_MAX; } ledcWrite(b_c_in1, 0); ledcWrite(b_c_in2, pwm); b_status = REVERSE; } void Drv8835::brakeA() { // AOUTをブレーキ ledcWrite(a_c_in1, VALUE_MAX); ledcWrite(a_c_in2, VALUE_MAX); a_status = BRAKE; } void Drv8835::brakeB() { // BOUTをブレーキ ledcWrite(b_c_in1, VALUE_MAX); ledcWrite(b_c_in2, VALUE_MAX); b_status = BRAKE; } void Drv8835::coastA() { // AOUTを空転 ledcWrite(a_c_in1, 0); ledcWrite(a_c_in2, 0); a_status = COAST; } void Drv8835::coastB() { // BOUTを空転 ledcWrite(b_c_in1, 0); ledcWrite(b_c_in2, 0); b_status = COAST; } int Drv8835::getStatusA() { // Aのステータスを返す return a_status; } int Drv8835::getStatusB() { // Bのステータスを返す return b_status; }
回路については詳しくないので色々と間違いや足りないところもあると思いますが、ブレッドボード上で2つのモータをマイコンで制御できたのでこれで進めることにしました。
3:自作シャーシの設計
回路がひとまず決まったので、この回路に合うように自作シャーシを設計していきました。
まずはモータボックス部分からやろうということで試行錯誤と微調整を繰り返し、最終的には以下のモデルになりました。
- モータボックスモデル(モータとギア有)
- モータボックスモデル(モータとギアなし)
2つ穴を開けているのはギア部分を隠すようなカバーを取り付けるための穴です。実際にそのカバーも最後にモデルを作成しました。
- ギアカバー(モータとギア有)
- ギアカバー(モータとギアなし)
モータボックスができたので次は実際にモータボックスのギアと噛み合ってそれを車輪に伝えるギアボックス部分を作成していきました。
ここでは汎用性も考え、同じものを2つ作成することでうまく動くようにしたいのとモータボックスとはパチ組みできる形にしたいと思いました。
こちらも試行錯誤と微調整を繰り返した結果、以下のモデルになりました。
- ギアボックス(ギア有)
- ギアボックス(ギアなし)
- ギアボックス(モータボックス有)
次に2つのギアボックスを繋ぐボディ部分を作成していきました。
ユニバーサル基板やギアボックスを固定するためのナットを入れる穴やネジ穴、プロペラシャフトが跳ねた時に押さえつける突起部分などを試行錯誤しつつ、最終的に以下のモデルになりました。
ちなみに電池ボックスは下側に配置し、裏から電池をセットするようにしています。
- ボディ(ギアボックス有)
- ボディ(ギアボックスなし)
これでモデルが完成したので、あとはこれらを3Dプリンタで印刷しました。ボディ以外は全て2つずつ印刷しました。
あとは回路以外の不足している部品などを購入しました。
シャーシ側として購入したものは以下になります。
- シャーシ部品一覧
部品名 | 個数 | 備考 |
---|---|---|
M3 六角ナット | 7 | 各種固定用。ナットの厚みは約2mm |
M3×15ネジ 10本入 | 1 | ユニバーサル基板固定用。実際に使うのは2本 |
M3×8ネジ 185本入 | 1 | ギアボックス固定用。実際に使うのは4本 |
60mm 中空スレンレスシャフト 2本入 | 1 | 六角シャフト。切断して使う |
ミニ4駆 アルミシャフトストッパー 4個 | 1 | 六角シャフト切断後の固定用 |
M2 ステンレスワッシャー 100個入り | 1 | 実際に使うのは4つでタイヤ用 |
大径ナローホイルタイヤ | 1 | タイヤ |
ARシャーシ セッティング ギヤセット | 2 | プロペラシャフトとギア用 |
M3 5mm スペーサー 100個入 | 1 | 実際に使うのは2つ |
銅箔テープ | 1 | モータボックス用 |
モータボックスには銅箔テープを貼り付けてその先に導線をはんだ付けしました。こうすることでモータ自体の交換もできるようにしました。
4:組み立てと動作確認
ユニバーサル基板をはんだ付けし、3Dプリンタで出力した部品と購入した部品を組み合わせた完成形は以下のようになりました。
ギアは比較的力の出る赤とうす茶色の組み合わせにしました。
実際に動かしてみると、ちゃんとPS3コントローラとペアリングが成功した後、〇ボタンで前進、△ボタンで後進はできていました。
しかしR1やL1ボタンで方向転換しようとすると遅いながらも方向転換できる時もありますが、ガリガリ音を立ててギアが空転して動かない場合もありました。
おそらくモータボックスとギアボックスとのギアのかみ合わせが悪いのが原因かと思っていますが、そうなると色々なところの再設計が必要になるので今後の課題ということで今回は終わりにすることにしました。
一応動作確認の動画も貼り付けておきます。
作ってみたミニ四駆の動画です pic.twitter.com/EW9EKxuon9
— シン・ほいっぷ (@sin_deviding) 2022年6月21日
以上が3DプリンタとM5 Atom Liteでミニ四駆ラジコンを作ってみた内容になります。
3Dモデル自体を公開してもよいのですが、素人なのであまり綺麗に作れていないのと現時点で課題が残っているものを公開してもどうかという思いがあります。
また3Dプリンタの機種や精度、個体差によっても出力結果が違ってくる(ミニ四駆程度の大きさであれば0.3mmのずれでも結構大きい)のでモデルを出力してもうまく動く保証はないため特に意味はないと考え公開しないようにしています。
割と時間をかけて今回のミニ四駆ラジコンを作ってみましたが、ミニ四駆の精度に改めて驚かされましたし、モデリングの微調整などかなり勉強にはなりました。
また時間があれば再設計をしたり、当初の目的だったステアリング制御のものを作ってみたいと思います。
・参考資料