R-sec new welcome Project

電子工作編

GitHub

make own tyny ROVER

小型ローバーを自作しながら、電子工作と制御の基礎を楽しく体験するための講座です。

PCサイト スマホサイト

2026年4月から5月にかけて、同じ内容で2回開催する予定です。各回とも 9:00 から 13:00 まで進行する想定でまとめています。

2026年4月

第1回の日程を表示しています

Mon
Tue
Wed
Thu
Fri
Sat
Sun
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

第1回 1日目 🍖

20
21
22
23
24
25
26

第1回 2日目

27
28
29
30
1
2

第1回 3日目

3

2026年5月

第2回の日程を表示しています

Mon
Tue
Wed
Thu
Fri
Sat
Sun
27
28
29
30
1
2
3
4
5
6
7
8
9

第2回 1日目 🍖

10
11
12
13
14
15
16
17

第2回 2日目

18
19
20
21
22
23

第2回 3日目

24
25
26
27
28
29
30
31

予定一覧

第1回

4/19、4/26、5/2

  1. 1日目は組み立て、環境構築、サークル説明、野田・葛飾合同BBQです。
  2. 2日目はテストコードを使って実際に動かす講座です。
  3. 3日目は完成まで仕上げる日です。

開催場所: 東京理科大学葛飾キャンパス

教室等の詳細は後日連絡します。

各日 9:00 - 13:00

第2回

5/9、5/17、5/23

  1. 1日目は組み立て、環境構築、サークル説明、野田・葛飾合同BBQです。
  2. 2日目はテストコードを使って実際に動かす講座です。
  3. 3日目は完成まで仕上げる日です。

開催場所: 東京理科大学野田キャンパス

教室等の詳細は後日連絡します。

各日 9:00 - 13:00

注: BBQの詳細は公式SNSなどで最新情報を確認してください。

前書き

ようこそ新入生の皆様、東京理科大学宇宙工学エンジニアサークルR-secへ。 工学とは、幅広い領域で、当サークルでも、回路設計、機械設計、コーディング等々いろいろ体験できます。 工学って静かな人が黙々とするもの!?と思っている方、 大間違いです。MIT出身の有名なエンジニアの仕事の大半はなんと会議です。残念ながら、優秀なエンジニアほど、人との会話に時間を割きます。 ですから、皆さんには、安易にAI等に聞くのでなく、是非センパイに聞いてください。 個人の技術力に差はありますが、R-secでのモノづくり経験、コミュニケーションにおいてはセンパイ(なはず)です。 わざわざ集まってものづくりをする意義、楽しさを我々はこのプロジェクトを通じて提供します。
長くなりましたが、 このプロジェクトは基本的に個人で進めていただき、集まる日だけ躓いたところ等の相談や、お互いで技術交流をして頂きます。 それでは、愉しいR-secライフを!

Teamsの使い方

新歓プロジェクトの連絡、質問、資料共有は主に Teams を使って進めます。学校から配布される@ed.tus.ac.jpを使ってアカウントを作成してください。

最初にやること

  1. チームに参加する 招待をしますので、新歓プロジェクトフォーム入力完了後、メールにて招待を送信します。招待が来るまで少々お待ちください。
  2. 通知を確認する 既定では通知がならないので、以下の画像を参照して設定してください。すべてのチャンネルで適用してください。
    Teams の通知設定画面 その1
    通知設定画面 1 枚目
    Teams の通知設定画面 その2
    通知設定画面 2 枚目
  3. 自己紹介をする 自己紹介をしてみてください。

センパイ紹介

ここに新歓プロジェクトをサポートするセンパイたちの紹介を載せられます。

清水

役職: 代表

所属: 葛飾 機械

出身: 岡山

後輩を片目にみるM1がたまりません。

鈴木

役職: 電装

所属: 野田 電気電子情報工学科

出身: 川崎(治安のいいほう)

パンツが見えることもときたまございます

平井

役職: 便利屋

所属: 野田 機械

出身: 大阪にある我孫子じゃない我孫子

雑用が好きと公言

矢萩

役職: 構造

所属: 葛飾 物理?工学?

出身: 23区

やはやはと呼ばれると喜びます

佐藤

役職: 便利屋

所属: 遙かなた たぶん機械?

出身: 大都会

仮免学科1回、実技3回、本免技能3回落ちた

谷川

役職: なんでも

所属: 葛飾 電気

出身:

スぺバル聞いてください

永江

役職: 広報

所属: 葛飾

出身:

overview_miniRC

クリックすると 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(目安) リンクなし

お見積もり計算機

必須パーツ(価格確定分)

マイコン(Seeed Studio XIAO ESP32C3 ¥1,080
バッテリー(リチウムイオンバッテリー 約¥1,500
XT30コネクター ¥70
FEETECH ギアードモーター FM90 × 2 ¥660
XHコネクター × 2 ¥30
TB6612 モータードライバー ¥380
コネクター用ハウジング 4P × 2 ¥20
基板用スライドスイッチ ¥40
汎用整流用ダイオード 1N4007 ¥100
3mm砲弾型LED ¥10
降圧コンバーター(¥998/5個入り) ¥200
OLEDディスプレイ(¥745/2個入り) ¥373
GY-BMP280-3.3 気圧・温湿度センサー(¥859/6個入り) ¥143
基板 ¥124

必須パーツ(要別途確認)

タイヤ 選定中
抵抗 約200Ω 別途準備

その他費用

フィラメント・ねじ類(目安) ¥300

オプションパーツ(チェックで合計に加算)

合計(目安)(前後します) ¥2,360

※ 「要別途確認」のパーツと価格未定のオプションは含まれていません。
マイコン・バッテリーの選択変更・オプションのチェックで自動更新されます。

支払い方法

  • 🏦
    銀行振込 口座番号は担当者から連絡します。
  • 💴
    現金手渡し 活動日に担当者へ直接お渡しください。
  • 📱
    PayPay 個人送金 ポイント払い不可・送金先は担当者から連絡します。

設計

材料選びに加えて、どんなローバーにしたいかを設計段階で選べるようにしてあります。

バランス重視

初めて作るときに扱いやすい標準設計です。走行・制御・拡張のしやすさを均等に確保します。

おすすめタイヤ ストリートタイプ
重視ポイント 安定した走行と調整のしやすさ
メモ 車体中央にバッテリーを置くとバランスが取りやすいです。

組み立て方

調整中

開発環境セットアップ

使用言語は C++ を使用します。巷ではpythonという便利な言語がありますが、 pythonはそれ以前の言語と違い、実用と多用途に特化した言語と言えます。しかし、多くのエンジニアは、初修の言語としてC言語を学びます。が、C言語はC++などと違いコンパイルエラーをあまりはいてくれません。ミスを見つけるのに時間がかかってしまいます。 ですから、今回は最もクラシックでロジカルな言語C++を選択します。もちろん、pythonでの開発ができるマイコンですので、 どうしてもしたい場合はセンパイまで。開発環境は C++ 用の 2 パターンを案内しておきます。ディレクトリの管理を大事にしたい人やこれからバンバンプログラミングをしていく方はPlatformIO(通称PIO)を強くお勧めします。しかし、万能で多機能な分、設定、使いかたが煩雑です。あまりにパソコン等に慣れていない場合は逆にArduinoIDEを推奨いたします。尚 ArduinoIDEはなんとスクラッチで書くこともできます(もちろん一番おすすめしないです)。わからない単語はある程度リンクをのせておきますのでクリックして調べてみてください。

PlatformIO(推奨)

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(アイスクエアド通信)通信と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 制御で動かす練習です。PWM は ON と OFF を高速で切り替えて、モーターに加わる平均的な電圧を調整する方法です。これにより、ただ回すだけでなく、ゆっくり回したり速く回したりといった速度調整ができます。

LED の明るさを変えるときに使った analogWrite() と考え方はかなり近いです。まずは値を少しずつ変えながら、どのくらいで回り始めるかを観察してみてください。詳しくは資料を参照してください。

電圧のイメージ

VM = Vcc × duty ratio

duty ratio は ON の時間の割合です。たとえば半分だけ ON なら 0.5 になります。

このセクションで意識すること

  • analogWrite()0 から 255 の値で出力の強さを変えます。
  • モータードライバでは、方向を決めるピンと PWM で速さを決めるピンを分けて使うことが多いです。
Sample Flow

今回の流れ

シリアル通信で 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指定する必要があるようです。

RCカーの作成

最後のステップです。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) をダウンロード
コラム: 変数・関数の命名規則

変数名や関数名は、何を表しているかがひと目で分かる名前にします。短すぎる名前よりも、役割が伝わる名前を優先してください。

このページでは、変数は ledPinmotorSpeed のような lowerCamelCase を基本にし、関数も readSensor()updateMotor() のように動作が分かる名前をおすすめします。

一方で、atmp のような意味が伝わりにくい名前は、あとで読み返したときに困りやすいです。自分以外の人が見ても分かる名前を意識しておくと、修正もかなり楽になります。 前書きでも述べましたが、コーディングはチームで行う場合がほとんどです。おすすめの書として、オライリー社出版の「リーダブルコード」をおすすめします。

コラム: ポインタ

(内容は後で記述します)

コラム: 文字列

(内容は後で記述します)

資料

配布資料や補足資料の PDF をここで確認できます。

基板データ(回路全体.step)をダウンロード

関数の基本

PDF を別タブで開く

シリアル通信と PlatformIO

PDF を別タブで開く