|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: MIT |
| 5 | + */ |
| 6 | +/*! |
| 7 | + @file m5_unit_unified_wiring.hpp |
| 8 | + @brief Opt-in, header-only board-aware connection helpers for M5UnitUnified examples |
| 9 | + @note Include this LAST. It self-includes the Arduino cores (Wire/SPI/HardwareSerial) and detects |
| 10 | + M5Unified/M5GFX/M5HAL via guard macros without including them. |
| 11 | +*/ |
| 12 | +#ifndef M5_UNIT_UNIFIED_WIRING_HPP |
| 13 | +#define M5_UNIT_UNIFIED_WIRING_HPP |
| 14 | + |
| 15 | +#include "../M5UnitUnified.hpp" // core (UnitUnified / Component, brings in M5HAL) |
| 16 | +#if defined(ARDUINO) |
| 17 | +#include <Wire.h> |
| 18 | +#include <SPI.h> |
| 19 | +#include <HardwareSerial.h> |
| 20 | +#endif |
| 21 | +// NOTE: M5Unified.h / M5GFX are NOT included; detected via __M5UNIFIED_HPP__ / __M5_I2C_CLASS_H__. |
| 22 | + |
| 23 | +namespace m5 { |
| 24 | +namespace unit { |
| 25 | +namespace wiring { |
| 26 | + |
| 27 | +///@name I2C (low-level builders) |
| 28 | +///@{ |
| 29 | +#if defined(ARDUINO) |
| 30 | +//! @brief Add a unit on an explicit TwoWire bus (re-inits the bus on the given pins) |
| 31 | +inline bool i2cWire(UnitUnified& units, Component& unit, TwoWire& wire, const int sda, const int scl, |
| 32 | + const uint32_t clock) |
| 33 | +{ |
| 34 | + M5_LIB_LOGI("wiring: I2C(Wire) sda=%d scl=%d clock=%lu", sda, scl, (unsigned long)clock); |
| 35 | + wire.end(); |
| 36 | + wire.begin(sda, scl, clock); |
| 37 | + return units.add(unit, wire); |
| 38 | +} |
| 39 | +#endif |
| 40 | + |
| 41 | +#if defined(__M5_I2C_CLASS_H__) |
| 42 | +//! @brief Add a unit on an m5::I2C_Class bus (e.g. M5.In_I2C / M5.Ex_I2C) |
| 43 | +inline bool i2cClass(UnitUnified& units, Component& unit, m5::I2C_Class& i2c) |
| 44 | +{ |
| 45 | + M5_LIB_LOGI("wiring: I2C(I2C_Class)"); |
| 46 | + i2c.begin(); |
| 47 | + return units.add(unit, i2c); |
| 48 | +} |
| 49 | +#endif |
| 50 | + |
| 51 | +#if defined(M5_HAL_HPP) |
| 52 | +//! @brief Add a unit on a software (bit-bang) I2C bus via M5HAL on explicit pins |
| 53 | +inline bool i2cSoftware(UnitUnified& units, Component& unit, const int sda, const int scl) |
| 54 | +{ |
| 55 | + M5_LIB_LOGI("wiring: I2C(Software/M5HAL) sda=%d scl=%d", sda, scl); |
| 56 | + m5::hal::bus::I2CBusConfig cfg; |
| 57 | + cfg.pin_sda = m5::hal::gpio::getPin(sda); |
| 58 | + cfg.pin_scl = m5::hal::gpio::getPin(scl); |
| 59 | + auto bus = m5::hal::bus::i2c::getBus(cfg); |
| 60 | + return units.add(unit, bus ? bus.value() : nullptr); |
| 61 | +} |
| 62 | +#endif |
| 63 | +///@} |
| 64 | + |
| 65 | +///@name I2C (high-level board-aware) |
| 66 | +///@{ |
| 67 | +#if defined(__M5UNIFIED_HPP__) // ⇒ ARDUINO also defined; Wire / I2C_Class / M5HAL all available |
| 68 | +//! @brief NessoN1 connection choice: PortB = direct GROVE (SoftwareI2C), PortA = PbHub / QWIIC (Wire) |
| 69 | +enum class NessoPort { PortB, PortA }; |
| 70 | + |
| 71 | +/*! |
| 72 | + @brief Add a unit on the board's default I2C, picking the right backend automatically |
| 73 | + @param units UnitUnified manager |
| 74 | + @param unit Unit Component to add |
| 75 | + @param clock I2C clock in Hz |
| 76 | + @param nesso NessoN1 only: caller picks the port. PortB = direct GROVE (SoftwareI2C, for normal |
| 77 | + units); PortA = QWIIC / PbHub (hardware Wire, e.g. NFC). |
| 78 | + @return True if successful |
| 79 | + @note NessoN1: PortB -> i2cSoftware (M5HAL, C6 GROVE can't use Wire), PortA -> i2cWire (Wire on |
| 80 | + port_a). NanoC6 -> M5.Ex_I2C (I2C_Class). Other boards -> i2cWire (Wire on port_a). |
| 81 | +*/ |
| 82 | +inline bool addI2C(UnitUnified& units, Component& unit, const uint32_t clock = 100000, |
| 83 | + const NessoPort nesso = NessoPort::PortB) |
| 84 | +{ |
| 85 | + const auto board = M5.getBoard(); |
| 86 | + M5_LIB_LOGI("wiring: addI2C board=0x%02x nesso=%d", (int)board, (int)nesso); |
| 87 | + if (board == m5::board_t::board_ArduinoNessoN1) { |
| 88 | + if (nesso == NessoPort::PortB) { |
| 89 | + // Direct GROVE on C6: Wire can't drive it -> SoftwareI2C (M5HAL) |
| 90 | + return i2cSoftware(units, unit, M5.getPin(m5::pin_name_t::port_b_out), |
| 91 | + M5.getPin(m5::pin_name_t::port_b_in)); |
| 92 | + } |
| 93 | + // PortA (QWIIC / PbHub): hardware Wire works (matches M5Unit-NFC) |
| 94 | + return i2cWire(units, unit, Wire, M5.getPin(m5::pin_name_t::port_a_sda), M5.getPin(m5::pin_name_t::port_a_scl), |
| 95 | + clock); |
| 96 | + } |
| 97 | + if (board == m5::board_t::board_M5NanoC6) { |
| 98 | + return i2cClass(units, unit, M5.Ex_I2C); |
| 99 | + } |
| 100 | + return i2cWire(units, unit, Wire, M5.getPin(m5::pin_name_t::port_a_sda), M5.getPin(m5::pin_name_t::port_a_scl), |
| 101 | + clock); |
| 102 | +} |
| 103 | +#endif |
| 104 | +///@} |
| 105 | + |
| 106 | +///@name GPIO (high-level board-aware) |
| 107 | +///@{ |
| 108 | +#if defined(__M5UNIFIED_HPP__) |
| 109 | +/*! |
| 110 | + @brief Add a unit on GPIO, preferring PortB and falling back to PortA |
| 111 | + @param units UnitUnified manager |
| 112 | + @param unit Unit Component to add |
| 113 | + @return True if successful |
| 114 | + @note When PortB is unavailable, Wire.end() is called before reusing PortA's pins. |
| 115 | +*/ |
| 116 | +inline bool addGPIO(UnitUnified& units, Component& unit) |
| 117 | +{ |
| 118 | + auto rx = M5.getPin(m5::pin_name_t::port_b_in); |
| 119 | + auto tx = M5.getPin(m5::pin_name_t::port_b_out); |
| 120 | + if (rx < 0 || tx < 0) { |
| 121 | + Wire.end(); |
| 122 | + rx = M5.getPin(m5::pin_name_t::port_a_pin1); |
| 123 | + tx = M5.getPin(m5::pin_name_t::port_a_pin2); |
| 124 | + } |
| 125 | + M5_LIB_LOGI("wiring: GPIO rx=%d tx=%d", (int)rx, (int)tx); |
| 126 | + return units.add(unit, static_cast<int8_t>(rx), static_cast<int8_t>(tx)); |
| 127 | +} |
| 128 | +#endif |
| 129 | +///@} |
| 130 | + |
| 131 | +///@name UART |
| 132 | +///@{ |
| 133 | +#if defined(ARDUINO) |
| 134 | +//! @brief The board's default Serial for a Port unit, chosen by SoC UART count |
| 135 | +inline HardwareSerial& defaultUartSerial() |
| 136 | +{ |
| 137 | +#if defined(CONFIG_IDF_TARGET_ESP32C6) |
| 138 | + return Serial1; |
| 139 | +#elif SOC_UART_NUM > 2 |
| 140 | + return Serial2; |
| 141 | +#elif SOC_UART_NUM > 1 |
| 142 | + return Serial1; |
| 143 | +#else |
| 144 | +#error "Not enough Serial" |
| 145 | +#endif |
| 146 | +} |
| 147 | +#endif |
| 148 | + |
| 149 | +#if defined(__M5UNIFIED_HPP__) |
| 150 | +/*! |
| 151 | + @brief Add a unit on UART, preferring PortC and falling back to PortA |
| 152 | + @param units UnitUnified manager |
| 153 | + @param unit Unit Component to add |
| 154 | + @param serial HardwareSerial to begin and use |
| 155 | + @param baud Baud rate |
| 156 | + @param config Serial config (e.g. SERIAL_8N1) |
| 157 | + @return True if successful |
| 158 | + @note On NanoC6, M5.Ex_I2C.release() is called before using PortA: M5.begin() attaches the m5gfx |
| 159 | + I2C peripheral to those external pins, which would otherwise conflict with UART. No Wire.end() |
| 160 | + is called (unlike addI2C) -- UART uses Serial, not Wire, so the internal I2C bus is left intact. |
| 161 | +*/ |
| 162 | +inline bool addUART(UnitUnified& units, Component& unit, HardwareSerial& serial, const uint32_t baud = 115200, |
| 163 | + const uint32_t config = SERIAL_8N1) |
| 164 | +{ |
| 165 | + auto rx = M5.getPin(m5::pin_name_t::port_c_rxd); |
| 166 | + auto tx = M5.getPin(m5::pin_name_t::port_c_txd); |
| 167 | + if (rx < 0 || tx < 0) { |
| 168 | + if (M5.getBoard() == m5::board_t::board_M5NanoC6) { |
| 169 | + M5.Ex_I2C.release(); // free the m5gfx I2C peripheral M5.begin() attached to these pins |
| 170 | + } |
| 171 | + rx = M5.getPin(m5::pin_name_t::port_a_pin1); |
| 172 | + tx = M5.getPin(m5::pin_name_t::port_a_pin2); |
| 173 | + } |
| 174 | + M5_LIB_LOGI("wiring: UART rx=%d tx=%d baud=%lu", (int)rx, (int)tx, (unsigned long)baud); |
| 175 | + serial.begin(baud, config, rx, tx); |
| 176 | + return units.add(unit, serial); |
| 177 | +} |
| 178 | +#endif |
| 179 | +///@} |
| 180 | + |
| 181 | +///@name SPI |
| 182 | +///@{ |
| 183 | +#if defined(ARDUINO) |
| 184 | +//! @brief Add a unit on an already-begun SPI bus (CS is taken from the unit ctor / address) |
| 185 | +inline bool spiBus(UnitUnified& units, Component& unit, SPIClass& spi, const SPISettings& settings) |
| 186 | +{ |
| 187 | + M5_LIB_LOGI("wiring: SPI"); |
| 188 | + return units.add(unit, spi, settings); |
| 189 | +} |
| 190 | +#endif |
| 191 | + |
| 192 | +#if defined(__M5UNIFIED_HPP__) // dereferences M5 -> needs M5Unified (which implies ARDUINO, so SPI exists) |
| 193 | +/*! |
| 194 | + @brief Add a unit on the board's shared SD/SPI bus, beginning it on demand |
| 195 | + @param units UnitUnified manager |
| 196 | + @param unit Unit Component to add |
| 197 | + @param settings SPI settings (clock, bit order, data mode) |
| 198 | + @return True if successful |
| 199 | + @note Resolves the sd_spi_* pins (the M5 shared SPI bus). SPI.begin() is called only if the bus is |
| 200 | + not already begun. CS is taken from the unit ctor / address (this library drives it). |
| 201 | +*/ |
| 202 | +inline bool addSPI(UnitUnified& units, Component& unit, const SPISettings& settings) |
| 203 | +{ |
| 204 | + const auto sclk = M5.getPin(m5::pin_name_t::sd_spi_sclk); |
| 205 | + const auto mosi = M5.getPin(m5::pin_name_t::sd_spi_mosi); |
| 206 | + const auto miso = M5.getPin(m5::pin_name_t::sd_spi_miso); |
| 207 | + M5_LIB_LOGI("wiring: addSPI sclk=%d miso=%d mosi=%d", (int)sclk, (int)miso, (int)mosi); |
| 208 | + if (!SPI.bus()) { |
| 209 | + SPI.begin(sclk, miso, mosi); |
| 210 | + } |
| 211 | + return spiBus(units, unit, SPI, settings); |
| 212 | +} |
| 213 | +#endif |
| 214 | +///@} |
| 215 | + |
| 216 | +///@note Hat pin tables are survey-derived; re-verify per Hat product before relying on them. |
| 217 | +///@name Hat (2-pin header, board-aware) |
| 218 | +///@{ |
| 219 | +#if defined(__M5UNIFIED_HPP__) |
| 220 | +/*! |
| 221 | + @brief Add a unit on the board's Hat I2C header (NessoN1 uses Wire1, others Wire) |
| 222 | + @param units UnitUnified manager |
| 223 | + @param unit Unit Component to add |
| 224 | + @param clock I2C clock in Hz |
| 225 | + @return True if successful |
| 226 | + @note Some Hats need extra pin pre-setup (e.g. HatHEART does pinMode(scl, OUTPUT)); do that in the |
| 227 | + caller before this call, or build the connection with the low-level i2cWire. |
| 228 | +*/ |
| 229 | +inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock = 400000) |
| 230 | +{ |
| 231 | + const auto board = M5.getBoard(); |
| 232 | + int sda = -1, scl = -1; |
| 233 | + switch (board) { |
| 234 | + case m5::board_t::board_M5StickC: |
| 235 | + case m5::board_t::board_M5StickCPlus: |
| 236 | + case m5::board_t::board_M5StickCPlus2: |
| 237 | + sda = 0; |
| 238 | + scl = 26; |
| 239 | + break; |
| 240 | + case m5::board_t::board_M5StickS3: |
| 241 | + sda = 8; |
| 242 | + scl = 0; |
| 243 | + break; |
| 244 | + case m5::board_t::board_M5StackCoreInk: |
| 245 | + sda = 25; |
| 246 | + scl = 26; |
| 247 | + break; |
| 248 | + case m5::board_t::board_ArduinoNessoN1: |
| 249 | + sda = 6; |
| 250 | + scl = 7; |
| 251 | + break; |
| 252 | + default: |
| 253 | + M5_LIB_LOGE("wiring: Hat I2C unsupported board=0x%02x", (int)board); |
| 254 | + return false; |
| 255 | + } |
| 256 | + const bool useWire1 = (board == m5::board_t::board_ArduinoNessoN1); |
| 257 | + TwoWire& wire = useWire1 ? Wire1 : Wire; |
| 258 | + M5_LIB_LOGI("wiring: addHatI2C board=0x%02x sda=%d scl=%d clock=%lu wire=%s", (int)board, sda, scl, |
| 259 | + (unsigned long)clock, useWire1 ? "Wire1" : "Wire"); |
| 260 | + return i2cWire(units, unit, wire, sda, scl, clock); |
| 261 | +} |
| 262 | + |
| 263 | +/*! |
| 264 | + @brief Add a unit on the board's Hat GPIO header |
| 265 | + @param units UnitUnified manager |
| 266 | + @param unit Unit Component to add |
| 267 | + @return True if successful |
| 268 | +*/ |
| 269 | +inline bool addHatGPIO(UnitUnified& units, Component& unit) |
| 270 | +{ |
| 271 | + const auto board = M5.getBoard(); |
| 272 | + int rx = -1, tx = -1; |
| 273 | + switch (board) { |
| 274 | + case m5::board_t::board_M5StickC: |
| 275 | + case m5::board_t::board_M5StickCPlus: |
| 276 | + case m5::board_t::board_M5StickCPlus2: |
| 277 | + rx = 26; |
| 278 | + tx = 0; |
| 279 | + break; |
| 280 | + case m5::board_t::board_M5StickS3: |
| 281 | + rx = 0; |
| 282 | + tx = 8; |
| 283 | + break; |
| 284 | + case m5::board_t::board_M5StackCoreInk: |
| 285 | + rx = 26; |
| 286 | + tx = 25; |
| 287 | + break; |
| 288 | + case m5::board_t::board_ArduinoNessoN1: |
| 289 | + rx = 5; |
| 290 | + tx = 4; |
| 291 | + break; |
| 292 | + default: |
| 293 | + M5_LIB_LOGE("wiring: Hat GPIO unsupported board=0x%02x", (int)board); |
| 294 | + return false; |
| 295 | + } |
| 296 | + M5_LIB_LOGI("wiring: addHatGPIO board=0x%02x rx=%d tx=%d", (int)board, rx, tx); |
| 297 | + return units.add(unit, static_cast<int8_t>(rx), static_cast<int8_t>(tx)); |
| 298 | +} |
| 299 | + |
| 300 | +/*! |
| 301 | + @brief Add a unit on the board's Hat UART header |
| 302 | + @param units UnitUnified manager |
| 303 | + @param unit Unit Component to add |
| 304 | + @param serial HardwareSerial to begin and use |
| 305 | + @param baud Baud rate |
| 306 | + @param config Serial config (e.g. SERIAL_8N1) |
| 307 | + @return True if successful |
| 308 | +*/ |
| 309 | +inline bool addHatUART(UnitUnified& units, Component& unit, HardwareSerial& serial, const uint32_t baud = 115200, |
| 310 | + const uint32_t config = SERIAL_8N1) |
| 311 | +{ |
| 312 | + const auto board = M5.getBoard(); |
| 313 | + int rx = -1, tx = -1; |
| 314 | + switch (board) { |
| 315 | + case m5::board_t::board_M5StickCPlus: |
| 316 | + case m5::board_t::board_M5StickCPlus2: |
| 317 | + rx = 26; |
| 318 | + tx = 0; |
| 319 | + break; |
| 320 | + case m5::board_t::board_M5StickS3: |
| 321 | + rx = 0; |
| 322 | + tx = 8; |
| 323 | + break; |
| 324 | + case m5::board_t::board_M5StackCoreInk: |
| 325 | + rx = 26; |
| 326 | + tx = 25; |
| 327 | + break; |
| 328 | + case m5::board_t::board_ArduinoNessoN1: |
| 329 | + rx = 7; |
| 330 | + tx = 6; |
| 331 | + break; |
| 332 | + default: |
| 333 | + M5_LIB_LOGE("wiring: Hat UART unsupported board=0x%02x", (int)board); |
| 334 | + return false; |
| 335 | + } |
| 336 | + M5_LIB_LOGI("wiring: addHatUART board=0x%02x rx=%d tx=%d baud=%lu", (int)board, rx, tx, (unsigned long)baud); |
| 337 | + serial.begin(baud, config, rx, tx); |
| 338 | + return units.add(unit, serial); |
| 339 | +} |
| 340 | +#endif |
| 341 | +///@} |
| 342 | + |
| 343 | +///@name Error handling |
| 344 | +///@{ |
| 345 | +#if defined(__M5UNIFIED_HPP__) |
| 346 | +//! @brief Fill the display red and halt (for begin() failure in examples) |
| 347 | +[[noreturn]] inline void failStop() |
| 348 | +{ |
| 349 | + M5.Display.fillScreen(0xF800 /* TFT_RED */); |
| 350 | + while (true) { |
| 351 | + m5::utility::delay(10000); |
| 352 | + } |
| 353 | +} |
| 354 | +#endif |
| 355 | +///@} |
| 356 | + |
| 357 | +} // namespace wiring |
| 358 | +} // namespace unit |
| 359 | +} // namespace m5 |
| 360 | +#endif // M5_UNIT_UNIFIED_WIRING_HPP |
0 commit comments