Skip to content

Commit 4c4311c

Browse files
PaulDWhiteclaude
andcommitted
Add ESC LED control and motor beep for arm/disarm
- Update SINE-ESC-CAN library to v0.0.4 (led-control branch) which adds SetLedControl (Data ID 218) and includes SetMotorSound (Data ID 214) - Add setMotorSound() to SineEsc class to wire up motor beep protocol - Add ESC LED aviation strobe pattern (red/green double pulse) - Add motor beep on arm (ascending tone) and disarm (descending tone) matching the hand controller buzzer cadence - Rename LED_RED/GREEN/YELLOW macros to NEOPIXEL_* to avoid collision with SineEsc::LedPattern enum Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2b32b79 commit 4c4311c

6 files changed

Lines changed: 124 additions & 27 deletions

File tree

extra_script.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22
import subprocess
33

44
Import("env")
5-
folder = env.GetProjectOption("custom_src_folder")
6-
7-
# Generic
8-
env.Replace(
9-
PROJECT_SRC_DIR="$PROJECT_DIR/src/" + folder
10-
)
115

126
def get_git_revision_short_hash():
137
try:

inc/sp140/esc.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ bool hasMotorIDet2Error(uint16_t errorCode);
5858
bool hasSwHwIncompatError(uint16_t errorCode);
5959
bool hasBootloaderBadError(uint16_t errorCode);
6060

61+
// ESC LED control
62+
void setESCLedControl(const uint16_t *ledData, uint8_t ledNum);
63+
void startESCLedStrobeTest();
64+
65+
// ESC motor beep
66+
void setESCMotorSound(const uint8_t *beepData, uint8_t beepDataLen, uint8_t beepNum);
67+
void escMotorBeepArm();
68+
void escMotorBeepDisarm();
69+
6170
// for debugging
6271
void dumpThrottleResponse(const sine_esc_SetThrottleSettings2Response *res);
6372
void dumpESCMessages(); // dumps all messages to USBSerial

inc/sp140/utilities.h

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
#ifndef INC_SP140_UTILITIES_H_
2-
#define INC_SP140_UTILITIES_H_
3-
4-
#include <Arduino.h>
5-
6-
// Function to get unique chip ID
7-
String chipId();
1+
#ifndef INC_SP140_UTILITIES_H_
2+
#define INC_SP140_UTILITIES_H_
3+
4+
#include <Arduino.h>
5+
6+
// Function to get unique chip ID
7+
String chipId();
88

99
// Definitions for main rainbow colors in WRGB format for NeoPixel.
1010
// The 32-bit color value is WRGB. W (White) is ignored for RGB pixels.
1111
// The next bytes are R (Red), G (Green), and B (Blue).
1212
// For example, YELLOW is 0x00FFFF00, with FF for Red and Green, and 00 for Blue.
1313

14-
#define LED_RED 0x00FF0000
15-
#define LED_ORANGE 0x00FF7F00
16-
#define LED_YELLOW 0x00FFFF00
17-
#define LED_GREEN 0x0000FF00
18-
#define LED_BLUE 0x000000FF
19-
#define LED_INDIGO 0x004B0082
20-
#define LED_VIOLET 0x008000FF
14+
#define NEOPIXEL_RED 0x00FF0000
15+
#define NEOPIXEL_ORANGE 0x00FF7F00
16+
#define NEOPIXEL_YELLOW 0x00FFFF00
17+
#define NEOPIXEL_GREEN 0x0000FF00
18+
#define NEOPIXEL_BLUE 0x000000FF
19+
#define NEOPIXEL_INDIGO 0x004B0082
20+
#define NEOPIXEL_VIOLET 0x008000FF
2121

2222
#endif // INC_SP140_UTILITIES_H_

platformio.ini

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
[platformio]
1212
lib_dir = libraries
1313
include_dir = inc
14+
src_dir = src/sp140
1415
default_envs =
1516
OpenPPG-CESP32S3-CAN-SP140
1617

@@ -42,6 +43,7 @@ build_flags =
4243
-D CORE_DEBUG_LEVEL=2
4344
-D CONFIG_ARDUINO_LOOP_STACK_SIZE=8192
4445
-Wno-error=format
46+
-Wno-error=int-in-bool-context
4547
;-D BLE_PAIR_ON_BOOT
4648
build_type = debug
4749
debug_speed = 12000
@@ -58,7 +60,7 @@ lib_deps =
5860
adafruit/Adafruit CAN@0.2.3
5961
adafruit/Adafruit MCP2515@0.2.1
6062
https://github.com/rlogiacco/CircularBuffer@1.4.0
61-
https://github.com/openppg/SINE-ESC-CAN#8caa93996b5d000fe10ca5265bd1c472dfdf885b
63+
https://github.com/openppg/SINE-ESC-CAN#03045012a838c566b34bc0ae00cef457dd6d04d2
6264
https://github.com/openppg/ANT-BMS-CAN#fd54852bc6f1c9608e37af9ca7c13ea4135c095b
6365
lvgl/lvgl@^9.5.0
6466
h2zero/NimBLE-Arduino@^2.3.9
@@ -105,9 +107,9 @@ build_flags =
105107
-std=c++17
106108
build_src_filter =
107109
-<*>
108-
+<sp140/lvgl/lvgl_main_screen.cpp>
109-
+<sp140/lvgl/lvgl_updates.cpp>
110-
+<sp140/lvgl/lvgl_alerts.cpp>
110+
+<lvgl/lvgl_main_screen.cpp>
111+
+<lvgl/lvgl_updates.cpp>
112+
+<lvgl/lvgl_alerts.cpp>
111113
test_filter = test_screenshots
112114
lib_deps =
113115
lvgl/lvgl@^9.5.0

src/sp140/esc.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,93 @@ bool setupTWAI() {
258258
return true;
259259
}
260260

261+
/**
262+
* Send an LED control sequence to the ESC
263+
* @param ledData Array of packed LED entries (use SineEsc::makeLedControlEntry)
264+
* @param ledNum Number of entries (max 10)
265+
*/
266+
void setESCLedControl(const uint16_t *ledData, uint8_t ledNum) {
267+
if (!escTwaiInitialized) return;
268+
esc.setLedControl(ledData, ledNum);
269+
}
270+
271+
/**
272+
* Send a motor beep sequence to the ESC
273+
* @param beepData Raw byte array of 3-byte entries (tone, duration_10ms, volume)
274+
* @param beepDataLen Length of beepData in bytes (entries * 3)
275+
* @param beepNum Number of beep entries
276+
*/
277+
void setESCMotorSound(const uint8_t *beepData, uint8_t beepDataLen, uint8_t beepNum) {
278+
if (!escTwaiInitialized) return;
279+
esc.setMotorSound(beepData, beepDataLen, beepNum);
280+
}
281+
282+
// Helper to pack a beep entry into 3 bytes in a buffer
283+
// tone: 0-9, duration: in 10ms units, volume: 0-100
284+
static void packBeepEntry(uint8_t *buf, uint8_t tone, uint8_t duration10ms, uint8_t volume) {
285+
buf[0] = tone;
286+
buf[1] = duration10ms;
287+
buf[2] = volume;
288+
}
289+
290+
/**
291+
* Play ascending arm beep on ESC motor (matches hand controller: C7 -> E7)
292+
* Uses two tones at 100ms each, ascending pitch
293+
*/
294+
void escMotorBeepArm() {
295+
uint8_t beepData[6];
296+
packBeepEntry(&beepData[0], 3, 10, 50); // lower tone, 100ms, 50% vol
297+
packBeepEntry(&beepData[3], 6, 10, 50); // higher tone, 100ms, 50% vol
298+
setESCMotorSound(beepData, 6, 2);
299+
USBSerial.println("ESC: arm beep sent");
300+
}
301+
302+
/**
303+
* Play descending disarm beep on ESC motor (matches hand controller: E7 -> C7)
304+
* Uses two tones at 100ms each, descending pitch
305+
*/
306+
void escMotorBeepDisarm() {
307+
uint8_t beepData[6];
308+
packBeepEntry(&beepData[0], 6, 10, 50); // higher tone, 100ms, 50% vol
309+
packBeepEntry(&beepData[3], 3, 10, 50); // lower tone, 100ms, 50% vol
310+
setESCMotorSound(beepData, 6, 2);
311+
USBSerial.println("ESC: disarm beep sent");
312+
}
313+
314+
// FreeRTOS task: aviation strobe pattern
315+
// Quick red pulse, quick green pulse, then off for ~1.5s, repeat
316+
static void escLedStrobeTask(void *pvParameters) {
317+
// Wait for ESC connection before starting
318+
vTaskDelay(pdMS_TO_TICKS(3000));
319+
320+
const uint16_t pattern[] = {
321+
SineEsc::makeLedControlEntry(SineEsc::LED_RED, 1), // red flash 50ms
322+
SineEsc::makeLedControlEntry(SineEsc::LED_OFF, 2), // gap 100ms
323+
SineEsc::makeLedControlEntry(SineEsc::LED_GREEN, 1), // green flash 50ms
324+
SineEsc::makeLedControlEntry(SineEsc::LED_OFF, 30), // off 1500ms
325+
};
326+
327+
USBSerial.println("ESC LED aviation strobe started");
328+
329+
for (;;) {
330+
setESCLedControl(pattern, 4);
331+
USBSerial.println("ESC LED: sent aviation strobe");
332+
// Wait for full cycle (50 + 100 + 50 + 1500 = 1700ms) before resending
333+
vTaskDelay(pdMS_TO_TICKS(1700));
334+
}
335+
}
336+
337+
static TaskHandle_t escLedStrobeTaskHandle = NULL;
338+
339+
/**
340+
* Start the ESC LED strobe test pattern
341+
* Creates a FreeRTOS task that repeatedly sends the strobe sequence
342+
*/
343+
void startESCLedStrobeTest() {
344+
if (escLedStrobeTaskHandle != NULL) return; // already running
345+
xTaskCreate(escLedStrobeTask, "ESCLedTest", 2048, NULL, 1, &escLedStrobeTaskHandle);
346+
}
347+
261348
/**
262349
* Debug function to dump ESC throttle response data to serial
263350
* @param res Pointer to the throttle response structure from ESC

src/sp140/main.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ UnifiedBatteryData unifiedBatteryData = {0.0f, 0.0f, 0.0f, 0.0f}; // volts, amp
107107
// Throttle PWM smoothing buffer is managed in throttle.cpp
108108

109109
Adafruit_NeoPixel pixels(1, 21, NEO_GRB + NEO_KHZ800);
110-
uint32_t led_color = LED_RED; // current LED color
110+
uint32_t led_color = NEOPIXEL_RED; // current LED color
111111

112112
// Global variable for device state
113113
volatile DeviceState currentState = DISARMED;
@@ -735,7 +735,7 @@ void setup() {
735735
initVibeMotor();
736736
}
737737

738-
setLEDColor(LED_YELLOW); // Indicate boot in progress
738+
setLEDColor(NEOPIXEL_YELLOW); // Indicate boot in progress
739739

740740
// SPI bus (shared between display and BMS CAN)
741741
setupSPI(board_config);
@@ -766,7 +766,7 @@ void setup() {
766766
perfModeSwitch();
767767
}
768768

769-
setLEDColor(LED_GREEN);
769+
setLEDColor(NEOPIXEL_GREEN);
770770

771771
// Show splash screen (blocking)
772772
if (xSemaphoreTake(lvglMutex, portMAX_DELAY) == pdTRUE) {
@@ -815,6 +815,9 @@ void setup() {
815815
// =========================================================================
816816
setupTasks();
817817

818+
// Start ESC LED strobe test
819+
startESCLedStrobeTest();
820+
818821
// =========================================================================
819822
// PHASE 7: Start External Interfaces
820823
// =========================================================================
@@ -968,6 +971,7 @@ void resumeLEDTask() {
968971
void runDisarmAlert() {
969972
u_int16_t disarm_melody[] = {2637, 2093};
970973
playMelody(disarm_melody, 2);
974+
escMotorBeepDisarm();
971975
pulseVibeMotor();
972976
}
973977

@@ -1204,6 +1208,7 @@ bool armSystem() {
12041208
vTaskSuspend(blinkLEDTaskHandle);
12051209
setLEDs(HIGH); // solid LED while armed
12061210
playMelody(arm_melody, 2);
1211+
escMotorBeepArm();
12071212
// runVibePattern(arm_vibes, 7);
12081213
pulseVibeMotor(); // Ensure this is the active call
12091214
return true;

0 commit comments

Comments
 (0)