Skip to content

Commit fa6ea07

Browse files
PaulDWhiteclaude
andcommitted
Add PowerPack Flash/Test QC tool firmware
Standalone firmware for the ESP32-S3 hand controller that serves as a factory QC tool for PowerPack assemblies. Separate PlatformIO environment (PowerPack-FlashTest) with its own source tree under src/powerpack/. Features: - ESC firmware update over CAN bus (DroneCAN protocol) - Full state machine: restart -> bootloader -> chunk transfer -> verify - Embedded SE24250D V2.33.102 firmware binary (90KB, 360 chunks) - 150ms timeout with 10 retries per message - ESC configuration writer via DroneCAN param.GetSet (service ID 11) - Hand-coded encode/decode for param.GetSet and ExecuteOpcode - 97 production parameters from OpenPPG V1.11 config - Save to flash via ExecuteOpcode(SAVE) + restart - Comprehensive QC sensor validation with motor spin-up test - Phase 1: Idle checks (HW ID, FW version, temps, voltage, errors) - Phase 2: Smooth ramp to full throttle tracking peaks - Phase 3: Hold at max, record peak RPM/current/voltage - Phase 4: Ramp down, cooldown, post-run temp measurement - Evaluates: direction, peak RPM, phase current, voltage sag, temp rise, self-check errors, spin errors - Safety abort on critical faults during motor run - ST7735 display UI with progress bars and color-coded results - NeoPixel LED status (blue=waiting, yellow=working, green=pass, red=fail) - Button control: short press=config+test, long press=flash+config+test - USB serial CLI: start, config, test, status, help Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 50ef221 commit fa6ea07

13 files changed

Lines changed: 8455 additions & 0 deletions

inc/powerpack/config.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#ifndef POWERPACK_CONFIG_H_
2+
#define POWERPACK_CONFIG_H_
3+
4+
// CAN bus pins (same hardware as SP140 hand controller)
5+
#define ESC_TX_PIN 2
6+
#define ESC_RX_PIN 3
7+
8+
// Local CAN node ID for this tool
9+
#define LOCAL_NODE_ID 0x01
10+
11+
// Default ESC node ID
12+
#define ESC_NODE_ID 0x20
13+
14+
// Firmware update settings
15+
#define FW_UPDATE_TIMEOUT_MS 150
16+
#define FW_UPDATE_MAX_RETRIES 10
17+
#define FW_CHUNK_SIZE 256
18+
19+
// QC test thresholds
20+
#define QC_TEMP_MIN_C -10.0f // Minimum reasonable ambient temp
21+
#define QC_TEMP_MAX_C 80.0f // Max temp for idle ESC
22+
#define QC_MOTOR_TEMP_VALID_MIN_C -20.0f
23+
#define QC_MOTOR_TEMP_VALID_MAX_C 140.0f
24+
#define QC_VBUS_MIN_V 48.0f
25+
#define QC_VBUS_MAX_V 108.0f
26+
27+
// Display (ST7735 via SPI)
28+
#define DISPLAY_CS_PIN 10
29+
#define DISPLAY_DC_PIN 14
30+
#define DISPLAY_RST_PIN 15
31+
#define SPI_MOSI_PIN 11
32+
#define SPI_MISO_PIN 13
33+
#define SPI_SCLK_PIN 12
34+
35+
// Button (GPIO 1 on the SP140 hand controller hardware)
36+
#define BUTTON_PIN 1
37+
38+
// NeoPixel LED
39+
#define NEOPIXEL_PIN 21
40+
41+
// Buzzer
42+
#define BUZZER_PIN 8
43+
44+
// Vibration motor
45+
#define VIBE_PIN 46
46+
47+
#endif // POWERPACK_CONFIG_H_

inc/powerpack/dronecan_param.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#ifndef POWERPACK_DRONECAN_PARAM_H_
2+
#define POWERPACK_DRONECAN_PARAM_H_
3+
4+
// Hand-coded DroneCAN param.GetSet (ID 11) and param.ExecuteOpcode (ID 10)
5+
// encode/decode. These are not generated by the DSDL compiler in the SINE ESC CAN
6+
// library, so we implement the subset needed for config writing.
7+
8+
#include <stdint.h>
9+
#include <stdbool.h>
10+
#include <string.h>
11+
#include <canard.h>
12+
13+
// =============================================================================
14+
// param.Value union (tag is 3 bits)
15+
// =============================================================================
16+
// Tag values:
17+
// 0 = empty
18+
// 1 = integer_value (int64)
19+
// 2 = real_value (float32)
20+
// 3 = boolean_value (uint8)
21+
// 4 = string_value (uint8[<=128])
22+
23+
#define PARAM_VALUE_TAG_EMPTY 0
24+
#define PARAM_VALUE_TAG_INTEGER 1
25+
#define PARAM_VALUE_TAG_REAL 2
26+
#define PARAM_VALUE_TAG_BOOLEAN 3
27+
#define PARAM_VALUE_TAG_STRING 4
28+
29+
struct ParamValue {
30+
uint8_t tag; // 3-bit union tag
31+
union {
32+
int64_t integer_value;
33+
float real_value;
34+
uint8_t boolean_value;
35+
struct {
36+
uint8_t data[128];
37+
uint8_t len;
38+
} string_value;
39+
};
40+
};
41+
42+
// =============================================================================
43+
// param.GetSet (Service ID 11)
44+
// Signature: 0xA7B622F939D1A4D5
45+
// =============================================================================
46+
#define UAVCAN_PARAM_GETSET_ID 11
47+
#define UAVCAN_PARAM_GETSET_SIGNATURE 0xA7B622F939D1A4D5ULL
48+
49+
struct ParamGetSetRequest {
50+
uint16_t index; // uint13 (we use uint16, top 3 bits ignored)
51+
ParamValue value; // Value to set (empty = read only)
52+
char name[93]; // uint8[<=92] name + null terminator
53+
uint8_t name_len;
54+
};
55+
56+
struct ParamGetSetResponse {
57+
ParamValue value; // Actual value after set
58+
ParamValue default_value;
59+
char name[93];
60+
uint8_t name_len;
61+
};
62+
63+
// =============================================================================
64+
// param.ExecuteOpcode (Service ID 10)
65+
// Signature: 0x3B131AC5EB69D2CDULL
66+
// =============================================================================
67+
#define UAVCAN_PARAM_EXECUTEOPCODE_ID 10
68+
#define UAVCAN_PARAM_EXECUTEOPCODE_SIGNATURE 0x3B131AC5EB69D2CDULL
69+
70+
#define PARAM_OPCODE_SAVE 0
71+
#define PARAM_OPCODE_ERASE 1
72+
73+
struct ParamExecuteOpcodeRequest {
74+
uint8_t opcode;
75+
int64_t argument; // int48 - reserved, keep zero
76+
};
77+
78+
struct ParamExecuteOpcodeResponse {
79+
int64_t argument; // int48
80+
bool ok;
81+
};
82+
83+
// =============================================================================
84+
// Encode/Decode functions
85+
// =============================================================================
86+
87+
// Encode a ParamValue into a buffer at bit offset.
88+
// Returns number of bits written.
89+
uint32_t paramValue_encode(const ParamValue* val, uint8_t* buffer, uint32_t bit_ofs);
90+
91+
// Decode a ParamValue from a transfer at bit offset.
92+
// Returns number of bits consumed.
93+
uint32_t paramValue_decode(const CanardRxTransfer* transfer, uint32_t bit_ofs, ParamValue* val);
94+
95+
// Encode param.GetSet request
96+
uint32_t paramGetSetRequest_encode(const ParamGetSetRequest* req, uint8_t* buffer);
97+
98+
// Decode param.GetSet response
99+
bool paramGetSetResponse_decode(const CanardRxTransfer* transfer, ParamGetSetResponse* res);
100+
101+
// Encode param.ExecuteOpcode request
102+
uint32_t paramExecuteOpcodeRequest_encode(const ParamExecuteOpcodeRequest* req, uint8_t* buffer);
103+
104+
// Decode param.ExecuteOpcode response
105+
bool paramExecuteOpcodeResponse_decode(const CanardRxTransfer* transfer, ParamExecuteOpcodeResponse* res);
106+
107+
#endif // POWERPACK_DRONECAN_PARAM_H_

inc/powerpack/esc_config.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef POWERPACK_ESC_CONFIG_H_
2+
#define POWERPACK_ESC_CONFIG_H_
3+
4+
#include <stdint.h>
5+
6+
enum class ConfigState : uint8_t {
7+
IDLE,
8+
WRITING_PARAMS,
9+
SAVING, // ExecuteOpcode SAVE
10+
RESTARTING, // RestartNode after save
11+
SUCCESS,
12+
FAILED
13+
};
14+
15+
struct ConfigProgress {
16+
ConfigState state;
17+
uint16_t currentParam;
18+
uint16_t totalParams;
19+
const char* currentParamName;
20+
const char* statusMsg;
21+
uint8_t percentComplete;
22+
};
23+
24+
// Initialize the config writer (call after CAN is up)
25+
void escConfigInit();
26+
27+
// Start writing the production config to the ESC.
28+
// Uses the embedded config table.
29+
void escConfigStart();
30+
31+
// Drive the config writer state machine. Returns true while running.
32+
bool escConfigTick();
33+
34+
// Get current progress
35+
const ConfigProgress& escConfigGetProgress();
36+
37+
bool escConfigSucceeded();
38+
39+
#endif // POWERPACK_ESC_CONFIG_H_

inc/powerpack/esc_flasher.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#ifndef POWERPACK_ESC_FLASHER_H_
2+
#define POWERPACK_ESC_FLASHER_H_
3+
4+
#include <stdint.h>
5+
6+
enum class FlashState : uint8_t {
7+
IDLE,
8+
RESTARTING, // Sent RestartNode, waiting for ESC to reboot
9+
CHECKING_BOOT, // Sent GetBootStatus, waiting for bootloader confirmation
10+
STARTING, // Sent StartFwUpgrade with header info
11+
SENDING_DATA, // Sending firmware chunks via SendFwData
12+
ENDING, // Sent EndFwUpgrade, waiting for confirmation
13+
SUCCESS,
14+
FAILED
15+
};
16+
17+
struct FlashProgress {
18+
FlashState state;
19+
uint16_t currentChunk;
20+
uint16_t totalChunks;
21+
uint8_t retryCount;
22+
const char* statusMsg;
23+
uint8_t percentComplete; // 0-100
24+
};
25+
26+
// Initialize the flasher module (call after CAN is up)
27+
void escFlasherInit();
28+
29+
// Start the firmware update process.
30+
// fwData points to the complete firmware file (including 32-byte header).
31+
// fwLen is the total file size in bytes.
32+
void escFlasherStart(const uint8_t* fwData, uint32_t fwLen);
33+
34+
// Call this repeatedly from a task/loop to drive the state machine.
35+
// Returns true while the process is still running.
36+
bool escFlasherTick();
37+
38+
// Get current progress for display
39+
const FlashProgress& escFlasherGetProgress();
40+
41+
// Check if flasher is idle (not running)
42+
bool escFlasherIsIdle();
43+
44+
// Check if last flash was successful
45+
bool escFlasherSucceeded();
46+
47+
#endif // POWERPACK_ESC_FLASHER_H_

inc/powerpack/param_table.h

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#ifndef POWERPACK_PARAM_TABLE_H_
2+
#define POWERPACK_PARAM_TABLE_H_
3+
4+
#include <stdint.h>
5+
6+
// Production configuration parameters from "OpenPPG V1.11 Config Shipping"
7+
// Each entry: { param_name, integer_value }
8+
// String and array params handled separately.
9+
10+
struct ParamEntry {
11+
const char* name;
12+
int64_t value;
13+
};
14+
15+
// Production config - derived from the JSON config file.
16+
// Array/curve params are excluded here (handled separately if needed).
17+
static const ParamEntry PRODUCTION_CONFIG[] = {
18+
// Board params
19+
{"node_id", 32},
20+
{"can_baudrate", 0}, // 0 = 1000K
21+
{"uart_baudrate", 0}, // 0 = 115200
22+
{"vbus_upper_limit", 108},
23+
{"vbus_lower_limit", 48},
24+
{"over_voltage_threshold", 105},
25+
{"overt_voltage_tolerance", 6},
26+
{"overt_voltage_tolerance2", 8},
27+
{"dcbus_lpf_hz", 500},
28+
{"led_color", 1}, // 1 = Green
29+
{"standby_led_type", 1}, // 1 = Always On
30+
{"rs485_led_port", 0}, // 0 = Led
31+
32+
// Motor params
33+
{"motor_pole_pairs", 31},
34+
{"motor_rs", 147},
35+
{"motor_ld", 122},
36+
{"motor_lq", 122},
37+
{"ls_coef", 100},
38+
{"motor_kv", 340},
39+
{"motor_max_current", 2200},
40+
{"carrier_freq_khz", 16},
41+
{"current_loop_coef", 25},
42+
43+
// Control params
44+
{"ctrl_input_type", 0}, // 0 = Pwm+CommPwm
45+
{"throttle_recover_check", 1}, // 1 = Enable
46+
{"direction", 1}, // 1 = Inversion
47+
{"idling_speed_rpm", 60},
48+
{"motor_max_rpm", 2500},
49+
{"stop_type", 0}, // 0 = Free Stop
50+
{"min_startup_speed_enable", 0}, // Disable
51+
{"min_startup_speed", 200},
52+
53+
// Speed params
54+
{"idling_acc_krpmps", 300},
55+
{"max_acc_krpmps", 300},
56+
{"max_dec_krpmps", 300},
57+
{"max_accel_current", 2100},
58+
{"max_decel_current", -15},
59+
{"acc_type", 0}, // 0 = Normal Mode
60+
{"idling_acc_speed_rpm", 1},
61+
{"fast_stop_decel_krpmps", 1000},
62+
{"fast_stop_decel_current", -50},
63+
{"speed_ref_lpf", 650},
64+
{"f_speed_loop_kp", 300},
65+
{"f_speed_loop_ki", 59},
66+
{"s_speed_loop_kp", 150},
67+
{"s_speed_loop_ki", 29},
68+
{"speed_loop_anti_windup_enable", 0},
69+
{"speed_loop_anti_windup_coef", 1},
70+
71+
// Throttle params
72+
{"ppm_period_tolerance", 1000},
73+
{"ppm_lost_time_ms", 300},
74+
{"hyst_ppm", 100},
75+
{"throttle_type", 0}, // 0 = Normal
76+
{"ppm_curve_type", 1}, // 1 = Linear Speed
77+
{"normal_pwm_start", 10500}, // 1050us in 0.1us units
78+
{"normal_pwm_end", 19500}, // 1950us
79+
{"normal_pwm_curve_comp_coef", 3000},
80+
81+
// Advance params
82+
{"noload_detect_enable", 1},
83+
{"load_dectect_speed_rpm", 750},
84+
{"load_detect_current", 80},
85+
{"field_weakening_enable", 1},
86+
{"field_weakening_max_current", -200},
87+
{"acc_comp_enable", 1},
88+
{"acc_comp_coef", 30},
89+
{"rs_temp_comp_enable", 1},
90+
{"rs_temp_comp_coef", 100},
91+
{"motor_sound_enable", 1},
92+
{"pndef_motor_sound_volume", 3},
93+
94+
// Observer params
95+
{"observer_type", 0},
96+
{"observer_coef", 20},
97+
{"observer_filter_freq", 1000},
98+
99+
// Protect params
100+
{"stall_enable", 1},
101+
{"stall_idle_speed_current", 400},
102+
{"stall_full_speed_current", 2200},
103+
{"stall_protected_duration", 1000},
104+
{"stall_count", 2},
105+
{"stall_recover_enable", 0},
106+
{"overcurrent_count", 2},
107+
{"high_temp_protect_enable", 1},
108+
{"mos_high_temp_limit_1", 110},
109+
{"mos_high_temp_limit_2", 140},
110+
{"cap_high_temp_limit_1", 95},
111+
{"cap_high_temp_limit_2", 105},
112+
{"mcu_high_temp_limit_1", 100},
113+
{"mcu_high_temp_limit_2", 125},
114+
{"low_voltage_protect_enable", 0},
115+
{"low_protect_voltage_limit_1", 55},
116+
{"low_protect_voltage_limit_2", 35},
117+
{"low_protect_voltage_ratio", 40},
118+
{"ibus_max_current", 2000},
119+
{"ibus_limit_duration", 1000},
120+
{"max_power_limit", 25000},
121+
{"power_limit", 1},
122+
123+
// Bidirectional throttle params
124+
{"positive_ppm_start", 15000},
125+
{"positive_ppm_end", 20000},
126+
{"negative_ppm_start", 10000},
127+
{"negative_ppm_end", 15000},
128+
{"postive_pwm_curve_comp_coef", 1905},
129+
{"negative_pwm_curve_comp_coef", 1905},
130+
131+
// Position mode params
132+
{"position_mode_enable", 0},
133+
{"position_mode_enable_work_type", 0},
134+
{"position_mode_speed_loop_kp", 680},
135+
{"position_mode_speed_loop_ki", 1500},
136+
{"position_mode_speed_loop_kd", 480},
137+
{"position_loop_pid_limit_1", 300},
138+
{"position_loop_pid_limit_2", 1},
139+
{"position_loop_kp", 30},
140+
{"position_loop_ki", 10},
141+
{"position_loop_kd", 30},
142+
{"position_mode_current_loop_coef", 20},
143+
{"position_mode_max_current", 200},
144+
{"position_mode_acc_krpms", 50},
145+
{"position_mode_speed_pll_freq", 1200},
146+
};
147+
148+
static const uint16_t PRODUCTION_CONFIG_COUNT = sizeof(PRODUCTION_CONFIG) / sizeof(PRODUCTION_CONFIG[0]);
149+
150+
#endif // POWERPACK_PARAM_TABLE_H_

0 commit comments

Comments
 (0)