Skip to content

Commit 278a329

Browse files
committed
Refactor ESC LED/tone handling and status lights
Introduce EscStatusLightMode and new APIs to request ESC status light mode and queue motor beeps; centralize ESC LED/tone logic in esc.cpp (syncEscOutputs, sendEscStatusLight, pending tone queue) and call it from readESCTelemetry. Rename and simplify motor-sound API (setESCMotorSound signature changed) and replace immediate beep/LED task functions with queue/request versions (startESCLedStrobeTest, escMotorBeepArm/Disarm removed). Rename NEOPIXEL_* color macros to STATUS_LED_* and update usages in main.cpp. Update main to request ESC status light mode based on connection, battery/OTA/state and to queue arm/disarm beeps. Also bump SINE-ESC-CAN dependency reference in platformio.ini to a newer commit.
1 parent 4c4311c commit 278a329

5 files changed

Lines changed: 214 additions & 136 deletions

File tree

inc/sp140/esc.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ inline bool isMotorTempValidC(float tempC) {
1515
#include <SineEsc.h>
1616
#include <CanardAdapter.h>
1717

18+
enum class EscStatusLightMode : uint8_t {
19+
OFF = 0,
20+
READY,
21+
FLIGHT,
22+
CAUTION,
23+
};
24+
1825
void initESC();
1926
void setESCThrottle(int throttlePWM);
2027
void readESCTelemetry();
@@ -60,12 +67,12 @@ bool hasBootloaderBadError(uint16_t errorCode);
6067

6168
// ESC LED control
6269
void setESCLedControl(const uint16_t *ledData, uint8_t ledNum);
63-
void startESCLedStrobeTest();
70+
void requestEscStatusLightMode(EscStatusLightMode mode);
6471

6572
// ESC motor beep
66-
void setESCMotorSound(const uint8_t *beepData, uint8_t beepDataLen, uint8_t beepNum);
67-
void escMotorBeepArm();
68-
void escMotorBeepDisarm();
73+
void setESCMotorSound(const uint8_t *beepData, uint8_t beepNum);
74+
void queueEscMotorBeepArm();
75+
void queueEscMotorBeepDisarm();
6976

7077
// for debugging
7178
void dumpThrottleResponse(const sine_esc_SetThrottleSettings2Response *res);

inc/sp140/utilities.h

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,12 @@
66
// Function to get unique chip ID
77
String chipId();
88

9-
// Definitions for main rainbow colors in WRGB format for NeoPixel.
10-
// The 32-bit color value is WRGB. W (White) is ignored for RGB pixels.
11-
// The next bytes are R (Red), G (Green), and B (Blue).
12-
// For example, YELLOW is 0x00FFFF00, with FF for Red and Green, and 00 for Blue.
13-
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
21-
22-
#endif // INC_SP140_UTILITIES_H_
9+
// Definitions for the controller status LED colors in WRGB format.
10+
// Keep this limited to the status colors we actually use so it does not imply
11+
// ESC protocol colors that the SINE library does not support.
12+
13+
#define STATUS_LED_RED 0x00FF0000
14+
#define STATUS_LED_YELLOW 0x00FFFF00
15+
#define STATUS_LED_GREEN 0x0000FF00
16+
17+
#endif // INC_SP140_UTILITIES_H_

platformio.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ lib_deps =
6060
adafruit/Adafruit CAN@0.2.3
6161
adafruit/Adafruit MCP2515@0.2.1
6262
https://github.com/rlogiacco/CircularBuffer@1.4.0
63-
https://github.com/openppg/SINE-ESC-CAN#03045012a838c566b34bc0ae00cef457dd6d04d2
63+
https://github.com/openppg/SINE-ESC-CAN#2ab56a4e5b52e4456317c8ee3e3d802b232c6148
6464
https://github.com/openppg/ANT-BMS-CAN#fd54852bc6f1c9608e37af9ca7c13ea4135c095b
6565
lvgl/lvgl@^9.5.0
6666
h2zero/NimBLE-Arduino@^2.3.9

src/sp140/esc.cpp

Lines changed: 167 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,141 @@
1111

1212
#define TELEMETRY_TIMEOUT_MS 50 // Threshold to determine stale ESC telemetry in ms
1313

14-
static CanardAdapter adapter;
15-
static uint8_t memory_pool[1024] __attribute__((aligned(8)));
16-
static SineEsc esc(adapter);
17-
static unsigned long lastSuccessfulCommTimeMs = 0; // Store millis() time of last successful ESC comm
18-
// Flag set by requestEscHardwareInfo() (may be called from BLE task),
19-
// consumed safely inside readESCTelemetry() on the throttle task.
20-
static volatile bool s_hwInfoRequested = false;
21-
22-
// ESC runtime accumulation — unwraps the uint16 time_10ms counter (~10.9 min period)
23-
// Unsigned subtraction naturally handles wrap: (uint16_t)(current - last) is correct even across rollover.
24-
static uint16_t sEscLastTime10ms = 0;
25-
static uint32_t sEscAccumulatedRuntimeMs = 0;
26-
static bool sEscFirstUpdate = true;
27-
28-
29-
STR_ESC_TELEMETRY_140 escTelemetryData = {
30-
.escState = TelemetryState::NOT_CONNECTED,
31-
.running_error = 0,
14+
static CanardAdapter adapter;
15+
static uint8_t memory_pool[1024] __attribute__((aligned(8)));
16+
static SineEsc esc(adapter);
17+
static unsigned long lastSuccessfulCommTimeMs = 0; // Store millis() time of last successful ESC comm
18+
// Flag set by requestEscHardwareInfo() (may be called from BLE task),
19+
// consumed safely inside readESCTelemetry() on the throttle task.
20+
static volatile bool s_hwInfoRequested = false;
21+
22+
enum class PendingEscTone : uint8_t {
23+
NONE = 0,
24+
ARM,
25+
DISARM,
26+
};
27+
28+
static volatile EscStatusLightMode sRequestedStatusLightMode =
29+
EscStatusLightMode::OFF;
30+
static EscStatusLightMode sLastSentStatusLightMode = EscStatusLightMode::OFF;
31+
static unsigned long sLastStatusLightSendMs = 0;
32+
static bool sHaveSentStatusLight = false;
33+
static volatile PendingEscTone sPendingEscTone = PendingEscTone::NONE;
34+
35+
// ESC runtime accumulation — unwraps the uint16 time_10ms counter (~10.9 min period)
36+
// Unsigned subtraction naturally handles wrap: (uint16_t)(current - last) is correct even across rollover.
37+
static uint16_t sEscLastTime10ms = 0;
38+
static uint32_t sEscAccumulatedRuntimeMs = 0;
39+
static bool sEscFirstUpdate = true;
40+
41+
namespace {
42+
43+
constexpr uint8_t kEscToneVolumePct = 50;
44+
constexpr uint8_t kEscToneDuration10ms = 10;
45+
46+
void buildEscMotorTone(uint8_t* out, PendingEscTone tone) {
47+
if (tone == PendingEscTone::ARM) {
48+
SineEsc::makeBeepEntry(&out[0], 3, kEscToneDuration10ms, kEscToneVolumePct);
49+
SineEsc::makeBeepEntry(&out[3], 6, kEscToneDuration10ms, kEscToneVolumePct);
50+
} else {
51+
SineEsc::makeBeepEntry(&out[0], 6, kEscToneDuration10ms, kEscToneVolumePct);
52+
SineEsc::makeBeepEntry(&out[3], 3, kEscToneDuration10ms, kEscToneVolumePct);
53+
}
54+
}
55+
56+
unsigned long escStatusLightRefreshMs(EscStatusLightMode mode) {
57+
switch (mode) {
58+
case EscStatusLightMode::FLIGHT:
59+
return 1700;
60+
case EscStatusLightMode::READY:
61+
case EscStatusLightMode::CAUTION:
62+
return 1000;
63+
case EscStatusLightMode::OFF:
64+
default:
65+
return 0;
66+
}
67+
}
68+
69+
void sendEscStatusLight(EscStatusLightMode mode) {
70+
switch (mode) {
71+
case EscStatusLightMode::READY: {
72+
const uint16_t pattern[] = {
73+
SineEsc::makeLedControlEntry(SineEsc::LED_GREEN_BREATH, 20),
74+
};
75+
esc.setLedControl(pattern, 1);
76+
break;
77+
}
78+
case EscStatusLightMode::FLIGHT: {
79+
const uint16_t pattern[] = {
80+
SineEsc::makeLedControlEntry(SineEsc::LED_GREEN, 1),
81+
SineEsc::makeLedControlEntry(SineEsc::LED_OFF, 2),
82+
SineEsc::makeLedControlEntry(SineEsc::LED_GREEN, 1),
83+
SineEsc::makeLedControlEntry(SineEsc::LED_OFF, 30),
84+
};
85+
esc.setLedControl(pattern, 4);
86+
break;
87+
}
88+
case EscStatusLightMode::CAUTION: {
89+
const uint16_t pattern[] = {
90+
SineEsc::makeLedControlEntry(SineEsc::LED_YELLOW_BREATH, 14),
91+
};
92+
esc.setLedControl(pattern, 1);
93+
break;
94+
}
95+
case EscStatusLightMode::OFF:
96+
default: {
97+
const uint16_t pattern[] = {
98+
SineEsc::makeLedControlEntry(SineEsc::LED_OFF, 20),
99+
};
100+
esc.setLedControl(pattern, 1);
101+
break;
102+
}
103+
}
104+
}
105+
106+
void syncEscOutputs() {
107+
const bool escConnected =
108+
escTwaiInitialized &&
109+
escTelemetryData.escState == TelemetryState::CONNECTED;
110+
111+
if (!escConnected) {
112+
sPendingEscTone = PendingEscTone::NONE;
113+
sHaveSentStatusLight = false;
114+
sLastSentStatusLightMode = EscStatusLightMode::OFF;
115+
sLastStatusLightSendMs = 0;
116+
return;
117+
}
118+
119+
const PendingEscTone pendingTone = sPendingEscTone;
120+
if (pendingTone != PendingEscTone::NONE) {
121+
uint8_t beepData[6];
122+
buildEscMotorTone(beepData, pendingTone);
123+
esc.setMotorSound(beepData, 2);
124+
sPendingEscTone = PendingEscTone::NONE;
125+
}
126+
127+
const EscStatusLightMode requestedMode = sRequestedStatusLightMode;
128+
const unsigned long now = millis();
129+
const bool needsRefresh =
130+
sHaveSentStatusLight &&
131+
escStatusLightRefreshMs(requestedMode) > 0 &&
132+
(now - sLastStatusLightSendMs) >= escStatusLightRefreshMs(requestedMode);
133+
134+
if (!sHaveSentStatusLight || requestedMode != sLastSentStatusLightMode ||
135+
needsRefresh) {
136+
sendEscStatusLight(requestedMode);
137+
sLastSentStatusLightMode = requestedMode;
138+
sLastStatusLightSendMs = now;
139+
sHaveSentStatusLight = true;
140+
}
141+
}
142+
143+
} // namespace
144+
145+
146+
STR_ESC_TELEMETRY_140 escTelemetryData = {
147+
.escState = TelemetryState::NOT_CONNECTED,
148+
.running_error = 0,
32149
.selfcheck_error = 0
33150
};
34151

@@ -174,16 +291,17 @@ void readESCTelemetry() {
174291
}
175292

176293
// Populate static hardware info whenever available (comes in once after boot)
177-
if (model.hasGetHardwareInfoResponse) {
178-
const sine_esc_GetHwInfoResponse *hw = &model.getHardwareInfoResponse;
179-
escTelemetryData.hardware_id = hw->hardware_id;
180-
escTelemetryData.fw_version = hw->app_version;
181-
escTelemetryData.bootloader_version = hw->bootloader_version;
182-
memcpy(escTelemetryData.sn_code, hw->sn_code, sizeof(escTelemetryData.sn_code));
183-
}
184-
185-
adapter.processTxRxOnce(); // Process CAN messages
186-
}
294+
if (model.hasGetHardwareInfoResponse) {
295+
const sine_esc_GetHwInfoResponse *hw = &model.getHardwareInfoResponse;
296+
escTelemetryData.hardware_id = hw->hardware_id;
297+
escTelemetryData.fw_version = hw->app_version;
298+
escTelemetryData.bootloader_version = hw->bootloader_version;
299+
memcpy(escTelemetryData.sn_code, hw->sn_code, sizeof(escTelemetryData.sn_code));
300+
}
301+
302+
syncEscOutputs();
303+
adapter.processTxRxOnce(); // Process CAN messages
304+
}
187305

188306
/**
189307
* Request ESC hardware info (HW ID, FW version, bootloader version, serial number).
@@ -263,87 +381,27 @@ bool setupTWAI() {
263381
* @param ledData Array of packed LED entries (use SineEsc::makeLedControlEntry)
264382
* @param ledNum Number of entries (max 10)
265383
*/
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-
}
384+
void setESCLedControl(const uint16_t *ledData, uint8_t ledNum) {
385+
if (!escTwaiInitialized) return;
386+
esc.setLedControl(ledData, ledNum);
387+
}
388+
389+
void requestEscStatusLightMode(EscStatusLightMode mode) {
390+
sRequestedStatusLightMode = mode;
391+
}
392+
393+
void setESCMotorSound(const uint8_t *beepData, uint8_t beepNum) {
394+
if (!escTwaiInitialized) return;
395+
esc.setMotorSound(beepData, beepNum);
396+
}
397+
398+
void queueEscMotorBeepArm() {
399+
sPendingEscTone = PendingEscTone::ARM;
400+
}
401+
402+
void queueEscMotorBeepDisarm() {
403+
sPendingEscTone = PendingEscTone::DISARM;
404+
}
347405

348406
/**
349407
* Debug function to dump ESC throttle response data to serial

0 commit comments

Comments
 (0)