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

3
.gitignore vendored
View File

@@ -4,7 +4,8 @@
*.a
*.so
*.bin
output
output/ws63
output/package
libs_url
open_source
temp

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)
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 is_finished = false;
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;
// 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)

BIN
output/LPT262_hilink.fwpkg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
output/package.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
{"ver": 1, "app": "3A23309AAF0B692188B9C56755C084522354143382C5309A2D3258C3E6C834A500B53089C7A65C43392D51DFEF32BBC125B613C6B37656F59E142F0D713AD6115955749E72C53C3BA8CD96D2C69CB534231411DEAE5C4F1782DA2760BB04E0199813CAC4CBA339BEBF3D734FBF0955235DD160C0387CA4AD28BFC8766508F627D0B46BE48319EAC371921D27D806C9F3464BF4DFBAE09ADB95232386B021EEFBD3FAD2C6B405A1E9C13BB0420CAB5EF02D3D543090162EB8E51C2E1D10F3EE4EE5FE71A0BA8DF1E95E4A36694908DDB16E08F54B606E13CACB530F71320CC9DF6C3DCB70DC6E54BDA7B3C1720E7ECCB54E88950CFC3D4D700224AB40D9E568A89571387053E9BB3D77889DAA102DB1FC4CE38751F2E6A0BD27B0EC51B8EA015CE0E475ADF3E3CDB49580E84E228803A691F39D958D3F28B0E4D53C670A27B5AB436D11F83A8AAEB6538628D32F674C398B6CF1C295BAB9499E19EA993B11D07524E025EF6FF88F42331C3BE36689468DAF74C6B811187E2D7953251B9228B8A8C32422F97C0122332BB295C110F36291A845CD2FA2D6BC34E37B7CBE23083067AE8AF9346DEEEFCC168D2D4658A94AB5049858C11E8BE4672F492DE9B4046E4B0EEEA5E3D6FE8809743B2B6111718F2038742D7C48EE81BBD5D485373D34C70868622D3233179ADED3E8C5026A2217F6938E85AA7A6754CE869A8127656D364C00F05E95FE7738AD05E79CACA11368FAE6717C71C469FC934FF221953E9F7CA301D52266EA04E8184A6D5AF66621F90139D0642D0350FA3F28ED49CF23AC20D03710992FBE7E3DBCEBD737385AE906D8D885A6118F2416A45FFA93EC3483EDADE5864419C451689382274A6DEC6FD6734D3975199C9FFB561202FEAB34F195D595E791C43315669E430FB9E7288EF87BC4498A4DF1A93918FA7C7805F7131CCF092F762F28C0A502EC955661D19C624CD0B61E88F81F9D57108A971D268273CEABEE8013FF69661C1A29BBD995111C6A64314013FBA647F77B736AE8E330F3D7157E6C3774AFBCC38D643C7108363127CB03B902C6D32C202A73EE7099A65902A14959BA60D70D055A89A6C3E489489F355E418A02EF8A543D8DF826C59AD8FABD1564AF4FEE6224FCA67974F6F63E17D3036A50120E5449669A4D792E919F88C4BBEEC64E542216D35ED1DE5A21434FC521776D37F5C50257B639AA3F47D1CC1888A19961CD6F5D7C9BEF30D5EACE785DB24268E360581533F98458376DD1E770BF3612B6D1F9C62395F48BEDE23AE1514E24C79360CE44291140AFEC5576503E6EBBC1D453DC9ED772F6AA506112D26C5B806EC4E0EAEC6A98F6F1683AC86494FA565B7CE2A0540F45AC6A8A556CDEE96B315EF45ED456EF46F1E5C234ABF92B121CCE688D2163BDF57FB6098B4742FBC504AC5E03230ADF90428D53F024F9C20964CA8AEA8A9042D1CEF0A2BA9C88A011C1004A9B8F7F9EBEDA365056947E9426D868829904D70DA74B7276D93829D151333F3506E83032A8CF68630BD5D0A39237FDF0BADD131AB77B6092987F07139588081D140EB2A0CA25BF9267FBC809457528A7C5DFA1729552A8D76B3362B9915E84795CD770EB84A3AE73DE78F04D37E8FC2F53E2F72853D57F170FC2551F7CBBB9B63D119AC4775120418F3865792FEFAAFF69038BFD3AA3C2C476B26DFB2B48F3B3904203C394782FE1C40F718001C31504539827DDC80900966524DCE965006F86EBDA9C46226BC1DA2387E2AE9565DFEE621F0DA936298371D1AD83860B4E607BB473E4103ADF872D1A3CA9EB171CF73077B4AFC2D7FC3737FAE5337290D8116CE0826F347C621502BA313EEF20F1F67D00A7EB1BB555DE7B0FCDD05C747D7615136B35079DA4BF9A6EED5F0C5448B8364FB03476275BD823C8C7F3CBCC1D0BD41C5B49538D65C00C85F9C8D50201EDA6266D7A2A375141F7F5E69E1AD06932C3A6C044EAE908D43657EA22C5FA797FE8D04592541477B362753A05", "hilink": "BEC62B3A1B90717BABD8C9AD15D3BE1560977111A022C59CEF399A97E8B1D1D2D981EE972F4C881D0D3D30D8165FA46BEB1F07B4B8F1AECEA4335DFC7388E1234641EDD1AF31DFEDB616EBF0A9900778C03C4467D3CBCD6540F51D66CBE0FF998506BA06448D8BEB6CDA8E9E79BE4C8BF15449A4DEFEB22F342B09F03683E17D699DA475D08A2F079EDA59B99A96CD0E468B089224120EE96B86A8BB4B250B35E0E8B8A1E77FE8A13F50CF8B018FA5A91475DDA932141DAF1F5C4D9C5D7F8DC8E3CB582D0DB0EA8823396BF6BC0A30D68B968BEE77AB6014036385A68BB35701D274404A9BFA24348E7321F63FEA98B7"}

View File

@@ -0,0 +1 @@
{"ver": 1, "app": "3A23309AAF0B692188B9C56755C084522354143382C5309A2D3258C3E6C834A500B53089C7A65C43392D51DFEF32BBC125B613C6B37656F59E142F0D713AD6115955749E72C53C3BA8CD96D2C69CB534231411DEAE5C4F1782DA2760BB04E0199813CAC4CBA339BEBF3D734FBF0955235DD160C0387CA4AD28BFC8766508F627D0B46BE48319EAC371921D27D806C9F3464BF4DFBAE09ADB95232386B021EEFBD3FAD2C6B405A1E9C13BB0420CAB5EF02D3D543090162EB8E51C2E1D10F3EE4EE5FE71A0BA8DF1E95E4A36694908DDB16E08F54B606E13CACB530F71320CC9DF6C3DCB70DC6E54BDA7B3C1720E7ECCB54E88950CFC3D4D700224AB40D9E568A89571387053E9BB3D77889DAA102DB1FC4CE38751F2E6A0BD27B0EC51B8EA015CE0E475ADF3E3CDB49580E84E228803A691F39D958D3F28B0E4D53C670A27B5AB436D11F83A8AAEB6538628D32F674C398B6CF1C295BAB9499E19EA993B11D07524E025EF6FF88F42331C3BE36689468DAF74C6B811187E2D7953251B9228B8A8C32422F97C0122332BB295C110F36291A845CD2FA2D6BC34E37B7CBE23083067AE8AF9346DEEEFCC168D2D4658A94AB5049858C11E8BE4672F492DE9B4046E4B0EEEA5E3D6FE8809743B2B6111718F2038742D7C48EE81BBD5D485373D34C70868622D3233179ADED3E8C5026A2217F6938E85AA7A6754CE869A8127656D364C00F05E95FE7738AD05E79CACA11368FAE6717C71C469FC934FF221953E9F7CA301D52266EA04E8184A6D5AF66621F90139D0642D0350FA3F28ED49CF23AC20D03710992FBE7E3DBCEBD737385AE906D8D885A6118F2416A45FFA93EC3483EDADE5864419C451689382274A6DEC6FD6734D3975199C9FFB561202FEAB34F195D595E791C43315669E430FB9E7288EF87BC4498A4DF1A93918FA7C7805F7131CCF092F762F28C0A502EC955661D19C624CD0B61E88F81F9D57108A971D268273CEABEE8013FF69661C1A29BBD995111C6A64314013FBA647F77B736AE8E330F3D7157E6C3774AFBCC38D643C7108363127CB03B902C6D32C202A73EE7099A65902A14959BA60D70D055A89A6C3E489489F355E418A02EF8A543D8DF826C59AD8FABD1564AF4FEE6224FCA67974F6F63E17D3036A50120E5449669A4D792E919F88C4BBEEC64E542216D35ED1DE5A21434FC521776D37F5C50257B639AA3F47D1CC1888A19961CD6F5D7C9BEF30D5EACE785DB24268E360581533F98458376DD1E770BF3612B6D1F9C62395F48BEDE23AE1514E24C79360CE44291140AFEC5576503E6EBBC1D453DC9ED772F6AA506112D26C5B806EC4E0EAEC6A98F6F1683AC86494FA565B7CE2A0540F45AC6A8A556CDEE96B315EF45ED456EF46F1E5C234ABF92B121CCE688D2163BDF57FB6098B4742FBC504AC5E03230ADF90428D53F024F9C20964CA8AEA8A9042D1CEF0A2BA9C88A011C1004A9B8F7F9EBEDA365056947E9426D868829904D70DA74B7276D93829D151333F3506E83032A8CF68630BD5D0A39237FDF0BADD131AB77B6092987F07139588081D140EB2A0CA25BF9267FBC809457528A7C5DFA1729552A8D76B3362B9915E84795CD770EB84A3AE73DE78F04D37E8FC2F53E2F72853D57F170FC2551F7CBBB9B63D119AC4775120418F3865792FEFAAFF69038BFD3AA3C2C476B26DFB2B48F3B3904203C394782FE1C40F718001C31504539827DDC80900966524DCE965006F86EBDA9C46226BC1DA2387E2AE9565DFEE621F0DA936298371D1AD83860B4E607BB473E4103ADF872D1A3CA9EB171CF73077B4AFC2D7FC3737FAE5337290D8116CE0826F347C621502BA313EEF20F1F67D00A7EB1BB555DE7B0FCDD05C747D7615136B35079DA4BF9A6EED5F0C5448B8364FB03476275BD823C8C7F3CBCC1D0BD41C5B49538D65C00C85F9C8D50201EDA6266D7A2A375141F7F5E69E1AD06932C3A6C044EAE908D43657EA22C5FA797FE8D04592541477B362753A05", "hilink": "BEC62B3A1B90717BABD8C9AD15D3BE1560977111A022C59CEF399A97E8B1D1D2D981EE972F4C881D0D3D30D8165FA46BEB1F07B4B8F1AECEA4335DFC7388E1234641EDD1AF31DFEDB616EBF0A9900778C03C4467D3CBCD6540F51D66CBE0FF998506BA06448D8BEB6CDA8E9E79BE4C8BF15449A4DEFEB22F342B09F03683E17D699DA475D08A2F079EDA59B99A96CD0E468B089224120EE96B86A8BB4B250B35E0E8B8A1E77FE8A13F50CF8B018FA5A91475DDA932141DAF1F5C4D9C5D7F8DC8E3CB582D0DB0EA8823396BF6BC0A30D68B968BEE77AB6014036385A68BB35701D274404A9BFA24348E7321F63FEA98B7"}

BIN
output/ws63-liteos_all.zip Executable file

Binary file not shown.