**Compile Errors Fixed:** 1. src/battery.c — add #include <stdbool.h> 2. src/main.c — fix BUZZER_PATTERN_ARM_CHIME undeclared (replace with buzzer_play_melody) 3. src/main.c — fix bno055_active undeclared (replace with bno055_is_ready()) 4. src/servo.c — remove duplicate ServoState typedef 5. src/fan.c — pass TIM_HandleTypeDef* not TIM_TypeDef* (use static s_htim1) 6. src/watchdog.c — use proper hiwdg handle (static s_hiwdg) 7. src/ultrasonic.c — (no changes needed - already correct) **Linker Errors Fixed:** 1. i2c1_write / i2c1_read — implement in i2c1.c with HAL I2C master transmit/receive 2. servo_tick — already implemented in servo.c 3. imu_calibrated — add stub function in main.c 4. crsf_is_active — add stub function in main.c All 11 errors resolved. Build verified to pass. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
230 lines
7.3 KiB
C
230 lines
7.3 KiB
C
#include "servo.h"
|
|
#include "config.h"
|
|
#include "stm32f7xx_hal.h"
|
|
#include <string.h>
|
|
|
|
/* ================================================================
|
|
* Servo PWM Protocol
|
|
* ================================================================
|
|
* TIM4 at 50 Hz (20 ms period)
|
|
* APB1 clock: 54 MHz
|
|
* Prescaler: 53 (54 MHz / 54 = 1 MHz)
|
|
* ARR: 19999 (1 MHz / 20000 = 50 Hz)
|
|
* CCR: 500-2500 (0.5-2.5 ms out of 20 ms)
|
|
*
|
|
* Servo pulse mapping:
|
|
* 500 µs → 0° (full left/down)
|
|
* 1500 µs → 90° (center)
|
|
* 2500 µs → 180° (full right/up)
|
|
*/
|
|
|
|
#define SERVO_PWM_FREQ 50u /* 50 Hz */
|
|
#define SERVO_PERIOD_MS 20u /* 20 ms = 1/50 Hz */
|
|
#define SERVO_CLOCK_HZ 1000000u /* 1 MHz timer clock */
|
|
#define SERVO_PRESCALER 53u /* APB1 54 MHz / 54 = 1 MHz */
|
|
#define SERVO_ARR 19999u /* 1 MHz / 20000 = 50 Hz */
|
|
|
|
static ServoState s_servo = {0};
|
|
static TIM_HandleTypeDef s_tim_handle = {0};
|
|
|
|
/* ================================================================
|
|
* Helper functions
|
|
* ================================================================
|
|
*/
|
|
|
|
static uint16_t angle_to_pulse_us(uint16_t degrees)
|
|
{
|
|
/* Linear interpolation: 0° → 500µs, 180° → 2500µs */
|
|
if (degrees > 180) degrees = 180;
|
|
|
|
uint32_t pulse = SERVO_MIN_US + (uint32_t)degrees * (SERVO_MAX_US - SERVO_MIN_US) / 180;
|
|
return (uint16_t)pulse;
|
|
}
|
|
|
|
static uint16_t pulse_us_to_angle(uint16_t pulse_us)
|
|
{
|
|
/* Inverse mapping: 500µs → 0°, 2500µs → 180° */
|
|
if (pulse_us < SERVO_MIN_US) pulse_us = SERVO_MIN_US;
|
|
if (pulse_us > SERVO_MAX_US) pulse_us = SERVO_MAX_US;
|
|
|
|
uint32_t angle = (uint32_t)(pulse_us - SERVO_MIN_US) * 180 / (SERVO_MAX_US - SERVO_MIN_US);
|
|
return (uint16_t)angle;
|
|
}
|
|
|
|
static void update_pwm(ServoChannel channel)
|
|
{
|
|
/* Convert pulse width (500-2500 µs) to CCR value */
|
|
/* At 1 MHz timer clock: 1 µs = 1 count */
|
|
uint32_t ccr_value = s_servo.pulse_us[channel];
|
|
|
|
if (channel == SERVO_PAN) {
|
|
__HAL_TIM_SET_COMPARE(&s_tim_handle, SERVO_PAN_CHANNEL, ccr_value);
|
|
} else {
|
|
__HAL_TIM_SET_COMPARE(&s_tim_handle, SERVO_TILT_CHANNEL, ccr_value);
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* Public API
|
|
* ================================================================
|
|
*/
|
|
|
|
void servo_init(void)
|
|
{
|
|
/* Initialize state */
|
|
memset(&s_servo, 0, sizeof(s_servo));
|
|
|
|
/* Center both servos at 90° */
|
|
for (int i = 0; i < SERVO_COUNT; i++) {
|
|
s_servo.current_angle_deg[i] = 90;
|
|
s_servo.target_angle_deg[i] = 90;
|
|
s_servo.pulse_us[i] = SERVO_CENTER_US;
|
|
}
|
|
|
|
/* Configure GPIO PB6 (CH1) and PB7 (CH2) as TIM4 PWM */
|
|
__HAL_RCC_GPIOB_CLK_ENABLE();
|
|
|
|
GPIO_InitTypeDef gpio_init = {0};
|
|
gpio_init.Mode = GPIO_MODE_AF_PP;
|
|
gpio_init.Pull = GPIO_NOPULL;
|
|
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
gpio_init.Alternate = SERVO_AF;
|
|
|
|
/* Configure PB6 (pan) */
|
|
gpio_init.Pin = SERVO_PAN_PIN;
|
|
HAL_GPIO_Init(SERVO_PAN_PORT, &gpio_init);
|
|
|
|
/* Configure PB7 (tilt) */
|
|
gpio_init.Pin = SERVO_TILT_PIN;
|
|
HAL_GPIO_Init(SERVO_TILT_PORT, &gpio_init);
|
|
|
|
/* Configure TIM4: 50 Hz PWM */
|
|
__HAL_RCC_TIM4_CLK_ENABLE();
|
|
|
|
s_tim_handle.Instance = SERVO_TIM;
|
|
s_tim_handle.Init.Prescaler = SERVO_PRESCALER;
|
|
s_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
|
|
s_tim_handle.Init.Period = SERVO_ARR;
|
|
s_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
|
s_tim_handle.Init.RepetitionCounter = 0;
|
|
|
|
HAL_TIM_PWM_Init(&s_tim_handle);
|
|
|
|
/* Configure TIM4_CH1 (pan) for PWM */
|
|
TIM_OC_InitTypeDef oc_init = {0};
|
|
oc_init.OCMode = TIM_OCMODE_PWM1;
|
|
oc_init.Pulse = SERVO_CENTER_US;
|
|
oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
|
|
oc_init.OCFastMode = TIM_OCFAST_DISABLE;
|
|
|
|
HAL_TIM_PWM_ConfigChannel(&s_tim_handle, &oc_init, SERVO_PAN_CHANNEL);
|
|
HAL_TIM_PWM_Start(&s_tim_handle, SERVO_PAN_CHANNEL);
|
|
|
|
/* Configure TIM4_CH2 (tilt) for PWM */
|
|
oc_init.Pulse = SERVO_CENTER_US;
|
|
HAL_TIM_PWM_ConfigChannel(&s_tim_handle, &oc_init, SERVO_TILT_CHANNEL);
|
|
HAL_TIM_PWM_Start(&s_tim_handle, SERVO_TILT_CHANNEL);
|
|
}
|
|
|
|
void servo_set_angle(ServoChannel channel, uint16_t degrees)
|
|
{
|
|
if (channel >= SERVO_COUNT) return;
|
|
if (degrees > 180) degrees = 180;
|
|
|
|
s_servo.current_angle_deg[channel] = degrees;
|
|
s_servo.target_angle_deg[channel] = degrees;
|
|
s_servo.pulse_us[channel] = angle_to_pulse_us(degrees);
|
|
|
|
/* Stop any sweep in progress */
|
|
s_servo.is_sweeping[channel] = false;
|
|
|
|
/* Update PWM immediately */
|
|
update_pwm(channel);
|
|
}
|
|
|
|
uint16_t servo_get_angle(ServoChannel channel)
|
|
{
|
|
if (channel >= SERVO_COUNT) return 0;
|
|
return s_servo.current_angle_deg[channel];
|
|
}
|
|
|
|
void servo_set_pulse_us(ServoChannel channel, uint16_t pulse_us)
|
|
{
|
|
if (channel >= SERVO_COUNT) return;
|
|
if (pulse_us < SERVO_MIN_US) pulse_us = SERVO_MIN_US;
|
|
if (pulse_us > SERVO_MAX_US) pulse_us = SERVO_MAX_US;
|
|
|
|
s_servo.pulse_us[channel] = pulse_us;
|
|
s_servo.current_angle_deg[channel] = pulse_us_to_angle(pulse_us);
|
|
s_servo.target_angle_deg[channel] = s_servo.current_angle_deg[channel];
|
|
|
|
/* Stop any sweep in progress */
|
|
s_servo.is_sweeping[channel] = false;
|
|
|
|
/* Update PWM immediately */
|
|
update_pwm(channel);
|
|
}
|
|
|
|
void servo_sweep(ServoChannel channel, uint16_t start_deg, uint16_t end_deg, uint32_t duration_ms)
|
|
{
|
|
if (channel >= SERVO_COUNT) return;
|
|
if (duration_ms == 0) return;
|
|
if (start_deg > 180) start_deg = 180;
|
|
if (end_deg > 180) end_deg = 180;
|
|
|
|
s_servo.sweep_start_deg[channel] = start_deg;
|
|
s_servo.sweep_end_deg[channel] = end_deg;
|
|
s_servo.sweep_duration_ms[channel] = duration_ms;
|
|
s_servo.sweep_start_ms[channel] = 0; /* Will be set on first tick */
|
|
s_servo.is_sweeping[channel] = true;
|
|
}
|
|
|
|
void servo_tick(uint32_t now_ms)
|
|
{
|
|
for (int ch = 0; ch < SERVO_COUNT; ch++) {
|
|
if (!s_servo.is_sweeping[ch]) continue;
|
|
|
|
/* Initialize start time on first call */
|
|
if (s_servo.sweep_start_ms[ch] == 0) {
|
|
s_servo.sweep_start_ms[ch] = now_ms;
|
|
}
|
|
|
|
uint32_t elapsed = now_ms - s_servo.sweep_start_ms[ch];
|
|
uint32_t duration = s_servo.sweep_duration_ms[ch];
|
|
|
|
if (elapsed >= duration) {
|
|
/* Sweep complete */
|
|
s_servo.is_sweeping[ch] = false;
|
|
s_servo.current_angle_deg[ch] = s_servo.sweep_end_deg[ch];
|
|
s_servo.pulse_us[ch] = angle_to_pulse_us(s_servo.sweep_end_deg[ch]);
|
|
} else {
|
|
/* Linear interpolation */
|
|
int16_t start = (int16_t)s_servo.sweep_start_deg[ch];
|
|
int16_t end = (int16_t)s_servo.sweep_end_deg[ch];
|
|
int32_t delta = end - start;
|
|
|
|
/* angle = start + (delta * elapsed / duration) */
|
|
int32_t angle_i32 = start + (delta * (int32_t)elapsed / (int32_t)duration);
|
|
s_servo.current_angle_deg[ch] = (uint16_t)angle_i32;
|
|
s_servo.pulse_us[ch] = angle_to_pulse_us(s_servo.current_angle_deg[ch]);
|
|
}
|
|
|
|
/* Update PWM */
|
|
update_pwm((ServoChannel)ch);
|
|
}
|
|
}
|
|
|
|
bool servo_is_sweeping(void)
|
|
{
|
|
for (int i = 0; i < SERVO_COUNT; i++) {
|
|
if (s_servo.is_sweeping[i]) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void servo_stop_sweep(ServoChannel channel)
|
|
{
|
|
if (channel >= SERVO_COUNT) return;
|
|
s_servo.is_sweeping[channel] = false;
|
|
}
|