use core::fmt;
use flipperzero_sys as sys;
use crate::furi::time::FuriDuration;
pub const INTERNAL_LED_CONTROLLER: DeviceAddress = DeviceAddress::new(0x30);
pub const INTERNAL_BATTERY_FUEL_GAUGE: DeviceAddress = DeviceAddress::new(0x55);
pub const INTERNAL_BATTERY_CHARGER: DeviceAddress = DeviceAddress::new(0x6B);
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct DeviceAddress(u8);
impl fmt::Debug for DeviceAddress {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("DeviceAddress")
.field(&(self.0 >> 1))
.finish()
}
}
impl ufmt::uDebug for DeviceAddress {
#[inline]
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
f.debug_tuple("DeviceAddress")?
.field(&(self.0 >> 1))?
.finish()
}
}
impl DeviceAddress {
pub const fn new(addr: u8) -> Self {
Self(addr << 1)
}
}
#[derive(Clone, Copy)]
enum BusKind {
Internal,
External,
}
#[derive(Clone, Copy)]
pub struct Bus(BusKind);
impl Bus {
pub const INTERNAL: Self = Self(BusKind::Internal);
pub const EXTERNAL: Self = Self(BusKind::External);
pub fn acquire(self) -> BusHandle {
BusHandle::acquire(match self.0 {
BusKind::Internal => &raw const sys::furi_hal_i2c_handle_power,
BusKind::External => &raw const sys::furi_hal_i2c_handle_external,
})
}
pub fn with_handle<T>(self, f: impl FnOnce(BusHandle) -> T) -> T {
f(self.acquire())
}
}
pub struct BusHandle {
handle: *const sys::FuriHalI2cBusHandle,
}
impl Drop for BusHandle {
fn drop(&mut self) {
unsafe { sys::furi_hal_i2c_release(self.handle) };
}
}
impl BusHandle {
fn acquire(handle: *const sys::FuriHalI2cBusHandle) -> Self {
unsafe { sys::furi_hal_i2c_acquire(handle) };
Self { handle }
}
pub fn enumerate_devices(
&mut self,
per_device_timeout: FuriDuration,
) -> impl Iterator<Item = DeviceAddress> + '_ {
(0x00..0x80).filter_map(move |addr| {
let device = DeviceAddress::new(addr);
self.is_device_ready(device, per_device_timeout)
.then_some(device)
})
}
pub fn is_device_ready(&mut self, device: DeviceAddress, timeout: FuriDuration) -> bool {
unsafe {
sys::furi_hal_i2c_is_device_ready(self.handle, device.0, timeout.as_millis() as u32)
}
}
pub fn read_u8(
&mut self,
device: DeviceAddress,
reg_addr: u8,
timeout: FuriDuration,
) -> Result<u8, Error> {
let mut data = 0;
if unsafe {
sys::furi_hal_i2c_read_reg_8(
self.handle,
device.0,
reg_addr,
&mut data,
timeout.as_millis() as u32,
)
} {
Ok(data)
} else {
Err(Error::TransferFailed)
}
}
pub fn read_u16(
&mut self,
device: DeviceAddress,
reg_addr: u8,
timeout: FuriDuration,
) -> Result<u16, Error> {
let mut data = 0;
if unsafe {
sys::furi_hal_i2c_read_reg_16(
self.handle,
device.0,
reg_addr,
&mut data,
timeout.as_millis() as u32,
)
} {
Ok(data)
} else {
Err(Error::TransferFailed)
}
}
pub fn read_exact(
&mut self,
device: DeviceAddress,
mem_addr: u8,
buf: &mut [u8],
timeout: FuriDuration,
) -> Result<(), Error> {
if unsafe {
sys::furi_hal_i2c_read_mem(
self.handle,
device.0,
mem_addr,
buf.as_mut_ptr(),
buf.len(),
timeout.as_millis() as u32,
)
} {
Ok(())
} else {
Err(Error::TransferFailed)
}
}
pub fn write_u8(
&mut self,
device: DeviceAddress,
reg_addr: u8,
data: u8,
timeout: FuriDuration,
) -> Result<(), Error> {
if unsafe {
sys::furi_hal_i2c_write_reg_8(
self.handle,
device.0,
reg_addr,
data,
timeout.as_millis() as u32,
)
} {
Ok(())
} else {
Err(Error::TransferFailed)
}
}
pub fn write_u16(
&mut self,
device: DeviceAddress,
reg_addr: u8,
data: u16,
timeout: FuriDuration,
) -> Result<(), Error> {
if unsafe {
sys::furi_hal_i2c_write_reg_16(
self.handle,
device.0,
reg_addr,
data,
timeout.as_millis() as u32,
)
} {
Ok(())
} else {
Err(Error::TransferFailed)
}
}
pub fn write_all(
&mut self,
device: DeviceAddress,
mem_addr: u8,
data: &[u8],
timeout: FuriDuration,
) -> Result<(), Error> {
if unsafe {
sys::furi_hal_i2c_write_mem(
self.handle,
device.0,
mem_addr,
data.as_ptr(),
data.len(),
timeout.as_millis() as u32,
)
} {
Ok(())
} else {
Err(Error::TransferFailed)
}
}
pub fn tx(
&mut self,
device: DeviceAddress,
data: &[u8],
timeout: FuriDuration,
) -> Result<(), Error> {
unsafe {
sys::furi_hal_i2c_tx(
self.handle,
device.0,
data.as_ptr(),
data.len(),
timeout.as_millis() as u32,
)
}
.then_some(())
.ok_or(Error::TransferFailed)
}
pub fn rx(
&mut self,
device: DeviceAddress,
data: &mut [u8],
timeout: FuriDuration,
) -> Result<(), Error> {
unsafe {
sys::furi_hal_i2c_rx(
self.handle,
device.0,
data.as_mut_ptr(),
data.len(),
timeout.as_millis() as u32,
)
}
.then_some(())
.ok_or(Error::TransferFailed)
}
pub fn trx(
&mut self,
device: DeviceAddress,
write: &[u8],
read: &mut [u8],
timeout: FuriDuration,
) -> Result<(), Error> {
unsafe {
sys::furi_hal_i2c_trx(
self.handle,
device.0,
write.as_ptr(),
write.len(),
read.as_mut_ptr(),
read.len(),
timeout.as_millis() as u32,
)
}
.then_some(())
.ok_or(Error::TransferFailed)
}
pub fn transaction(
&mut self,
device: DeviceAddress,
operations: &mut [Operation],
timeout: FuriDuration,
) -> Result<(), Error> {
self.transaction_impl(device, operations, timeout)
}
#[cfg(any(feature = "embedded-hal", feature = "embedded-hal-0"))]
fn write_read_impl(
&mut self,
device: DeviceAddress,
write: &[u8],
read: &mut [u8],
timeout: FuriDuration,
) -> Result<(), Error> {
unsafe {
sys::furi_hal_i2c_tx_ext(
self.handle,
device.0.into(),
false,
write.as_ptr(),
write.len(),
sys::FuriHalI2cBeginStart,
sys::FuriHalI2cEndAwaitRestart,
timeout.as_millis() as u32,
) && sys::furi_hal_i2c_rx_ext(
self.handle,
device.0.into(),
false,
read.as_mut_ptr(),
read.len(),
sys::FuriHalI2cBeginRestart,
sys::FuriHalI2cEndStop,
timeout.as_millis() as u32,
)
}
.then_some(())
.ok_or(Error::TransferFailed)
}
fn transaction_impl<'a, O>(
&mut self,
device: DeviceAddress,
operations: &mut [O],
timeout: FuriDuration,
) -> Result<(), Error>
where
O: OperationLike + 'a,
{
use sys::{
FuriHalI2cBeginRestart as BeginRestart, FuriHalI2cBeginResume as BeginResume,
FuriHalI2cBeginStart as BeginStart, FuriHalI2cEndAwaitRestart as EndAwaitRestart,
FuriHalI2cEndPause as EndPause, FuriHalI2cEndStop as EndStop,
};
let mut operations = operations.iter_mut().peekable();
let mut start = BeginStart;
let address = device.0.into();
while let Some(op) = operations.next() {
let (end, next_start) = match (op.kind(), operations.peek().map(|next| next.kind())) {
(OperationKind::Read, Some(OperationKind::Read))
| (OperationKind::Write, Some(OperationKind::Write)) => (EndPause, BeginResume),
(_, Some(_)) => (EndAwaitRestart, BeginRestart),
(_, None) => (EndStop, BeginStart),
};
let result = unsafe {
match op.as_op() {
Operation::Read(buffer) => flipperzero_sys::furi_hal_i2c_rx_ext(
self.handle,
address,
false,
buffer.as_mut_ptr(),
buffer.len(),
start,
end,
timeout.as_millis() as u32,
),
Operation::Write(buffer) => flipperzero_sys::furi_hal_i2c_tx_ext(
self.handle,
address,
false,
buffer.as_ptr(),
buffer.len(),
start,
end,
timeout.as_millis() as u32,
),
}
};
if !result {
return Err(Error::TransferFailed);
}
start = next_start;
}
Ok(())
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
TransferFailed,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Operation<'a> {
Read(&'a mut [u8]),
Write(&'a [u8]),
}
enum OperationKind {
Read,
Write,
}
trait OperationLike {
fn as_op(&mut self) -> Operation<'_>;
fn kind(&self) -> OperationKind;
}
impl OperationLike for Operation<'_> {
fn as_op(&mut self) -> Operation<'_> {
match self {
Operation::Read(buffer) => Operation::Read(buffer),
Operation::Write(buffer) => Operation::Write(buffer),
}
}
fn kind(&self) -> OperationKind {
match self {
Operation::Read(_) => OperationKind::Read,
Operation::Write(_) => OperationKind::Write,
}
}
}
#[cfg(any(feature = "embedded-hal", feature = "embedded-hal-0"))]
pub struct EmbeddedHalBus {
bus: Bus,
timeout: FuriDuration,
}
#[cfg(any(feature = "embedded-hal", feature = "embedded-hal-0"))]
impl EmbeddedHalBus {
pub fn new(bus: Bus, timeout: FuriDuration) -> Self {
Self { bus, timeout }
}
pub fn set_timeout(&mut self, timeout: FuriDuration) {
self.timeout = timeout
}
}
#[cfg(feature = "embedded-hal")]
impl embedded_hal::i2c::Error for Error {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
embedded_hal::i2c::ErrorKind::Other
}
}
#[cfg(feature = "embedded-hal")]
impl embedded_hal::i2c::ErrorType for EmbeddedHalBus {
type Error = Error;
}
#[cfg(feature = "embedded-hal")]
impl OperationLike for embedded_hal::i2c::Operation<'_> {
fn as_op(&mut self) -> Operation<'_> {
match self {
embedded_hal::i2c::Operation::Read(buffer) => Operation::Read(buffer),
embedded_hal::i2c::Operation::Write(buffer) => Operation::Write(buffer),
}
}
fn kind(&self) -> OperationKind {
match self {
embedded_hal::i2c::Operation::Read(_) => OperationKind::Read,
embedded_hal::i2c::Operation::Write(_) => OperationKind::Write,
}
}
}
#[cfg(feature = "embedded-hal")]
impl embedded_hal::i2c::I2c for EmbeddedHalBus {
fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
self.bus
.acquire()
.transaction_impl(DeviceAddress::new(address), operations, self.timeout)
}
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.bus
.acquire()
.rx(DeviceAddress::new(address), read, self.timeout)
}
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.bus
.acquire()
.tx(DeviceAddress::new(address), write, self.timeout)
}
fn write_read(
&mut self,
address: u8,
write: &[u8],
read: &mut [u8],
) -> Result<(), Self::Error> {
self.bus
.acquire()
.write_read_impl(DeviceAddress::new(address), write, read, self.timeout)
}
}
#[cfg(feature = "embedded-hal-0")]
impl OperationLike for embedded_hal_0::blocking::i2c::Operation<'_> {
fn as_op(&mut self) -> Operation<'_> {
match self {
embedded_hal_0::blocking::i2c::Operation::Read(buffer) => Operation::Read(buffer),
embedded_hal_0::blocking::i2c::Operation::Write(buffer) => Operation::Write(buffer),
}
}
fn kind(&self) -> OperationKind {
match self {
embedded_hal_0::blocking::i2c::Operation::Read(_) => OperationKind::Read,
embedded_hal_0::blocking::i2c::Operation::Write(_) => OperationKind::Write,
}
}
}
#[cfg(feature = "embedded-hal-0")]
impl embedded_hal_0::blocking::i2c::Read for EmbeddedHalBus {
type Error = Error;
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.bus
.acquire()
.rx(DeviceAddress::new(address), buffer, self.timeout)
}
}
#[cfg(feature = "embedded-hal-0")]
impl embedded_hal_0::blocking::i2c::Write for EmbeddedHalBus {
type Error = Error;
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.bus
.acquire()
.tx(DeviceAddress::new(address), bytes, self.timeout)
}
}
#[cfg(feature = "embedded-hal-0")]
impl embedded_hal_0::blocking::i2c::WriteRead for EmbeddedHalBus {
type Error = Error;
fn write_read(
&mut self,
address: u8,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Self::Error> {
self.bus
.acquire()
.write_read_impl(DeviceAddress::new(address), bytes, buffer, self.timeout)
}
}
#[cfg(feature = "embedded-hal-0")]
impl embedded_hal_0::blocking::i2c::Transactional for EmbeddedHalBus {
type Error = Error;
fn exec(
&mut self,
address: u8,
operations: &mut [embedded_hal_0::blocking::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
self.bus
.acquire()
.transaction_impl(DeviceAddress::new(address), operations, self.timeout)
}
}
#[flipperzero_test::tests]
mod tests {
use super::{
Bus, DeviceAddress, INTERNAL_BATTERY_CHARGER, INTERNAL_BATTERY_FUEL_GAUGE,
INTERNAL_LED_CONTROLLER,
};
use crate::furi::time::FuriDuration;
#[test]
fn enumerate_devices() {
const INTERNAL_DEVICES: &[DeviceAddress] = &[
INTERNAL_LED_CONTROLLER,
INTERNAL_BATTERY_FUEL_GAUGE,
INTERNAL_BATTERY_CHARGER,
];
let mut bus = Bus::INTERNAL.acquire();
for (i, device) in bus
.enumerate_devices(FuriDuration::from_millis(50))
.enumerate()
{
if let Some(&expected) = INTERNAL_DEVICES.get(i) {
assert_eq!(expected, device);
}
}
}
}