use core::slice;
#[cfg(feature = "controller")]
use core::time::Duration;
use low_level::{
CMD_DEVICE_FAMILY, CMD_FIRMWARE_VERSION, CMD_FLASH_SIZE, CMD_MCU_IDCODE,
CMD_REBOOT_TO_BOOTLOADER, CMD_RESET, CMD_WHO_AM_I,
};
const I2C_ADDR: u8 = 0x57;
#[derive(Debug)]
pub enum Error<I2cError> {
I2c(I2cError),
#[cfg(feature = "controller")]
InvalidFrequency(u32),
#[cfg(feature = "controller")]
InvalidDuration(Duration),
#[cfg(feature = "controller")]
InvalidRawSpeed,
#[cfg(feature = "controller")]
InvalidVersion((u8, u8, u8)),
#[cfg(feature = "controller")]
UnknownFirmwareFound,
}
impl<I2CError: core::fmt::Display> core::fmt::Display for Error<I2CError> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::I2c(error) => write!(f, "I2C error: {error}"),
#[cfg(feature = "controller")]
Error::InvalidFrequency(freq) => write!(f, "invalid frequency {freq} Hz"),
#[cfg(feature = "controller")]
Error::InvalidDuration(d) => write!(f, "invalid duration {d:?}"),
#[cfg(feature = "controller")]
Error::InvalidRawSpeed => write!(f, "invalid raw speed -128"),
#[cfg(feature = "controller")]
Error::InvalidVersion((major, minor, patch)) => {
let (req_major, req_minor, req_patch) =
crate::controller::REQUIRED_FIRMWARE_VERSION;
write!(
f,
"invalid firmware version {major}.{minor}.{patch} (expected {req_major}.{req_minor}.{req_patch})",
)
}
#[cfg(feature = "controller")]
Error::UnknownFirmwareFound => write!(f, "unknown program found on controller"),
}
}
}
impl<I2CError: core::error::Error> core::error::Error for Error<I2CError> {}
pub type Result<T, I2cError> = core::result::Result<T, Error<I2cError>>;
pub trait Device {
type I2cError;
fn write(&mut self, buf: impl AsRef<[u8]>) -> Result<(), Self::I2cError>;
fn write_read(
&mut self,
write: impl AsRef<[u8]>,
read: impl AsMut<[u8]>,
) -> Result<(), Self::I2cError>;
fn firmware_version(&mut self) -> Result<(u8, u8, u8), Self::I2cError> {
let mut result = [0; 3];
self.write_read([CMD_FIRMWARE_VERSION], &mut result)?;
Ok(result.into())
}
fn who_am_i(&mut self) -> Result<WhoAmI, Self::I2cError> {
let mut me: u8 = 0;
self.write_read([CMD_WHO_AM_I], slice::from_mut(&mut me))?;
Ok(match me {
addr if addr == I2C_ADDR | 0x80 => WhoAmI::Bootloader,
addr if addr == I2C_ADDR => WhoAmI::Controller,
addr => WhoAmI::Other(addr),
})
}
fn reset(&mut self) -> Result<(), Self::I2cError> {
self.write([CMD_RESET])
}
fn device_family(&mut self) -> Result<Option<(u8, u8)>, Self::I2cError> {
let mut data = [0; 2];
self.write_read([CMD_DEVICE_FAMILY], &mut data)?;
Ok((data != [0, 0]).then_some((data[0], data[1])))
}
fn mcu_kind(&mut self) -> Result<Option<(u16, u16)>, Self::I2cError> {
let mut data = [0; 4];
self.write_read([CMD_MCU_IDCODE], &mut data)?;
Ok((data != [0; 4]).then_some((
u16::from_le_bytes([data[0], data[1]]),
u16::from_le_bytes([data[2], data[3]]),
)))
}
fn flash_size(&mut self) -> Result<u16, Self::I2cError> {
let mut data = [0; 2];
self.write_read([CMD_FLASH_SIZE], &mut data)?;
Ok(u16::from_le_bytes([data[0], data[1]]))
}
fn switch_to_bootloader(&mut self) -> Result<(), Self::I2cError> {
self.write([CMD_REBOOT_TO_BOOTLOADER])
}
}
impl<I2c: embedded_hal::i2c::I2c> Device for I2c {
type I2cError = I2c::Error;
fn write(&mut self, buf: impl AsRef<[u8]>) -> Result<(), Self::I2cError> {
(self as &mut I2c)
.write(I2C_ADDR, buf.as_ref())
.map_err(Error::I2c)
}
fn write_read(
&mut self,
write: impl AsRef<[u8]>,
mut read: impl AsMut<[u8]>,
) -> Result<(), Self::I2cError> {
(self as &mut I2c)
.write_read(I2C_ADDR, write.as_ref(), read.as_mut())
.map_err(Error::I2c)
}
}
pub enum WhoAmI {
Bootloader,
Controller,
Other(u8),
}
#[must_use]
pub fn brand_name(identity_code: u8, continuation_code: u8) -> Option<&'static str> {
match (identity_code, continuation_code) {
(0x20, 0x00) => Some("STMicroelectronics"),
(0x3b, 0x04) => Some("ARM generic (APM32?)"),
_ => None,
}
}
#[must_use]
pub fn mcu_kind(dev_id: u16, rev_id: u16) -> (&'static str, &'static str) {
match (dev_id, rev_id) {
(0x412, 0x1000) => ("low-density", "A"),
(0x412, _) => ("low-density", "?"),
(0x410, 0x0000) => ("medium-density", "A"),
(0x410, 0x2000) => ("medium-density", "B"),
(0x410, 0x2001) => ("medium-density", "Z"),
(0x410, 0x2003) => ("medium-density", "1/2/3/X/Y"),
(0x410, _) => ("medium-density", "?"),
(0x414, 0x1000) => ("high-density", "1/A"),
(0x414, 0x1001) => ("high-density", "Z"),
(0x414, 0x1003) => ("high-density", "1/2/3/X/Y"),
(0x414, _) => ("high-density", "?"),
(0x430, 0x1000) => ("XL-density", "1/A"),
(0x430, _) => ("XL-density", "?"),
(0x418, 0x1000) => ("connectivity", "A"),
(0x418, 0x1001) => ("connectivity", "Z"),
(0x418, _) => ("connectivity", "?"),
_ => ("?", "?"),
}
}
pub(crate) mod low_level {
pub const CMD_FIRMWARE_VERSION: u8 = 0x08;
pub const CMD_WHO_AM_I: u8 = 0x0f;
pub const CMD_RESET: u8 = 0xe0;
pub const CMD_REBOOT_TO_BOOTLOADER: u8 = 0xe1;
#[cfg(feature = "low-level")]
pub const CMD_DEVICE_ID: u8 = 0xf0;
pub const CMD_DEVICE_FAMILY: u8 = 0xf1;
pub const CMD_MCU_IDCODE: u8 = 0xf2;
pub const CMD_FLASH_SIZE: u8 = 0xf3;
}