H-Bridge Unit v1.1 + M5Dial: possible library bug, HPWR issues & safety note โ test report (please correct me if I'm wrong)
Hardware: M5Stack Dial v1 (ESP32-S3) + M5Stack H-Bridge Unit v1.1 (STM32F030 + RZ7899, FW v2)
Arduino Core: ESP32 3.3.7 | M5Unified: 0.2.15 | M5GFX: 0.2.21 | M5UnitHbridge: 1.0.0
Overview
We spent several sessions trying to get the H-Bridge Unit v1.1 working reliably with the M5Stack Dial v1. What started as a simple "connect and drive a motor" task turned into a systematic investigation of all possible port, voltage, and I2C combinations.
This post documents the full test matrix with serial output and current measurements. We observed three things that surprised us โ please read carefully and let us know if we misunderstood something or if there is a known solution we missed:
- The official M5UnitHbridge library does not seem to work with this hardware โ we believe it is due to a missing Repeated Start condition in its I2C read implementation, but we may be wrong.
- Wire.begin() must be called after M5Dial.begin() โ contrary to what you might expect, this re-configures the hardware I2C peripheral to Port A and works correctly. Is this intended behavior?
- HPWR mode does not behave as expected โ forward direction produces no output, backward direction appears limited to ~9% effective duty cycle regardless of speed value. We tried PWM frequency changes, common GND, different ports and I2C methods โ all without improvement. Could this be a firmware issue, or are we missing a required initialization step?
We hope this is useful to others, and we very much welcome corrections or explanations from M5Stack or the community.
TL;DR
Getting the H-Bridge Unit v1.1 working with M5Dial is not straightforward. This post documents a complete test matrix covering all port/voltage/I2C combinations. The short version:
- 5V mode + Hardware I2C (Wire) + correct registers = works, but USB power limits motor load to ~500mA
- M5UnitHbridge library = broken for this hardware (wrong I2C read protocol, motor does not respond)
- HPWR mode appears non-functional in FW v2: forward channel dead, backward works at fixed ~9% duty cycle regardless of speed value โ firmware bug suspected
Hardware Setup
| Item | Detail |
|---|---|
| Controller | M5Stack Dial v1 (ESP32-S3) |
| Driver | M5Stack H-Bridge Unit v1.1 (STM32F030 + RZ7899, FW v2) |
| Grove Port A | SDA=G13, SCL=G15 |
| Grove Port B | SDA=G2, SCL=G1 |
| HPWR supply | 13.5V @ external terminals, 470ยตF cap across VIN+/GND |
Key Discovery 1: Wire.begin() After M5Dial.begin()
M5Dial.begin() initializes the internal I2C bus (touch controller) on G11/G12.
WireafterM5Dial.begin()โ internal bus, not Port AWire1.begin(13, 15)โ G15 held LOW by touch controller โ SCL blocked- โ
Solution: Call
Wire.begin(13, 15)afterM5Dial.begin()โ this re-configures the hardware I2C peripheral to Port A pins and works correctly
auto cfg = M5.config();
M5Dial.begin(cfg, false, false);
// ...
Wire.begin(13, 15);
Wire.setClock(100000);
Key Discovery 2: Correct Register Map
The H-Bridge register layout differs from various online sources. Verified by M5UnitHbridge library source:
| Register | Address | Description |
|---|---|---|
| REG_DIR | 0x00 | Direction: 0=STOP, 1=FORWARD, 2=BACKWARD |
| REG_SPEED8 | 0x01 | Speed 8-bit (0โ255) |
| REG_SPEED16 | 0x02 | Speed 16-bit little-endian |
| REG_PWM_FREQ | 0x04 | PWM frequency little-endian |
| REG_CURRENT | 0x30 | Motor current IEEE-754 float, 4 bytes (v1.1 only) |
| REG_FW | 0xFE | Firmware version (uint8) |
Key Discovery 3: M5UnitHbridge Library Broken for This Hardware
The official M5UnitHbridge library uses endTransmission() (with STOP condition) before requestFrom():
// Library readBytes() โ BROKEN for H-Bridge v1.1
_wire->beginTransmission(addr);
_wire->write(reg);
_wire->endTransmission(); // โ sends STOP
_wire->requestFrom(addr, length); // โ new START (not Repeated Start)
The STM32F030 in the H-Bridge requires Repeated Start between write and read. Without it:
getFirmwareVersion()returns 255 instead of 2- Write operations appear to succeed but the motor does not respond
โ
Solution: Use Wire directly with endTransmission(false) for reads:
Wire.beginTransmission(HBRIDGE_ADDR);
Wire.write(reg);
Wire.endTransmission(false); // Repeated Start โ NO stop condition
Wire.requestFrom(HBRIDGE_ADDR, len);
Complete Test Matrix
| # | Port | Pins | Power | I2C | Result | Notes |
|---|---|---|---|---|---|---|
| 1a | COM A | G13/G15 | 5V | Hardware Wire | โ Motor runs | Brownout at ~78% load โ USB 5V insufficient for full load |
| 1b | COM A | G13/G15 | 5V | M5UnitHbridge lib | โ No movement | FW=255, writes silent. Library incompatible. |
| 2a | COM A | G13/G15 | 5V | Bit-Banging | โ Motor runs | Brownout at ~24% โ GPIO switching adds extra current spikes |
| 2b | COM B | G2/G1 | 5V | Bit-Banging | โ Motor twitches | Brownout at ~25%, same as 2a. Port makes no difference. |
| 3a | COM A | G13/G15 | HPWR | Hardware Wire | โ Forward only ~4mV | FW=2 โ, writes accepted (readback confirmed), forward channel inactive |
| 3b | COM A | G13/G15 | HPWR | Bit-Banging | โก Asymmetric | Forward โ (4mV, inactive). Backward โ (-1.2V, 128mA, clean ramp to 255 and back) |
| 4 | COM B | G2/G1 | HPWR | Bit-Banging | โ No movement | Same HPWR issue, FW=2 โ, readback correct, 79mA draw, 4mV output |
Serial Console Excerpts
Test 1a โ 5V, Hardware Wire, correct registers (motor runs, brownout at ~78%)
FW-Version: 2
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
write reg=0x00 val=1 ... err=0
write reg=0x01 val=0 ... err=0
spd= 0
...
write reg=0x01 val=198 ... err=0
spd=198
E BOD: Brownout detector was triggered
Test 1b โ 5V, M5UnitHbridge library (motor does not respond)
begin: OK
FW-Version: 255 โ should be 2 โ Repeated Start missing in library
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
spd= 0 ... spd=255
[2] Vorwaerts: halte 100% fuer 3s
...
=== Fertig ===
โ no brownout, no motor current, motor never moved
Test 4 โ HPWR, Bit-Banging, with register readback (motor does not respond)
FW-Version: 2 (read OK)
[1] Readback-Test: dir=1 spd=128
Readback: dir=1 (soll=1) spd=128 (soll=128) โ registers confirmed correct
[2] Vorwaerts: Rampe 0->255 (100ms/Schritt)
spd=0 ... spd=255
[3] Vorwaerts: halte 100% fuer 3s
Motor dreht nicht
Messungen: 13.5V confirmed, 79mA from supply, 4mV at motor terminals
Test 3b โ HPWR, Bit-Banging, COM A (asymmetric: forward dead, backward works)
FW-Version: 2
[1] Vorwaerts: Rampe 0->255 โ Motor does NOT turn, ~4mV at terminals
[5] Rueckwaerts: Rampe 0->255 โ Motor RUNS, -1.2V at terminals, 128mA from supply
(~9% duty cycle at 13.5V, regardless of speed value)
[7] Rueckwaerts: Rampe 255->0 โ Motor slows cleanly and stops at spd=0
Test 5 โ HPWR, register snapshot (default PWM frequency)
[R] Register-Snapshot (Firmware-Defaults):
0x00 DIR = 0 (0=STOP,1=FWD,2=BWD)
0x01 SPD8 = 0
0x02 SPD16 = 0
0x04 FREQ = 1000 Hz โ default is audible range โ explains motor beeping
HPWR Mode โ Appears Non-Functional in Firmware v2
After extensive testing across all port/I2C combinations, our conclusion is that HPWR mode is functionally broken in firmware v2.
Additional tests (Tests 5 & 6)
- Default PWM frequency: 1000 Hz (register 0x04) โ audible range, explains the motor "beeping"
- Setting frequency to 10000 Hz: no improvement, forward direction still inactive
- Connecting external supply GND to Grove GND: no improvement
HPWR test results across all combinations
| Test | Port | I2C | Forward | Backward | Notes |
|---|---|---|---|---|---|
| 3a | COM A | Hardware Wire | โ 4mV | n/a | Writes confirmed OK via err=0 |
| 3b | COM A | Bit-Banging | โ 4mV | โ -1.2V / 128mA | Asymmetric โ only backward works |
| 4 | COM B | Bit-Banging | โ 4mV | n/a | Readback confirmed dir/spd correct |
Asymmetric behavior detail (Test 3b)
Forward (dir=1):
- ~4mV at motor terminals regardless of speed value
- Brief 120mA spike from supply at spd=0โ10, drops to 80ยตA by spd=40
- Motor does not turn
Backward (dir=2):
- -1.2V at motor terminals โ only โ9% effective duty cycle at 13.5V supply, regardless of speed value (0โ255)
- 128mA from external supply
- Motor runs through full ramp 0โ255โ0 and stops correctly
- Requires a manual push past static friction at low speeds
Evidence pointing to firmware bug
- I2C communication confirmed working (FW=2 readable, register readback correct)
- Speed value has no effect on output voltage in HPWR mode (9% fixed regardless of spd=1 or spd=255)
- PWM frequency change has no effect
- GND connection has no effect
- Issue present on both COM A and COM B
- Issue present with both Hardware I2C and Bit-Banging
- 5V mode works correctly on the same hardware
Our interpretation: The STM32F030 firmware v2 may not properly implement HPWR mode โ the DIP switch position appears to be detected but the RZ7899 drive logic may not be correctly configured for external supply operation. However, we acknowledge we could be missing a required initialization step or configuration. We would greatly appreciate clarification from M5Stack or anyone who has successfully used HPWR mode.
Working Configuration (Minimal Code)
#include <M5Dial.h>
#define HBRIDGE_ADDR 0x20
#define REG_DIR 0x00
#define REG_SPEED8 0x01
bool hb_write(uint8_t reg, uint8_t val) {
Wire.beginTransmission(HBRIDGE_ADDR);
Wire.write(reg);
Wire.write(val);
return Wire.endTransmission() == 0;
}
bool hb_read(uint8_t reg, uint8_t* out) {
Wire.beginTransmission(HBRIDGE_ADDR);
Wire.write(reg);
Wire.endTransmission(false); // Repeated Start
return Wire.requestFrom((uint8_t)HBRIDGE_ADDR, (uint8_t)1) == 1
&& (*out = Wire.read(), true);
}
void setup() {
auto cfg = M5.config();
M5Dial.begin(cfg, false, false);
Wire.begin(13, 15); // COM A, after M5Dial.begin()
Wire.setClock(100000);
// DIP switch: 5V mode โ HPWR forward channel does not work
hb_write(REG_DIR, 1); // FORWARD
hb_write(REG_SPEED8, 128); // 50%
}
โ ๏ธ Safety Note: H-Bridge Runs Independently After ESP32 Reset
The STM32F030 inside the H-Bridge has a lower minimum operating voltage than the ESP32-S3. This means:
Scenario 1 โ Brownout:
The ESP32-S3 crashes (brownout at ~700mA from Grove 5V). The STM32F030 keeps running and holds the last direction + speed. The motor continues turning until power is physically removed.
Scenario 2 โ Reset button:
Pressing the M5Dial reset button resets the ESP32-S3, but the STM32F030 is unaffected. Motor keeps running.
Scenario 3 โ Software crash:
Any ESP32-S3 crash leaves the H-Bridge in its last state.
Safe practice โ always send STOP as the first action in setup():
Wire.begin(13, 15);
Wire.setClock(100000);
Wire.beginTransmission(HBRIDGE_ADDR);
Wire.write(0x00); Wire.write(0); // DIR = STOP
Wire.endTransmission();
Wire.beginTransmission(HBRIDGE_ADDR);
Wire.write(0x01); Wire.write(0); // SPEED = 0
Wire.endTransmission();
// ... rest of setup
Current Measurements (5V Mode, Grove Side)
| State | Motor Voltage | Grove 5V Current |
|---|---|---|
| Idle (M5Dial + H-Bridge logic) | โ | ~300mA |
| Motor running (after manual push) | 3.3V | ~700mA |
| Brownout threshold (USB) | โ | ~500mA |
Note: USB 2.0 limit is 500mA โ sustained motor load exceeds this. Use a powered USB hub or a 5V supply with higher current rating. Do not rely on HPWR mode โ it is non-functional in FW v2.
Summary
| Issue | Root Cause | Fix |
|---|---|---|
| Wire on Port A silent | M5Dial.begin() uses Wire on G11/G12 | Call Wire.begin(13,15) after M5Dial.begin() |
| Wire1 SCL blocked | Touch controller holds G15 LOW | Don't use Wire1 |
| M5UnitHbridge FW=255 | Library uses STOP instead of Repeated Start | Use Wire directly with endTransmission(false) |
| 5V mode brownout | USB 5V insufficient for motor load >~500mA | Use powered USB hub or higher-current 5V supply |
| HPWR mode non-functional | STM32F030 FW v2 bug โ forward dead, backward fixed at ~9% duty cycle regardless of speed | No workaround โ requires firmware fix from M5Stack |
Tested May 2026 on M5Stack Dial v1 (ESP32-S3) + H-Bridge Unit v1.1 (STM32F030 + RZ7899, FW v2) / Arduino IDE 2.3.8 / ESP32 Core 3.3.7 / M5Unified 0.2.15
Appendix: Full Serial Output per Test
Test 1a โ COM A | 5V | Hardware Wire | correct registers
=== Test 1a: Wire direkt, korrekte Register ===
FW-Version: 2
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
write reg=0x00 val=1 ... err=0
write reg=0x01 val=0 ... err=0
spd= 0
write reg=0x01 val=198 ... err=0
spd=198
E BOD: Brownout detector was triggered
Lauf mit Ammeter (Grove-Seite):
write reg=0x01 val=52 ... err=0
spd= 52
E BOD: Brownout detector was triggered
Strom: ~300mA Ruhestrom, ~700mA beim Laufen (nach Anschieben), 3.3V an Motor
Test 1b โ COM A | 5V | M5UnitHbridge Library
=== Test 1b: M5UnitHbridge Library ===
begin: OK
FW-Version: 255 โ should be 2
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
spd= 0 ... spd=255
[2] Vorwaerts: halte 100% fuer 3s
[3] Vorwaerts: Rampe 255->0
[4] STOP ... [8] STOP
=== Fertig ===
โ no brownout, no motor current, motor never moved
Test 2a โ COM A | 5V | Bit-Banging
=== Test 2a: COM A | 5V | Bit-Banging ===
[0] I2C Scan... Gefunden: 0x20 <- H-Bridge
FW-Version: 2
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
spd= 0 ... spd= 62
E BOD: Brownout detector was triggered
โ Motor dreht. Brownout bei spd=62 (~24%). GPIO-Umschalten erhoht Strombedarf.
Test 2b โ COM B | 5V | Bit-Banging
=== Test 2b: COM B | 5V | Bit-Banging ===
SDA=G2 (gelb), SCL=G1 (weiss)
[0] I2C Scan... Gefunden: 0x20 <- H-Bridge
FW-Version: 2 (read OK)
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
spd= 0 ... spd= 63
E BOD: Brownout detector was triggered
โ Motor hat gezuckt, Spannung messbar. Brownout bei spd=63, identisch mit 2a.
Test 3a โ COM A | HPWR | Hardware Wire
=== Test 3a: COM A | HPWR | Wire direkt ===
SDA=G13, SCL=G15
FW-Version: 2
[1] Vorwaerts: Rampe 0->255 (100ms/Schritt)
write reg=0x00 val=1 ... err=0
write reg=0x01 val=0 ... err=0
spd= 0 ... spd=255
[2] Vorwaerts: halte 100% fuer 3s
[3] Vorwaerts: Rampe 255->0
=== Fertig ===
Messungen: 13.5V OK, 0.083mA aus Netzteil, Motor dreht nicht.
Test 3b โ COM A | HPWR | Bit-Banging
=== Test 3b: COM A | HPWR | Bit-Banging ===
SDA=G13, SCL=G15
[0] I2C Scan... Gefunden: 0x20 <- H-Bridge
FW-Version: 2
[1] Vorwaerts: Rampe 0->255 โ Motor dreht NICHT, ~4mV, 120mA-Spike bei spd=0..10
[5] Rueckwaerts: Rampe 0->255 โ Motor dreht โ, -1.2V, 128mA aus Netzteil
[7] Rueckwaerts: Rampe 255->0 โ Motor haelt sauber bei spd=0
=== Fertig ===
Asymmetrie: Vorwaerts tot, Rueckwaerts ~9% eff. Duty Cycle bei 13.5V
Test 4 โ COM B | HPWR | Bit-Banging
=== Test 4: COM B | HPWR | Bit-Banging ===
SDA=G2 (gelb), SCL=G1 (weiss)
[0] I2C Scan... Gefunden: 0x20 <- H-Bridge
FW-Version: 2 (read OK)
[1] Readback-Test: dir=1 spd=128
Readback: dir=1 (soll=1) spd=128 (soll=128) โ Register korrekt geschrieben
[2] Vorwaerts: Rampe 0->255 ... Motor dreht nicht
Messungen: 13.5V OK, 79mA aus Netzteil, 4mV an Motor.
Test 5 โ COM A | HPWR | Bit-Banging | PWM-Frequenz auslesen
=== Test 5: COM A | HPWR | Bit-Banging | FreqRead ===
SDA=G13, SCL=G15
[0] I2C Scan... Gefunden: 0x20 <- H-Bridge
FW-Version: 2
[R] Register-Snapshot (Firmware-Defaults):
0x00 DIR = 0 (0=STOP,1=FWD,2=BWD)
0x01 SPD8 = 0
0x02 SPD16 = 0
0x04 FREQ = 1000 Hz โ default PWM frequency
Motor dreht nicht.
Test 6 โ COM A | HPWR | Bit-Banging | PWM-Frequenz auf 10kHz gesetzt
=== Test 6: COM A | HPWR | Bit-Banging | FreqSet 10kHz ===
SDA=G13, SCL=G15
[F] Setze PWM-Frequenz auf 10000 Hz...
FREQ nach Set = 10000 Hz โ write accepted
[R] Register-Snapshot:
0x04 FREQ = 10000 Hz
[1] Vorwaerts: Rampe 0->255 โ Motor dreht nicht. Frequenz hat keinen Einfluss.