Skip to content

Add ESP32-C6 support#5248

Open
gandarez wants to merge 55 commits intotinygo-org:devfrom
gandarez:feat/esp32-c6
Open

Add ESP32-C6 support#5248
gandarez wants to merge 55 commits intotinygo-org:devfrom
gandarez:feat/esp32-c6

Conversation

@gandarez
Copy link
Copy Markdown
Contributor

@gandarez gandarez commented Mar 13, 2026

Summary

  • Adds full ESP32-C6 (RISC-V) target support including runtime, machine peripherals, interrupt handling, linker script, and flash tooling
  • The ESP32-C6-DevKitC boots, runs user programs, and outputs serial via USB-Serial-JTAG
  • Fixes chip-specific SPI flash frequency encoding in the firmware image header (0x00 for 80MHz on C6 vs 0x0F on C3)

What's included

  • Runtime: watchdog disabling (TIMG0/1, LP_WDT, SWD), 160MHz PLL clock setup, USB-Serial-JTAG serial I/O, SYSTIMER-based timekeeping
  • Interrupts: PLIC-style vectored interrupt controller with 31 priority levels
  • Machine peripherals: GPIO (30 pins), ADC, I2C, SPI, PWM (LEDC)
  • Linker script: unified 512KB SRAM (IRAM/DRAM at 0x40800000), flash-mapped IROM/DROM with MMU page alignment
  • Flash tooling: esp32jtag flash method, correct chip ID (0x000D), ESP32-C6 frequency encoding in image header
  • Board definition: ESP32-C6-DevKitC pin mappings, WS2812/NeoPixel LED on GPIO8, UART/I2C/SPI default pins

Test plan

  • Test on real device
  • Firmware boots without checksum errors
  • Serial output works via USB-Serial-JTAG (println in a loop)
  • time.Sleep works correctly
  • GPIO output verified with external LED
  • I2C/SPI/ADC/PWM peripheral testing
  • WS2812 onboard LED via tinygo.org/x/drivers/ws2812

Closes: #4029

jespino and others added 30 commits January 12, 2026 13:38
* machine/attiny85: add PWM support for Timer0 and Timer1

Add complete PWM implementation for ATtiny85, supporting both Timer0
and Timer1 with their respective output channels:
- Timer0: 8-bit timer for pins PB0 (OC0A) and PB1 (OC0B)
- Timer1: 8-bit high-speed timer for pins PB1 (OC1A) and PB4 (OC1B)

Timer1 provides more flexible period control with configurable top value
(OCR1C) and extended prescaler options (1-16384), making it well-suited
for LED PWM control and other applications requiring variable frequencies.

Implements full PWM interface including Configure, SetPeriod, Channel,
Set, SetInverting, Top, Counter, and Period methods.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* machine/digispark: document PWM support on pins

Add documentation to the Digispark board file indicating which pins
support PWM output:
- P0 (PB0): Timer0 channel A
- P1 (PB1): Timer0 channel B or Timer1 channel A
- P4 (PB4): Timer1 channel B

Includes package comment explaining Timer0 vs Timer1 capabilities,
with Timer1 recommended for more flexible frequency control.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* machine/attiny85: optimize PWM prescaler lookups

Replace verbose switch statements with more efficient implementations:

- SetPeriod: Use bit shift (top >>= prescaler-1) instead of 15-case
  switch for dividing uint64 by power-of-2 prescaler values

- Period: Replace switch statements with compact uint16 lookup tables
  for both Timer0 and Timer1, casting to uint64 only when needed

This addresses review feedback about inefficient switch-based lookups.
On AVR, this approach is significantly smaller:
- Bit shifts for uint64 division: ~34 bytes vs ~140 bytes
- uint16 tables: 22 bytes code + 32/16 bytes data vs ~140 bytes
- Total savings: ~190 bytes (68% reduction)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* examples/pwm: add digispark support and smoketest

Add digispark.go configuration for PWM example using Timer1 with pins P1 (LED) and P4. Also add digispark PWM example to GNUmakefile smoketests.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…ons for all peripheral reset/unreset operations

Signed-off-by: deadprogram <ron@hybridgroup.com>
…em resources/power usage

Signed-off-by: deadprogram <ron@hybridgroup.com>
This simplifies the process of constructing and encoding layout bitmaps.
Instead of creating big integers and merging them, we can create a pre-sized bitmap and set positions within it.

This also changes the encoding logic to allow larger layouts to be encoded inline.
We would previously not encode a layout inline unless the size was less than the width of the data field.
This is overly conservative.
A layout can be encoded inline as long as:
1. The size fits within the size field.
2. All set bits in the bitmap fit into the data field.
Signed-off-by: deadprogram <ron@hybridgroup.com>
* testdata: more corpus entries

* testdata: remove skipwasi for dchest/siphash build issues
* machine/attiny85: add USI-based SPI support

Implement SPI communication for ATTiny85 using the USI (Universal Serial
Interface) hardware in three-wire mode. The ATTiny85 lacks dedicated SPI
hardware but can emulate SPI using the USI module with software clock
strobing.

Implementation details:
- Configure USI in three-wire mode for SPI operation
- Use clock strobing technique to shift data in/out
- Pin mapping: PB2 (SCK), PB1 (MOSI/DO), PB0 (MISO/DI)
- Support both Transfer() and Tx() methods

The implementation uses the USI control register (USICR) to toggle the
clock pin, which triggers automatic bit shifting in hardware. This is
more efficient than pure software bit-banging.

Current limitations:
- Frequency configuration not yet implemented (runs at max software speed)
- Only SPI Mode 0 (CPOL=0, CPHA=0) supported
- Only MSB-first bit order supported

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: add SPI frequency configuration support

Add software-based frequency control for USI SPI. The ATtiny85 USI lacks
hardware prescalers, so frequency is controlled via delay loops between
clock toggles.

- Calculate delay cycles based on requested frequency and CPU clock
- Fast path (no delay) when frequency is 0 or max speed requested
- Delay loop uses nop instructions for timing control

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: add SPI mode configuration support

Add support for all 4 SPI modes (Mode 0-3) using USI hardware:
- Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge
- Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge
- Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge
- Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge

CPOL is controlled by setting the clock pin idle state.
CPHA is controlled via the USICS0 bit in USICR.

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: add LSB-first bit order support

Add software-based LSB-first support for USI SPI. The USI hardware only
supports MSB-first, so bit reversal is done in software before sending
and after receiving.

Uses an efficient parallel bit swap algorithm (3 operations) to reverse
the byte.

Co-authored-by: Ona <no-reply@ona.com>

* GNUmakefile: add mcp3008 SPI example to digispark smoketest

Test the USI-based SPI implementation for ATtiny85/digispark.

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: minimize SPI RAM footprint

Reduce SPI struct from ~14 bytes to 1 byte to fit in ATtiny85's limited
512 bytes of RAM.

Changes:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin management (user must handle CS)
- Remove frequency control (runs at max speed)
- Remove LSBFirst support

The SPI struct now only stores the USICR configuration byte.

Co-authored-by: Ona <no-reply@ona.com>

* Revert "machine/attiny85: minimize SPI RAM footprint"

This reverts commit 387ccad.

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: reduce SPI RAM usage by 10 bytes

Remove unnecessary fields from SPI struct while keeping all functionality:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin (user must manage it, standard practice)

Kept functional fields:
- delayCycles for frequency control
- usicrValue for SPI mode support
- lsbFirst for bit order support

SPI struct reduced from 14 bytes to 4 bytes.

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
* feat: Add Vicharak Shrike Lite

* Add shrike-lite to smoketest
* Add per-byte timeout budget for rp2 I2C

* run goimports
Signed-off-by: deadprogram <ron@hybridgroup.com>
Bump the GitHub Actions Nix install as well; nixpkgs 25.11 requires a
newer nix command.
Signed-off-by: deadprogram <ron@hybridgroup.com>
* esp32s3 spi

* stabilization freq cpu

* cheange clacl freq for spi

* fix linters

* esp32s3-spi: change default pins for esp32s3 xiao

* set default configuration

* esp32s3-spi: extends smoketests for esp32s3
This changes the order for initialization of the random number
seed generation on wasm platforms until after the heap has been
initialized. Should fix tinygo-org#5198

Signed-off-by: deadprogram <ron@hybridgroup.com>
Signed-off-by: deadprogram <ron@hybridgroup.com>
Signed-off-by: deadprogram <ron@hybridgroup.com>
The compiler may generate calls to fminimum/fmaximum on some platforms.
Neither of the libm implementations we statically link against have these functions yet.
Implement them ourselves.
* esp32s3-i2c implement interface

* esp32s3-i2c: disable m5stamp_c3

* added simple tests without Listen

* replace smoke tests

* esp32s3-i2c: fix allocation in tx
… same as expected by the go coverage tool

Signed-off-by: deadprogram <ron@hybridgroup.com>
* esp3s3 pwm

* esp32s3-pwm: added spi alias for esp32s3 for tests

* esp32s3-pwm: fix linters

* esp32s3-pwm: fix rename

* esp32s3-pwm: replace if to switch

* esp32s3-pwm: chip specific registry

* esp32s3-pwm: fix linters
… stm32-svd files (tinygo-org#5212)

* tools/gen-device-svd: orderPeripherals: prevent skipping base peripherals derived by name

Recent SVDs from stm32-rs, like the one for stm32u595, define derivedFrom
attributes that do not refer to a peripheral group name, but to a
peripheral name. For instance, the peripheral I2C5 may be derived from
"I2C1", not from "I2C". The previous algorithm records, in case the
group name is non-empty, only the group name in the
knownBasePeripherals map, not the name of the peripheral itself.
So in case of the base peripheral I2C1 with group name I2C: although
the peripheral gets added to the sorted list, it would be added to
knownBasePeripherals with the group name "I2C" as key, not with "I2C1".
A following peripheral, I2C5, derived from I2C1, with an empty group
name, would be recorded as known with key "I2C5", but omitted from the
sorted list, because "I2C1" is not recognized as known.
The following peripheral SEC_I2C5, derived from I2C5, with empty group
name, would be added to both the knownPeripherals map and the sorted list.
So if, later, the sorted list is examined, it would find SEC_I2C5
earlier than its base peripheral I2C5, which would be missing from
"peripheralDict", resulting in a nil pointer access.
This patch makes sure that, to stay with the example, that
"I2C1" is recorded as known too (not only the group name "I2C"),
so that "I2C5" won't be skipped anymore, preventing the program from
crashing.

* tools/gen-device-svd: orderPeripherals: ensure ordered content of missingBasePeripherals

After the first run, missingBasePeripherals may contain peripherals
with dependencies that are not guaranteed to be in proper order.

This change implements additional loop runs that try to reduce the
size of the missingBasePeripherals as far as possible.

[With recent SVDs from stm32-rs this change will not produce different
results, though (since these source files contain already properly
ordered peripherals).]

* tools/gen-device-svd: Register: move dim array decoding to utility type dimArray

This allows encoding of dim increment and array indices to be re-used
by other elements supporting dim arrays.

This change just restructures parts of register specific code,
it does not change the output of the program.

* tools/gen-device-svd: parseBitfields: support field dim arrays

Patched SVD files from stm32-rs recently contain many fields with
dim array parameters and names containing %s (like "CC%sIF").
This change adjusts parseBitfields so that these field elements
get resolved.

* tools/gen-device-svd: SVDField: allow multiple enumeratedValues

In recent patched SVD files from stm32-rs there may be two
enumeratedValues elements per SVDField, not just one.
The SVD specification allows up to two entries (they may be used
to define different enums for read and write access).

This change extends SVDField and parseBitfields so that
two enumeratedValues are processed like a single one.

* tools/gen-device-svd: orderPeripherals: sort peripherals of same group with larger number of registers/bitfields first

In group "TIM" there may be general purpose timers like TIM16 and
advanced timers like TIM1. The advanced peripheral may contain a larger
number of registers than the general purpose ones.
TIM1 may contain CCR1..CCR4, SMCR and OR1, while TIM16 only knows
about CCR1.
Unfortunately in some SVDs, like the one for stm32g031, TIM16 is defined
before TIM1. Since register and bitfield constants are generated taking
only the first peripheral of a group into account, the resulting .go
file may lack definitions for e.g. CCR2..CCR4, SMCR, and OR1.

This change adjusts orderPeripherals so that, to stay with the example,
a peripheral like TIM1 will be moved in front of TIM16, resulting in an
output file containing the larger set of definitions.

* tools/gen-device-svd: SVDEnumeration: support isDefault

Recent SVD files created by stm32-rs use "isDefault" in enumeratedValue
elements for purposes like the Div1 enum for clock prescaler registers
without specifying a specific value.
Previously, these enumeratedValues would be skipped because of the
enumEl.Value == 0 condition, and the corresponding const definitions
like "RCC_CFGR2_PPRE2_Div1 = 0x0" would be missing from the
resulting .go files, so existing code relying on these constants would
not compile anymore.
This change adds a utility type enumDefaultResolver that helps
finding an actual value that is unused by the enumeratedValues that
are defined for the field. More examples for values marked
as "isDefault", along with their resolved values:

  DAC_CR_WAVE1_Triangle => 2
  IWDG_PR_PR_DivideBy256 => 6
  DAC_CR_MAMP2_Amp4095 => 0xb

* tools/gen-device-svd: support derivedFrom attribute at field level

This ensures that some more constants are included in the .go files
that would otherwise be skipped (like e.g. ADC_SMPR2_SMP1_Cycles*
of some STM32 devices), which prevented compilation of some programs.

To avoid extending a lot of func argument lists, and since there
is no context.Context in use yet, this change introduces a
global derivationContext.

* tools/gen-device-svd: tweak: stm32: ensure USART_ISR_TXE/TXFNF are present

* tools/gen-device-svd: stm32: ensure CCMR*_Output alternate registers are sorted first

* tools/gen-device-svd: stm32: add IWDG peripheral alias if SVD defines IWDG1
….s to ..722.s

In stm32-rs, stm32f7x2.svd got replaced by stm32f722.svd and stm32f732.svd.
This change adjusts the target definition where stm32f7x2 is used,
knieriem and others added 20 commits February 26, 2026 09:14
Recent changes in stm32-svd result in a change from previous
16-bit register access to 32-bit access.
Both access types are allowed, according to the register manuals.
Signed-off-by: deadprogram <ron@hybridgroup.com>
This adds usage of the new espflash package to perform flashing on
ESP32, ESP32S3, ESP32C3, & ESP8266 boards. This means that you no longer
have to install esptool.py in order to flash ESP32 based boards.

Signed-off-by: deadprogram <ron@hybridgroup.com>
…lt-in esp32flash

Signed-off-by: deadprogram <ron@hybridgroup.com>
Signed-off-by: deadprogram <ron@hybridgroup.com>
…om number generator.

Signed-off-by: deadprogram <ron@hybridgroup.com>
* save

* esp32s3: save

* adc

* worker on esp32s3

* worker after flash arduino

* save

* fix

* simple adc

* added adc

* esp32s3-adc: rm debug

* esp32s3-adc: clear

* last refactor

* linters

* esp32s3-adc: recover example

* esp32s3-adc: reuse fuse for esp32c3

* esp32s3-adc: refactor bugs

* esp32s3-adc: fix adc2 for esp32c3

* esp32s3-adc: group to adc files

* esp32s3-adc: revert changing board

* esp32s3-adc: recover example adc

* esp32s3-adc: fix edge values adc & added smoketests

* esp32s3-adc: rename methods

* esp32s3-adc: extends adc tests

* esp32s3-adc: drop debug

* esp32s3-adc: added ADCX const

* esp32s3-adc: change adc tests

* esp32s3-adc: added comment for esp32c3

* esp32s3-adc: drop debug empty loops

* esp32s3-adc: drop duplicate gpio

* esp32s3-adc: change return values to 0..65520
* begin adding ring512 implementation

* refactor to make operation driven fuzz test

* refactor USBCDC.Read to use ring512

* try txhandler separate to flush

* working USBCDC with large packets

* fix binary size

* remove comment

* documentation improvements
This refactoring reduces code duplication from the esp32c3/esp32s3 ADC
implementation, by reusing the register/efuse calibration code since the
same basic procedures are used by both processors.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This switches the rp2040 and rp2350 to use the tasks scheduler
by default instead of using the cores scheduler. Too many race
conditions at present, we need to look into exactly why. In
the meantime, going back to the tasks as default will address
a lot of these intermittent problems.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This refactors and corrects the SPI implentation for the
ESP32C3 and ESP32S3 processors. There was a lot of duplicated
code, as well as some errors such as incorrectly calculating
speed on the esp32c3 implementation.

This will also be helpful when adding additional processors
that use very similar peripheral registers.

Signed-off-by: deadprogram <ron@hybridgroup.com>
Signed-off-by: deadprogram <ron@hybridgroup.com>
This adds a new flash method 'esp32jtag' to explicitly define when this
reset method is needed. When flashing esp32c3/esp32s3 boards on Windows
this method of board reset is required, otherwise the reset does not
take place and the board cannot be flashed.

Signed-off-by: deadprogram <ron@hybridgroup.com>
MCAUSE was never being cleared after handling an interrupt.
On RISC-V, mret does NOT zero MCAUSE — it retains the last
trap cause. Every other TinyGo RISC-V target (FE310, K210,
QEMU) explicitly does riscv.MCAUSE.Set(0) after handling.
The ESP32-C3 was missing this.

Signed-off-by: deadprogram <ron@hybridgroup.com>
Signed-off-by: deadprogram <ron@hybridgroup.com>
… enable serial

The super watchdog write-protect key was copied from ESP32-C3 (0x8F1D312A)
but ESP32-C6 uses 0x50D83AA1, so the SWD was never actually disabled and
would reset the chip after a few seconds.

The clock configuration was also setting HS_DIV_NUM=0, running the CPU at
480MHz (SPLL/1) instead of the intended 160MHz (SPLL/3). This caused USB
Serial/JTAG PHY timing failures, preventing any serial output. Fixed by
setting HS_DIV_NUM=2 and explicitly configuring AHB/APB bus dividers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…atch

The sarEnable() method references regI2C and analog config constants
defined in machine_esp32xx_adc.go, which excludes m5stamp_c3. Moving it
to a new file with the matching build constraint prevents undefined
symbol errors for that target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gandarez and others added 3 commits March 13, 2026 17:41
ESP32-C6 uses rv32imac ISA (with atomics), unlike ESP32-C3 which uses
rv32imc. The features string was copied from ESP32-C3 and incorrectly
disabled the atomic-related LLVM features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -0,0 +1,79 @@
//go:build esp32c6

// This file contains the default pin mappings for the ESP32-C6 generic target.
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.

There is not really such a thing as "generic" target. From the comments below, shouldn't this be board_esp32c6_devkitc?

})
}

func (uart *UART) serveInterrupt(num int) {
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.

This function is named handleInterrupt most places elsewhere in TinyGo.

@@ -0,0 +1,19 @@
//go:build esp32c3 || esp32s3
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.

Perhaps naming like machine_esp32x3_periph.go?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe not merge, but copy?

@deadprogram
Copy link
Copy Markdown
Member

deadprogram commented Mar 15, 2026

Just as an overall comment, it would be a lot easier to review/test this as a series of PRs which each added another set of capabilities. See the recent work on esp32s3 as a example of this.

You can perhaps take the commits from this PR (once all tested/working on real hardware) and then have your coding tools take that set of commits and turn them into a series of atomic commits for review.

For example:

  • commit that adds the basic processor/runtime
  • commit that adds gpio
  • commit that adds i2c
  • commit that adds spi

@deadprogram
Copy link
Copy Markdown
Member

@gandarez this is PR I am working on for STM32u585 with the commit structuring I mentioned above:

#5251

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.