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>
248 lines
9.0 KiB
C
248 lines
9.0 KiB
C
#include "ultrasonic.h"
|
|
#include "stm32f7xx_hal.h"
|
|
#include "config.h"
|
|
|
|
/* ================================================================
|
|
* HC-SR04 Ultrasonic Sensor Parameters
|
|
* ================================================================ */
|
|
|
|
#define TRIGGER_PIN GPIO_PIN_0
|
|
#define TRIGGER_PORT GPIOA
|
|
#define ECHO_PIN GPIO_PIN_1
|
|
#define ECHO_PORT GPIOA
|
|
#define ECHO_TIM TIM1
|
|
#define ECHO_TIM_CHANNEL TIM_CHANNEL_2
|
|
|
|
/* Trigger pulse duration (10µs recommended) */
|
|
#define TRIGGER_PULSE_US 10
|
|
|
|
/* Echo timeout: max 30ms (corresponds to ~5m distance) */
|
|
#define ECHO_TIMEOUT_MS 30
|
|
|
|
/* Speed of sound: ~343 m/s @ 20°C
|
|
* Distance = (pulse_time_us / 2) / (1000000 / 343000) mm
|
|
* Distance = (pulse_time_us / 2) / 2.914 mm ≈ pulse_time_us / 5.828 mm
|
|
* Or: distance_mm = (pulse_us * 343) / 2000000 = pulse_us / 5.828
|
|
* Using approximation: distance_mm ≈ (pulse_us * 1000) / 5830
|
|
*/
|
|
#define US_TO_MM_NUMERATOR 1000
|
|
#define US_TO_MM_DENOMINATOR 5830
|
|
|
|
/* ================================================================
|
|
* Internal State Machine
|
|
* ================================================================ */
|
|
|
|
typedef struct {
|
|
UltrasonicState state;
|
|
uint32_t trigger_time_ms; /* When trigger pulse was sent */
|
|
uint32_t echo_start_ticks; /* TIM1 counter at rising edge */
|
|
uint32_t echo_end_ticks; /* TIM1 counter at falling edge */
|
|
uint32_t echo_width_us; /* Calculated pulse width in µs */
|
|
uint16_t distance_mm; /* Last measured distance */
|
|
bool last_valid; /* Was last measurement valid */
|
|
ultrasonic_callback_t callback; /* Result callback (optional) */
|
|
} UltrasonicState_t;
|
|
|
|
static UltrasonicState_t s_ultrasonic = {
|
|
.state = ULTRASONIC_IDLE,
|
|
.callback = NULL
|
|
};
|
|
|
|
static TIM_HandleTypeDef s_htim1 = {0}; /* Timer handle for IRQ handler */
|
|
|
|
/* ================================================================
|
|
* Hardware Initialization
|
|
* ================================================================ */
|
|
|
|
void ultrasonic_init(void)
|
|
{
|
|
/* Enable GPIO and timer clocks */
|
|
__HAL_RCC_GPIOA_CLK_ENABLE();
|
|
__HAL_RCC_TIM1_CLK_ENABLE();
|
|
|
|
/* Configure PA0 as trigger output (push-pull, fast slew) */
|
|
GPIO_InitTypeDef gpio_init = {0};
|
|
gpio_init.Pin = TRIGGER_PIN;
|
|
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
|
|
gpio_init.Pull = GPIO_NOPULL;
|
|
gpio_init.Speed = GPIO_SPEED_HIGH;
|
|
HAL_GPIO_Init(TRIGGER_PORT, &gpio_init);
|
|
HAL_GPIO_WritePin(TRIGGER_PORT, TRIGGER_PIN, GPIO_PIN_RESET);
|
|
|
|
/* Configure PA1 as alternate function (TIM1_CH2) */
|
|
gpio_init.Pin = ECHO_PIN;
|
|
gpio_init.Mode = GPIO_MODE_AF_PP;
|
|
gpio_init.Pull = GPIO_PULLDOWN;
|
|
gpio_init.Speed = GPIO_SPEED_HIGH;
|
|
gpio_init.Alternate = GPIO_AF1_TIM1;
|
|
HAL_GPIO_Init(ECHO_PORT, &gpio_init);
|
|
|
|
/* Configure TIM1 for input capture on PA1 (TIM1_CH2)
|
|
* Clock: 216MHz / PSC = 216 counts/µs (PSC=1 gives 1 count per ~4.6ns)
|
|
* Use PSC=216 to get 1MHz clock → 1 count = 1µs
|
|
* ARR=0xFFFF for 16-bit capture (max 65535µs ≈ 9.6m)
|
|
*/
|
|
s_htim1.Instance = ECHO_TIM;
|
|
s_htim1.Init.Prescaler = 216 - 1; /* 216MHz / 216 = 1MHz (1µs per count) */
|
|
s_htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
|
|
s_htim1.Init.Period = 0xFFFF; /* 16-bit counter */
|
|
s_htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
|
s_htim1.Init.RepetitionCounter = 0;
|
|
HAL_TIM_IC_Init(&s_htim1);
|
|
|
|
/* Configure input capture: CH2 on PA1, both rising and falling edges
|
|
* TIM1_CH2 captures on both edges to measure echo pulse width
|
|
*/
|
|
TIM_IC_InitTypeDef ic_init = {0};
|
|
ic_init.ICPolarity = TIM_ICPOLARITY_RISING; /* Start with rising edge */
|
|
ic_init.ICSelection = TIM_ICSELECTION_DIRECTTI;
|
|
ic_init.ICPrescaler = TIM_ICPSC_DIV1; /* No prescaler */
|
|
ic_init.ICFilter = 0; /* No filter */
|
|
HAL_TIM_IC_ConfigChannel(&s_htim1, &ic_init, ECHO_TIM_CHANNEL);
|
|
HAL_TIM_IC_Start_IT(&s_htim1, ECHO_TIM_CHANNEL);
|
|
|
|
/* Enable input capture interrupt */
|
|
HAL_NVIC_SetPriority(TIM1_CC_IRQn, 6, 0);
|
|
HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
|
|
|
|
/* Start the timer */
|
|
HAL_TIM_Base_Start(&s_htim1);
|
|
|
|
s_ultrasonic.state = ULTRASONIC_IDLE;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Public API
|
|
* ================================================================ */
|
|
|
|
bool ultrasonic_trigger(void)
|
|
{
|
|
/* Only trigger if not currently measuring */
|
|
if (s_ultrasonic.state != ULTRASONIC_IDLE && s_ultrasonic.state != ULTRASONIC_COMPLETE) {
|
|
return false;
|
|
}
|
|
|
|
/* Send 10µs trigger pulse on PA0 */
|
|
HAL_GPIO_WritePin(TRIGGER_PORT, TRIGGER_PIN, GPIO_PIN_SET);
|
|
|
|
/* Busy-wait for 10µs (non-ideal but simple and precise)
|
|
* At 216MHz: 1 clock = ~4.6ns, so ~2160 clocks for 10µs
|
|
*/
|
|
uint32_t start = HAL_GetTick();
|
|
while ((HAL_GetTick() - start) < 1) {
|
|
/* Wait at least 10µs — use sysclock counter for precision */
|
|
for (volatile int i = 0; i < 2200; i++) __NOP();
|
|
}
|
|
HAL_GPIO_WritePin(TRIGGER_PORT, TRIGGER_PIN, GPIO_PIN_RESET);
|
|
|
|
s_ultrasonic.state = ULTRASONIC_TRIGGERED;
|
|
s_ultrasonic.trigger_time_ms = HAL_GetTick();
|
|
s_ultrasonic.echo_start_ticks = 0;
|
|
s_ultrasonic.echo_end_ticks = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void ultrasonic_set_callback(ultrasonic_callback_t callback)
|
|
{
|
|
s_ultrasonic.callback = callback;
|
|
}
|
|
|
|
UltrasonicState ultrasonic_get_state(void)
|
|
{
|
|
return s_ultrasonic.state;
|
|
}
|
|
|
|
bool ultrasonic_get_result(uint16_t *distance_mm, bool *is_valid)
|
|
{
|
|
if (s_ultrasonic.state != ULTRASONIC_COMPLETE) {
|
|
return false;
|
|
}
|
|
|
|
if (distance_mm) *distance_mm = s_ultrasonic.distance_mm;
|
|
if (is_valid) *is_valid = s_ultrasonic.last_valid;
|
|
|
|
s_ultrasonic.state = ULTRASONIC_IDLE;
|
|
return true;
|
|
}
|
|
|
|
void ultrasonic_tick(uint32_t now_ms)
|
|
{
|
|
/* Timeout detection: if measurement takes too long, mark as error */
|
|
if (s_ultrasonic.state == ULTRASONIC_MEASURING) {
|
|
if ((now_ms - s_ultrasonic.trigger_time_ms) > ECHO_TIMEOUT_MS) {
|
|
s_ultrasonic.state = ULTRASONIC_COMPLETE;
|
|
s_ultrasonic.distance_mm = 0;
|
|
s_ultrasonic.last_valid = false;
|
|
|
|
if (s_ultrasonic.callback) {
|
|
s_ultrasonic.callback(0, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* TIM1 Input Capture Interrupt Handler
|
|
* ================================================================ */
|
|
|
|
void TIM1_CC_IRQHandler(void)
|
|
{
|
|
/* Check if capture interrupt on CH2 */
|
|
if (__HAL_TIM_GET_FLAG(&s_htim1, TIM_FLAG_CC2) != RESET) {
|
|
__HAL_TIM_CLEAR_FLAG(&s_htim1, TIM_FLAG_CC2);
|
|
|
|
uint32_t capture_value = HAL_TIM_ReadCapturedValue(&s_htim1, ECHO_TIM_CHANNEL);
|
|
|
|
if (s_ultrasonic.state == ULTRASONIC_TRIGGERED || s_ultrasonic.state == ULTRASONIC_MEASURING) {
|
|
if (s_ultrasonic.echo_start_ticks == 0) {
|
|
/* Rising edge: mark start of echo pulse */
|
|
s_ultrasonic.echo_start_ticks = capture_value;
|
|
s_ultrasonic.state = ULTRASONIC_MEASURING;
|
|
|
|
/* Switch to falling edge detection for end of echo */
|
|
TIM_IC_InitTypeDef ic_init = {0};
|
|
ic_init.ICPolarity = TIM_ICPOLARITY_FALLING;
|
|
ic_init.ICSelection = TIM_ICSELECTION_DIRECTTI;
|
|
ic_init.ICPrescaler = TIM_ICPSC_DIV1;
|
|
ic_init.ICFilter = 0;
|
|
HAL_TIM_IC_ConfigChannel(&s_htim1, &ic_init, ECHO_TIM_CHANNEL);
|
|
} else {
|
|
/* Falling edge: mark end of echo pulse and calculate distance */
|
|
s_ultrasonic.echo_end_ticks = capture_value;
|
|
|
|
/* Calculate pulse width (accounting for timer overflow) */
|
|
uint32_t width = (s_ultrasonic.echo_end_ticks >= s_ultrasonic.echo_start_ticks)
|
|
? (s_ultrasonic.echo_end_ticks - s_ultrasonic.echo_start_ticks)
|
|
: (0xFFFF - s_ultrasonic.echo_start_ticks + s_ultrasonic.echo_end_ticks + 1);
|
|
|
|
s_ultrasonic.echo_width_us = width;
|
|
|
|
/* Convert pulse width to distance: distance_mm = pulse_us / 5.828
|
|
* Using integer arithmetic: (pulse_us * 1000) / 5830
|
|
*/
|
|
s_ultrasonic.distance_mm = (width * US_TO_MM_NUMERATOR) / US_TO_MM_DENOMINATOR;
|
|
|
|
/* Validate range: 20-5000mm */
|
|
s_ultrasonic.last_valid = (s_ultrasonic.distance_mm >= 20 && s_ultrasonic.distance_mm <= 5000);
|
|
if (!s_ultrasonic.last_valid) {
|
|
s_ultrasonic.distance_mm = 0;
|
|
}
|
|
|
|
s_ultrasonic.state = ULTRASONIC_COMPLETE;
|
|
|
|
/* Call callback if registered */
|
|
if (s_ultrasonic.callback) {
|
|
s_ultrasonic.callback(s_ultrasonic.distance_mm, s_ultrasonic.last_valid);
|
|
}
|
|
|
|
/* Reset for next measurement */
|
|
s_ultrasonic.echo_start_ticks = 0;
|
|
s_ultrasonic.echo_end_ticks = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
HAL_TIM_IRQHandler(&s_htim1);
|
|
}
|