fix(spotlight): 优化PWM调光算法解决低亮度抖动

1. 修改PWM频率为4000Hz,与80MHz基准时钟形成整数倍关系(20000 ticks),消除计算取整导致的物理层抖动。
2. 实现基于时间轴的渐变算法替代原有的步进累加逻辑,消除累积误差,确保渐变时长精确且不受系统负载影响。
3. 调整渐变刷新间隔为8ms,提升视觉平滑度。
This commit is contained in:
2025-11-22 13:02:12 +08:00
parent 46cdd963a9
commit dcef9664ae
17 changed files with 72 additions and 84 deletions

View File

@@ -147,7 +147,7 @@ typedef struct __attribute__((packed, aligned(1))) {
// PWM频率和周期定义
#define PWM_FREQUENCY 3000 // PWM频率 3KHz
#define PWM_FREQUENCY 4000 // PWM频率 4KHz
//渐变范围
#define SMOOTH_TIME_MAX 30

View File

@@ -106,17 +106,19 @@ typedef struct {
static struct fade_ctx_t {
struct{
// 渐变控制参数
int32_t target_brightness;
int32_t target_cct;
int32_t current_brightness;
int32_t current_cct;
int32_t step_brightness;
int32_t step_cct;
int32_t start_brightness; // 起始亮度
int32_t start_cct; // 起始色温
int32_t target_brightness; // 目标亮度
int32_t target_cct; // 目标色温
int32_t current_brightness; // 当前亮度
int32_t current_cct; // 当前色温
bool is_fading;
bool fade_completed;
bool is_closing_fade; // 标记是否为关灯渐变
uint16_t fade_time; //s
uint32_t smooth_time_us; // 渐变总时长(us)
uint16_t fade_time; // s
uint32_t duration_ms; // 渐变总时长(ms)
uint32_t start_time_ms; // 渐变开始时间(ms)
uint32_t update_interval; // 更新间隔(us)
volatile bool timer_active; // 渐变定时器是否允许自重启(竞态保护)
// 打印限制器
@@ -197,6 +199,7 @@ static void *fade_task(const char *arg)
unused(arg);
e_printf("[fade_task] Task started\r\n");
device_control_t device_control = {};
while (fade_ctx.task_running) {
// 等待渐变信号
if (osal_sem_down_timeout(&fade_ctx.sem, 100*1000) != OSAL_SUCCESS) {
@@ -207,37 +210,51 @@ static void *fade_task(const char *arg)
continue;
}
// 1. 获取当前时间
uint32_t now = uapi_systick_get_ms();
// 2. 计算流逝时间
uint32_t elapsed = now - fade_ctx.start_time_ms;
// 使用打印限制器控制打印频率
if (should_print(&fade_ctx.print_limiter)) {
e_printf("%s Brightness: curr:%d, target:%d, step:%d, cct:curr:%d, target:%d, step:%d, closing:%d\r\n",
e_printf("%s Brightness: %d->%d, CCT: %d->%d, Progress: %d/%d ms\r\n",
fade_ctx.print_limiter.prefix,
fade_ctx.current_brightness, fade_ctx.target_brightness, fade_ctx.step_brightness,
fade_ctx.current_cct, fade_ctx.target_cct, fade_ctx.step_cct, fade_ctx.is_closing_fade);
fade_ctx.start_brightness, fade_ctx.target_brightness,
fade_ctx.start_cct, fade_ctx.target_cct,
elapsed, fade_ctx.duration_ms);
}
// 检查是否达到目标值
bool brightness_reached = (fade_ctx.step_brightness > 0) ?
(fade_ctx.current_brightness >= fade_ctx.target_brightness) :
(fade_ctx.current_brightness <= fade_ctx.target_brightness);
bool cct_reached = (fade_ctx.step_cct > 0) ?
(fade_ctx.current_cct >= fade_ctx.target_cct) :
(fade_ctx.current_cct <= fade_ctx.target_cct);
// 更新当前值
if (!brightness_reached) {
fade_ctx.current_brightness += fade_ctx.step_brightness;
}
if (!cct_reached) {
fade_ctx.current_cct += fade_ctx.step_cct;
bool is_finished = false;
// 3. 判断是否结束
if (elapsed >= fade_ctx.duration_ms) {
// 强制设为目标值,确保最终结果精确
fade_ctx.current_brightness = fade_ctx.target_brightness;
fade_ctx.current_cct = fade_ctx.target_cct;
is_finished = true;
} else {
// 4. 核心算法:计算当前应有的值 (线性插值)
// 公式Val = Start + (Diff * Elapsed) / Duration
int32_t diff_bri = fade_ctx.target_brightness - fade_ctx.start_brightness;
int32_t diff_cct = fade_ctx.target_cct - fade_ctx.start_cct;
// 注意:运算顺序很重要,先乘后除以保留精度,但要防止溢出
// 使用 int64_t 避免乘法溢出
fade_ctx.current_brightness = fade_ctx.start_brightness +
(int32_t)((int64_t)diff_bri * elapsed / fade_ctx.duration_ms);
fade_ctx.current_cct = fade_ctx.start_cct +
(int32_t)((int64_t)diff_cct * elapsed / fade_ctx.duration_ms);
}
// 达到目标,停止渐变
if (brightness_reached && cct_reached) {
if (is_finished) {
e_printf("[fade_task] Fade completed, Final brightness: %d, CCT: %d, closing_fade: %d\r\n",
fade_ctx.target_brightness, fade_ctx.target_cct, fade_ctx.is_closing_fade);
fade_ctx.is_fading = false;
fade_ctx.fade_completed = true;
fade_ctx.current_brightness = fade_ctx.target_brightness;
fade_ctx.current_cct = fade_ctx.target_cct;
fade_ctx.timer_active = false; // 防止回调自重启
uapi_timer_stop(fade_ctx.timer_handle);
}
@@ -607,69 +624,37 @@ void update_pwm_output(bool on_state, uint16_t duty_cw_val, uint16_t duty_ww_val
#define ABS(x) ((x) < 0 ? -(x) : (x))
// 计算渐变步长
// 计算渐变参数(时间轴模式初始化)
static void calculate_fade_steps(struct fade_ctx_t *pfade_ctx)
{
// 从g_device_control获取渐变时间
pfade_ctx->smooth_time_us = pfade_ctx->fade_time * 1000 * 1000;//s->us
e_printf("fade brightness:%d->%d, cct:%d->%d, time:%ds\r\n",
pfade_ctx->current_brightness, pfade_ctx->target_brightness, pfade_ctx->current_cct, pfade_ctx->target_cct, pfade_ctx->fade_time);
// 计算亮度渐变步长
// 1. 记录锚点数据
pfade_ctx->start_time_ms = uapi_systick_get_ms();
pfade_ctx->duration_ms = pfade_ctx->fade_time * 1000; // s -> ms
pfade_ctx->start_brightness = pfade_ctx->current_brightness;
pfade_ctx->start_cct = pfade_ctx->current_cct;
e_printf("fade init: brightness %d->%d, cct %d->%d, duration %dms\r\n",
pfade_ctx->start_brightness, pfade_ctx->target_brightness,
pfade_ctx->start_cct, pfade_ctx->target_cct,
pfade_ctx->duration_ms);
// 2. 特殊情况处理:无渐变时间或无变化
int32_t brightness_diff = pfade_ctx->target_brightness - pfade_ctx->current_brightness;
int32_t cct_diff = pfade_ctx->target_cct - pfade_ctx->current_cct;
// 计算需要的总步数(取亮度和色温中变化较大的那个)
uint32_t max_diff = (ABS(brightness_diff) > ABS(cct_diff)) ? ABS(brightness_diff) : ABS(cct_diff);
uint32_t min_steps = 0;
uint32_t available_interval = 0;
// 如果变化太小或者不需要渐变,直接设置目标值
if (max_diff == 0 || pfade_ctx->smooth_time_us == 0) {
if ((brightness_diff == 0 && cct_diff == 0) || pfade_ctx->duration_ms == 0) {
pfade_ctx->current_brightness = pfade_ctx->target_brightness;
pfade_ctx->current_cct = pfade_ctx->target_cct;
pfade_ctx->step_brightness = 0;
pfade_ctx->step_cct = 0;
pfade_ctx->update_interval = FADE_INTERVAL_MIN;
goto lab_exit;
// 标记为瞬间完成,不启动定时器,但需要触发一次更新
// 这里通过设置极短时间来让task立即完成
pfade_ctx->duration_ms = 0;
}
min_steps = max_diff;
// 计算实际可用的时间间隔
available_interval = pfade_ctx->smooth_time_us / min_steps;
// 如果时间间隔太小(小于允许的最小更新间隔),则增加步长
if (available_interval < FADE_INTERVAL_MIN) {
// 重新计算步长,确保在指定时间内完成
pfade_ctx->update_interval = FADE_INTERVAL_MIN;
int32_t actual_steps = pfade_ctx->smooth_time_us / pfade_ctx->update_interval;
// 计算新的步长
pfade_ctx->step_brightness = brightness_diff / actual_steps;
pfade_ctx->step_cct = cct_diff / actual_steps;
// 确保至少有一个最小步长
if (pfade_ctx->step_brightness == 0 && brightness_diff != 0) {
pfade_ctx->step_brightness = (brightness_diff > 0) ? 1 : -1;
}
if (pfade_ctx->step_cct == 0 && cct_diff != 0) {
pfade_ctx->step_cct = (cct_diff > 0) ? 1 : -1;
}
} else {
// 使用计算出的时间间隔
pfade_ctx->update_interval = available_interval;
pfade_ctx->step_brightness = (brightness_diff > 0) ? 1 : -1;
pfade_ctx->step_cct = (cct_diff > 0) ? 1 : -1;
}
// 如果亮度或色温没有变化则步长为0
if (brightness_diff == 0) {
pfade_ctx->step_brightness = 0;
}
if (cct_diff == 0) {
pfade_ctx->step_cct = 0;
}
lab_exit:
e_printf("fade max_diff:%d, fade time:%uus, Brightness: diff:%d, step:%d, CCT: diff:%d, step:%d, update_interval:%uus\r\n",
max_diff, pfade_ctx->smooth_time_us, brightness_diff, pfade_ctx->step_brightness, cct_diff, pfade_ctx->step_cct, pfade_ctx->update_interval);
// 3. 设置定时器刷新频率
// 固定使用较快的刷新率以保证平滑度,例如 10ms (100Hz)
// 如果系统负载高,可以适当降低,例如 20ms
pfade_ctx->update_interval = 8 * 1000; // 8ms in us
}
static void cancel_current_light_fade(void)