diff --git a/project/example/ble/ble_peripheral/app/src/bridge_pwm.c b/project/example/ble/ble_peripheral/app/src/bridge_pwm.c index 53952ca..a1d6295 100644 --- a/project/example/ble/ble_peripheral/app/src/bridge_pwm.c +++ b/project/example/ble/ble_peripheral/app/src/bridge_pwm.c @@ -27,6 +27,7 @@ #define BRIDGE_DUAL_ACTIVE_US 450U #define BRIDGE_MIN_DUAL_PULSE_US 1U #define BRIDGE_TIMER_TICKS_PER_US 16U +#define BRIDGE_PDM_THRESHOLD (BRIDGE_MIX_SCALE * 4096UL) #define BRIDGE_GPIO_PORT 0U #define BRIDGE_GPIO_MASK ((1UL << IO_PWM_W) | (1UL << IO_PWM_C)) @@ -62,6 +63,14 @@ static volatile uint16_t next_w_us; static volatile uint16_t next_c_us; static volatile uint16_t next_wc_off_us; static volatile uint16_t next_cw_off_us; +static volatile uint32_t next_pdm_w_step; +static volatile uint32_t next_pdm_c_step; +static volatile uint8_t next_pdm_enabled; +static volatile uint32_t pdm_w_accumulator; +static volatile uint32_t pdm_c_accumulator; +static volatile uint8_t frame_pdm_enabled; +static volatile uint8_t frame_w_emit; +static volatile uint8_t frame_c_emit; static uint16_t cached_w_mix = 0xFFFFU; static uint16_t cached_c_mix = 0xFFFFU; static uint8_t bridge_ready; @@ -215,6 +224,14 @@ void Bridge_Off(void) next_c_us = 0U; next_wc_off_us = 0U; next_cw_off_us = 0U; + next_pdm_w_step = 0U; + next_pdm_c_step = 0U; + next_pdm_enabled = 0U; + pdm_w_accumulator = 0U; + pdm_c_accumulator = 0U; + frame_pdm_enabled = 0U; + frame_w_emit = 0U; + frame_c_emit = 0U; cached_w_mix = 0xFFFFU; cached_c_mix = 0xFFFFU; bridge_force_off(); @@ -223,6 +240,10 @@ void Bridge_Off(void) __RAM_CODE void Bridge_Deadtime_Expired(void) { + uint32_t w_step; + uint32_t c_step; + uint8_t pdm_enabled; + if (!bridge_ready || (deviceStatus == POWEROFF && !Light_Transition_FadeActive)) { Bridge_Off(); @@ -238,7 +259,43 @@ __RAM_CODE void Bridge_Deadtime_Expired(void) frame_c_us = next_c_us; frame_wc_off_us = next_wc_off_us; frame_cw_off_us = next_cw_off_us; - bridge_gpio_state(BRIDGE_W); + pdm_enabled = next_pdm_enabled; + w_step = next_pdm_w_step; + c_step = next_pdm_c_step; + + if (pdm_enabled && !frame_pdm_enabled) { + pdm_w_accumulator = BRIDGE_PDM_THRESHOLD - w_step; + pdm_c_accumulator = BRIDGE_PDM_THRESHOLD - c_step; + } + frame_pdm_enabled = pdm_enabled; + + if (frame_pdm_enabled) { + pdm_w_accumulator += w_step; + if (pdm_w_accumulator >= BRIDGE_PDM_THRESHOLD) { + pdm_w_accumulator -= BRIDGE_PDM_THRESHOLD; + frame_w_emit = 1U; + } else { + frame_w_emit = 0U; + } + + pdm_c_accumulator += c_step; + if (pdm_c_accumulator >= BRIDGE_PDM_THRESHOLD) { + pdm_c_accumulator -= BRIDGE_PDM_THRESHOLD; + frame_c_emit = 1U; + } else { + frame_c_emit = 0U; + } + } else { + pdm_w_accumulator = 0U; + pdm_c_accumulator = 0U; + frame_w_emit = 1U; + frame_c_emit = 1U; + } + + if (frame_w_emit) + bridge_gpio_state(BRIDGE_W); + else + bridge_gpio_off(); timer_stage = BRIDGE_STAGE_END_W; bridge_timer_start(frame_w_us); } else if (timer_stage == BRIDGE_STAGE_END_W) { @@ -246,7 +303,10 @@ __RAM_CODE void Bridge_Deadtime_Expired(void) timer_stage = BRIDGE_STAGE_START_C; bridge_timer_start(frame_wc_off_us); } else if (timer_stage == BRIDGE_STAGE_START_C) { - bridge_gpio_state(BRIDGE_C); + if (frame_c_emit) + bridge_gpio_state(BRIDGE_C); + else + bridge_gpio_off(); timer_stage = BRIDGE_STAGE_END_C; bridge_timer_start(frame_c_us); } else if (timer_stage == BRIDGE_STAGE_END_C) { @@ -268,6 +328,8 @@ __RAM_CODE void Bridge_Service_1ms(void) uint32_t total; uint32_t total_on_us; uint32_t total_off_us; + uint32_t pdm_w_step; + uint32_t pdm_c_step; uint16_t pwm_duty; uint8_t single_state; @@ -283,6 +345,42 @@ __RAM_CODE void Bridge_Service_1ms(void) c_mix = clamp_mix_value(C_PWM_duty); total = w_mix + c_mix; + /* + * Below a physical 1 us pulse, keep the pulse width valid and distribute + * 1 us pulses across 2 kHz frames. This extends a mixed-light power-off + * fade below the normal timer resolution without creating sub-us edges. + */ + pdm_w_step = + BRIDGE_DUAL_ACTIVE_US * Light_Transition_W_Output_Q12; + pdm_c_step = + BRIDGE_DUAL_ACTIVE_US * Light_Transition_C_Output_Q12; + if (deviceStatus == POWEROFF && Light_Transition_FadeActive && + pdm_w_step != 0U && pdm_c_step != 0U && + pdm_w_step < BRIDGE_PDM_THRESHOLD && + pdm_c_step < BRIDGE_PDM_THRESHOLD) { + next_w_us = BRIDGE_MIN_DUAL_PULSE_US; + next_c_us = BRIDGE_MIN_DUAL_PULSE_US; + total_off_us = + BRIDGE_FRAME_US - (2U * BRIDGE_MIN_DUAL_PULSE_US); + next_wc_off_us = (uint16_t)(total_off_us / 2U); + next_cw_off_us = + (uint16_t)(total_off_us - next_wc_off_us); + next_pdm_w_step = pdm_w_step; + next_pdm_c_step = pdm_c_step; + next_pdm_enabled = 1U; + cached_w_mix = (uint16_t)w_mix; + cached_c_mix = (uint16_t)c_mix; + goto dual_output_ready; + } + + if (next_pdm_enabled) { + cached_w_mix = 0xFFFFU; + cached_c_mix = 0xFFFFU; + } + next_pdm_enabled = 0U; + next_pdm_w_step = 0U; + next_pdm_c_step = 0U; + if (total == 0U) { Bridge_Off(); return; @@ -351,6 +449,7 @@ __RAM_CODE void Bridge_Service_1ms(void) cached_c_mix = (uint16_t)c_mix; } +dual_output_ready: if (output_mode == BRIDGE_MODE_DUAL && timer_stage != BRIDGE_STAGE_IDLE) { return; diff --git a/project/example/ble/ble_peripheral/app/src/light_transition.c b/project/example/ble/ble_peripheral/app/src/light_transition.c index 9df5c3c..450b49e 100644 --- a/project/example/ble/ble_peripheral/app/src/light_transition.c +++ b/project/example/ble/ble_peripheral/app/src/light_transition.c @@ -3,6 +3,8 @@ #include "light_transition.h" volatile uint8_t Light_Transition_FadeActive; +volatile uint32_t Light_Transition_W_Output_Q12; +volatile uint32_t Light_Transition_C_Output_Q12; #define BRIGHTNESS_SMOOTH_SHIFT 4U #define OUTPUT_SMOOTH_SHIFT 4U @@ -230,6 +232,8 @@ void Light_Transition_Task(void) uint16_t weak_mix; uint16_t required_total; uint16_t floor_w; + uint32_t scaled_w_q12; + uint32_t scaled_c_q12; uint8_t power_transitioning; /* @@ -288,8 +292,12 @@ void Light_Transition_Task(void) } } - target_w = (uint16_t)(((uint32_t)target_w * fade_scale + 2048U) >> 12); - target_c = (uint16_t)(((uint32_t)target_c * fade_scale + 2048U) >> 12); + scaled_w_q12 = (uint32_t)target_w * fade_scale; + scaled_c_q12 = (uint32_t)target_c * fade_scale; + Light_Transition_W_Output_Q12 = scaled_w_q12; + Light_Transition_C_Output_Q12 = scaled_c_q12; + target_w = (uint16_t)((scaled_w_q12 + 2048U) >> 12); + target_c = (uint16_t)((scaled_c_q12 + 2048U) >> 12); if(Mode == mode1 && powered_on && !power_transitioning) { diff --git a/project/example/ble/ble_peripheral/app/src/light_transition.h b/project/example/ble/ble_peripheral/app/src/light_transition.h index ab64306..29baf41 100644 --- a/project/example/ble/ble_peripheral/app/src/light_transition.h +++ b/project/example/ble/ble_peripheral/app/src/light_transition.h @@ -4,6 +4,8 @@ #include extern volatile uint8_t Light_Transition_FadeActive; +extern volatile uint32_t Light_Transition_W_Output_Q12; +extern volatile uint32_t Light_Transition_C_Output_Q12; #define Light_Transition_RequestOn() (Light_Transition_FadeActive = 0U) #define Light_Transition_RequestOff() (Light_Transition_FadeActive = 1U)