Skip to main content

m5stack_core/io/
pps.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use embassy_time::{Duration, Instant, Ticker, with_timeout};
3
4use crate::driver::pps::PpsDriver;
5pub use crate::driver::pps::{PpsError, PpsRunningMode};
6use crate::io::shared_i2c::SharedI2cBus;
7
8pub struct PpsReadings {
9    pub voltage: f32,
10    pub current: f32,
11    pub temperature: f32,
12    pub input_voltage: f32,
13    pub running_mode: PpsRunningMode,
14}
15
16pub struct PpsSetpoint {
17    pub current_limit: Option<f32>,
18    pub voltage_limit: Option<f32>,
19    pub enabled: Option<bool>,
20}
21
22pub struct PpsResources {
23    pub i2c: &'static SharedI2cBus,
24}
25
26const PPS_LOOP_TIME_MS: u64 = 500;
27
28async fn read_pps(pps: &mut PpsDriver) -> Result<PpsReadings, PpsError> {
29    let voltage = pps.get_voltage().await?;
30    let current = pps.get_current().await?;
31    let temperature = pps.get_temperature().await?;
32    let input_voltage = pps.get_input_voltage().await?;
33    let running_mode = pps.get_running_mode().await?;
34    Ok(PpsReadings {
35        voltage,
36        current,
37        temperature,
38        input_voltage,
39        running_mode,
40    })
41}
42
43async fn write_pps(pps: &mut PpsDriver, setpoint: &PpsSetpoint) -> Result<(), PpsError> {
44    debug!(
45        "write_pps: cl: {:?} vl: {:?} enabled: {:?}",
46        setpoint.current_limit, setpoint.voltage_limit, setpoint.enabled
47    );
48    if let Some(cl) = setpoint.current_limit {
49        pps.set_current(cl).await?;
50    }
51    if let Some(vl) = setpoint.voltage_limit {
52        pps.set_voltage(vl).await?;
53    }
54    match setpoint.enabled {
55        Some(en) => {
56            pps.enable(en).await?;
57        }
58        None => (),
59    }
60    Ok(())
61}
62
63async fn poll_pps(
64    pps: &mut PpsDriver,
65    on_read: fn(&PpsReadings),
66    get_setpoint: fn() -> PpsSetpoint,
67) -> Result<(), PpsError> {
68    let setpoint = get_setpoint();
69    write_pps(pps, &setpoint).await?;
70    let readings = read_pps(pps).await?;
71    on_read(&readings);
72    Ok(())
73}
74
75/// Full PPS loop: 500ms ticker, 1500ms timeout, error counting (break after 10).
76///
77/// Expected behaviour on a bench / HIL rig **without** PPS hardware: the I2C
78/// probe at 0x35 NACKs on every cycle, `error_count` saturates within ~5 s,
79/// and the task logs:
80///
81/// ```text
82/// [WARN ] PPS error: I2C master error: AcknowledgeCheckFailed(Unknown)   x10
83/// [ERROR] stopping PPS task after 10 consecutive errors
84/// ```
85///
86/// Then the task exits cleanly. The board's other tasks (BLE, LVGL, logger,
87/// serial_cmd, control loop) keep running. **This is not a failure** —
88/// during development and HIL testing the PPS module is absent, so the
89/// errors and the "stopping" log are expected and can be ignored. The check
90/// is there for production benches where a wedged PPS shouldn't keep the
91/// I2C bus hot forever.
92pub async fn pps_loop(
93    resources: PpsResources,
94    on_read: fn(&PpsReadings),
95    get_setpoint: fn() -> PpsSetpoint,
96) {
97    let mut pps = PpsDriver::new(resources.i2c, 0x35);
98    pps.enable(false).await.ok();
99
100    let mut ticker = Ticker::every(Duration::from_millis(PPS_LOOP_TIME_MS));
101    let mut error_count = 0;
102    loop {
103        let loop_start = Instant::now();
104        let timeout_result = with_timeout(
105            Duration::from_millis(PPS_LOOP_TIME_MS * 3),
106            poll_pps(&mut pps, on_read, get_setpoint),
107        )
108        .await;
109        match timeout_result {
110            Ok(poll_result) => match poll_result {
111                Ok(_) => {
112                    error_count = 0;
113                }
114                Err(err) => {
115                    warn!("PPS error: {}", err);
116                    error_count += 1;
117                    if error_count > 10 {
118                        error!("stopping PPS task after 10 consecutive errors");
119                        break;
120                    }
121                }
122            },
123            Err(err) => {
124                error!("timeout in io i2c loop: {:?}", err);
125                ticker.reset_at(Instant::now() - Duration::from_millis(PPS_LOOP_TIME_MS));
126            }
127        }
128        let loop_time = loop_start.elapsed();
129        debug!("io loop time: {:?} ms", loop_time.as_millis());
130        ticker.next().await;
131    }
132}