4/19、4/26、5/2
- 1日目は組み立て、環境構築、サークル説明、野田・葛飾合同BBQです。
- 2日目はテストコードを使って実際に動かす講座です。
- 3日目は完成まで仕上げる日です。
開催場所: 東京理科大学葛飾キャンパス
教室等の詳細は後日連絡します。
各日 9:00 - 13:00
make own tyny ROVER
小型ローバーを自作しながら、電子工作と制御の基礎を楽しく体験するための講座です。
2026年4月から5月にかけて、同じ内容で2回開催する予定です。各回とも 9:00 から 13:00 まで進行する想定でまとめています。
第1回の日程を表示しています
第1回 1日目 🍖
第1回 2日目
第1回 3日目
第2回の日程を表示しています
第2回 1日目 🍖
第2回 2日目
第2回 3日目
開催場所: 東京理科大学葛飾キャンパス
教室等の詳細は後日連絡します。
各日 9:00 - 13:00
開催場所: 東京理科大学野田キャンパス
教室等の詳細は後日連絡します。
各日 9:00 - 13:00
注: BBQの詳細は公式SNSなどで最新情報を確認してください。
ようこそ新入生の皆様、東京理科大学宇宙工学エンジニアサークルR-secへ。
工学とは、幅広い領域で、当サークルでも、回路設計、機械設計、コーディング等々いろいろ体験できます。
工学って静かな人が黙々とするもの!?と思っている方、
大間違いです。MIT出身の有名なエンジニアの仕事の大半はなんと会議です。残念ながら、優秀なエンジニアほど、人との会話に時間を割きます。
ですから、皆さんには、安易にAI等に聞くのでなく、是非センパイに聞いてください。
個人の技術力に差はありますが、R-secでのモノづくり経験、コミュニケーションにおいてはセンパイ(なはず)です。
わざわざ集まってものづくりをする意義、楽しさを我々はこのプロジェクトを通じて提供します。
長くなりましたが、
このプロジェクトは基本的に個人で進めていただき、集まる日だけ躓いたところ等の相談や、お互いで技術交流をして頂きます。
それでは、愉しいR-secライフを!
新歓プロジェクトの連絡、質問、資料共有は主に Teams を使って進めます。学校から配布される@ed.tus.ac.jpを使ってアカウントを作成してください。
ここに新歓プロジェクトをサポートするセンパイたちの紹介を載せられます。
役職: 代表
所属: 葛飾 機械
出身: 岡山
後輩を片目にみるM1がたまりません。
役職: 電装
所属: 野田 電気電子情報工学科
出身: 川崎(治安のいいほう)
パンツが見えることもときたまございます
役職: 便利屋
所属: 野田 機械
出身: 大阪にある我孫子じゃない我孫子
雑用が好きと公言
役職: 構造
所属: 葛飾 物理?工学?
出身: 23区
やはやはと呼ばれると喜びます
役職: 便利屋
所属: 遙かなた たぶん機械?
出身: 大都会
仮免学科1回、実技3回、本免技能3回落ちた
役職: なんでも
所属: 葛飾 電気
出身:
スぺバル聞いてください
役職: 広報
所属: 葛飾
出身:
クリックすると mini RC の概観動画が見れます。
制作に使用する材料の一覧です。価格や購入先も確認できます。
| 材料名 | 価格 | 購入URL |
|---|---|---|
|
ローバー全体の制御を担当する頭脳部分です。 XIAO ESP32S3 Sense を選ぶと、Wi-Fi経由でカメラ映像を見ながらリアルタイムに操縦できる構成にできます。 |
¥1,080 | 商品ページを見る |
|
各パーツへ電力を供給する電源です。 カメラを使う場合や高出力が必要な場合はリチウムイオンバッテリーを選んだ方がよいですが、高くなりやすいです。 |
約¥1,500 | 商品ページを見る |
|
XT30コネクター オス 基板取付用 XT30PW-M
バッテリーと基板の電源ラインを着脱しやすくする電源コネクターです。 |
¥70 | 商品ページを見る |
|
降圧コンバーター
バッテリー電圧を必要な電圧まで下げて安定供給します。 |
¥200(¥998 / 5個入り) | 商品ページを見る |
|
FEETECH ギアードモーター FM90
左右のタイヤを回してローバーを走行させる駆動用モーターです。2個必要です。 |
¥330 × 2 | 商品ページを見る |
|
XHコネクター ベース付ポスト サイド型 2P S2B-XH-A(LF)(SN)
モーターとの接続に使う基板用コネクターです。左右のモーター用に2個必要です。 |
¥15 × 2 | 商品ページを見る |
|
積層セラミックコンデンサー 1μF50V X7R 5mm オプション
モーターのアウトプットに追加できる任意部品です。回転時に出るノイズを抑え、誤動作や電源ラインへの影響を減らしやすくします。 |
¥20 | 商品ページを見る |
|
TB6612モータードライバー
マイコンの信号を受けてモーターの回転方向や速度を制御します。 |
¥380 | 商品ページを見る |
|
姿勢センサIMU オプション
車体の傾きや動きを検出したいときに追加できる任意のセンサです。 |
約¥600 | 商品ページを見る |
|
GY-BMP280-3.3 気圧・温湿度センサーモジュール
周囲の気圧・温度・湿度を計測します。 |
¥143(¥859 / 6個入り) | 商品ページを見る |
|
シャープ測距モジュール GP2Y0A21YK オプション
前方との距離を非接触で測りたいときに追加できる任意の距離センサです。 |
¥550 | 商品ページを見る |
|
AliExpress オプション部品 オプション
tofカメラです。各ピクセルごとの深度を測定できます。 |
約¥1,500 | 商品ページを見る |
|
路面をとらえて走行性能を決める足まわりの部品です。 3種類の候補から選べるようにしてあり、選択に合わせてイメージを表示します。 |
選定中 |
ストリートタイプ |
|
OLEDディスプレイ
状態表示や簡単な情報表示を行う小型画面です。 |
¥373(¥745 / 2個入り) | 商品ページを見る |
|
コネクター用ハウジング 4P × 2
OLEDとの接続に使う4ピンのハウジングです。2個必要です。 |
¥10 × 2 | 商品ページを見る |
|
基板用スライドスイッチ
メイン電源のオンオフ切り替えに使う基板取付用の小型スイッチです。 |
¥40 | 商品ページを見る |
|
汎用整流用ダイオード 1000V1A 1N4007
電源基板の整流用に使うダイオードです。逆接続対策や電流の逆流防止にも使えます。 |
¥100 / 20本 | 商品ページを見る |
|
3mm砲弾型黄緑色LED 570nm 100mcd 70° OSG8HA3Z74A
電源のON/OFF確認用に使うインジケーターLEDです。 |
¥10 | 商品ページを見る |
|
抵抗 約200Ω
電源確認用LEDの電圧降下と電流制限に使う抵抗です。 |
別途準備 | リンクなし |
|
基板
各パーツを実装・配線するためのプリント基板です。 |
¥124 | リンクなし |
|
フィラメント・ねじ類
3Dプリント用フィラメントおよび組み立てに使うねじ類の費用です。 |
¥300(目安) | リンクなし |
必須パーツ(価格確定分)
必須パーツ(要別途確認)
その他費用
オプションパーツ(チェックで合計に加算)
※ 「要別途確認」のパーツと価格未定のオプションは含まれていません。
マイコン・バッテリーの選択変更・オプションのチェックで自動更新されます。
支払い方法
材料選びに加えて、どんなローバーにしたいかを設計段階で選べるようにしてあります。
初めて作るときに扱いやすい標準設計です。走行・制御・拡張のしやすさを均等に確保します。
調整中
使用言語は C++ を使用します。巷ではpythonという便利な言語がありますが、 pythonはそれ以前の言語と違い、実用と多用途に特化した言語と言えます。しかし、多くのエンジニアは、初修の言語としてC言語を学びます。が、C言語はC++などと違いコンパイルエラーをあまりはいてくれません。ミスを見つけるのに時間がかかってしまいます。 ですから、今回は最もクラシックでロジカルな言語C++を選択します。もちろん、pythonでの開発ができるマイコンですので、 どうしてもしたい場合はセンパイまで。開発環境は C++ 用の 2 パターンを案内しておきます。ディレクトリの管理を大事にしたい人やこれからバンバンプログラミングをしていく方はPlatformIO(通称PIO)を強くお勧めします。しかし、万能で多機能な分、設定、使いかたが煩雑です。あまりにパソコン等に慣れていない場合は逆にArduinoIDEを推奨いたします。尚 ArduinoIDEはなんとスクラッチで書くこともできます(もちろん一番おすすめしないです)。わからない単語はある程度リンクをのせておきますのでクリックして調べてみてください。
C++ 開発の推奨環境です。セットアップ手順はこの欄に追記してください。
コメント等は極力日本語にしますが、英語で基本的には書きます。
Arduino 系のプログラムは、最初に 1 回だけ実行される setup() と、その後くり返し実行される loop() を基本にして構成します。setup()の前に定義を書きます。ここら辺は使いながら慣れてください(規則はいくらでもありますから、説明していくときりがないです)。
このコードを実際にコピペするのでなくて、手入力して書き込んでみてください。もちろんコメントアウトは書かなくていいですが、コメントアウトは適宜使用してみてください。
#include <Arduino.h> // ここにライブラリを追加
const int ledPin = 8; //ピンの定義 GPIOで記述する場合はD8とする。これはケースバイケース
void setup() {//setup関数は最初に1回だけ実行される関数。void型であることには注意。voidの説明は後にします。
pinMode(ledPin, OUTPUT); //ピンのモードを出力に設定
}
void loop() {//繰り返し実行される関数
digitalWrite(ledPin, HIGH); //LEDを点灯
delay(1000); //1秒待つ
digitalWrite(ledPin, LOW); //LEDを消灯
delay(1000); //1秒待つ
}
基本構造を書けるようになったら、次は値の変更、関数の追加、条件分岐の利用を試します。少しずつ変更して、どの記述がどの動作に対応しているかを見ていくのが大事です。 ここら辺は、書いて真似て制御文の意味を考えていく、体裁的なことというより、慣れるセクションです。 以下のサンプルコードを真似して自らの手で書いて変化を見てください。 そして、そのコードをteamsで共有してください。 ここら辺からエラーが発生しやすくなります。気軽にセンパイに聞いてみてください。
サンプルコード1
#include <Arduino.h> // ここにライブラリを追加
const int ledPin = 8; //ピンの定義 GPIOで記述する場合はD8とする。これはケースバイケース
void setup() {
}
void loop() {
for(int i = 0; i < 6; i++) { //漸かにLEDの明るさを変えるループ
analogWrite(ledPin, 50*i); //LEDを点灯
delay(500); //500ミリ秒待つ
}
}
サンプルコード2
関数を分けて、点滅と明るさの変化を切り替えて動かすサンプルコードです。関数の作り方、使い方はあとで説明します。
#include <Arduino.h>//ここにライブラリを追加
const int ledPin = 8; //ピンの定義 GPIOで記述する場合はD8とする。これはケースバイケース
void blink(){
analogWrite(ledPin, 255); //LEDを点灯
delay(1000); //1000ミリ秒待つ
analogWrite(ledPin, 0); //LEDを消灯
delay(1000); //1000ミリ秒待つ
}
void illumination(){
for (int i =0; i < 10; i++){
analogWrite(ledPin, i*25); //LEDの明るさを変える
delay(100); //100ミリ秒待つ
}
for (int i =9; i >= 0; i--){
analogWrite(ledPin, i*25); //LEDの明るさを変える
delay(100); //100ミリ秒待つ
}
}
void setup() {
//私はここにpinMode(ledPin, OUTPUT); //LEDピンを出力に設定するコードを入れたところ光りませんでした。
//pinModeを設定後にanalog出力にしたため、digitalWriteで点灯させることができませんでした。
blink(); //点滅関数を呼び出す
illumination(); //明るさ変化関数を呼び出す
}
void loop() {//関数を使って記述した場合。同じような処理の場合、関数を使うことでloop文の中を減らせる
blink();
illumination();
blink();
blink();
illumination();
}
サンプルコード3
ここに2つ目のコードについての説明を書いてください。
// ここに2つ目のコードを書いてください
void setup() {
}
void loop() {
}
ここに3つ目のコードについての説明を書いてください。
// ここに3つ目のコードを書いてください
void setup() {
}
void loop() {
}
続いてはシリアル通信の一つI2C(アイスクエアド通信)通信とOLEDのキャラクタデバイスについてです。I2Cの最大の特徴は、少ない本数の配線で複数のデバイスと通信できる点です。 SDA(データを送る線) と SCL(クロックを送る線) の 2 本を主に使います。 マスター(親)とスレーブ(子)の関係で通信を行い、マスターがスレーブに対してアドレスを指定して通信します。 これにより、複数のデバイスを同じ線でつなげることができます。 詳しくは、こちらを見てください。
I2C通信について詳しく知る今回は、OLEDを用いて、I2C通信はどういったもので、どうコーディングするべきかを体験して頂きます。 そのため、多くのライブラリを用いますが、ライブラリの内容についてはあまり深く説明しません(説明しても意味ないから)。 とりあえず、コードを真似して動かしてみてください。尚、シリアル通信を見る方法は、資料を参照ください。
サンプルコード
#include //ここにライブラリを追加
#include //I2C通信ライブラリ
#include //OLEDディスプレイライブラリ
#include //グラフィックライブラリ
#define SCREEN_WIDTH 128 // OLEDの表示幅, ピクセル単位
#define SCREEN_HEIGHT 64 // OLEDの表示高さ, ピクセル単位
#define OLED_RESET -1 // OLEDリセットピン(使用しない場合は-1)
#define SCREEN_ADDRESS 0x3C // よく使われるSSD1306のI2Cアドレス
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);//シリアル通信の初期化。通信速度は115200bpsに設定
Wire.begin();// I2C通信の初期化
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {//3.3V電源を使用する場合はSSD1306_SWITCHCAPVCCを指定し、よく使われるI2Cアドレス0x3Cを指定してOLEDを初期化
while (true) {
Serial.println("OLED init failed");// OLEDの初期化に失敗した場合はシリアルモニタにエラーメッセージを表示
delay(100);
}
}// OLEDの初期化に失敗した場合はここで止まる
display.clearDisplay();// OLEDの画面をクリア これがないと前の表示が残ることがある。 これはOLED側のマイコンのメモリに前の表示が残っているためで、OLEDを初期化した後は必ず画面をクリアすることが推奨される
display.setTextSize(1);// テキストサイズを1に設定(デフォルトは1)
display.setTextColor(SSD1306_WHITE);// テキストカラーを白に設定
display.setCursor(0, 0);// テキストの表示位置を左上に設定
display.println("welcome to R-sec");//ln()は改行を行う関数。これで文字列を表示した後に次の行に移る 次の文から改行が始まる
display.println("Hello, World!");// 文字列を表示
display.display();
}
void loop() {
delay(1000);
}
次は、OLED上に任意の文字以外の要素を表示する方法についてです。 これは、以外にもシンプルであり、I2C通信にはあまり関係ないため、あまり深く説明しません。 これこそ、AIの出番です。「SSD1306にかわいい顔を表示するサンプルコードを書いて」等と 入力してみてください。
次はシリアル通信です。シリアル通信を使うと、マイコンの中で何が起きているかをパソコン側へ文字として送れます。
センサの値を確認したり、どこまで処理が進んだかを調べたりするときにとても便利です。
最初のうちは LED の点滅だけでなく、Serial.print() や Serial.println() を使って状態を表示する練習をしておくと、デバッグがかなりやりやすくなります。
デバックとは、プログラムの動作を追跡し、問題を特定するための手法です。思った動きをしないとき、どこが原因なのか発見するのに役立ちます。
実は、void setup(){}でSerial.begin(speed)と入力しその後Serial.print(letter or number)
と入力するだけです。これを使って今のエラーコードが出るコードを修正してください。これに関しては、コピペで行って構いません。シリアル通信については、資料を参照ください。
サンプルコード
#include
const int led = 8;
int state = LOW;
void setup() {
Serial.begin(9600);
pinMode(led, OUTPUT);
}
void loop() {
state == !state;
//Serial.println(state);
digitalWrite(led, state);
delay(500);
}
このコードでは、state の値を切り替えたいのに、比較に使う記号を書いてしまっています。代入と比較の違いを見直してみてください。
もうひとつのポイントは、シリアルモニタに状態を表示する行です。コメントアウトを外すと、LED の状態がどう変わっているか追いやすくなります。
state が本当に変化しているかを Serial.println(state); で確認しながら修正すると、原因を見つけやすいです。
LOWとHIGHは0と1と同値です。
このコードでは、0.5秒ごとに state の値をシリアルモニタへ送っています。PlatformIO ならシリアルモニタを開いて確認でき、Arduino IDE でも同じように表示を見られます。
このコードだと、ledが光りません。問題はloop関数内にあります。
次は、モーターを PWM 制御で動かす練習です。PWM は ON と OFF を高速で切り替えて、モーターに加わる平均的な電圧を調整する方法です。これにより、ただ回すだけでなく、ゆっくり回したり速く回したりといった速度調整ができます。
LED の明るさを変えるときに使った analogWrite() と考え方はかなり近いです。まずは値を少しずつ変えながら、どのくらいで回り始めるかを観察してみてください。詳しくは資料を参照してください。
duty ratio は ON の時間の割合です。たとえば半分だけ ON なら 0.5 になります。
analogWrite() は 0 から 255 の値で出力の強さを変えます。シリアル通信で duty ratio を入力 → PWM 値を計算 → モーターを回す
シリアル通信でPCとマイコンを接続するやり方は、組み込みシステムの基本的な操作です。 理解が難しいので、ここでは取り上げませんが、以下のコードを真似て見てください。 詳細はセンパイに聞くか、資料を参照ください。
サンプルコード
#include
const int motorR_Pin1=2;
const int motorR_Pin2=1;
const int motorL_Pin1=3;
const int motorL_Pin2=10;
const int motorR_PWM=D0;
const int motorL_PWM=D6;
int dutyRatio=0;
int OUTPUT_M=0;
void setup() {
pinMode(motorR_Pin1,OUTPUT);
pinMode(motorR_Pin2,OUTPUT);
pinMode(motorL_Pin1,OUTPUT);
pinMode(motorL_Pin2,OUTPUT);
pinMode(motorR_PWM,OUTPUT);
pinMode(motorL_PWM,OUTPUT);
delay(3000);
Serial.begin(115200);
while(!Serial) {//シリアルポートが開くのを待つ
delay(100);
Serial.println("waiting for serial...");
}
}
void loop() {
Serial.println("set motor duty ratio");
Serial.print("ratio: ");
while(Serial.available()){//シリアルポートからの入力を待つ
delay(100);
dutyRatio=Serial.readStringUntil('\n').toInt();
Serial.print(dutyRatio);
Serial.println("%");
}
OUTPUT_M=255*dutyRatio/100;
digitalWrite(motorR_Pin1,HIGH);
digitalWrite(motorR_Pin2,HIGH);
digitalWrite(motorL_Pin1,HIGH);
digitalWrite(motorL_Pin2,HIGH);
analogWrite(motorR_PWM,OUTPUT_M);
analogWrite(motorL_PWM,OUTPUT_M);
delay(1000);
Serial.flush();//シリアルポートのバッファをクリア こうすることで次の入力を待つことができる
}
このコードでは、PWM の値を少しずつ上げながらモーターの回転の強さを変えています。同時にシリアルモニタへ現在の値を表示しているので、「どの値くらいから回り始めるか」「最大にするとどれくらい速くなるか」を確認しやすくなっています。
まず、PWM に使っているピンがそのマイコンで PWM 出力に対応しているか確認してください。ピンによっては analogWrite() が使えない場合があります。
次に、モータードライバの電源と GND が正しくつながっているかを見直してください。信号線だけ合っていても、電源まわりが不足していると回りません。
最後に、analogWrite() の値を小さくしすぎていないかも確認してみてください。モーターはある程度以上の値でないと回り始めないことがあります。
xiaoではなぜか PWM 出力にするためにはピンをGPIO指定する必要があるようです。
最後のステップです。BLE通信を使ってRCカーを操作します。BLEとは Bluetooth Low Energy の略で、低消費電力で短距離の通信ができる技術です。 Wi-fiやBluettoth Classicと比べて、消費電力が少ないため、バッテリー駆動のデバイスでよく使われます。 ただ、BLEは常時通信をしないことで、省電力を実現しているため、通信のタイミングや方法が少し特殊です。 ここでは、BLE通信の基本的な流れと、RCカーの操作に必要なコードの例を紹介します。 このセクションは、実装を目的としますので、コードの深い理解は必要としません。 とりあえず、コードを真似して動かしてみてください。 尚、リモコンは、Pythonで書いています。pythonのPCへの導入方法は、資料を参照ください。
ESP32 (XIAO) 側サンプルコード
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
//必ずGPIO指定。何故かこうしないとクラッシュする
const int motorR_Pin1 = D2;// GPIO2
const int motorR_Pin2 = D1;// GPIO1
const int motorL_Pin1 = D3;// GPIO3
const int motorL_Pin2 = D10;// GPIO10
const int motorR_PWM = D0;// GPIO0
const int motorL_PWM = D6;// GPIO6
// Nordic UART Service (NUS) UUID — controller.py と同じ
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // サービスUUID (NUS) 重複してはならないため要設定がいる
#define RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // Python → ESP32
#define TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // ESP32 → Python
BLECharacteristic* pTxCharacteristic = nullptr;// BLE通知用グローバル変数
// モーター制御
int gSpeed = 153; // 現在の速度 (0-255)、デフォルト153
String gCmd = "STOP"; // 現在実行中のコマンド
// モーター制御関数。speedの正負で回転方向を制御し、絶対値で速度を制御します。
void setMotor(int pin1, int pin2, int pwmPin, int speed) {
if (speed > 0) {
digitalWrite(pin1, HIGH);
digitalWrite(pin2, LOW);
} else if (speed < 0) {
digitalWrite(pin1, LOW);
digitalWrite(pin2, HIGH);
} else {
digitalWrite(pin1, LOW);
digitalWrite(pin2, LOW);
}
analogWrite(pwmPin, abs(speed));
}
// 左右のモーターを同時に制御する関数
void driveMotors(int left, int right) {
setMotor(motorL_Pin1, motorL_Pin2, motorL_PWM, left);
setMotor(motorR_Pin1, motorR_Pin2, motorR_PWM, right);
}
// 現在の gCmd を gSpeed で再適用する(速度変更時に即時反映)
void applyCmd() {
if (gCmd == "FORWARD") driveMotors( gSpeed, gSpeed);
else if (gCmd == "BACK") driveMotors(-gSpeed, -gSpeed);
else if (gCmd == "LEFT") driveMotors(-gSpeed, gSpeed);
else if (gCmd == "RIGHT") driveMotors( gSpeed, -gSpeed);
else driveMotors(0, 0);
}
// Python へ文字列を通知送信
void sendNotify(const char* msg) {
if (pTxCharacteristic == nullptr) return;
pTxCharacteristic->setValue((uint8_t*)msg, strlen(msg));
pTxCharacteristic->notify();
}
// BLE書き込みコールバック (Python → ESP32)
// コマンド例: "FORWARD", "BACK", "LEFT", "RIGHT", "STOP"
class RxCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pCharacteristic) override {
std::string raw = pCharacteristic->getValue();
if (raw.empty()) return;
// 末尾の改行・スペースを除去
while (!raw.empty() && (raw.back() == '\n' || raw.back() == '\r' || raw.back() == ' ')) {
raw.pop_back();
}
String cmd = String(raw.c_str());
cmd.trim();
Serial.printf("BLE command: %s\n", cmd.c_str());
if (cmd == "FORWARD" || cmd == "BACK" || cmd == "LEFT" || cmd == "RIGHT") {
gCmd = cmd;
applyCmd();
sendNotify(("OK " + cmd).c_str());
} else if (cmd == "STOP") {
gCmd = "STOP";
driveMotors(0, 0);
sendNotify("OK STOP");
} else if (cmd.startsWith("SPEED:")) {
int pct = cmd.substring(6).toInt(); // 0-100 %
pct = constrain(pct, 0, 100);
gSpeed = (int)(pct * 255L / 100); // map to 0-255 for analogWrite
applyCmd(); // 走行中なら即反映
sendNotify(("OK SPEED:" + String(pct)).c_str());
} else if (cmd == "STATUS") {
int pct = (int)(gSpeed * 100L / 255);
sendNotify(("STATUS cmd:" + gCmd + " speed:" + String(pct) + "%").c_str());
} else {
Serial.printf("Unknown command: %s\n", cmd.c_str());
sendNotify("ERR UNKNOWN");
}
}
};
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) override {
Serial.println("BLE connected");
}
void onDisconnect(BLEServer* pServer) override {
Serial.println("BLE disconnected — restarting advertising");
driveMotors(0, 0); // 切断時はモーター停止
BLEDevice::getAdvertising()->start();
}
};
void setup() {
pinMode(motorR_Pin1, OUTPUT);
pinMode(motorR_Pin2, OUTPUT);
pinMode(motorL_Pin1, OUTPUT);
pinMode(motorL_Pin2, OUTPUT);
pinMode(motorR_PWM, OUTPUT);
pinMode(motorL_PWM, OUTPUT);
driveMotors(0, 0); // 起動時は停止
Serial.begin(115200);
// BLE初期化
BLEDevice::init("mini_RC_OLED");
BLEServer* pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
BLEService* pService = pServer->createService(SERVICE_UUID);
// RX: Python → ESP32 (書き込み)
BLECharacteristic* pRxCharacteristic = pService->createCharacteristic(
RX_UUID,
BLECharacteristic::PROPERTY_WRITE
);
pRxCharacteristic->setCallbacks(new RxCallbacks());
// TX: ESP32 → Python (通知)
pTxCharacteristic = pService->createCharacteristic(
TX_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
pTxCharacteristic->addDescriptor(new BLE2902());
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->start();
Serial.println("BLE started. Device: mini_RC_OLED");
}
void loop() {
delay(10);
}
コントローラー (controller.py) をダウンロード
変数名や関数名は、何を表しているかがひと目で分かる名前にします。短すぎる名前よりも、役割が伝わる名前を優先してください。
このページでは、変数は ledPin や motorSpeed のような lowerCamelCase を基本にし、関数も readSensor() や updateMotor() のように動作が分かる名前をおすすめします。
一方で、a や tmp のような意味が伝わりにくい名前は、あとで読み返したときに困りやすいです。自分以外の人が見ても分かる名前を意識しておくと、修正もかなり楽になります。
前書きでも述べましたが、コーディングはチームで行う場合がほとんどです。おすすめの書として、オライリー社出版の「リーダブルコード」をおすすめします。
(内容は後で記述します)
(内容は後で記述します)
配布資料や補足資料の PDF をここで確認できます。
基板データ(回路全体.step)をダウンロード