Skip to content

Commit 6d734f0

Browse files
committed
Merge branch 'develop'
2 parents 9bf6d99 + 41eb4c2 commit 6d734f0

22 files changed

Lines changed: 428 additions & 396 deletions

.github/workflows/arduino-m5-build-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
- m5stack_dinmeter
8181
- m5stack_fire
8282
- m5stack_nano_c6
83-
# - m5stack_nano_h2
83+
- m5stack_nano_h2
8484
- m5stack_paper
8585
- m5stack_papers3
8686
# - m5stack_poe_cam

.github/workflows/platformio-build-check.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
- AtomS3
7272
- AtomS3R
7373
- NanoC6
74+
- NanoH2
7475
- StickCPlus
7576
- StickCPlus2
7677
- StickS3

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,19 @@ docs/html/
3737
# PlatformIO
3838
.pio/
3939

40+
# ESP-IDF (idf.py) build artifacts
41+
build/
42+
managed_components/
43+
sdkconfig
44+
sdkconfig.old
45+
dependencies.lock
46+
4047
# IDE
4148
.vscode/
49+
.clangd
50+
51+
# clangd index cache
52+
.cache/
4253

4354
# macOS
4455
.DS_Store

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,25 @@ Library for M5Stack light-related units using [M5UnitUnified](https://github.com
66
M5UnitUnified is a library for unified handling of various M5 units products.
77

88
### SKU:U136
9-
Unit DLight is a digital ambient light detection sensor that uses the BH1750FVI illuminance sensor IC (I2C interface). It has a built-in 16-bit AD converter supporting illuminance value detection ranging from 1 to 65535 lx. It is characterized by its small size and low power consumption, making it suitable for various illuminance detection and light control adjustment scenarios.
9+
Unit DLight is a digital ambient light detection sensor. The hardware uses a BH1750FVI illuminance sensor IC (I2C interface), with built-in 16-bit AD conversion supporting (1 ~ 65535 lx) illuminance value detection. It features a small size and low power consumption, making it suitable for various illuminance detection and light control adjustment scenarios.
1010

1111
### SKU:U134
1212
Hat DLight is a digital ambient light detection sensor compatible with the M5StickC/C Plus series. The hardware uses the BH1750FVI illuminance sensor IC (I2C interface), with a built-in 16-bit AD converter, supporting illuminance value detection from 1 to 65535 lx. It features a small size and low power consumption, making it suitable for various illuminance detection and light control adjustment scenarios.
1313

1414
### SKU:U021
1515
Unit Light is a light intensity detection sensor. It integrates a photoresistor and a 10K adjustable resistor, capable of detecting light intensity and setting a light intensity threshold. The resistance of the photoresistor decreases as the incident light intensity increases, thus detecting the change in voltage, and obtaining light intensity data through AD conversion. To achieve more accurate light intensity measurements, this Unit also adopts the LM393 dual differential comparator, used to compare the differential voltage between the photoresistor and the varistor.
1616

17+
#### Analog reading notes (chip-generation dependent)
18+
19+
Raw ADC values from `unit.analog()` are **not directly comparable across boards** because ESP32 ADC characteristics differ by chip generation:
20+
21+
- **ESP32 classic** (Core / Fire / StickCPlus 等): ATTEN_DB_11 is non-linear above ~2.45 V; dark readings tend to saturate near 4095 due to ADC compression, not true full-scale.
22+
- **ESP32-C6 / -H2 / -S3 / -P4** (NanoC6 / NanoH2 / CoreS3 / StickS3 / Tab5): ATTEN_DB_12 is linear across 0–3.3 V; the same physical light level yields a lower raw value (~3900) than on ESP32 classic — this is the more accurate reading.
23+
24+
Reference: [Espressif Developer Blog — Comparing ADC Performance of Espressif SoCs (2025-08)](https://developer.espressif.com/blog/2025/08/adc-performance/)
25+
26+
**Always calibrate per board** via `unit.calibrateDark()` / `unit.calibrateBright()` (BtnA click / hold in `PlotToSerial` example) so that `unit.normalized()` returns a portable 0–100 % value regardless of chip.
27+
1728
## Related Link
1829
See also examples using conventional methods here.
1930

boards/m5stack-nanoc6.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"build": {
33
"core": "esp32",
44
"extra_flags": [
5-
"-DARDUINO_M5STACK_NANOC6"
5+
"-DARDUINO_M5STACK_NANO_C6"
66
],
77
"f_cpu": "160000000L",
88
"f_flash": "80000000L",

boards/m5stack-nanoh2.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"build": {
3+
"core": "esp32",
4+
"extra_flags": [
5+
"-DARDUINO_M5STACK_NANO_H2"
6+
],
7+
"f_cpu": "96000000L",
8+
"f_flash": "16000000L",
9+
"flash_mode": "qio",
10+
"mcu": "esp32h2",
11+
"variant": "esp32h2"
12+
},
13+
"connectivity": [
14+
"bluetooth",
15+
"zigbee"
16+
],
17+
"debug": {
18+
"openocd_target": "esp32h2.cfg"
19+
},
20+
"frameworks": [
21+
"arduino",
22+
"espidf"
23+
],
24+
"name": "M5Stack NanoH2",
25+
"upload": {
26+
"flash_size": "4MB",
27+
"maximum_ram_size": 327680,
28+
"maximum_size": 4194304,
29+
"require_upload_port": true,
30+
"speed": 460800
31+
},
32+
"url": "https://docs.m5stack.com/en/core/NanoH2",
33+
"vendor": "M5Stack"
34+
}

examples/UnitUnified/UnitDLight/PlotToSerial/main/PlotToSerial.cpp

Lines changed: 14 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
BtnA hold: cycle resolution Low -> High -> High2 (also re-measures
1313
for instant feedback in either mode)
1414
*/
15+
#include <driver/gpio.h>
1516
#include <M5Unified.h>
1617
#include <M5UnitUnified.h>
1718
#include <M5UnitUnifiedLIGHT.h>
18-
#include <Wire.h>
19-
#include <M5HAL.hpp> // For NessoN1
20-
#include <cmath> // std::isnan
21-
#include <limits> // std::numeric_limits
19+
#include <wiring/m5_unit_unified_wiring.hpp> // Board-aware connection helpers (include last)
20+
#include <cmath> // std::isnan
21+
#include <limits> // std::numeric_limits
2222

2323
// *************************************************************
2424
// Choose one define symbol to match the unit you are using
@@ -153,51 +153,17 @@ void draw_status()
153153
canvas.pushSprite(0, 0);
154154
}
155155

156-
#if defined(USING_HAT_DLIGHT)
157-
struct I2cPins {
158-
int sda;
159-
int scl;
160-
};
161-
162-
I2cPins get_hat_i2c_pins(const m5::board_t board)
163-
{
164-
switch (board) {
165-
case m5::board_t::board_M5StickC:
166-
case m5::board_t::board_M5StickCPlus:
167-
case m5::board_t::board_M5StickCPlus2:
168-
return {0, 26};
169-
case m5::board_t::board_M5StickS3:
170-
return {8, 0};
171-
case m5::board_t::board_M5StackCoreInk:
172-
return {25, 26};
173-
case m5::board_t::board_ArduinoNessoN1:
174-
return {6, 7};
175-
default:
176-
return {-1, -1};
177-
}
178-
}
179-
#endif
180-
181156
} // namespace
182157

183158
void setup()
184159
{
185-
auto m5cfg{M5.config()};
186-
#if defined(USING_HAT_DLIGHT)
187-
m5cfg.pmic_button = false; // Disable BtnPWR on StickC series
188-
m5cfg.internal_imu = false;
189-
m5cfg.internal_rtc = false;
190-
#endif
191-
192-
M5.begin(m5cfg);
160+
M5.begin();
193161
M5.setTouchButtonHeightByRatio(100);
194162

195163
if (lcd.height() > lcd.width()) {
196164
lcd.setRotation(1);
197165
}
198166

199-
auto board{M5.getBoard()};
200-
201167
// Prioritize response speed over resolution so brief lighting changes
202168
// (e.g. a flash) are tracked in near-real-time.
203169
// Low-res + MTREG_MIN = 24 * 31 / 69 ~= 11ms per sample (4 lx resolution).
@@ -210,61 +176,19 @@ void setup()
210176
}
211177

212178
#if defined(USING_HAT_DLIGHT)
213-
const auto pins{get_hat_i2c_pins(board)};
214-
M5_LOGI("getHatPin: SDA:%d SCL:%d", pins.sda, pins.scl);
215-
if (pins.sda < 0 || pins.scl < 0) {
216-
M5_LOGE("Illegal pin number");
217-
lcd.fillScreen(TFT_RED);
218-
while (true) {
219-
m5::utility::delay(10000);
220-
}
221-
}
222-
223-
pinMode(pins.scl, OUTPUT);
224-
225-
auto& wire{(board == m5::board_t::board_ArduinoNessoN1) ? Wire1 : Wire};
226-
wire.end();
227-
wire.begin(pins.sda, pins.scl, 400 * 1000U);
228-
// m5::utility::delay(50); // Allow BH1750 VCC rails to settle after Wire.begin().
229-
if (!Units.add(unit, wire) || !Units.begin()) {
179+
// HatDLight: NessoN1 uses Wire1, other StickC-family boards use Wire.
180+
// Drive SCL as OUTPUT before bus init (some boards leave it floating on reset).
181+
const auto pins = m5::unit::wiring::hatI2CPins();
182+
gpio_set_direction(static_cast<gpio_num_t>(pins.scl), GPIO_MODE_OUTPUT);
183+
if (!m5::unit::wiring::addHatI2C(Units, unit, 400 * 1000U) || !Units.begin()) {
230184
M5_LOGE("Failed to begin");
231-
lcd.fillScreen(TFT_RED);
232-
while (true) {
233-
m5::utility::delay(10000);
234-
}
185+
m5::unit::wiring::failStop();
235186
}
236187
#else
237-
// NessoN1: Use SoftwareI2C via M5HAL for GROVE port.
238-
// NanoC6: Use M5.Ex_I2C (m5::I2C_Class) directly.
239-
bool unit_ready{};
240-
if (board == m5::board_t::board_ArduinoNessoN1) {
241-
auto pin_num_sda{M5.getPin(m5::pin_name_t::port_b_out)};
242-
auto pin_num_scl{M5.getPin(m5::pin_name_t::port_b_in)};
243-
M5_LOGI("getPin(M5HAL): SDA:%d SCL:%d", pin_num_sda, pin_num_scl);
244-
m5::hal::bus::I2CBusConfig i2c_cfg;
245-
i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda);
246-
i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl);
247-
auto i2c_bus{m5::hal::bus::i2c::getBus(i2c_cfg)};
248-
M5_LOGI("Bus:%d", i2c_bus.has_value());
249-
unit_ready = Units.add(unit, i2c_bus ? i2c_bus.value() : nullptr) && Units.begin();
250-
} else if (board == m5::board_t::board_M5NanoC6) {
251-
M5_LOGI("Using M5.Ex_I2C");
252-
unit_ready = Units.add(unit, M5.Ex_I2C) && Units.begin();
253-
} else {
254-
auto pin_num_sda{M5.getPin(m5::pin_name_t::port_a_sda)};
255-
auto pin_num_scl{M5.getPin(m5::pin_name_t::port_a_scl)};
256-
M5_LOGI("getPin: SDA:%d SCL:%d", pin_num_sda, pin_num_scl);
257-
Wire.end();
258-
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
259-
// m5::utility::delay(50); // Allow BH1750 VCC rails to settle after Wire.begin().
260-
unit_ready = Units.add(unit, Wire) && Units.begin();
261-
}
262-
if (!unit_ready) {
188+
// UnitDLight: NessoN1 -> SoftwareI2C (M5HAL), NanoC6 / NanoH2 -> M5.Ex_I2C, others -> Wire
189+
if (!m5::unit::wiring::addI2C(Units, unit, 400 * 1000U) || !Units.begin()) {
263190
M5_LOGE("Failed to begin");
264-
lcd.fillScreen(TFT_RED);
265-
while (true) {
266-
m5::utility::delay(10000);
267-
}
191+
m5::unit::wiring::failStop();
268192
}
269193
#endif
270194

examples/UnitUnified/UnitLight/PlotToSerial/main/PlotToSerial.cpp

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <M5Unified.h>
1313
#include <M5UnitUnified.h>
1414
#include <M5UnitUnifiedLIGHT.h>
15+
#include <wiring/m5_unit_unified_wiring.hpp> // Board-aware connection helpers (include last)
1516

1617
namespace {
1718
auto& lcd = M5.Display;
@@ -23,25 +24,6 @@ m5::unit::UnitLight unit;
2324
constexpr uint32_t DRAW_INTERVAL_MS{200};
2425
uint32_t last_draw_ms{0};
2526

26-
struct IoPins {
27-
int rx;
28-
int tx;
29-
};
30-
31-
IoPins get_gpio_pins(const m5::board_t board)
32-
{
33-
// Port.B on M5Core family (analog=in, digital=out on U021 layout)
34-
auto rx{M5.getPin(m5::pin_name_t::port_b_in)};
35-
auto tx{M5.getPin(m5::pin_name_t::port_b_out)};
36-
if (rx >= 0 && tx >= 0) {
37-
return {rx, tx};
38-
}
39-
// Fallback to Port.A (use SCL as analog input, SDA as digital input on Grove).
40-
rx = M5.getPin(m5::pin_name_t::port_a_scl);
41-
tx = M5.getPin(m5::pin_name_t::port_a_sda);
42-
return {rx, tx};
43-
}
44-
4527
void draw_status()
4628
{
4729
// Skip drawing on boards without a display (Atom / AtomLite / StampS3 / NanoC6 etc.)
@@ -91,31 +73,17 @@ void draw_status()
9173

9274
void setup()
9375
{
94-
auto m5cfg{M5.config()};
95-
M5.begin(m5cfg);
76+
M5.begin();
9677
M5.setTouchButtonHeightByRatio(100);
9778

9879
if (lcd.height() > lcd.width()) {
9980
lcd.setRotation(1);
10081
}
10182

102-
const auto board{M5.getBoard()};
103-
const auto pins{get_gpio_pins(board)};
104-
M5_LOGI("UnitLight pins: RX=%d (analog) TX=%d (digital)", pins.rx, pins.tx);
105-
if (pins.rx < 0 || pins.tx < 0) {
106-
M5_LOGE("No GPIO pins available for this board (%u)", static_cast<unsigned>(board));
107-
lcd.fillScreen(TFT_RED);
108-
while (true) {
109-
m5::utility::delay(10000);
110-
}
111-
}
112-
113-
if (!Units.add(unit, pins.rx, pins.tx) || !Units.begin()) {
83+
// U021 uses both analog input (rx) and digital comparator output (tx); PortB preferred, PortA fallback.
84+
if (!m5::unit::wiring::addGPIO(Units, unit) || !Units.begin()) {
11485
M5_LOGE("Failed to begin");
115-
lcd.fillScreen(TFT_RED);
116-
while (true) {
117-
m5::utility::delay(10000);
118-
}
86+
m5::unit::wiring::failStop();
11987
}
12088

12189
// Prepare off-screen canvas: 1-bit depth, no PSRAM — keeps flicker and memory footprint low.

examples/UnitUnified/UnitLight/ViaPbHub/main/ViaPbHub.cpp

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
*/
1414
#include <M5Unified.h>
1515
#include <M5UnitUnified.h>
16-
#include <M5HAL.hpp>
1716
#include <M5UnitUnifiedLIGHT.h>
18-
#include <M5UnitUnifiedHUB.h> // UnitPbHub
17+
#include <M5UnitUnifiedHUB.h> // UnitPbHub
18+
#include <wiring/m5_unit_unified_wiring.hpp> // Board-aware connection helpers (include last)
1919

2020
namespace {
2121
auto& lcd = M5.Display;
@@ -82,51 +82,13 @@ void setup()
8282

8383
if (!hub.add(unit, 3)) { // PbHub ch:3 -> UnitLight
8484
M5_LOGE("Failed to add children");
85-
lcd.fillScreen(TFT_RED);
86-
while (true) {
87-
m5::utility::delay(10000);
88-
}
85+
m5::unit::wiring::failStop();
8986
}
9087

91-
auto board = M5.getBoard();
92-
93-
// NessoN1: Arduino Wire (I2C_NUM_0) cannot be used for GROVE port.
94-
// Wire is used by M5Unified In_I2C for internal devices.
95-
// Reconfiguring Wire to GROVE pins breaks In_I2C.
96-
// Solution: Use SoftwareI2C via M5HAL for the GROVE port.
97-
// NanoC6: Wire.begin() on GROVE pins conflicts with m5::I2C_Class
98-
// registered by Ex_I2C.setPort() on the same I2C_NUM_0.
99-
// Solution: Use M5.Ex_I2C directly instead of Arduino Wire.
100-
bool unit_ready{};
101-
if (board == m5::board_t::board_ArduinoNessoN1) {
102-
// NessoN1: GROVE is port_b (port_a is reserved for internal use)
103-
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_b_out);
104-
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_b_in);
105-
M5_LOGI("getPin(M5HAL): SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
106-
m5::hal::bus::I2CBusConfig i2c_cfg;
107-
i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda);
108-
i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl);
109-
auto i2c_bus = m5::hal::bus::i2c::getBus(i2c_cfg);
110-
M5_LOGI("Bus:%d", i2c_bus.has_value());
111-
unit_ready = Units.add(hub, i2c_bus ? i2c_bus.value() : nullptr) && Units.begin();
112-
} else if (board == m5::board_t::board_M5NanoC6) {
113-
M5_LOGI("Using M5.Ex_I2C");
114-
unit_ready = Units.add(hub, M5.Ex_I2C) && Units.begin();
115-
} else {
116-
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
117-
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
118-
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
119-
Wire.end();
120-
Wire.begin(pin_num_sda, pin_num_scl, 400000U);
121-
unit_ready = Units.add(hub, Wire) && Units.begin();
122-
}
123-
124-
if (!unit_ready) {
88+
// UnitPbHub: NessoN1 -> SoftwareI2C (M5HAL), NanoC6 / NanoH2 -> M5.Ex_I2C, others -> Wire
89+
if (!m5::unit::wiring::addI2C(Units, hub, 400 * 1000U) || !Units.begin()) {
12590
M5_LOGE("Failed to begin");
126-
lcd.fillScreen(TFT_RED);
127-
while (true) {
128-
m5::utility::delay(10000);
129-
}
91+
m5::unit::wiring::failStop();
13092
}
13193

13294
// Prepare off-screen canvas: 1-bit depth, no PSRAM — keeps flicker and memory footprint low.

0 commit comments

Comments
 (0)