Skip to main content

m5stack_core/driver/
pps.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Programmable Power Supply (PPS) module driver (I2C 0x35).
3//!
4//! Custom I2C command/response protocol with single-byte command register
5//! followed by 1–4 byte payload. Voltage/current values are IEEE 754 f32 LE.
6//!
7//! Read register map:
8//!   0x00  ModuleId         2 bytes, u16 LE
9//!   0x05  RunningMode      1 byte: 0=Off, 1=Voltage, 2=Current, 3=Unknown
10//!   0x07  DataFlag         1 byte
11//!   0x08  ReadbackVoltage  4 bytes, f32 LE (volts)
12//!   0x0C  ReadbackCurrent  4 bytes, f32 LE (amps)
13//!   0x10  Temperature      4 bytes, f32 LE (°C)
14//!   0x14  InputVoltage     4 bytes, f32 LE (volts)
15//!   0x50  Address          1 byte
16//!   0x52  UID word 0       4 bytes
17//!   0x56  UID word 1       4 bytes
18//!   0x5A  UID word 2       4 bytes
19//!
20//! Write register map:
21//!   0x04  Enable           1 byte: 0=disable, 1=enable
22//!   0x18  SetVoltage       4 bytes, f32 LE (volts)
23//!   0x1C  SetCurrent       4 bytes, f32 LE (amps)
24//!
25//! Hardware: M5Stack PPS module (SKU: U136)
26//! Ref: <https://docs.m5stack.com/en/unit/PPS>
27use esp_hal::{Async, i2c::master::I2c};
28use thiserror_no_std::Error;
29
30use crate::io::shared_i2c::SharedI2cBus;
31
32#[repr(u8)]
33#[derive(Debug, Default, Clone, Copy)]
34pub enum PpsRunningMode {
35    Off = 0,
36    Voltage = 1,
37    Current = 2,
38    #[default]
39    Unknown = 3,
40}
41
42impl PpsRunningMode {
43    pub fn from_u8(v: u8) -> Option<Self> {
44        match v {
45            0 => Some(Self::Off),
46            1 => Some(Self::Voltage),
47            2 => Some(Self::Current),
48            3 => Some(Self::Unknown),
49            _ => None,
50        }
51    }
52}
53
54#[derive(Debug, Error)]
55pub enum PpsError {
56    #[error("unknown error")]
57    Unknown,
58
59    #[error("Failed to read from PPS module")]
60    ReadError,
61
62    #[error("Command not implemented")]
63    UnsupportedCommand,
64
65    #[error("I2C master error: {0:?}")]
66    I2cMasterError(#[from] esp_hal::i2c::master::Error),
67}
68
69type I2cType = I2c<'static, Async>;
70
71#[allow(dead_code)]
72enum ReadCommand {
73    ModuleId,
74    GetRunningMode,
75    GetDataFlag,
76    ReadbackVoltage,
77    ReadbackCurrent,
78    GetTemperature,
79    GetInputVoltage,
80    GetAddress,
81    PsuUidW0,
82    PsuUidW1,
83    PsuUidW2,
84}
85
86pub enum ReadResult {
87    ModuleId(u16),
88    RunningMode(PpsRunningMode),
89    ReadbackVoltage(f32),
90    ReadbackCurrent(f32),
91    Temperature(f32),
92    InputVoltage(f32),
93}
94
95impl ReadCommand {
96    fn get_read_command(&self) -> (u8, usize) {
97        match self {
98            ReadCommand::ModuleId => (0x0, 2),
99            ReadCommand::GetRunningMode => (0x05, 1),
100            ReadCommand::GetDataFlag => (0x07, 1),
101            ReadCommand::ReadbackVoltage => (0x08, 4),
102            ReadCommand::ReadbackCurrent => (0x0c, 4),
103            ReadCommand::GetTemperature => (0x10, 4),
104            ReadCommand::GetInputVoltage => (0x14, 4),
105            ReadCommand::GetAddress => (0x50, 1),
106            ReadCommand::PsuUidW0 => (0x52, 4),
107            ReadCommand::PsuUidW1 => (0x56, 4),
108            ReadCommand::PsuUidW2 => (0x5a, 4),
109        }
110    }
111
112    fn evaluate_result(&self, buffer: &[u8; 4]) -> Result<ReadResult, PpsError> {
113        match self {
114            ReadCommand::ModuleId => Ok(ReadResult::ModuleId(
115                (buffer[1] as u16) << 8 | buffer[0] as u16,
116            )),
117            ReadCommand::GetRunningMode => Ok(ReadResult::RunningMode(
118                PpsRunningMode::from_u8(buffer[0]).ok_or(PpsError::Unknown)?,
119            )),
120            ReadCommand::ReadbackVoltage => {
121                Ok(ReadResult::ReadbackVoltage(f32::from_le_bytes(*buffer)))
122            }
123            ReadCommand::ReadbackCurrent => {
124                Ok(ReadResult::ReadbackCurrent(f32::from_le_bytes(*buffer)))
125            }
126            ReadCommand::GetTemperature => Ok(ReadResult::Temperature(f32::from_le_bytes(*buffer))),
127            ReadCommand::GetInputVoltage => {
128                Ok(ReadResult::InputVoltage(f32::from_le_bytes(*buffer)))
129            }
130            _ => Err(PpsError::UnsupportedCommand),
131        }
132    }
133
134    pub async fn receive_async(
135        self,
136        i2c: &mut I2cType,
137        address: u8,
138    ) -> Result<ReadResult, PpsError> {
139        let (cmd, bytes_to_read) = self.get_read_command();
140        let mut buffer = [0_u8; 4];
141        i2c.write_read_async(address, &[cmd], &mut buffer[..bytes_to_read])
142            .await?;
143        self.evaluate_result(&buffer)
144    }
145}
146
147#[derive(Debug)]
148enum WriteCommand {
149    ModuleEnable(bool),
150    SetVoltage(f32),
151    SetCurrent(f32),
152}
153
154impl WriteCommand {
155    fn get_write_command(&self, buffer: &mut [u8; 5]) -> usize {
156        match self {
157            WriteCommand::ModuleEnable(enable) => {
158                buffer[0] = 0x04;
159                buffer[1] = *enable as u8;
160                2
161            }
162            WriteCommand::SetVoltage(voltage) => {
163                buffer[0] = 0x18;
164                buffer[1..].copy_from_slice(voltage.to_le_bytes().as_slice());
165                5
166            }
167            WriteCommand::SetCurrent(current) => {
168                buffer[0] = 0x1c;
169                buffer[1..].copy_from_slice(current.to_le_bytes().as_slice());
170                5
171            }
172        }
173    }
174
175    pub async fn send_async(self, i2c: &mut I2cType, address: u8) -> Result<(), PpsError> {
176        debug!("send: {:?} to address 0x{:x}", self, address);
177        let mut buffer = [0x0_u8; 5];
178        let bytes_to_write = self.get_write_command(&mut buffer);
179        i2c.write_async(address, &buffer[..bytes_to_write]).await?;
180        Ok(())
181    }
182}
183
184pub struct PpsDriver {
185    i2c: &'static SharedI2cBus,
186    address: u8,
187}
188
189impl PpsDriver {
190    pub fn new(i2c: &'static SharedI2cBus, address: u8) -> Self {
191        Self { i2c, address }
192    }
193
194    pub async fn set_current(&mut self, current: f32) -> Result<&mut Self, PpsError> {
195        let cmd = WriteCommand::SetCurrent(current);
196        debug!("set current {}, sending command: {:?}", current, cmd);
197        let mut bus = self.i2c.lock().await;
198        cmd.send_async(&mut *bus, self.address).await?;
199        Ok(self)
200    }
201
202    pub async fn set_voltage(&mut self, voltage: f32) -> Result<&mut Self, PpsError> {
203        let cmd = WriteCommand::SetVoltage(voltage);
204        let mut bus = self.i2c.lock().await;
205        cmd.send_async(&mut *bus, self.address).await?;
206        Ok(self)
207    }
208
209    pub async fn enable(&mut self, enabled: bool) -> Result<&mut Self, PpsError> {
210        let cmd = WriteCommand::ModuleEnable(enabled);
211        let mut bus = self.i2c.lock().await;
212        cmd.send_async(&mut *bus, self.address).await?;
213        Ok(self)
214    }
215
216    pub async fn get_running_mode(&mut self) -> Result<PpsRunningMode, PpsError> {
217        let mut bus = self.i2c.lock().await;
218        match ReadCommand::GetRunningMode
219            .receive_async(&mut *bus, self.address)
220            .await?
221        {
222            ReadResult::RunningMode(mode) => Ok(mode),
223            _ => Err(PpsError::ReadError),
224        }
225    }
226
227    pub async fn get_voltage(&mut self) -> Result<f32, PpsError> {
228        let mut bus = self.i2c.lock().await;
229        match ReadCommand::ReadbackVoltage
230            .receive_async(&mut *bus, self.address)
231            .await?
232        {
233            ReadResult::ReadbackVoltage(voltage) => Ok(voltage),
234            _ => Err(PpsError::ReadError),
235        }
236    }
237
238    pub async fn get_current(&mut self) -> Result<f32, PpsError> {
239        let mut bus = self.i2c.lock().await;
240        match ReadCommand::ReadbackCurrent
241            .receive_async(&mut *bus, self.address)
242            .await?
243        {
244            ReadResult::ReadbackCurrent(current) => Ok(current),
245            _ => Err(PpsError::ReadError),
246        }
247    }
248
249    pub async fn get_temperature(&mut self) -> Result<f32, PpsError> {
250        let mut bus = self.i2c.lock().await;
251        match ReadCommand::GetTemperature
252            .receive_async(&mut *bus, self.address)
253            .await?
254        {
255            ReadResult::Temperature(temp) => Ok(temp),
256            _ => Err(PpsError::ReadError),
257        }
258    }
259
260    pub async fn get_input_voltage(&mut self) -> Result<f32, PpsError> {
261        let mut bus = self.i2c.lock().await;
262        match ReadCommand::GetInputVoltage
263            .receive_async(&mut *bus, self.address)
264            .await?
265        {
266            ReadResult::InputVoltage(voltage) => Ok(voltage),
267            _ => Err(PpsError::ReadError),
268        }
269    }
270
271    #[allow(dead_code)]
272    pub async fn get_module_id(&mut self) -> Result<u16, PpsError> {
273        let mut bus = self.i2c.lock().await;
274        match ReadCommand::ModuleId
275            .receive_async(&mut *bus, self.address)
276            .await?
277        {
278            ReadResult::ModuleId(id) => Ok(id),
279            _ => Err(PpsError::ReadError),
280        }
281    }
282}