Skip to content

Commit bc1e0cb

Browse files
committed
Fix Component::assign(Bus*) bus-type match and add wiring helper
1 parent 05391b1 commit bc1e0cb

2 files changed

Lines changed: 388 additions & 3 deletions

File tree

src/M5UnitComponent.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,35 @@ Component* Component::child(const uint8_t ch) const
135135

136136
bool Component::assign(m5::hal::bus::Bus* bus)
137137
{
138-
if (_addr) {
139-
_adapter = std::make_shared<AdapterI2C>(bus, _addr, _component_cfg.clock);
138+
if (!bus) {
139+
return false;
140+
}
141+
142+
// Bus type and unit access capability must agree
143+
switch (bus->getBusType()) {
144+
case m5::hal::types::BusType::I2C:
145+
if (!canAccessI2C() || !_addr) return false;
146+
_adapter = std::make_shared<AdapterI2C>(bus, _addr, _component_cfg.clock);
147+
return static_cast<bool>(_adapter);
148+
149+
case m5::hal::types::BusType::SPI:
150+
if (!canAccessSPI()) return false;
151+
M5_LIB_LOGE("M5HAL SPI bus assign is not yet implemented");
152+
return false;
153+
154+
case m5::hal::types::BusType::UART:
155+
if (!canAccessUART()) return false;
156+
M5_LIB_LOGE("M5HAL UART bus assign is not yet implemented");
157+
return false;
158+
159+
case m5::hal::types::BusType::GPIO:
160+
if (!canAccessGPIO()) return false;
161+
M5_LIB_LOGE("M5HAL GPIO bus assign is not yet implemented");
162+
return false;
163+
164+
default:
165+
return false;
140166
}
141-
return static_cast<bool>(_adapter);
142167
}
143168

144169
bool Component::assign(TwoWire& wire)
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
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

Comments
 (0)