Skip to content

Add ESP32-H2 and the ESP32-H2-DevKitM-1 board - #5750

Merged
deadprogram merged 3 commits into
tinygo-org:devfrom
zombieleet:esp32h2
Sep 26, 2026
Merged

deadprogram merged 3 commits into
tinygo-org:devfrom
zombieleet:esp32h2

Conversation

@zombieleet

@zombieleet zombieleet commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

This PR adds the ESP32-H2 chip and the Espressif ESP32-H2-DevKitM-1 board.

The ESP32-H2 is close to the ESP32-C6

The ESP32-H2 is not very different from the ESP32-C6. Both chips use the same RISC-V core, the same PLIC interrupt controller, the same PCR clock and reset block, the same interrupt matrix, the same USB Serial/JTAG block and the same memory map. So this port uses the existing ESP32-C6 code where it can:

  • The ESP32-C6 startup assembly (src/device/esp/esp32c6.S). The H2 ROM has the same cache functions, at different addresses.
  • The shared PLIC and timer interrupt code (runtime_esp32xx_plic.go, added in runtime/esp32: prepare the shared runtime for more RISC-V chips #5749).
  • The ESP32-C6 interrupt dispatcher (interrupt_esp32c6.go) and USB Serial/JTAG driver (machine_esp32c6_usb.go), with a build tag.

machine_esp32h2.go started as a copy of machine_esp32c6.go. It changes only the parts in the table below.

Item ESP32-C6 ESP32-H2 (this PR)
CPU clock 160 MHz from PLL 32 MHz from XTAL (96 MHz PLL in a later PR)
Crystal 40 MHz 32 MHz
HP SRAM 512 KB 320 KB
ROM cache function addresses C6 values H2 values (esp32h2.rom.ld)
TIMG0 tick PLL_F80M / 2, 25 ns XTAL / 4, 125 ns
TIMG clock select values 0 XTAL, 1 PLL_F80M, 2 RC_FAST 0 XTAL, 1 RC_FAST, 2 PLL_F48M
UART clock FOSC 80 MHz XTAL 32 MHz (PCR SCLK_SEL 3)
UART CLK_CONF fields SCLK_SEL, SCLK_DIV_* in the UART block In PCR. The UART block has TX_SCLK_EN and SCLK_EN only
UART CPU interrupt 7 13 (see notes)
GPIO GPIO0 to GPIO30 GPIO0 to GPIO27
Image chip ID 0x000d 0x0010
Flash clock code 80 MHz = 0x0 48 MHz = 0xF

Commits

  1. builder: add the esp32h2 firmware image format. Chip ID 0x0010 and flash code 0xF (48 MHz, the H2 maximum), from esptool targets/esp32h2.py.
  2. runtime,machine,targets: add ESP32-H2 support. The esp32h2 chip target, the linker script, the H2 clock setup, the machine package and the build tags on the shared files.
  3. machine,targets: add the ESP32-H2-DevKitM-1 board. The board uses the port printed UART (a CP2102N on UART0), so it uses esp32flash and serial: uart. The onboard LED is a WS2812 on GPIO8, so the board file defines WS2812 and BUTTON (GPIO9) but not LED, the same as other boards with only an addressable LED. The smoke test builds examples/machinetest and examples/serial.

Tested on an ESP32-H2-DevKitM-1 (N4)

Test Result
Flash with esp32flash on the UART port Pass
examples/serial Pass, lines 1.000 s apart
examples/echo (UART RX interrupt) Pass
GPIO output (external LED on GPIO10) Pass
GPIO input with pull-up and pull-down Pass
Pin-change interrupt (BOOT button) Pass
USB Serial/JTAG port Builds, not tested

make smoketest-esp passes. The ESP32-C6 binaries do not change with this PR.

@zombieleet
zombieleet marked this pull request as ready for review September 25, 2026 18:35
@zombieleet

Copy link
Copy Markdown
Contributor Author
IMG_4473.mov
IMG_4472.mov

@deadprogram

Copy link
Copy Markdown
Member

@zombieleet please rebase this PR against dev now that #5749 has been merged. You can try a stacked PR next time, perhaps? Thanks.

@zombieleet

Copy link
Copy Markdown
Contributor Author

@zombieleet please rebase this PR against dev now that #5749 has been merged. You can try a stacked PR next time, perhaps? Thanks.

Yah, I actually wanted to do that, but stacked pr only works on branches that are in the tinygo repo itself

Add the ESP32-H2 chip ID (0x0010) and the flash speed/size byte to the ESP
image header. For the ESP32-H2, the flash clock code 0xF is 48 MHz, which is
the maximum. The flasher changes the flash size to the real value.

See esptool targets/esp32h2.py (IMAGE_CHIP_ID, FLASH_FREQUENCY).
Add the esp32h2 chip target. The ESP32-H2 uses the same RISC-V core, PLIC
and USB Serial/JTAG block as the ESP32-C6, so it uses the ESP32-C6 startup
assembly and the shared PLIC, interrupt and USB files with a build tag.

Differences from the ESP32-C6:
- 320 KB SRAM and different ROM function addresses in the linker script.
- The CPU runs at 32 MHz from the XTAL. TIMG0 uses the XTAL divided by 4,
  which gives 125 ns for each tick.
- The UART uses the 32 MHz XTAL (PCR SCLK_SEL 3). The default UART0 pins
  are GPIO24 (TX) and GPIO23 (RX).
- The UART interrupt uses CPU interrupt 13. Interrupts 3, 4 and 7 are
  bound to the CLINT and cannot be used.

The linker script also pads the end of .text so that .rodata is at a
16-byte aligned flash offset, and it checks with ASSERT that .text and
.rodata have the same address as their flash offset.

References: ESP-IDF components/soc/esp32h2, components/esp_rom/esp32h2,
components/esp_hal_uart/esp32h2, components/esp_hal_timg/esp32h2 and
components/esp_hw_support/port/esp32h2/esp_cpu_intr.c.
Add the Espressif ESP32-H2-DevKitM-1 board.

The board target uses the port printed UART, which is a CP2102N USB to
UART bridge on UART0. So it uses esp32flash with the classic reset, and
machine.Serial uses the UART.

The onboard LED is a WS2812 on GPIO8, not a plain LED. The board file
defines WS2812 and BUTTON (the BOOT button on GPIO9), but not LED, the
same as other boards that have only an addressable LED. The smoke test
builds examples/machinetest and examples/serial.

See the user guide:
https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32h2/esp32-h2-devkitm-1/user_guide.html
@zombieleet

Copy link
Copy Markdown
Contributor Author

@deadprogram rebased. Thanks

@deadprogram

Copy link
Copy Markdown
Member

Thanks for the rebase and for this port @zombieleet. The following is edited from an automated review:

  1. targets/esp32h2.ld adds the .rodata alignment line and the two ASSERT checks, but targets/esp32c6.ld does not have them. Does the C6 have the same latent issue? Perhaps add them to the C6 script too, here or in a follow-up.

  2. esp32c6.S, interrupt_esp32c6.go and machine_esp32c6_usb.go are now shared with the H2 but still carry the C6 name. Should you rename them in a later PR so it is clear they cover both chips? Seems like a good idea maybe?

@deadprogram

Copy link
Copy Markdown
Member

@zombieleet please see #5755 for some C6 fixes discovered while testing this PR.

@deadprogram deadprogram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again @zombieleet for working on this. I addressed one of my comments in a new PR, and the other one can wait for a future moment. Now merging.

@deadprogram
deadprogram merged commit 481fac5 into tinygo-org:dev Sep 26, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants