|
| 1 | +// update: 2025/04/30 v3 コア完全対応 + タイマー再設計 + 広告名明示 |
| 2 | + |
| 3 | +#include <BLEDevice.h> |
| 4 | +#include <BLEUtils.h> |
| 5 | +#include <BLEServer.h> |
| 6 | +#include <BLE2902.h> |
| 7 | + |
| 8 | +/*========== BLE UUID ==========*/ |
| 9 | +#define SERVICE_UUID "389CAAF0-843F-4D3B-959D-C954CCE14655" |
| 10 | +#define GET_BATTERY_VOLTAGE "389CAAF1-843F-4D3B-959D-C954CCE14655" |
| 11 | +#define SET_MOTOR_PWM "389CAAF2-843F-4D3B-959D-C954CCE14655" |
| 12 | +#define SET_PORT_OUT "389CAAF3-843F-4D3B-959D-C954CCE14655" |
| 13 | +#define SET_SERVO_POSITION "389CAAF4-843F-4D3B-959D-C954CCE14655" |
| 14 | +#define BURST_COMMAND "389CAAF5-843F-4D3B-959D-C954CCE14655" |
| 15 | +#define GET_FUNCTIONS "389CAAFF-843F-4D3B-959D-C954CCE14655" |
| 16 | + |
| 17 | +/*========== I/O (Seeed Xiao ESP32-S3) ==========*/ |
| 18 | +#define power_led 1 |
| 19 | +#define servo_pin 2 |
| 20 | +#define motor0_pin 3 |
| 21 | +#define motor1_pin 4 |
| 22 | +#define port0_led 5 |
| 23 | +#define port1_led 6 |
| 24 | + |
| 25 | +/*========== globals ==========*/ |
| 26 | +BLECharacteristic *pCharacteristic1; |
| 27 | +BLECharacteristic *pCharacteristic2; |
| 28 | +BLECharacteristic *pCharacteristic3; |
| 29 | +BLECharacteristic *pCharacteristic4; |
| 30 | +BLECharacteristic *pCharacteristic5; |
| 31 | +BLECharacteristic *pCharacteristic6; |
| 32 | +BLEAdvertising *pAdvertising; |
| 33 | + |
| 34 | +unsigned char motor_power = 127; |
| 35 | +unsigned char servo_power = 127; |
| 36 | + |
| 37 | +volatile uint32_t interruptCounter = 0; // 割り込みカウンタ |
| 38 | +hw_timer_t *timer = nullptr; |
| 39 | +portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; |
| 40 | +static int light = LOW; // LED トグル用 |
| 41 | + |
| 42 | +/*========== timer ISR ==========*/ |
| 43 | +void IRAM_ATTR onTimer() { |
| 44 | + portENTER_CRITICAL_ISR(&timerMux); |
| 45 | + ++interruptCounter; |
| 46 | + portEXIT_CRITICAL_ISR(&timerMux); |
| 47 | +} |
| 48 | + |
| 49 | +/*========== BLE Callbacks ==========*/ |
| 50 | +class MyCallbacks : public BLECharacteristicCallbacks { |
| 51 | + void onWrite(BLECharacteristic *pCharacteristic) override { |
| 52 | + String value = pCharacteristic->getValue(); |
| 53 | + if (value.length() < 4) return; // データ不足時は無視 |
| 54 | + |
| 55 | + motor_power = value[1]; // DC モータ |
| 56 | + if (value[2] == 3) { // ポート LED |
| 57 | + digitalWrite(port0_led, HIGH); |
| 58 | + digitalWrite(port1_led, HIGH); |
| 59 | + } else { |
| 60 | + digitalWrite(port0_led, LOW); |
| 61 | + digitalWrite(port1_led, LOW); |
| 62 | + } |
| 63 | + servo_power = value[3]; // サーボ |
| 64 | + } |
| 65 | +}; |
| 66 | + |
| 67 | +class ServerCallbacks : public BLEServerCallbacks { |
| 68 | + void onConnect(BLEServer*) override { |
| 69 | + Serial.println("connect."); |
| 70 | + motor_power = servo_power = 127; |
| 71 | + timerAlarm(timer, 100'000, true, 0); // LED:0.1 s 周期 |
| 72 | + } |
| 73 | + void onDisconnect(BLEServer*) override { |
| 74 | + Serial.println("disconnect."); |
| 75 | + motor_power = servo_power = 127; |
| 76 | + timerAlarm(timer, 500'000, true, 0); // LED:0.5 s 周期 |
| 77 | + } |
| 78 | +}; |
| 79 | + |
| 80 | +/*========== SETUP ==========*/ |
| 81 | +void setup() { |
| 82 | + Serial.begin(115200); |
| 83 | + Serial.print("starting..."); |
| 84 | + |
| 85 | + pinMode(servo_pin, OUTPUT); |
| 86 | + pinMode(motor0_pin, OUTPUT); |
| 87 | + pinMode(motor1_pin, OUTPUT); |
| 88 | + pinMode(power_led, OUTPUT); |
| 89 | + pinMode(port0_led, OUTPUT); |
| 90 | + pinMode(port1_led, OUTPUT); |
| 91 | + |
| 92 | + /*----- ハードウェアタイマー -----*/ |
| 93 | + timer = timerBegin(1'000'000); // 1 MHz (1 µs 分解能) |
| 94 | + timerAttachInterrupt(timer, &onTimer); |
| 95 | + timerAlarm(timer, 500'000, true, 0); // 初期:0.5 s 周期 |
| 96 | + |
| 97 | + /*----- BLE 初期化 -----*/ |
| 98 | + BLEDevice::init("ESP Racer"); |
| 99 | + BLEServer *pServer = BLEDevice::createServer(); |
| 100 | + BLEService *pService = pServer->createService(SERVICE_UUID); |
| 101 | + |
| 102 | + pCharacteristic1 = pService->createCharacteristic(GET_BATTERY_VOLTAGE, BLECharacteristic::PROPERTY_READ); |
| 103 | + pCharacteristic2 = pService->createCharacteristic(SET_MOTOR_PWM, BLECharacteristic::PROPERTY_WRITE_NR); |
| 104 | + pCharacteristic3 = pService->createCharacteristic(SET_PORT_OUT, BLECharacteristic::PROPERTY_WRITE_NR); |
| 105 | + pCharacteristic4 = pService->createCharacteristic(SET_SERVO_POSITION, BLECharacteristic::PROPERTY_WRITE_NR); |
| 106 | + pCharacteristic5 = pService->createCharacteristic(BURST_COMMAND, BLECharacteristic::PROPERTY_WRITE_NR); |
| 107 | + pCharacteristic6 = pService->createCharacteristic(GET_FUNCTIONS, BLECharacteristic::PROPERTY_READ); |
| 108 | + |
| 109 | + pCharacteristic5->setCallbacks(new MyCallbacks()); |
| 110 | + pServer->setCallbacks(new ServerCallbacks()); |
| 111 | + |
| 112 | + pService->start(); |
| 113 | + |
| 114 | + /*----- 広告データに名前を明示 -----*/ |
| 115 | + pAdvertising = pServer->getAdvertising(); |
| 116 | + pAdvertising->addServiceUUID(SERVICE_UUID); |
| 117 | + pAdvertising->setScanResponse(true); // 名前はスキャン応答へ |
| 118 | + BLEAdvertisementData scanData; |
| 119 | + scanData.setName("ESP Racer"); // ブラウザ等に表示される名前 |
| 120 | + pAdvertising->setScanResponseData(scanData); |
| 121 | + pAdvertising->start(); |
| 122 | + |
| 123 | + Serial.println("done."); |
| 124 | +} |
| 125 | + |
| 126 | +/*========== LOOP ==========*/ |
| 127 | +void loop() { |
| 128 | + constexpr unsigned int control_phase = 16'000; // 16 ms |
| 129 | + unsigned int servo_on = 1'500 + (servo_power - 127) * 4; // 1012–1992 µs |
| 130 | + unsigned int motor_on = control_phase * abs(motor_power - 127) / 200; |
| 131 | + |
| 132 | + /*----- 電源 LED 点滅 -----*/ |
| 133 | + if (interruptCounter > 0) { |
| 134 | + portENTER_CRITICAL(&timerMux); |
| 135 | + --interruptCounter; |
| 136 | + portEXIT_CRITICAL(&timerMux); |
| 137 | + |
| 138 | + light ^= 1; |
| 139 | + digitalWrite(power_led, light); |
| 140 | + } |
| 141 | + |
| 142 | + /*----- DC モータ & サーボ制御 -----*/ |
| 143 | + if (motor_on > servo_on) { // モータ優先 |
| 144 | + if (motor_power > 127) { // 正転 |
| 145 | + digitalWrite(motor0_pin, HIGH); |
| 146 | + digitalWrite(motor1_pin, LOW); |
| 147 | + motor_on += control_phase / 4; |
| 148 | + } else if (motor_power < 127) { // 逆転 |
| 149 | + digitalWrite(motor0_pin, LOW); |
| 150 | + digitalWrite(motor1_pin, HIGH); |
| 151 | + motor_on += control_phase / 4; |
| 152 | + } else { // 停止 |
| 153 | + digitalWrite(motor0_pin, HIGH); |
| 154 | + digitalWrite(motor1_pin, HIGH); |
| 155 | + } |
| 156 | + digitalWrite(servo_pin, HIGH); |
| 157 | + delayMicroseconds(servo_on); |
| 158 | + digitalWrite(servo_pin, LOW); |
| 159 | + delayMicroseconds(motor_on - servo_on); |
| 160 | + digitalWrite(motor0_pin, LOW); |
| 161 | + digitalWrite(motor1_pin, LOW); |
| 162 | + delayMicroseconds(control_phase - motor_on); |
| 163 | + |
| 164 | + } else { // サーボのみ |
| 165 | + digitalWrite(servo_pin, HIGH); |
| 166 | + delayMicroseconds(servo_on); |
| 167 | + digitalWrite(servo_pin, LOW); |
| 168 | + delayMicroseconds(control_phase - servo_on); |
| 169 | + } |
| 170 | +} |
0 commit comments