i2c.cpp
1 #include "i2c.h" 2 #include <config.h> 3 #include "console/icons.h" 4 5 #include <Arduino.h> 6 #include <Wire.h> 7 8 static bool legacy_power_enabled = false; 9 static bool mux_present = false; 10 static bool mux_odd_power_enabled = false; 11 static bool mux_even_power_enabled = false; 12 13 TCA9548 hardware::i2c::mux(config::i2c::MUX_ADDR, &Wire1); 14 15 namespace { 16 17 void disable_legacy_power_rail(void) { 18 if (!legacy_power_enabled) return; 19 digitalWrite(config::i2c::LEGACY_POWER_GPIO, LOW); 20 legacy_power_enabled = false; 21 } 22 23 void enable_legacy_power_rail(void) { 24 if (legacy_power_enabled) return; 25 pinMode(config::i2c::LEGACY_POWER_GPIO, OUTPUT); 26 digitalWrite(config::i2c::LEGACY_POWER_GPIO, HIGH); 27 delay(100); 28 legacy_power_enabled = true; 29 } 30 31 void disable_mux_power_rails(void) { 32 pinMode(config::i2c::MUX_POWER_GPIO_ODD, OUTPUT); 33 pinMode(config::i2c::MUX_POWER_GPIO_EVEN, OUTPUT); 34 digitalWrite(config::i2c::MUX_POWER_GPIO_ODD, LOW); 35 digitalWrite(config::i2c::MUX_POWER_GPIO_EVEN, LOW); 36 mux_odd_power_enabled = false; 37 mux_even_power_enabled = false; 38 } 39 40 void enable_mux_power_for_channel(int8_t mux_channel) { 41 // New board routing is fixed and intentionally spelled out here: 42 // channels 0,2,4,6 -> GPIO 6 43 // channels 1,3,5,7 -> GPIO 1 44 switch (mux_channel) { 45 case 0: 46 case 2: 47 case 4: 48 case 6: 49 pinMode(config::i2c::MUX_POWER_GPIO_EVEN, OUTPUT); 50 digitalWrite(config::i2c::MUX_POWER_GPIO_EVEN, HIGH); 51 digitalWrite(config::i2c::MUX_POWER_GPIO_ODD, LOW); 52 mux_even_power_enabled = true; 53 mux_odd_power_enabled = false; 54 break; 55 56 case 1: 57 case 3: 58 case 5: 59 case 7: 60 pinMode(config::i2c::MUX_POWER_GPIO_ODD, OUTPUT); 61 digitalWrite(config::i2c::MUX_POWER_GPIO_ODD, HIGH); 62 digitalWrite(config::i2c::MUX_POWER_GPIO_EVEN, LOW); 63 mux_odd_power_enabled = true; 64 mux_even_power_enabled = false; 65 break; 66 67 default: 68 disable_mux_power_rails(); 69 break; 70 } 71 delay(config::i2c::POWER_SETTLE_MS); 72 } 73 74 } 75 76 void hardware::i2c::enable() { 77 if (!mux_present) { 78 enable_legacy_power_rail(); 79 } 80 } 81 82 void hardware::i2c::disable() { 83 if (!mux_present) { 84 disable_legacy_power_rail(); 85 return; 86 } 87 disable_mux_power_rails(); 88 } 89 90 bool hardware::i2c::isEnabled() { 91 if (!mux_present) { 92 return legacy_power_enabled; 93 } 94 return mux_odd_power_enabled || mux_even_power_enabled; 95 } 96 97 bool hardware::i2c::initialize() { 98 Wire.begin(config::i2c::BUS_0.sda_gpio, config::i2c::BUS_0.scl_gpio, 99 config::i2c::FREQUENCY_KHZ * 1000); 100 Wire.setTimeOut(100); 101 102 Wire1.begin(config::i2c::BUS_1.sda_gpio, config::i2c::BUS_1.scl_gpio, 103 config::i2c::FREQUENCY_KHZ * 1000); 104 Wire1.setTimeOut(100); 105 106 mux_present = mux.begin(); 107 return true; 108 } 109 110 bool hardware::i2c::accessBus(BusDescriptor *descriptor) { 111 if (!descriptor) return false; 112 113 switch (descriptor->bus) { 114 case Bus::Bus0: 115 descriptor->wire = &Wire; 116 descriptor->ready = true; 117 return true; 118 case Bus::Bus1: 119 descriptor->wire = &Wire1; 120 descriptor->ready = true; 121 return true; 122 default: 123 descriptor->wire = nullptr; 124 descriptor->ready = false; 125 return false; 126 } 127 } 128 129 bool hardware::i2c::accessTopology(TopologySnapshot *snapshot) { 130 if (!snapshot) return false; 131 snapshot->legacy_power_enabled = legacy_power_enabled; 132 snapshot->mux_present = mux_present; 133 snapshot->mux_address = config::i2c::MUX_ADDR; 134 snapshot->mux_odd_power_enabled = mux_odd_power_enabled; 135 snapshot->mux_even_power_enabled = mux_even_power_enabled; 136 return true; 137 } 138 139 bool hardware::i2c::accessDevice(DeviceAccessCommand *command) { 140 if (!command) return false; 141 142 BusDescriptor descriptor = { 143 .bus = command->bus, 144 .wire = nullptr, 145 .ready = false, 146 }; 147 if (!hardware::i2c::accessBus(&descriptor) || !descriptor.ready) { 148 command->wire = nullptr; 149 command->ok = false; 150 return false; 151 } 152 153 if (command->mux_channel >= 0) { 154 if (!mux_present || command->bus != Bus::Bus1) { 155 command->wire = nullptr; 156 command->ok = false; 157 return false; 158 } 159 enable_mux_power_for_channel(command->mux_channel); 160 if (!mux.selectChannel((uint8_t)command->mux_channel)) { 161 command->wire = nullptr; 162 command->ok = false; 163 return false; 164 } 165 } else if (!mux_present) { 166 enable_legacy_power_rail(); 167 } 168 169 command->wire = descriptor.wire; 170 command->ok = true; 171 return true; 172 } 173 174 // clearSelection() not only deselects mux channels, it also drops both 175 // ODD/EVEN MUX_POWER_GPIO rails. This is intentional power saving, but it 176 // means any sensor that relies on internal state across reads (e.g., SCD30 177 // periodic measurement) cannot work on this board — the rail is cut between 178 // polls. Stateless sensors and SCD41 single-shot mode are compatible; see 179 // sensors/carbon_dioxide.cpp for the working pattern. 180 void hardware::i2c::clearSelection() { 181 if (mux_present) { 182 mux.disableAllChannels(); 183 disable_mux_power_rails(); 184 } 185 } 186 187 static inline int clamp(int pos, size_t limit) { 188 return (pos >= (int)limit) ? (int)limit - 1 : pos; 189 } 190 191 bool hardware::i2c::scan(ScanCommand *command) { 192 if (!command || !command->buffer || command->capacity == 0) return false; 193 char *buf = command->buffer; 194 size_t buf_size = command->capacity; 195 int pos = 0; 196 197 // Scan raw buses 198 pos += snprintf(buf + pos, buf_size - pos, "bus 0:\r\n"); 199 for (uint8_t addr = config::i2c::ADDR_MIN; addr < config::i2c::ADDR_MAX && pos < (int)buf_size - 16; addr++) { 200 Wire.beginTransmission(addr); 201 if (Wire.endTransmission() == 0) { 202 pos += snprintf(buf + pos, buf_size - pos, " 0x%02X\r\n", addr); 203 pos = clamp(pos, buf_size); 204 } 205 } 206 207 pos += snprintf(buf + pos, buf_size - pos, "bus 1:\r\n"); 208 for (uint8_t addr = config::i2c::ADDR_MIN; addr < config::i2c::ADDR_MAX && pos < (int)buf_size - 16; addr++) { 209 Wire1.beginTransmission(addr); 210 if (Wire1.endTransmission() == 0) { 211 pos += snprintf(buf + pos, buf_size - pos, " 0x%02X\r\n", addr); 212 pos = clamp(pos, buf_size); 213 } 214 } 215 216 // Scan mux channels 217 if (mux_present) { 218 pos += snprintf(buf + pos, buf_size - pos, "mux:\r\n"); 219 pos = clamp(pos, buf_size); 220 221 for (uint8_t ch = 0; ch < mux.channelCount() && pos < (int)buf_size - 32; ch++) { 222 mux.selectChannel(ch); 223 int found = 0; 224 pos += snprintf(buf + pos, buf_size - pos, " ch %d:", ch); 225 pos = clamp(pos, buf_size); 226 227 for (uint8_t addr = config::i2c::ADDR_MIN; addr < config::i2c::ADDR_MAX && pos < (int)buf_size - 16; addr++) { 228 Wire1.beginTransmission(addr); 229 if (Wire1.endTransmission() == 0) { 230 pos += snprintf(buf + pos, buf_size - pos, " 0x%02X", addr); 231 pos = clamp(pos, buf_size); 232 found++; 233 } 234 } 235 236 if (found == 0) { 237 pos += snprintf(buf + pos, buf_size - pos, " (empty)"); 238 pos = clamp(pos, buf_size); 239 } 240 pos += snprintf(buf + pos, buf_size - pos, "\r\n"); 241 pos = clamp(pos, buf_size); 242 } 243 244 hardware::i2c::clearSelection(); 245 } else { 246 pos += snprintf(buf + pos, buf_size - pos, "mux: (not present)\r\n"); 247 pos = clamp(pos, buf_size); 248 } 249 250 command->length = pos; 251 return true; 252 } 253 254 static const char *device_icon_at(uint8_t address) { 255 switch (address) { 256 case 0x39: return NF_FA_SIGNAL; // AS7343 spectral (placeholder) 257 case 0x40: return NF_FA_BOLT; // INA228 current 258 case 0x44: return NF_FA_THERMOMETER; // SHT3x temperature 259 case 0x48: return NF_FA_SIGNAL; // ADS1115 ADC 260 case 0x50: return NF_FA_DATABASE; // EEPROM 261 case 0x5C: case 0x5D: return NF_FA_TINT; // LPS25 pressure 262 case 0x61: case 0x62: return NF_FA_LEAF; // SCD30/SCD41 CO2 263 case 0x67: return NF_FA_THERMOMETER; // MCP9600 thermocouple 264 case 0x68: return NF_FA_CLOCK; // DS3231 RTC 265 case 0x70: return NF_FA_SITEMAP; // TCA9548A mux 266 default: return NF_FA_COG; // unknown 267 } 268 } 269 270 const char *hardware::i2c::deviceNameAt(uint8_t address, int8_t mux_channel) { 271 bool is_muxed = (mux_channel >= 0); 272 273 switch (address) { 274 case 0x39: return "Sparkfun AS7343 Spectral Sensor"; 275 case 0x40: return "Adafruit INA228 Current Monitor"; 276 case 0x44: return "Sensirion SHT3x Temperature & Humidity Sensor"; 277 case 0x48: return "ADS1115 16-Bit ADC - 4 Channel with Programmable Gain Amplifier"; 278 case 0x50: return is_muxed ? "sensor module EEPROM (on-board)" 279 : "Microchip Technology AT24C32 EEPROM"; 280 case 0x5C: case 0x5D: return "Adafruit LPS25 Pressure Sensor"; 281 case 0x61: return "Sensirion SCD30 CO2 Infrared Gas Sensor"; 282 case 0x62: return "Sensirion SCD41 CO2 Optical Gas Sensor"; 283 case 0x67: return "Adafruit MCP9600 Thermocouple Amplifier"; 284 case 0x68: return "Analog Devices DS3231 RTC"; 285 case 0x70: return "Adafruit TCA9548A 1-to-8 I2C Multiplexer Breakout"; 286 default: return "unknown"; 287 } 288 } 289 290 size_t hardware::i2c::discoverAll(DiscoveredDevice *devices, size_t capacity) { 291 if (!devices || capacity == 0) return 0; 292 size_t count = 0; 293 294 enable_legacy_power_rail(); 295 pinMode(config::i2c::MUX_POWER_GPIO_EVEN, OUTPUT); 296 digitalWrite(config::i2c::MUX_POWER_GPIO_EVEN, HIGH); 297 pinMode(config::i2c::MUX_POWER_GPIO_ODD, OUTPUT); 298 digitalWrite(config::i2c::MUX_POWER_GPIO_ODD, HIGH); 299 delay(config::i2c::DISCOVERY_SETTLE_MS); 300 301 TwoWire *buses[] = { &Wire, &Wire1 }; 302 for (uint8_t bus = 0; bus < 2 && count < capacity; bus++) { 303 for (uint8_t addr = config::i2c::ADDR_MIN; addr <= config::i2c::ADDR_MAX && count < capacity; addr++) { 304 if (addr == config::i2c::MUX_ADDR) continue; 305 buses[bus]->beginTransmission(addr); 306 if (buses[bus]->endTransmission() == 0) { 307 devices[count++] = { bus, addr, -1 }; 308 } 309 } 310 } 311 312 if (mux_present) { 313 for (uint8_t ch = 0; ch < mux.channelCount() && count < capacity; ch++) { 314 enable_mux_power_for_channel(ch); 315 mux.selectChannel(ch); 316 for (uint8_t addr = config::i2c::ADDR_MIN; addr <= config::i2c::ADDR_MAX && count < capacity; addr++) { 317 if (addr == config::i2c::MUX_ADDR) continue; 318 Wire1.beginTransmission(addr); 319 if (Wire1.endTransmission() == 0) { 320 devices[count++] = { 1, addr, (int8_t)ch }; 321 } 322 } 323 mux.disableAllChannels(); 324 } 325 disable_mux_power_rails(); 326 } 327 328 return count; 329 } 330 331 static hardware::i2c::DiscoveredDevice discovery_cache[hardware::i2c::MAX_DISCOVERED_DEVICES]; 332 static size_t discovery_count = 0; 333 static bool discovery_done = false; 334 335 bool hardware::i2c::runDiscovery() { 336 discovery_count = discoverAll(discovery_cache, MAX_DISCOVERED_DEVICES); 337 discovery_done = true; 338 for (size_t i = 0; i < discovery_count; i++) { 339 Serial.printf("[i2c] found 0x%02X on bus %d%s\n", 340 discovery_cache[i].address, discovery_cache[i].bus, 341 discovery_cache[i].mux_channel >= 0 ? " (mux)" : ""); 342 } 343 return discovery_count > 0; 344 } 345 346 // Searches the cached discovery results. Call runDiscovery() first 347 // during boot. Falls back to a full bus scan if the cache is empty. 348 bool hardware::i2c::findDevice(uint8_t address, DiscoveredDevice *result) { 349 if (!result) return false; 350 if (!discovery_done) runDiscovery(); 351 for (size_t i = 0; i < discovery_count; i++) { 352 if (discovery_cache[i].address == address) { 353 *result = discovery_cache[i]; 354 return true; 355 } 356 } 357 return false; 358 } 359 360 // ───────────────────────────────────────────────────────────────────────────── 361 // Tests 362 // ───────────────────────────────────────────────────────────────────────────── 363 #ifdef PIO_UNIT_TESTING 364 365 #include <testing/utils.h> 366 367 368 static void test_i2c_mux_init(void) { 369 GIVEN("Wire1 is available"); 370 test_ensure_wire1(); 371 372 WHEN("the mux is initialized"); 373 hardware::i2c::initialize(); 374 hardware::i2c::TopologySnapshot snapshot = {}; 375 hardware::i2c::accessTopology(&snapshot); 376 if (!snapshot.mux_present) { 377 TEST_IGNORE_MESSAGE("mux not present on this board"); 378 return; 379 } 380 } 381 382 static void test_i2c_mux_is_connected(void) { 383 WHEN("the mux connection is checked"); 384 hardware::i2c::TopologySnapshot snapshot = {}; 385 hardware::i2c::accessTopology(&snapshot); 386 if (!snapshot.mux_present) { 387 TEST_IGNORE_MESSAGE("mux not present on this board"); 388 return; 389 } 390 TEST_ASSERT_TRUE_MESSAGE(snapshot.mux_present, 391 "device: mux not found at 0x70"); 392 } 393 394 static void test_i2c_mux_channel_count(void) { 395 THEN("the mux has 8 channels"); 396 hardware::i2c::TopologySnapshot snapshot = {}; 397 hardware::i2c::accessTopology(&snapshot); 398 if (!snapshot.mux_present) { 399 TEST_IGNORE_MESSAGE("mux not present on this board"); 400 return; 401 } 402 TEST_ASSERT_EQUAL_UINT8_MESSAGE(8, hardware::i2c::mux.channelCount(), 403 "device: TCA9548A should have 8 channels"); 404 } 405 406 static void test_i2c_mux_select_and_verify(void) { 407 WHEN("channels are selected"); 408 hardware::i2c::TopologySnapshot snapshot = {}; 409 hardware::i2c::accessTopology(&snapshot); 410 if (!snapshot.mux_present) { 411 TEST_IGNORE_MESSAGE("mux not present on this board"); 412 return; 413 } 414 415 TEST_ASSERT_FALSE_MESSAGE(snapshot.mux_even_power_enabled, 416 "device: even mux rail should start disabled"); 417 TEST_ASSERT_FALSE_MESSAGE(snapshot.mux_odd_power_enabled, 418 "device: odd mux rail should start disabled"); 419 420 TEST_ASSERT_TRUE_MESSAGE(hardware::i2c::mux.selectChannel(0), 421 "device: selectChannel(0) failed"); 422 TEST_ASSERT_BIT_HIGH_MESSAGE(0, hardware::i2c::mux.getChannelMask(), 423 "device: bit 0 should be high after select(0)"); 424 425 TEST_ASSERT_TRUE_MESSAGE(hardware::i2c::mux.selectChannel(3), 426 "device: selectChannel(3) failed"); 427 TEST_ASSERT_BIT_HIGH_MESSAGE(3, hardware::i2c::mux.getChannelMask(), 428 "device: bit 3 should be high after select(3)"); 429 TEST_ASSERT_BIT_LOW_MESSAGE(0, hardware::i2c::mux.getChannelMask(), 430 "device: bit 0 should be low after select(3) — exclusive select"); 431 432 hardware::i2c::mux.disableAllChannels(); 433 } 434 435 static void test_i2c_mux_channel_power_mapping(void) { 436 WHEN("even and odd mux channels are accessed"); 437 hardware::i2c::TopologySnapshot snapshot = {}; 438 hardware::i2c::accessTopology(&snapshot); 439 if (!snapshot.mux_present) { 440 TEST_IGNORE_MESSAGE("mux not present on this board"); 441 return; 442 } 443 444 hardware::i2c::DeviceAccessCommand even_command = { 445 .bus = hardware::i2c::Bus::Bus1, 446 .mux_channel = 0, 447 .wire = nullptr, 448 .ok = false, 449 }; 450 TEST_ASSERT_TRUE_MESSAGE(hardware::i2c::accessDevice(&even_command), 451 "device: accessDevice failed for mux channel 0"); 452 hardware::i2c::accessTopology(&snapshot); 453 TEST_ASSERT_TRUE_MESSAGE(snapshot.mux_even_power_enabled, 454 "device: even mux channels should enable GPIO 6 rail"); 455 TEST_ASSERT_FALSE_MESSAGE(snapshot.mux_odd_power_enabled, 456 "device: odd mux rail should remain off for even channels"); 457 hardware::i2c::clearSelection(); 458 459 hardware::i2c::DeviceAccessCommand odd_command = { 460 .bus = hardware::i2c::Bus::Bus1, 461 .mux_channel = 1, 462 .wire = nullptr, 463 .ok = false, 464 }; 465 TEST_ASSERT_TRUE_MESSAGE(hardware::i2c::accessDevice(&odd_command), 466 "device: accessDevice failed for mux channel 1"); 467 hardware::i2c::accessTopology(&snapshot); 468 TEST_ASSERT_TRUE_MESSAGE(snapshot.mux_odd_power_enabled, 469 "device: odd mux channels should enable GPIO 1 rail"); 470 TEST_ASSERT_FALSE_MESSAGE(snapshot.mux_even_power_enabled, 471 "device: even mux rail should remain off for odd channels"); 472 hardware::i2c::clearSelection(); 473 474 hardware::i2c::accessTopology(&snapshot); 475 TEST_ASSERT_FALSE_MESSAGE(snapshot.mux_even_power_enabled, 476 "device: even mux rail should be off after clearSelection"); 477 TEST_ASSERT_FALSE_MESSAGE(snapshot.mux_odd_power_enabled, 478 "device: odd mux rail should be off after clearSelection"); 479 } 480 481 static void test_i2c_mux_scan(void) { 482 WHEN("all I2C buses and mux channels are scanned"); 483 hardware::i2c::initialize(); 484 hardware::i2c::runDiscovery(); 485 486 hardware::i2c::DiscoveredDevice devices[hardware::i2c::MAX_DISCOVERED_DEVICES]; 487 size_t count = hardware::i2c::discoverAll(devices, hardware::i2c::MAX_DISCOVERED_DEVICES); 488 489 TEST_ASSERT_GREATER_THAN_MESSAGE(0, (int)count, "device: no I2C devices found"); 490 491 char line[120]; 492 // TEST_MESSAGE escapes non-ASCII, so ASCII-only tree characters 493 TEST_MESSAGE(""); 494 snprintf(line, sizeof(line), "ESP32-S3"); 495 TEST_MESSAGE(line); 496 497 bool has_bus1 = false; 498 for (size_t i = 0; i < count; i++) { 499 if (devices[i].bus == 1) { has_bus1 = true; break; } 500 } 501 502 //------------------------------------------ 503 // Bus 0 504 //------------------------------------------ 505 const char *bus0_branch = has_bus1 ? "+" : "\\"; 506 snprintf(line, sizeof(line), "%s-- I2C Bus 0 (GPIO %d/%d)", 507 bus0_branch, config::i2c::BUS_0.sda_gpio, config::i2c::BUS_0.scl_gpio); 508 TEST_MESSAGE(line); 509 510 const char *bus0_cont = has_bus1 ? "|" : " "; 511 for (size_t i = 0; i < count; i++) { 512 if (devices[i].bus != 0 || devices[i].mux_channel >= 0) continue; 513 snprintf(line, sizeof(line), "%s \\-- 0x%02X %s", 514 bus0_cont, devices[i].address, 515 hardware::i2c::deviceNameAt(devices[i].address, devices[i].mux_channel)); 516 TEST_MESSAGE(line); 517 } 518 519 if (!has_bus1) return; 520 521 TEST_MESSAGE(bus0_cont); 522 523 //------------------------------------------ 524 // Bus 1 525 //------------------------------------------ 526 snprintf(line, sizeof(line), "\\-- I2C Bus 1 (GPIO %d/%d)", 527 config::i2c::BUS_1.sda_gpio, config::i2c::BUS_1.scl_gpio); 528 TEST_MESSAGE(line); 529 530 // Bus 1 unmuxed devices 531 for (size_t i = 0; i < count; i++) { 532 if (devices[i].bus != 1 || devices[i].mux_channel >= 0) continue; 533 if (devices[i].address == config::i2c::MUX_ADDR) continue; 534 snprintf(line, sizeof(line), " +-- 0x%02X %s", 535 devices[i].address, 536 hardware::i2c::deviceNameAt(devices[i].address, devices[i].mux_channel)); 537 TEST_MESSAGE(line); 538 } 539 540 // Mux header 541 TEST_MESSAGE(" |"); 542 snprintf(line, sizeof(line), " \\-- 0x%02X %s", 543 config::i2c::MUX_ADDR, 544 hardware::i2c::deviceNameAt(config::i2c::MUX_ADDR, -1)); 545 TEST_MESSAGE(line); 546 547 //------------------------------------------ 548 // Mux channels 549 //------------------------------------------ 550 for (int channel = 0; channel < 8; channel++) { 551 struct { uint8_t addr; const char *name; } channel_devices[8]; 552 int channel_count = 0; 553 554 for (size_t i = 0; i < count && channel_count < 8; i++) { 555 if (devices[i].bus != 1 || devices[i].mux_channel != channel) continue; 556 if (devices[i].address == 0x50) continue; 557 channel_devices[channel_count].addr = devices[i].address; 558 channel_devices[channel_count].name = hardware::i2c::deviceNameAt(devices[i].address, devices[i].mux_channel); 559 channel_count++; 560 } 561 562 if (channel_count == 0) continue; 563 564 bool is_last_channel = true; 565 for (int future = channel + 1; future < 8; future++) { 566 for (size_t i = 0; i < count; i++) { 567 if (devices[i].bus == 1 && devices[i].mux_channel == future && devices[i].address != 0x50) { 568 is_last_channel = false; 569 break; 570 } 571 } 572 if (!is_last_channel) break; 573 } 574 575 const char *branch = is_last_channel ? "\\" : "+"; 576 const char *cont = is_last_channel ? " " : "|"; 577 578 snprintf(line, sizeof(line), " %s-- %d: 0x%02X %s", 579 branch, channel, 580 channel_devices[0].addr, channel_devices[0].name); 581 TEST_MESSAGE(line); 582 583 for (int d = 1; d < channel_count; d++) { 584 snprintf(line, sizeof(line), " %s 0x%02X %s", 585 cont, 586 channel_devices[d].addr, channel_devices[d].name); 587 TEST_MESSAGE(line); 588 } 589 } 590 } 591 592 static void test_i2c_mux_disable_all_clears_mask(void) { 593 GIVEN("Wire1 is available"); 594 test_ensure_wire1(); 595 hardware::i2c::TopologySnapshot snapshot = {}; 596 hardware::i2c::accessTopology(&snapshot); 597 if (!snapshot.mux_present) { 598 TEST_IGNORE_MESSAGE("mux not present on this board"); 599 return; 600 } 601 602 hardware::i2c::mux.enableChannel(0); 603 hardware::i2c::mux.enableChannel(3); 604 hardware::i2c::mux.enableChannel(7); 605 WHEN("all channels are disabled"); 606 TEST_ASSERT_BITS_HIGH_MESSAGE(0x89, hardware::i2c::mux.getChannelMask(), 607 "device: channels 0, 3, 7 should all be enabled"); 608 609 hardware::i2c::mux.disableAllChannels(); 610 TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x00, hardware::i2c::mux.getChannelMask(), 611 "device: mask should be 0x00 after disableAllChannels"); 612 613 } 614 615 static void test_i2c_mux_enable_disable_roundtrip(void) { 616 GIVEN("Wire1 is available"); 617 test_ensure_wire1(); 618 hardware::i2c::TopologySnapshot snapshot = {}; 619 hardware::i2c::accessTopology(&snapshot); 620 if (!snapshot.mux_present) { 621 TEST_IGNORE_MESSAGE("mux not present on this board"); 622 return; 623 } 624 hardware::i2c::mux.disableAllChannels(); 625 626 WHEN("a channel is enabled then disabled"); 627 hardware::i2c::mux.enableChannel(2); 628 TEST_ASSERT_BIT_HIGH_MESSAGE(2, hardware::i2c::mux.getChannelMask(), 629 "device: bit 2 should be high after enableChannel(2)"); 630 631 hardware::i2c::mux.disableChannel(2); 632 TEST_ASSERT_BIT_LOW_MESSAGE(2, hardware::i2c::mux.getChannelMask(), 633 "device: bit 2 should be low after disableChannel(2)"); 634 TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x00, hardware::i2c::mux.getChannelMask(), 635 "device: full mask should be 0x00 after disable"); 636 637 } 638 639 void hardware::i2c::test() { 640 MODULE("I2C"); 641 RUN_TEST(test_i2c_mux_init); 642 RUN_TEST(test_i2c_mux_is_connected); 643 RUN_TEST(test_i2c_mux_channel_count); 644 RUN_TEST(test_i2c_mux_select_and_verify); 645 RUN_TEST(test_i2c_mux_scan); 646 RUN_TEST(test_i2c_mux_channel_power_mapping); 647 RUN_TEST(test_i2c_mux_disable_all_clears_mask); 648 RUN_TEST(test_i2c_mux_enable_disable_roundtrip); 649 } 650 651 #endif