i2c.rs
1 use esp_hal::i2c::master::{Config as I2cConfig, I2c}; 2 use esp_hal::time::Rate; 3 4 use crate::config::board; 5 6 pub const MAX_DISCOVERED_DEVICES: usize = 16; 7 pub const SENSOR_CANDIDATE_ADDRESSES: [u8; 4] = [0x44, 0x45, 0x46, 0x47]; 8 pub const SENSOR_MEASUREMENT_COMMAND: [u8; 2] = [0x24, 0x00]; 9 const I2C_SCAN_ADDRESS_MIN: u8 = 0x08; 10 const I2C_SCAN_ADDRESS_MAX: u8 = 0x77; 11 12 #[derive(Clone, Copy, defmt::Format)] 13 pub struct DiscoveredDevice { 14 pub bus: u8, 15 pub address: u8, 16 } 17 18 impl DiscoveredDevice { 19 pub fn bus_name(self) -> &'static str { 20 match self.bus { 21 0 => "i2c.0", 22 1 => "i2c.1", 23 _ => "i2c.?", 24 } 25 } 26 } 27 28 pub struct I2cSnapshot { 29 pub frequency_khz: u32, 30 pub power_gpio: u8, 31 pub buses: [BusStatusSnapshot; 2], 32 pub discovered_devices: heapless::Vec<DiscoveredDevice, MAX_DISCOVERED_DEVICES>, 33 } 34 35 #[derive(Clone, Copy)] 36 pub struct BusStatusSnapshot { 37 pub name: &'static str, 38 pub bus_index: u8, 39 pub sda_gpio: u8, 40 pub scl_gpio: u8, 41 } 42 43 static DISCOVERY_CACHE: critical_section::Mutex< 44 core::cell::RefCell<heapless::Vec<DiscoveredDevice, MAX_DISCOVERED_DEVICES>>, 45 > = critical_section::Mutex::new(core::cell::RefCell::new(heapless::Vec::new())); 46 47 static DISCOVERY_DONE: core::sync::atomic::AtomicBool = 48 core::sync::atomic::AtomicBool::new(false); 49 50 // ───────────────────────────────────────────────────────────────────────────── 51 // Bus initialization 52 // ───────────────────────────────────────────────────────────────────────────── 53 54 pub fn initialize_bus_0( 55 peripheral: esp_hal::peripherals::I2C0<'static>, 56 sda: esp_hal::peripherals::GPIO8<'static>, 57 scl: esp_hal::peripherals::GPIO9<'static>, 58 ) -> I2c<'static, esp_hal::Async> { 59 I2c::new( 60 peripheral, 61 I2cConfig::default().with_frequency(Rate::from_khz(board::i2c::FREQUENCY_KHZ)), 62 ) 63 .unwrap() 64 .with_sda(sda) 65 .with_scl(scl) 66 .into_async() 67 } 68 69 pub fn initialize_bus_1( 70 peripheral: esp_hal::peripherals::I2C1<'static>, 71 sda: esp_hal::peripherals::GPIO17<'static>, 72 scl: esp_hal::peripherals::GPIO18<'static>, 73 ) -> I2c<'static, esp_hal::Async> { 74 I2c::new( 75 peripheral, 76 I2cConfig::default().with_frequency(Rate::from_khz(board::i2c::FREQUENCY_KHZ)), 77 ) 78 .unwrap() 79 .with_sda(sda) 80 .with_scl(scl) 81 .into_async() 82 } 83 84 // ───────────────────────────────────────────────────────────────────────────── 85 // Discovery — mirrors C++ hardware::i2c::discoverAll / runDiscovery / findDevice 86 // ───────────────────────────────────────────────────────────────────────────── 87 88 pub async fn discover_all( 89 i2c0_bus: &mut Option<I2c<'static, esp_hal::Async>>, 90 i2c1_bus: &mut Option<I2c<'static, esp_hal::Async>>, 91 ) -> heapless::Vec<DiscoveredDevice, MAX_DISCOVERED_DEVICES> { 92 let mut devices = heapless::Vec::new(); 93 94 if let Some(bus) = i2c0_bus.as_mut() { 95 discover_bus_devices(0, bus, &mut devices).await; 96 } 97 98 if let Some(bus) = i2c1_bus.as_mut() { 99 discover_bus_devices(1, bus, &mut devices).await; 100 } 101 102 devices 103 } 104 105 pub async fn run_discovery( 106 i2c0_bus: &mut Option<I2c<'static, esp_hal::Async>>, 107 i2c1_bus: &mut Option<I2c<'static, esp_hal::Async>>, 108 ) -> usize { 109 let devices = discover_all(i2c0_bus, i2c1_bus).await; 110 let count = devices.len(); 111 112 for device in devices.iter() { 113 defmt::info!( 114 "[i2c] found {:#04x} on bus {=u8} — {}", 115 device.address, 116 device.bus, 117 device_name_at(device.address) 118 ); 119 } 120 121 critical_section::with(|cs| { 122 let mut cache = DISCOVERY_CACHE.borrow_ref_mut(cs); 123 cache.clear(); 124 for device in devices.iter() { 125 let _ = cache.push(*device); 126 } 127 }); 128 DISCOVERY_DONE.store(true, core::sync::atomic::Ordering::Release); 129 130 count 131 } 132 133 pub fn find_device(address: u8) -> Option<DiscoveredDevice> { 134 critical_section::with(|cs| { 135 DISCOVERY_CACHE 136 .borrow_ref(cs) 137 .iter() 138 .find(|d| d.address == address) 139 .copied() 140 }) 141 } 142 143 pub fn device_count() -> usize { 144 critical_section::with(|cs| DISCOVERY_CACHE.borrow_ref(cs).len()) 145 } 146 147 pub fn is_discovery_done() -> bool { 148 DISCOVERY_DONE.load(core::sync::atomic::Ordering::Acquire) 149 } 150 151 pub fn device_name_at(address: u8) -> &'static str { 152 match address { 153 0x40 => "Texas Instruments INA228 Current Monitor", 154 0x44 => "Sensirion SHT3x Temperature & Humidity Sensor", 155 0x48 => "Texas Instruments ADS1115 16-Bit ADC", 156 0x50 => "Microchip Technology AT24C32 EEPROM", 157 0x5C | 0x5D => "Adafruit LPS25 Pressure Sensor", 158 0x61 => "Sensirion SCD30 CO2 Infrared Gas Sensor", 159 0x62 => "Sensirion SCD41 CO2 Optical Gas Sensor", 160 0x68 => "Analog Devices DS3231 RTC", 161 0x70 => "Adafruit TCA9548A I2C Multiplexer", 162 _ => "unknown", 163 } 164 } 165 166 // ───────────────────────────────────────────────────────────────────────────── 167 // Snapshot 168 // ───────────────────────────────────────────────────────────────────────────── 169 170 pub fn snapshot() -> I2cSnapshot { 171 let mut discovered_devices = heapless::Vec::new(); 172 173 critical_section::with(|cs| { 174 for device in DISCOVERY_CACHE.borrow_ref(cs).iter() { 175 let _ = discovered_devices.push(*device); 176 } 177 }); 178 179 I2cSnapshot { 180 frequency_khz: board::i2c::FREQUENCY_KHZ, 181 power_gpio: board::i2c::LEGACY_POWER_GPIO, 182 buses: [ 183 BusStatusSnapshot { 184 name: "i2c.0", 185 bus_index: 0, 186 sda_gpio: board::i2c::BUS_0.sda_gpio, 187 scl_gpio: board::i2c::BUS_0.scl_gpio, 188 }, 189 BusStatusSnapshot { 190 name: "i2c.1", 191 bus_index: 1, 192 sda_gpio: board::i2c::BUS_1.sda_gpio, 193 scl_gpio: board::i2c::BUS_1.scl_gpio, 194 }, 195 ], 196 discovered_devices, 197 } 198 } 199 200 // ───────────────────────────────────────────────────────────────────────────── 201 // Mux helpers 202 // ───────────────────────────────────────────────────────────────────────────── 203 204 pub async fn select_mux_channel( 205 i2c_bus: &mut I2c<'static, esp_hal::Async>, 206 mux_channel: u8, 207 ) -> Result<(), &'static str> { 208 if mux_channel > 7 { 209 return Err("mux channel out of range"); 210 } 211 212 let mux_channel_mask = 1_u8 << mux_channel; 213 i2c_bus 214 .write_async(board::i2c::MUX_ADDR, &[mux_channel_mask]) 215 .await 216 .map_err(|_| "failed to select I2C mux channel")?; 217 Ok(()) 218 } 219 220 pub async fn clear_selection( 221 i2c_bus: &mut I2c<'static, esp_hal::Async>, 222 ) -> Result<(), &'static str> { 223 i2c_bus 224 .write_async(board::i2c::MUX_ADDR, &[0x00]) 225 .await 226 .map_err(|_| "failed to clear I2C mux selection")?; 227 Ok(()) 228 } 229 230 // ───────────────────────────────────────────────────────────────────────────── 231 // Utilities 232 // ───────────────────────────────────────────────────────────────────────────── 233 234 pub fn calculate_crc8(data_bytes: &[u8]) -> u8 { 235 let mut crc_value: u8 = 0xFF; 236 237 for data_byte in data_bytes { 238 crc_value ^= *data_byte; 239 for _ in 0..8 { 240 crc_value = if (crc_value & 0x80) != 0 { 241 (crc_value << 1) ^ 0x31 242 } else { 243 crc_value << 1 244 }; 245 } 246 } 247 248 crc_value 249 } 250 251 // ───────────────────────────────────────────────────────────────────────────── 252 // Internal 253 // ───────────────────────────────────────────────────────────────────────────── 254 255 async fn discover_bus_devices( 256 bus_index: u8, 257 i2c_bus: &mut I2c<'static, esp_hal::Async>, 258 devices: &mut heapless::Vec<DiscoveredDevice, MAX_DISCOVERED_DEVICES>, 259 ) { 260 for address in I2C_SCAN_ADDRESS_MIN..=I2C_SCAN_ADDRESS_MAX { 261 if address == board::i2c::MUX_ADDR { 262 continue; 263 } 264 265 if i2c_bus.write_async(address, &[]).await.is_ok() { 266 let _ = devices.push(DiscoveredDevice { 267 bus: bus_index, 268 address, 269 }); 270 if devices.is_full() { 271 break; 272 } 273 } 274 } 275 }