rmk_q1_pro_iso/backlight/
init.rs1use crate::backlight::{
4 gamma_correction::gamma_correction,
5 lock_indicator::{BACKLIGHT_CH, BacklightCmd},
6 mapping::iso_knob::LED_LAYOUT,
7};
8use embassy_stm32::{i2c, i2c::I2c, mode::Async};
9use embassy_time::{Duration, Ticker, Timer};
10use embedded_hal_async::i2c::ErrorType;
11use rmk::embassy_futures::select::{Either, select};
12use snled27351_driver::{
13 driver::Driver,
14 transport::i2c::{I2cTransport, I2cTransportError},
15};
16
17type Transport = I2cTransport<I2c<'static, Async, i2c::Master>, 2>;
19type BacklightDriver = Driver<Transport, 2>;
21
22const CAPS_LOCK_LED_INDEX: usize = 45;
24const INDICATOR_BRIGHTNESS: u8 = 100;
26const INDICATOR_RED: (u8, u8, u8) = (255, 0, 0);
28const INDICATOR_WHITE: (u8, u8, u8) = (255, 255, 255);
30const SOFTSTART_STEPS: u8 = 50;
32const SOFTSTART_RAMP_MS: u32 = 1000;
34const THERMAL_THROTTLE_BRIGHTNESS: u8 = 50;
37const THERMAL_POLL: Duration = Duration::from_secs(5);
39
40#[inline]
41const fn scale(value: u8, brightness_percent: u8) -> u8 {
42 let v = u16::from(value);
43 let p = u16::from(brightness_percent.min(100));
44 u8::try_from(v.saturating_mul(p).saturating_add(50).checked_div(100).unwrap_or_default()).unwrap_or(255)
45}
46
47#[inline]
48const fn correct(red: u8, green: u8, blue: u8, brightness_percent: u8) -> (u8, u8, u8) {
49 (
50 gamma_correction(scale(red, brightness_percent)),
51 gamma_correction(scale(green, brightness_percent)),
52 gamma_correction(scale(blue, brightness_percent)),
53 )
54}
55
56#[derive(Clone, Copy)]
59struct BacklightState {
60 caps_lock: bool,
62 brightness: u8,
64}
65
66impl BacklightState {
67 const fn new() -> Self { Self { caps_lock: false, brightness: 100 } }
68}
69
70async fn render_all(
80 driver: &mut BacklightDriver,
81 state: BacklightState,
82) -> Result<(), I2cTransportError<<I2c<'static, Async, i2c::Master> as ErrorType>::Error>> {
83 let (r, g, b) = correct(255, 255, 255, state.brightness);
84 driver.stage_all_leds(r, g, b);
85 render_indicator(driver, state).await
86}
87
88async fn render_indicator(
97 driver: &mut BacklightDriver,
98 state: BacklightState,
99) -> Result<(), I2cTransportError<<I2c<'static, Async, i2c::Master> as ErrorType>::Error>> {
100 let color = if state.caps_lock { INDICATOR_RED } else { INDICATOR_WHITE };
102 let (r, g, b) = correct(color.0, color.1, color.2, INDICATOR_BRIGHTNESS);
103 driver.stage_led(CAPS_LOCK_LED_INDEX, r, g, b);
104 driver.flush().await
105}
106
107#[optimize(size)]
116async fn softstart(
117 driver: &mut BacklightDriver,
118 base_red: u8,
119 base_green: u8,
120 base_blue: u8,
121 target_brightness: u8,
122) -> Result<(), I2cTransportError<<I2c<'static, Async, i2c::Master> as ErrorType>::Error>> {
123 let target = u32::from(target_brightness.min(100));
124 let steps = u32::from(SOFTSTART_STEPS.max(1));
125 let delay_ms = u64::from(SOFTSTART_RAMP_MS.checked_div(steps).unwrap_or(1).max(1));
126
127 for step in 0..=steps {
128 let percent = u8::try_from(target.saturating_mul(step).saturating_div(steps)).unwrap_or(0);
129 let (r, g, b) = correct(base_red, base_green, base_blue, percent);
130 driver.set_all_leds(r, g, b).await?;
131 if step < steps {
132 Timer::after_millis(delay_ms).await;
133 }
134 }
135 Ok(())
136}
137
138#[optimize(size)]
151pub async fn backlight_runner(i2c: I2c<'static, Async, i2c::Master>, addr0: u8, addr1: u8) -> ! {
152 let transport = I2cTransport::new(i2c, [addr0, addr1]);
153 let mut driver = BacklightDriver::new(transport, LED_LAYOUT);
154
155 if driver.init(0xFF).await.is_ok() {
156 let _ = softstart(&mut driver, 255, 255, 255, 100).await;
157 }
158
159 let rx = BACKLIGHT_CH.receiver();
160 let mut thermal_ticker = Ticker::every(THERMAL_POLL);
161 let mut state = BacklightState::new();
162
163 loop {
164 match select(rx.receive(), thermal_ticker.next()).await {
165 Either::First(cmd) => match cmd {
166 BacklightCmd::Indicators { caps } => {
167 state.caps_lock = caps;
168 let _ = render_indicator(&mut driver, state).await;
170 }
171 },
172 Either::Second(_) => {
173 let hot = driver.check_thermal_flag_set(0).await || driver.check_thermal_flag_set(1).await;
175 let new_brightness = if hot { THERMAL_THROTTLE_BRIGHTNESS } else { 100 };
176
177 if new_brightness != state.brightness {
179 state.brightness = new_brightness;
180 let _ = render_all(&mut driver, state).await;
181 }
182 }
183 }
184 }
185}