**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>
279 lines
8.3 KiB
C
279 lines
8.3 KiB
C
#include "fan.h"
|
|
#include "stm32f7xx_hal.h"
|
|
#include "config.h"
|
|
#include <string.h>
|
|
|
|
/* ================================================================
|
|
* Fan Hardware Configuration
|
|
* ================================================================ */
|
|
|
|
#define FAN_PIN GPIO_PIN_9
|
|
#define FAN_PORT GPIOA
|
|
#define FAN_TIM TIM1
|
|
#define FAN_TIM_CHANNEL TIM_CHANNEL_2
|
|
#define FAN_PWM_FREQ_HZ 25000 /* 25 kHz for brushless fan */
|
|
|
|
/* ================================================================
|
|
* Temperature Curve Parameters
|
|
* ================================================================ */
|
|
|
|
#define TEMP_OFF 40 /* Fan off below this (°C) */
|
|
#define TEMP_LOW 50 /* Low speed threshold (°C) */
|
|
#define TEMP_HIGH 70 /* High speed threshold (°C) */
|
|
|
|
#define SPEED_OFF 0 /* Speed at TEMP_OFF (%) */
|
|
#define SPEED_LOW 30 /* Speed at TEMP_LOW (%) */
|
|
#define SPEED_HIGH 100 /* Speed at TEMP_HIGH (%) */
|
|
|
|
/* ================================================================
|
|
* Internal State
|
|
* ================================================================ */
|
|
|
|
typedef struct {
|
|
uint8_t current_speed; /* Current speed 0-100% */
|
|
uint8_t target_speed; /* Target speed 0-100% */
|
|
int16_t last_temperature; /* Last temperature reading (°C) */
|
|
float ramp_rate_per_ms; /* Speed change rate (%/ms) */
|
|
uint32_t last_ramp_time_ms; /* When last ramp update occurred */
|
|
bool is_ramping; /* Speed is transitioning */
|
|
} FanState_t;
|
|
|
|
static FanState_t s_fan = {
|
|
.current_speed = 0,
|
|
.target_speed = 0,
|
|
.last_temperature = 0,
|
|
.ramp_rate_per_ms = 0.05f, /* 5% per 100ms default */
|
|
.last_ramp_time_ms = 0,
|
|
.is_ramping = false
|
|
};
|
|
|
|
static TIM_HandleTypeDef s_htim1 = {0};
|
|
|
|
/* ================================================================
|
|
* Hardware Initialization
|
|
* ================================================================ */
|
|
|
|
void fan_init(void)
|
|
{
|
|
/* Enable GPIO and timer clocks */
|
|
__HAL_RCC_GPIOA_CLK_ENABLE();
|
|
__HAL_RCC_TIM1_CLK_ENABLE();
|
|
|
|
/* Configure PA9 as TIM1_CH2 PWM output */
|
|
GPIO_InitTypeDef gpio_init = {0};
|
|
gpio_init.Pin = FAN_PIN;
|
|
gpio_init.Mode = GPIO_MODE_AF_PP;
|
|
gpio_init.Pull = GPIO_NOPULL;
|
|
gpio_init.Speed = GPIO_SPEED_HIGH;
|
|
gpio_init.Alternate = GPIO_AF1_TIM1;
|
|
HAL_GPIO_Init(FAN_PORT, &gpio_init);
|
|
|
|
/* Configure TIM1 for PWM:
|
|
* Clock: 216MHz / PSC = output frequency
|
|
* For 25kHz frequency: PSC = 346, ARR = 25
|
|
* Duty cycle = CCR / ARR (e.g., 12.5/25 = 50%)
|
|
*/
|
|
s_htim1.Instance = FAN_TIM;
|
|
s_htim1.Init.Prescaler = 346 - 1; /* 216MHz / 346 ≈ 624kHz clock */
|
|
s_htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
|
|
s_htim1.Init.Period = 25 - 1; /* 624kHz / 25 = 25kHz */
|
|
s_htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
|
s_htim1.Init.RepetitionCounter = 0;
|
|
HAL_TIM_PWM_Init(&s_htim1);
|
|
|
|
/* Configure PWM on CH2: 0% duty initially (fan off) */
|
|
TIM_OC_InitTypeDef oc_init = {0};
|
|
oc_init.OCMode = TIM_OCMODE_PWM1;
|
|
oc_init.Pulse = 0; /* Start at 0% duty (off) */
|
|
oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
|
|
oc_init.OCFastMode = TIM_OCFAST_DISABLE;
|
|
HAL_TIM_PWM_ConfigChannel(&s_htim1, &oc_init, FAN_TIM_CHANNEL);
|
|
|
|
/* Start PWM generation */
|
|
HAL_TIM_PWM_Start(&s_htim1, FAN_TIM_CHANNEL);
|
|
|
|
s_fan.current_speed = 0;
|
|
s_fan.target_speed = 0;
|
|
s_fan.last_ramp_time_ms = 0;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Temperature Curve Calculation
|
|
* ================================================================ */
|
|
|
|
static uint8_t fan_calculate_speed_from_temp(int16_t temp_celsius)
|
|
{
|
|
if (temp_celsius < TEMP_OFF) {
|
|
return SPEED_OFF; /* Off below 40°C */
|
|
}
|
|
|
|
if (temp_celsius < TEMP_LOW) {
|
|
/* Linear ramp from 0% to 30% between 40-50°C */
|
|
int32_t temp_offset = temp_celsius - TEMP_OFF; /* 0-10 */
|
|
int32_t temp_range = TEMP_LOW - TEMP_OFF; /* 10 */
|
|
int32_t speed_range = SPEED_LOW - SPEED_OFF; /* 30 */
|
|
uint8_t speed = SPEED_OFF + (temp_offset * speed_range) / temp_range;
|
|
return (speed > 100) ? 100 : speed;
|
|
}
|
|
|
|
if (temp_celsius < TEMP_HIGH) {
|
|
/* Linear ramp from 30% to 100% between 50-70°C */
|
|
int32_t temp_offset = temp_celsius - TEMP_LOW; /* 0-20 */
|
|
int32_t temp_range = TEMP_HIGH - TEMP_LOW; /* 20 */
|
|
int32_t speed_range = SPEED_HIGH - SPEED_LOW; /* 70 */
|
|
uint8_t speed = SPEED_LOW + (temp_offset * speed_range) / temp_range;
|
|
return (speed > 100) ? 100 : speed;
|
|
}
|
|
|
|
return SPEED_HIGH; /* 100% at 70°C and above */
|
|
}
|
|
|
|
/* ================================================================
|
|
* PWM Duty Cycle Control
|
|
* ================================================================ */
|
|
|
|
static void fan_set_pwm_duty(uint8_t percentage)
|
|
{
|
|
/* Clamp to 0-100% */
|
|
if (percentage > 100) percentage = 100;
|
|
|
|
/* Convert percentage to PWM counts
|
|
* ARR = 25 (0-24 counts for 0-96%, scale up to 25 for 100%)
|
|
* Duty = (percentage * 25) / 100
|
|
*/
|
|
uint32_t duty = (percentage * 25) / 100;
|
|
if (duty > 25) duty = 25;
|
|
|
|
/* Update CCR2 for TIM1_CH2 */
|
|
TIM1->CCR2 = duty;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Public API
|
|
* ================================================================ */
|
|
|
|
bool fan_set_speed(uint8_t percentage)
|
|
{
|
|
if (percentage > 100) {
|
|
return false;
|
|
}
|
|
|
|
s_fan.current_speed = percentage;
|
|
s_fan.target_speed = percentage;
|
|
s_fan.is_ramping = false;
|
|
fan_set_pwm_duty(percentage);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint8_t fan_get_speed(void)
|
|
{
|
|
return s_fan.current_speed;
|
|
}
|
|
|
|
bool fan_set_target_speed(uint8_t percentage)
|
|
{
|
|
if (percentage > 100) {
|
|
return false;
|
|
}
|
|
|
|
s_fan.target_speed = percentage;
|
|
if (percentage == s_fan.current_speed) {
|
|
s_fan.is_ramping = false;
|
|
} else {
|
|
s_fan.is_ramping = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void fan_update_temperature(int16_t temp_celsius)
|
|
{
|
|
s_fan.last_temperature = temp_celsius;
|
|
|
|
/* Calculate target speed from temperature curve */
|
|
uint8_t new_target = fan_calculate_speed_from_temp(temp_celsius);
|
|
fan_set_target_speed(new_target);
|
|
}
|
|
|
|
int16_t fan_get_temperature(void)
|
|
{
|
|
return s_fan.last_temperature;
|
|
}
|
|
|
|
FanState fan_get_state(void)
|
|
{
|
|
if (s_fan.current_speed == 0) return FAN_OFF;
|
|
if (s_fan.current_speed <= 30) return FAN_LOW;
|
|
if (s_fan.current_speed <= 60) return FAN_MEDIUM;
|
|
if (s_fan.current_speed <= 99) return FAN_HIGH;
|
|
return FAN_FULL;
|
|
}
|
|
|
|
void fan_set_ramp_rate(float percentage_per_ms)
|
|
{
|
|
if (percentage_per_ms <= 0) {
|
|
s_fan.ramp_rate_per_ms = 0.01f; /* Minimum rate */
|
|
} else if (percentage_per_ms > 10.0f) {
|
|
s_fan.ramp_rate_per_ms = 10.0f; /* Maximum rate */
|
|
} else {
|
|
s_fan.ramp_rate_per_ms = percentage_per_ms;
|
|
}
|
|
}
|
|
|
|
bool fan_is_ramping(void)
|
|
{
|
|
return s_fan.is_ramping;
|
|
}
|
|
|
|
void fan_tick(uint32_t now_ms)
|
|
{
|
|
if (!s_fan.is_ramping) {
|
|
return;
|
|
}
|
|
|
|
/* Calculate time elapsed since last ramp */
|
|
if (s_fan.last_ramp_time_ms == 0) {
|
|
s_fan.last_ramp_time_ms = now_ms;
|
|
return;
|
|
}
|
|
|
|
uint32_t elapsed = now_ms - s_fan.last_ramp_time_ms;
|
|
if (elapsed == 0) {
|
|
return; /* No time has passed */
|
|
}
|
|
|
|
/* Calculate speed change allowed in this time interval */
|
|
float speed_change = s_fan.ramp_rate_per_ms * elapsed;
|
|
int32_t new_speed;
|
|
|
|
if (s_fan.target_speed > s_fan.current_speed) {
|
|
/* Ramp up */
|
|
new_speed = s_fan.current_speed + (int32_t)speed_change;
|
|
if (new_speed >= s_fan.target_speed) {
|
|
s_fan.current_speed = s_fan.target_speed;
|
|
s_fan.is_ramping = false;
|
|
} else {
|
|
s_fan.current_speed = (uint8_t)new_speed;
|
|
}
|
|
} else {
|
|
/* Ramp down */
|
|
new_speed = s_fan.current_speed - (int32_t)speed_change;
|
|
if (new_speed <= s_fan.target_speed) {
|
|
s_fan.current_speed = s_fan.target_speed;
|
|
s_fan.is_ramping = false;
|
|
} else {
|
|
s_fan.current_speed = (uint8_t)new_speed;
|
|
}
|
|
}
|
|
|
|
/* Update PWM duty cycle */
|
|
fan_set_pwm_duty(s_fan.current_speed);
|
|
s_fan.last_ramp_time_ms = now_ms;
|
|
}
|
|
|
|
void fan_disable(void)
|
|
{
|
|
fan_set_speed(0);
|
|
}
|