Archive STM32 firmware to legacy/stm32/: - src/, include/, lib/USB_CDC/, platformio.ini, test stubs, flash_firmware.py - test/test_battery_adc.c, test_hw_button.c, test_pid_schedule.c, test_vesc_can.c, test_can_watchdog.c - USB_CDC_BUG.md Rename: stm32_protocol → esp32_protocol, mamba_protocol → balance_protocol, stm32_cmd_node → esp32_cmd_node, stm32_cmd_params → esp32_cmd_params, stm32_cmd.launch.py → esp32_cmd.launch.py, test_stm32_protocol → test_esp32_protocol, test_stm32_cmd_node → test_esp32_cmd_node Content cleanup across all files: - Mamba F722S → ESP32-S3 BALANCE - BlackPill → ESP32-S3 IO - STM32F722/F7xx → ESP32-S3 - stm32Mode/Version/Port → esp32Mode/Version/Port - STM32 State/Mode labels → ESP32 State/Mode - Jetson Nano → Jetson Orin Nano Super - /dev/stm32 → /dev/esp32 - stm32_bridge → esp32_bridge - STM32 HAL → ESP-IDF docs/SALTYLAB.md: - Update "Drone FC Details" to describe ESP32-S3 BALANCE board (Waveshare ESP32-S3 Touch LCD 1.28) - Replace verbose "Self-Balancing Control" STM32 section with brief note pointing to SAUL-TEE-SYSTEM-REFERENCE.md TEAM.md: Update Embedded Firmware Engineer role to ESP32-S3 / ESP-IDF No new functionality — cleanup only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
141 lines
4.3 KiB
C
141 lines
4.3 KiB
C
/*
|
||
* steering_pid.c — closed-loop yaw-rate PID for differential drive (Issue #616).
|
||
*
|
||
* Converts Jetson Twist.angular.z (encoded as steer * STEER_OMEGA_SCALE deg/s)
|
||
* into a differential wheel speed offset using IMU gyro Z as feedback.
|
||
*
|
||
* Anti-windup: integral clamped to ±STEER_INTEGRAL_MAX before Ki multiply.
|
||
* Rate limiter: output changes at most STEER_RAMP_RATE_PER_MS per ms so that
|
||
* a step in steering demand cannot disturb the balance PID.
|
||
*/
|
||
|
||
#include "steering_pid.h"
|
||
#include "jlink.h"
|
||
|
||
/* ---- steering_pid_init() ---- */
|
||
void steering_pid_init(steering_pid_t *s)
|
||
{
|
||
s->target_omega_dps = 0.0f;
|
||
s->actual_omega_dps = 0.0f;
|
||
s->integral = 0.0f;
|
||
s->prev_error = 0.0f;
|
||
s->output = 0;
|
||
s->enabled = true;
|
||
/* Initialize so first send_tlm call fires immediately */
|
||
s->last_tlm_ms = (uint32_t)(-(uint32_t)(1000u / (STEER_TLM_HZ > 0u ? STEER_TLM_HZ : 1u)));
|
||
}
|
||
|
||
/* ---- steering_pid_reset() ---- */
|
||
void steering_pid_reset(steering_pid_t *s)
|
||
{
|
||
s->target_omega_dps = 0.0f;
|
||
s->integral = 0.0f;
|
||
s->prev_error = 0.0f;
|
||
s->output = 0;
|
||
}
|
||
|
||
/* ---- steering_pid_set_target() ---- */
|
||
void steering_pid_set_target(steering_pid_t *s, float omega_dps)
|
||
{
|
||
if (!s->enabled) return;
|
||
s->target_omega_dps = omega_dps;
|
||
}
|
||
|
||
/* ---- steering_pid_update() ---- */
|
||
int16_t steering_pid_update(steering_pid_t *s, float actual_omega_dps, float dt)
|
||
{
|
||
if (!s->enabled || dt <= 0.0f) {
|
||
s->output = 0;
|
||
return 0;
|
||
}
|
||
|
||
s->actual_omega_dps = actual_omega_dps;
|
||
|
||
/* PID error */
|
||
float error = s->target_omega_dps - actual_omega_dps;
|
||
|
||
/* Proportional */
|
||
float p_term = STEER_KP * error;
|
||
|
||
/* Integral with anti-windup clamp */
|
||
s->integral += error * dt;
|
||
if (s->integral > STEER_INTEGRAL_MAX) s->integral = STEER_INTEGRAL_MAX;
|
||
if (s->integral < -STEER_INTEGRAL_MAX) s->integral = -STEER_INTEGRAL_MAX;
|
||
float i_term = STEER_KI * s->integral;
|
||
|
||
/* Derivative on error (avoids setpoint kick for smooth yaw changes) */
|
||
float d_term = 0.0f;
|
||
if (dt > 0.0f) {
|
||
d_term = STEER_KD * (error - s->prev_error) / dt;
|
||
}
|
||
s->prev_error = error;
|
||
|
||
/* Sum and clamp raw output */
|
||
float raw = p_term + i_term + d_term;
|
||
if (raw > (float)STEER_OUTPUT_MAX) raw = (float)STEER_OUTPUT_MAX;
|
||
if (raw < -(float)STEER_OUTPUT_MAX) raw = -(float)STEER_OUTPUT_MAX;
|
||
|
||
/* Rate limiter: bound change per step by STEER_RAMP_RATE_PER_MS * dt */
|
||
float max_step = (float)STEER_RAMP_RATE_PER_MS * (dt * 1000.0f);
|
||
float delta = raw - (float)s->output;
|
||
if (delta > max_step) delta = max_step;
|
||
if (delta < -max_step) delta = -max_step;
|
||
|
||
float limited = (float)s->output + delta;
|
||
/* Final clamp after rate limit */
|
||
if (limited > (float)STEER_OUTPUT_MAX) limited = (float)STEER_OUTPUT_MAX;
|
||
if (limited < -(float)STEER_OUTPUT_MAX) limited = -(float)STEER_OUTPUT_MAX;
|
||
|
||
s->output = (int16_t)limited;
|
||
return s->output;
|
||
}
|
||
|
||
/* ---- steering_pid_get_output() ---- */
|
||
int16_t steering_pid_get_output(const steering_pid_t *s)
|
||
{
|
||
return s->output;
|
||
}
|
||
|
||
/* ---- steering_pid_set_enabled() ---- */
|
||
void steering_pid_set_enabled(steering_pid_t *s, bool en)
|
||
{
|
||
if (!en && s->enabled) {
|
||
/* Disabling: zero out state */
|
||
s->target_omega_dps = 0.0f;
|
||
s->integral = 0.0f;
|
||
s->prev_error = 0.0f;
|
||
s->output = 0;
|
||
}
|
||
s->enabled = en;
|
||
}
|
||
|
||
/* ---- steering_pid_send_tlm() ---- */
|
||
void steering_pid_send_tlm(const steering_pid_t *s, uint32_t now_ms)
|
||
{
|
||
if (STEER_TLM_HZ == 0u) return;
|
||
|
||
uint32_t interval_ms = 1000u / STEER_TLM_HZ;
|
||
if ((now_ms - s->last_tlm_ms) < interval_ms) return;
|
||
/* Cast away const for timestamp update — only mutable field */
|
||
((steering_pid_t *)s)->last_tlm_ms = now_ms;
|
||
|
||
jlink_tlm_steering_t tlm;
|
||
|
||
/* Scale to ×10 for 0.1 deg/s resolution, clamped to int16 range */
|
||
float t = s->target_omega_dps * 10.0f;
|
||
if (t > 32767.0f) t = 32767.0f;
|
||
if (t < -32768.0f) t = -32768.0f;
|
||
tlm.target_x10 = (int16_t)t;
|
||
|
||
float a = s->actual_omega_dps * 10.0f;
|
||
if (a > 32767.0f) a = 32767.0f;
|
||
if (a < -32768.0f) a = -32768.0f;
|
||
tlm.actual_x10 = (int16_t)a;
|
||
|
||
tlm.output = s->output;
|
||
tlm.enabled = s->enabled ? 1u : 0u;
|
||
tlm._pad = 0u;
|
||
|
||
jlink_send_steering_tlm(&tlm);
|
||
}
|