From e09649592bf768e6288eba6ac52f380f68b9bad7 Mon Sep 17 00:00:00 2001 From: Victoria Fischer Date: Wed, 11 Mar 2026 14:01:40 +0100 Subject: [PATCH] tasks: first implementation of protocol for the TUSB320 USB-PD chip. also untested!!! --- src/bin/main.rs | 3 + src/lib.rs | 1 + src/tasks/mod.rs | 2 + src/tasks/usb_power.rs | 25 +++ src/tusb320.rs | 356 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 387 insertions(+) create mode 100644 src/tasks/usb_power.rs create mode 100644 src/tusb320.rs diff --git a/src/bin/main.rs b/src/bin/main.rs index 8afb9a0..fea3d9a 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -131,6 +131,9 @@ async fn main(spawner: Spawner) { spawner.must_spawn(renderbug_bike::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus), imu_interrupt)); #[cfg(feature="gps")] spawner.must_spawn(renderbug_bike::tasks::gps::gps_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus))); + + // TODO: Everything i2c should be turned on by default, I guess. we don't need features for individual sensors, but we can keep the option to disable the whole bus for testing purposes + spawner.must_spawn(renderbug_bike::tasks::usb_power::usb_task(I2cDevice::new(i2c_bus), pd_interrupt)); } #[cfg(feature="oled")] diff --git a/src/lib.rs b/src/lib.rs index 941629b..0cefea2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub mod tracing; pub mod storage; pub mod simdata; pub mod gpio_interrupt; +pub mod tusb320; extern crate alloc; diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 6c8883b..96a2879 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -13,6 +13,8 @@ pub mod demo; #[cfg(feature="oled")] pub mod oled_render; +pub mod usb_power; + // Prediction engines pub mod motion; diff --git a/src/tasks/usb_power.rs b/src/tasks/usb_power.rs new file mode 100644 index 0000000..6fb0975 --- /dev/null +++ b/src/tasks/usb_power.rs @@ -0,0 +1,25 @@ +use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use esp_hal::Async; +use log::*; + +use crate::{gpio_interrupt::PinInterrupt, tusb320::TUSB320}; + +#[embassy_executor::task] +pub async fn usb_task(bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>>, interrupt_pin: PinInterrupt<'static>) { + let mut tusb = TUSB320::new(bus); + + match tusb.get_device_id().await { + Ok(val) => { + info!("TUSB320 Device ID: {val:?}"); + }, + Err(_) => { + error!("Failed to read from TUSB320"); + } + } + + loop { + log::info!("Waiting for USB power interrupt..."); + interrupt_pin.wait_for_interrupt().await; + } +} \ No newline at end of file diff --git a/src/tusb320.rs b/src/tusb320.rs new file mode 100644 index 0000000..335f07c --- /dev/null +++ b/src/tusb320.rs @@ -0,0 +1,356 @@ +use embassy_embedded_hal::shared_bus::{I2cDeviceError, asynch::i2c::I2cDevice}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use esp_hal::Async; +use embedded_hal_async::i2c::I2c; + +// TODO: rewrite this to only use embedded_hal_async traits, then publish as a crate +const USB_ADDR: u8 = 0b1100000; +pub struct TUSB320 { + bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>> +} + +#[derive(Clone, Copy, Debug)] +pub enum RegisterAddress { + DeviceID = 0x00, + DeviceID1 = 0x01, + DeviceID2 = 0x02, + DeviceID3 = 0x03, + DeviceID4 = 0x04, + DeviceID5 = 0x05, + DeviceID6 = 0x06, + DeviceID7 = 0x07, + Status1 = 0x08, + Status2 = 0x09, + Status3 = 0x0a, + DisableRDRP = 0x45 +} + +#[derive(Clone, Copy, Debug)] +pub enum Register { + DeviceID, + DeviceID1, + DeviceID2, + DeviceID3, + DeviceID4, + DeviceID5, + DeviceID6, + DeviceID7, + CurrentAdvertisement, + CurrentDetect, + AccessoryConnected, + ActiveCableConnected, + AttachedState, + CableOrientation, + InterruptStatus, + DrpDutyCycle, + Debounce, + ModeSelect, + SoftReset, + DisableRDRP +} + +impl From for RegisterAddress { + fn from(reg: Register) -> Self { + match reg { + Register::DeviceID => RegisterAddress::DeviceID, + Register::DeviceID1 => RegisterAddress::DeviceID1, + Register::DeviceID2 => RegisterAddress::DeviceID2, + Register::DeviceID3 => RegisterAddress::DeviceID3, + Register::DeviceID4 => RegisterAddress::DeviceID4, + Register::DeviceID5 => RegisterAddress::DeviceID5, + Register::DeviceID6 => RegisterAddress::DeviceID6, + Register::DeviceID7 => RegisterAddress::DeviceID7, + + Register::CurrentAdvertisement => RegisterAddress::Status1, + Register::CurrentDetect => RegisterAddress::Status1, + Register::AccessoryConnected => RegisterAddress::Status1, + Register::ActiveCableConnected => RegisterAddress::Status1 + , + Register::AttachedState => RegisterAddress::Status2, + Register::CableOrientation => RegisterAddress::Status2, + Register::InterruptStatus => RegisterAddress::Status2, + Register::DrpDutyCycle => RegisterAddress::Status2, + + Register::Debounce => RegisterAddress::Status3, + Register::ModeSelect => RegisterAddress::Status3, + Register::SoftReset => RegisterAddress::Status3, + Register::DisableRDRP => RegisterAddress::DisableRDRP + } + } +} + +pub enum CurrentAdvertisement { + /// 500mA / 900mA + Default = 0b00, + /// 1.5A + Medium = 0b01, + /// 3A + High = 0b10, + /// Reserved, do not use + Reserved = 0b11 +} + +pub enum CurrentModeDetect { + /// Default value at startup. TODO: Does this mean 500ma, or just 'no connection'? + Default = 0b00, + /// 1.5A + Medium = 0b01, + /// 500ma + ThroughAccessory = 0b10, + /// 3A + High = 0b11, +} + +pub enum AccessoryConnectionState { + None = 0b000, + Audio = 0b100, + ThroughAccessory = 0b101, + DebugAccessory = 0b110, + // All other binary patterns are 'reserved' + Reserved +} + +pub enum AttachedState { + Unattached = 0b00, + Source = 0b01, + Sink = 0b10, + Accessory = 0b11 +} + +pub enum CableOrientation { + Normal = 0, + Flipped = 1 +} + +pub enum DrpDutyCycle { + /// 30% + Default = 0b00, + /// 40% + Fast = 0b01, + /// 50% + Faster = 0b10, + /// 60% + Fastest = 0b11 +} + +pub enum Debounce { + /// 133ms (default) + D133 = 0b00, + /// 116ms + D116 = 0b01, + /// 151ms + D151 = 0b10, + /// 168ms + D168 = 0b11 +} + +pub enum Mode { + UsePortPin = 0b00, + UFP = 0b01, + DFP = 0b10, + DRP = 0b11 +} + +impl TUSB320 { + pub const fn new(bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>>) -> Self { + Self { bus } + } + + pub async fn get_current_advertisement(&mut self) -> Result> { + let reg: RegisterAddress = Register::CurrentAdvertisement.into(); + let val = self.read_address(reg).await? >> 6; + match val & 0b11 { + 0b00 => Ok(CurrentAdvertisement::Default), + 0b01 => Ok(CurrentAdvertisement::Medium), + 0b10 => Ok(CurrentAdvertisement::High), + 0b11 => Ok(CurrentAdvertisement::Reserved), + _ => unreachable!() + } + } + + pub async fn set_current_advertisement(&mut self, adv: CurrentAdvertisement) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::CurrentAdvertisement.into(); + let mut val = self.read_address(reg).await?; + let mask = !(0b11 << 6); + val &= mask | ((adv as u8) << 6); + self.write_address(reg, val).await?; + Ok(()) + } + + pub async fn get_current_mode_detect(&mut self) -> Result> { + let reg: RegisterAddress = Register::CurrentDetect.into(); + let val = self.read_address(reg).await? >> 4; + match val & 0b11 { + 0b00 => Ok(CurrentModeDetect::Default), + 0b01 => Ok(CurrentModeDetect::Medium), + 0b10 => Ok(CurrentModeDetect::ThroughAccessory), + 0b11 => Ok(CurrentModeDetect::High), + _ => unreachable!() + } + } + + pub async fn get_accessory_connection_state(&mut self) -> Result> { + let reg: RegisterAddress = Register::AccessoryConnected.into(); + let val = self.read_address(reg).await? >> 1; + match val & 0b111 { + 0b000 => Ok(AccessoryConnectionState::None), + 0b100 => Ok(AccessoryConnectionState::Audio), + 0b101 => Ok(AccessoryConnectionState::ThroughAccessory), + 0b110 => Ok(AccessoryConnectionState::DebugAccessory), + _ => Ok(AccessoryConnectionState::Reserved) + } + } + + pub async fn get_cable_detected(&mut self) -> Result> { + let reg: RegisterAddress = Register::ActiveCableConnected.into(); + let val = self.read_address(reg).await?; + Ok((val & 0b1) == 1) + } + + pub async fn get_attached_state(&mut self) -> Result> { + let reg: RegisterAddress = Register::AttachedState.into(); + let val = self.read_address(reg).await? >> 6; + match val & 0b11 { + 0b00 => Ok(AttachedState::Unattached), + 0b01 => Ok(AttachedState::Source), + 0b10 => Ok(AttachedState::Sink), + 0b11 => Ok(AttachedState::Accessory), + _ => unreachable!() + } + } + + pub async fn get_cable_orientation(&mut self) -> Result> { + let reg: RegisterAddress = Register::CableOrientation.into(); + let val = self.read_address(reg).await? >> 5; + match val & 0b1 { + 0 => Ok(CableOrientation::Normal), + 1 => Ok(CableOrientation::Flipped), + _ => unreachable!() + } + } + + pub async fn get_interrupt_status(&mut self) -> Result> { + let reg: RegisterAddress = Register::InterruptStatus.into(); + Ok((self.read_address(reg).await? >> 4) == 1) + } + + pub async fn clear_interrupt(&mut self) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::InterruptStatus.into(); + let mut val = self.read_address(reg).await?; + val &= !(0b1 << 4); + self.write_address(reg, val).await + } + + pub async fn set_drp_duty_cycle(&mut self, duty: DrpDutyCycle) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::DrpDutyCycle.into(); + let mut val = self.read_address(reg).await?; + let mask = !(0b11 << 1); + val &= mask | ((duty as u8) << 1); + self.write_address(reg, val).await + } + + pub async fn get_drp_duty_cycle(&mut self) -> Result> { + let reg: RegisterAddress = Register::DrpDutyCycle.into(); + let val = self.read_address(reg).await? >> 1; + match val & 0b11 { + 0b00 => Ok(DrpDutyCycle::Default), + 0b01 => Ok(DrpDutyCycle::Fast), + 0b10 => Ok(DrpDutyCycle::Faster), + 0b11 => Ok(DrpDutyCycle::Fastest), + _ => unreachable!() + } + } + + pub async fn get_debounce(&mut self) -> Result> { + let reg: RegisterAddress = Register::Debounce.into(); + let val = self.read_address(reg).await? >> 6; + match val & 0b11 { + 0b00 => Ok(Debounce::D133), + 0b01 => Ok(Debounce::D116), + 0b10 => Ok(Debounce::D151), + 0b11 => Ok(Debounce::D168), + _ => unreachable!() + } + } + + pub async fn set_debounce(&mut self, debounce: Debounce) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::Debounce.into(); + let mut val = self.read_address(reg).await?; + let mask = !(0b11 << 6); + val &= mask | ((debounce as u8) << 6); + self.write_address(reg, val).await + } + + pub async fn get_mode(&mut self) -> Result> { + let reg: RegisterAddress = Register::ModeSelect.into(); + let val = self.read_address(reg).await? >> 4; + match val & 0b11 { + 0b00 => Ok(Mode::UsePortPin), + 0b01 => Ok(Mode::UFP), + 0b10 => Ok(Mode::DFP), + 0b11 => Ok(Mode::DRP), + _ => unreachable!() + } + } + + pub async fn set_mode(&mut self, mode: Mode) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::ModeSelect.into(); + let mut val = self.read_address(reg).await?; + let mask = !(0b11 << 4); + val &= mask | ((mode as u8) << 4); + self.write_address(reg, val).await + } + + pub async fn soft_reset(&mut self) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::SoftReset.into(); + let mut val = self.read_address(reg).await?; + val |= 0b1 << 3; + self.write_address(reg, val).await + } + + pub async fn set_rdrp_disabled(&mut self, disable: bool) -> Result<(), I2cDeviceError> { + let reg: RegisterAddress = Register::DisableRDRP.into(); + let mut val = self.read_address(reg).await?; + if disable { + val |= 0b1; + } else { + val &= !0b1; + } + self.write_address(reg, val).await + } + + pub async fn get_rdrp_disabled(&mut self) -> Result> { + let reg: RegisterAddress = Register::DisableRDRP.into(); + let val = self.read_address(reg).await? >> 2; + Ok((val & 0b1) == 1) + } + + pub async fn get_device_id(&mut self) -> Result<[u8;8], I2cDeviceError> { + Ok([ + self.read_address(RegisterAddress::DeviceID).await?, + self.read_address(RegisterAddress::DeviceID1).await?, + self.read_address(RegisterAddress::DeviceID2).await?, + self.read_address(RegisterAddress::DeviceID3).await?, + self.read_address(RegisterAddress::DeviceID4).await?, + self.read_address(RegisterAddress::DeviceID5).await?, + self.read_address(RegisterAddress::DeviceID6).await?, + self.read_address(RegisterAddress::DeviceID7).await? + ]) + } + + pub async fn read_address(&mut self, reg: RegisterAddress) -> Result> { + let mut response = [0; 1]; + // 1 means 'read' cycle + let i2c_addr = USB_ADDR | 1; + self.bus.write_read(i2c_addr, &[reg as u8], &mut response).await?; + Ok(response[0]) + } + + pub async fn write_address(&mut self, reg: RegisterAddress, value: u8) -> Result<(), I2cDeviceError> { + // 0 means 'write' cycle + let i2c_addr = USB_ADDR | 0; + self.bus.write(i2c_addr, &[reg as u8]).await?; + self.bus.write(i2c_addr, &[value]).await?; + Ok(()) + } +}