This is the engineering reference for the model: what it implements, register by register, for people writing firmware against it. For using the board in the editor, start with The ESP32-C6-DevKitC-1 and the board page.
ESP32-C6 board contract (Phase 9)
The ESP32-C6-DevKitC-1 is the second RISC-V board here. One kernel component (esp32c6 in crates/parts) wraps an ESP32-C6 SoC model (crates/esp32c6) on the same hart as the C3 (crates/rv32-core). The core is shared; the chip around it is not. This document is the contract, and it wins over any comment in the code.
The catalog type is esp32c6. Where a section says "as docs/esp32c3.md", that document is the contract for that part.
Read section 7 first if you are taking a build from here to real silicon. This model is smaller than the C6, and more of it than usual is marked "believed, not verified": section 7 says which addresses those are and what is absent altogether. Nothing here is guessed at quietly. Section 3.6, the SAR ADC's register offsets, is the one block whose layout is on that list.
1. Board pins (catalog order)
Top view, the two USB-C sockets at the bottom, the ESP32-C6-WROOM-1 module and its PCB antenna at the top. The board is 52.5 x 25.4 mm and its two 15-pin headers are 22.86 mm (exactly 0.9 inch) apart, so it straddles a breadboard's center channel.
Left header, top to bottom (15 holes): 3V3, RST, 4, 5, 6, 7, 0, 1, 8, 10, 11, 2, 3, GND, 5V.
Right header, top to bottom (15 holes): GNDb, TX, RX, 9, 12, 13, 14, 21, 20, 19, 18, 15, 23, 22, GNDc.
30 pins. TX and RX are GPIO 16 and 17: the silkscreen names them for their function because they are the console UART. Duplicated names carry a letter because catalog pin names are unique.
The chip has GPIO 0 to 30. GPIO 24 upwards are the SPI flash on this package and are on neither header.
Two pins on the board are not on a header at all:
- GPIO 8 has an addressable RGB LED soldered to it. A
pokedoes not reach it and the board part does not decode it: a WS2812 is a protocol, not a level. Wire thews2812part to GPIO 8 and it lights with the colors the sketch is sending, which is what therainbowexample does. - GPIO 9 is the BOOT button, active low. It is on the right header as well, so it can be wired to; the
pokegrounds it on the board.
Electrical: as docs/esp32c3.md section 1. Every GPIO drives push-pull at 3.3 V through R_STRONG, sits high-Z, or pulls up or down through 45 k. An input reads high at 1.65 V; a floating or contended net reads 0. 3V3 drives 3.3 V at R_SUPPLY, 5V drives 5 V, the three grounds drive 0 V. RST carries a 10 k pull-up to 3.3 V, and pulling it below 1.65 V holds the core in reset.
The exact silkscreen ordering of the two headers is this model's reading of the DevKitC-1 pinout. The GPIO numbers are the chip's and are what firmware depends on; their position on the header is what a diagram depends on. If the real board's rows are ordered differently, a circuit drawn here would need rewiring on a desk, and nothing else would change.
2. Memory map
| HP SRAM | 512 KB at 0x4080_0000 to 0x4087_FFFF |
| stack top | 0x4088_0000 |
| UART0 | 0x6000_0000, section 3.3 |
| LEDC | 0x6000_7000, section 3.7 |
| SYSTIMER | 0x6000_A000, section 3.4 |
| APB_SARADC | 0x6000_E000, section 3.6 (offsets inside believed) |
| IO_MUX | 0x6009_0000, section 3.2 |
| GPIO | 0x6009_1000, section 3.1 |
Every base address in this table is the ESP32-C6 Technical Reference Manual v1.2, table 5.3-2. An earlier version of this model put APB_SARADC at 0x6000_F000, which that table gives to the USB Serial/JTAG controller.
One window. This is the first thing that differs from the C3, where the same 400 KB RAM appears twice (at 0x4037_C000 for the instruction bus and 0x3FC7_C000 for the data bus), and every access has to try both. Here both buses reach the HP SRAM at the same addresses, so a load address is a run address, .data needs no copy at reset, and the bus is one range check rather than two.
Every PT_LOAD segment must land inside that range; anything else is refused with the offending address in the message, and the chip is left with no program.
3. Peripheral registers
Only the subset below is implemented. Anything else inside a mapped 4 KB block reads 0 and counts as a stray access; anything outside every block is an access fault.
3.1 GPIO (0x6009_1000)
| offset | register |
|---|---|
0x04 |
GPIO_OUT_REG |
0x08 |
GPIO_OUT_W1TS_REG |
0x0C |
GPIO_OUT_W1TC_REG |
0x20 |
GPIO_ENABLE_REG |
0x24 |
GPIO_ENABLE_W1TS_REG |
0x28 |
GPIO_ENABLE_W1TC_REG |
0x3C |
GPIO_IN_REG |
0x44 |
GPIO_STATUS_REG |
0x4C |
GPIO_STATUS_W1TC_REG |
0x74 + 4n |
GPIO_PINn_REG |
0x554 + 4n |
GPIO_FUNCn_OUT_SEL_CFG_REG |
Bits 0 to 30 exist; bit 31 is masked away on a write. GPIO_PINn_REG bit 2 PAD_DRIVER makes the pad open drain, and bits 7..9 INT_TYPE choose what raises the pin's interrupt: 0 off, 1 rising, 2 falling, 3 any edge, 4 low level, 5 high level. GPIO_FUNCn_OUT_SEL_CFG_REG at 128 makes the pad follow GPIO_OUT; 45 to 50 hand it to LEDC low-speed channels 0 to 5 (section 3.7, and those six indices are unverified); any other value selects a peripheral that is not modeled, and the model treats it as 128 rather than invent a level. A pad an LEDC channel is driving still needs its output driver on in GPIO_ENABLE_REG.
3.2 IO_MUX (0x6009_0000)
IO_MUX_GPIOn_REG at 0x04 + 4n. Bit 7 FUN_WPD, bit 8 FUN_WPU, bit 9 FUN_IE, bits 12..14 MCU_SEL of which 1 is plain GPIO. A pad only reaches GPIO_IN_REG with its input buffer enabled, which is what the hardware does. Every pin powers up at FUN_IE | (1 << 12), so a program that only ever writes GPIO_ENABLE and GPIO_OUT behaves the same on every pin.
3.3 UART0 (0x6000_0000)
The C3's offsets, as docs/esp32c3.md section 3.3 gives them: UART_FIFO_REG 0x00, UART_INT_RAW_REG 0x04, UART_INT_ST_REG 0x08, UART_INT_ENA_REG 0x0C, UART_INT_CLR_REG 0x10, UART_CLKDIV_REG 0x14, UART_STATUS_REG 0x1C, UART_CONF0_REG 0x20, UART_CONF1_REG 0x24 (the ESP32-C6 Technical Reference Manual v1.2, section 27 register summary). Two 128-byte FIFOs, and bytes leave the transmit FIFO at the baud rate rather than instantly.
Not the C3's bits, though. Checked against the same manual:
UART_CONF0_REG(the manual'sUART_CONF0_SYNC_REG, register 27.9): bit 22RXFIFO_RSTand bit 23TXFIFO_RSTempty the FIFOs. An earlier version of this model used the C3's bits 17 and 18, which on the C6 are other fields. On silicon a write to a_SYNCregister takes effect onceUART_REG_UPDATE(0x98) is set; the model applies it at once.UART_CONF1_REG(register 27.10): bits 0..7RXFIFO_FULL_THRHDand bits 8..15TXFIFO_EMPTY_THRHD, both resetting to 96. An earlier version of this model had nine-bit fields at 0 and 9.UART_STATUS_REG(register 27.21): bits 0..7RXFIFO_CNTand bits 16..23TXFIFO_CNT. The runtime reads them with eight-bit masks, as the manual gives them. It once read ten, which returned the same numbers because the two bits above each field are reserved and read 0; the header now matches the manual.
The divisor divides the 40 MHz crystal, which is what a C6 UART comes out of reset using; UART_CLKDIV resets to 347, which is 115 274 baud.
3.4 SYSTIMER (0x6000_A000)
Unit 0 and comparator 0. SYSTIMER_UNIT0_OP_REG 0x04, SYSTIMER_UNIT0_VALUE_HI_REG 0x40, ..._LO_REG 0x44, SYSTIMER_TARGET0_HI_REG 0x1C, ..._LO_REG 0x20, SYSTIMER_TARGET0_CONF_REG 0x34, SYSTIMER_COMP0_LOAD_REG 0x50, SYSTIMER_INT_ENA_REG 0x64, ..._RAW_REG 0x68, ..._CLR_REG 0x6C, ..._ST_REG 0x70. Verified against the ESP32-C6 Technical Reference Manual v1.2, section 13 register summary and registers 13.14 to 13.17. An earlier version of this model put comparator 0 at 0x50 to 0x5C, which on the C6 are the load strobes; the runtime's images were rebuilt when that was corrected.
The counter is a 52-bit 16 MHz tick count derived from simulated time rather than counted, so it never drifts from the rest of the circuit. Reading it is latch-then-read: set UPDATE, wait for VALUE_VALID, read HI then LO. One-shot and period mode both work.
3.5 Interrupts
As docs/esp32c3.md section 3.5. The C6's interrupt matrix is not modeled: GPIO and UART0 share the machine external interrupt, mcause 11, and the handler reads the two status registers to find out which fired; the SYSTIMER comparator is the machine timer interrupt, mcause 7. mstatus.MIE, mie.MTIE and mie.MEIE behave; mtvec runs in direct mode.
3.6 APB_SARADC (0x6000_E000)
ADC1 in one-shot mode, register for register as docs/esp32c3.md section 3.5 describes it: the path ESP-IDF's adc_oneshot driver and the Arduino core's analogRead take, and nothing else. What differs here is the pinout (seven channels, 0 to 6, which on this package are GPIO 0 to GPIO 6) and the block's base address.
The base address is the manual's (table 5.3-2); an earlier version of this model had 0x6000_F000, which is the USB Serial/JTAG controller. Every register offset in this section is unverified: they are the C3's, which is the family's layout, not a page of the C6 technical reference manual quoted back. A program that has to run on a real DevKitC-1 should check them first.
| offset | name | bits |
|---|---|---|
0x00 |
APB_SARADC_CTRL_REG |
bits 7..14 SAR_CLK_DIV. Stored and read back; conversion timing here is fixed |
0x04 |
APB_SARADC_CTRL2_REG |
stored, read back. The timer-triggered and DMA paths are not modeled |
0x0C |
APB_SARADC_FSM_WAIT_REG |
stored, read back |
0x38 |
APB_SARADC_ONETIME_SAMPLE_REG |
bits 23..24 ONETIME_ATTEN, bits 25..28 ONETIME_CHANNEL, bit 29 ONETIME_START, bit 30 SAR2_ONETIME_SAMPLE (stored, converts nothing), bit 31 SAR1_ONETIME_SAMPLE |
0x44 |
APB_SARADC_1_DATA_STATUS_REG |
bits 0..16 ADC1_DATA: the last ADC1 conversion, twelve bits wide |
0x64 |
APB_SARADC_INT_ENA_REG |
stored; no line reaches the core |
0x68 |
APB_SARADC_INT_RAW_REG |
bit 31 ADC1_DONE |
0x6C |
APB_SARADC_INT_ST_REG |
raw and enabled |
0x70 |
APB_SARADC_INT_CLR_REG |
write 1 to clear, same bit |
0x78 |
APB_SARADC_APB_ADC_CLKM_CONF_REG |
stored, read back |
One conversion: write ONETIME_SAMPLE with SAR1_ONETIME_SAMPLE, the channel and the attenuation, write it again with ONETIME_START up, wait for ADC1_DONE in INT_RAW, read the data register, clear the flag and drop ONETIME_START. A conversion takes a fixed 5 us of simulated time, which firmware really does wait for.
The result is twelve bits, round(4095 x Vin / Vfull), clamped, of the net voltage at the moment the conversion started, and ONETIME_ATTEN picks the full scale at the data sheet's nominal figures: 0 dB is 1.1 V, 2.5 dB 1.5 V, 6 dB 2.2 V and 11 dB 3.3 V. A divider at half the 3.3 V rail therefore reads about 2048 at 11 dB. A channel with nothing holding its pin reads 0 rather than noise.
ADC2, the DMA pattern table, the digital controller, the filters and threshold monitors, the calibration eFuses and the internal temperature channel are all absent, and ADC1_DONE never reaches mip.
3.7 LEDC (0x6000_7000)
The LED PWM controller, low-speed group, which is the only group this chip has: four timers and six channels. This is the block behind analogWrite, ledcAttach and ledcWrite. The channel and timer blocks sit where the C3's do, but the C6's LEDC is not the C3's register for register: the timer's resolution field is five bits and goes to twenty, HPOINT and DUTY are wider, and the global registers moved.
A timer counts 2^DUTY_RES counts at 80 MHz divided by CLK_DIV, a Q10.8 number of 80 MHz clocks per count, so f = 80e6 / ((CLK_DIV / 256) * 2^DUTY_RES) and 5 kHz at eight bits is DUTY_RES 8 with CLK_DIV 16000, exactly. A channel picks a timer, a start count HPOINT and a duty, and its output is high from HPOINT for DUTY >> 4 counts, wrapping round the top of the period. A duty of 0 is a solid low and a duty of 2^DUTY_RES a solid high, with no edge at all.
Verified: the base address (table 5.3-2) and every offset and bit in the table below, against the ESP32-C6 Technical Reference Manual v1.2, section 35.4 (register summary) and registers 35.1, 35.2, 35.8 to 35.14 and 35.23. An earlier version of this model used the C3's LEDC_CONF_REG at 0xD0 and LEDC_DATE_REG at 0xFC, the C3's fourteen-bit HPOINT, nineteen-bit DUTY and fourteen-bit resolution limit, and wrote LEDC_SCLK_SEL = 1, which on the C6 selects RC_FAST_CLK rather than the 80 MHz PLL clock. The C6's event-task, compare, capture and gamma-RAM registers (0x100 to 0x1CC) are not modeled.
| offset | name | bits |
|---|---|---|
0x00 + 0x14n |
LEDC_CHn_CONF0_REG |
bits 0..1 TIMER_SEL, bit 2 SIG_OUT_EN, bit 3 IDLE_LV, bit 4 PARA_UP (stored; a duty written here takes effect at once) |
0x04 + 0x14n |
LEDC_CHn_HPOINT_REG |
bits 0..19 HPOINT: the count the channel's pulse starts at |
0x08 + 0x14n |
LEDC_CHn_DUTY_REG |
bits 0..24 DUTY: the pulse width in sixteenths of a count |
0x0C + 0x14n |
LEDC_CHn_CONF1_REG |
bit 31 DUTY_START, the duty-fade trigger: stored, read back, and fades nothing |
0x10 + 0x14n |
LEDC_CHn_DUTY_R_REG |
the duty as it stands, read only |
0xA0 + 8n |
LEDC_TIMERn_CONF_REG |
bits 0..4 DUTY_RES (1 to 20; 0 is a timer nobody has configured and it counts nothing), bits 5..22 CLK_DIV, bit 23 PAUSE, bit 24 RST, bit 26 PARA_UP (stored); bit 25 is reserved |
0xA4 + 8n |
LEDC_TIMERn_VALUE_REG |
the counter, read only |
0xC0 |
LEDC_INT_RAW_REG |
bit n = timer n overflowed |
0xC4 |
LEDC_INT_ST_REG |
raw and enabled |
0xC8 |
LEDC_INT_ENA_REG |
stored; no line reaches the core |
0xCC |
LEDC_INT_CLR_REG |
write 1 to clear |
0x1F0 |
LEDC_CONF_REG |
bits 0..1 SCLK_SEL (0 = the 80 MHz PLL_F80M_CLK, the only source modeled; 1 RC_FAST_CLK, 2 XTAL_CLK; stored), bits 2..7 the gamma-RAM clock gates (stored), bit 31 CLK_EN (stored) |
0x1FC |
LEDC_DATE_REG |
a version stamp, read only: 0x0211_1150 |
A channel reaches a pad through the GPIO matrix: set the pad's GPIO_ENABLE bit and point its GPIO_FUNCn_OUT_SEL_CFG_REG at 45 + channel. Those signal indices, 45 to 50 for ledc_ls_sig_out0 to ledc_ls_sig_out5, are unverified too. Section 3.1's OUT_SEL of 128 still means the pad follows GPIO_OUT, and any value that is neither 128 nor an LEDC channel is treated as 128 rather than invented.
The counter is not stepped: it is a closed-form function of simulated time and so is the time of its next edge, so a core parked in wfi costs nothing while a PWM runs and the board is woken for the edges and for nothing else.
Not modeled: the high-speed group (this chip has none), the duty-fade and gamma hardware, clock sources other than the 80 MHz PLL clock, and the LEDC interrupt line into the interrupt matrix: LEDC_INT_RAW_REG can be polled and cleared but never reaches mip.
3.8 Mokxi network mailbox (0x6FFF_E000)
This block is not the ESP32-C6's. Every other address in this document is
the chip's, believed or quoted as section 7 sorts it, and an image built
against it is an image for a DevKitC-1. This one is Mokxi's own: the simulated
network of crates/net, mapped at an address the real part leaves unmapped
(the C6's peripherals sit in the bottom of the 0x6000_xxxx window and in
0x6009_xxxx, and nothing answers at the top of it), so that an image using it
faults on silicon rather than reading some other peripheral's register.
That fault is the honest answer, because the thing behind this block does not
exist on the chip. It is the same address the ESP32-C3 uses, because both parts
leave the same place empty.
There is no radio, no 802.11 and no real internet here. Nothing in the model
transmits, scans, associates, encrypts or opens a socket: a password is compared
as a string, a name that is not under .mokxi does not resolve, and the three
origins the network serves are functions in crates/net/src/http.rs. The
mailbox is the shape of the real seam rather than a fiction about a MAC: on a
real ESP32 the WiFi hardware is a closed binary blob with a command interface,
and Arduino's WiFi.h ends up on the other side of one of these.
docs/wifi.md is the contract for what is behind it; this section is only the
registers.
A command register, a status register, a 4096-byte ring each way and an
interrupt bit. Writing NET_CMD_REG runs the command; a command that takes
simulated time raises BUSY and clears it, with DONE in NET_INT_RAW_REG,
when it is finished.
| offset | name | bits |
|---|---|---|
0x00 |
NET_ID_REG |
reads 0x494B4F4D, and nothing else does. A sketch that wants to know whether it is on Mokxi reads this; on silicon the access faults |
0x04 |
NET_CMD_REG |
write a command code (table below); the write runs it |
0x08 |
NET_ARG_REG |
one argument word, read back: the port for LISTEN, the status for REPLY |
0x0C |
NET_STATUS_REG |
bit 0 BUSY, bit 1 ERR, bit 2 RX_READY, bit 3 LINK, bit 4 SERVE, bits 8..11 STATE |
0x10 |
NET_RESULT_REG |
what the last command answered |
0x14 |
NET_INT_RAW_REG |
bit 0 DONE, bit 1 EVENT |
0x18 |
NET_INT_ENA_REG |
the same two bits |
0x1C |
NET_INT_CLR_REG |
write 1 to clear, the same two bits |
0x20 |
NET_TX_LEN_REG |
bytes staged in the outbound ring |
0x24 |
NET_TX_DATA_REG |
write: push one byte (bits 0..7) into the outbound ring |
0x28 |
NET_RX_LEN_REG |
bytes waiting in the inbound ring |
0x2C |
NET_RX_DATA_REG |
read: pop one byte from the inbound ring, 0 when it is empty |
0x30 |
NET_RING_LEN_REG |
capacity of each ring in bytes, so firmware need not hard-code 4096 |
STATE is where the station is: 0 idle, 1 starting, 2 connecting, 3 connected,
4 failed, 5 no such network. NET_CMD_STATUS turns the same thing into the
Arduino wl_status_t code a sketch reads from WiFi.status().
NET_CMD_REG |
name | what it does |
|---|---|---|
| 0 | NOP |
nothing |
| 1 | RESET |
disconnect, drop both rings, drop any request |
| 2 | CONNECT |
the outbound ring holds ssid\0password; raises BUSY for one to three seconds |
| 3 | DISCONNECT |
drop the association and the address |
| 4 | STATUS |
NET_RESULT_REG = the wl_status_t code |
| 5 | IP |
NET_RESULT_REG = the leased address, first octet in the low byte |
| 6 | RSSI |
NET_RESULT_REG = the signal in dBm, sign extended |
| 7 | SSID |
the inbound ring gets the access point's name |
| 8 | MAC |
the inbound ring gets the board's six MAC bytes |
| 9 | REQUEST |
the outbound ring holds method\0url\0headers\0body; raises BUSY |
| 10 | POLL |
NET_RESULT_REG = 0 in flight, then the HTTP status, or a negative error with ERR up; the body lands in the inbound ring |
| 11 | LISTEN |
listen on the port in NET_ARG_REG |
| 12 | ACCEPT |
NET_RESULT_REG = 1 and the inbound ring holds method\0path\0query\0body, or 0 when nothing is waiting |
| 13 | REPLY |
answer the accepted request with the status in NET_ARG_REG; the outbound ring holds content-type\0body |
| 14 | STOP |
stop listening |
| 15 | WL_GPIO_SET |
drive the wireless part's own GPIOs from NET_ARG_REG. Not this board: this part's radio has no such pin, so it sets ERR and drives nothing. It is the path a Pico W's on-board LED takes (docs/picow.md section 3.8) |
| 16 | WL_GPIO_GET |
read them back, and the same ERR here |
The negative POLL codes: -1 the name does not resolve, -2 the station is not
associated, -3 https:// and TLS is not simulated, -4 a body longer than the
ring, -5 a URL this network cannot parse, -6 a request is already in flight.
firmware/include/HTTPClient.h turns them into the Arduino-shaped codes a
sketch compares against, and docs/wifi.md lists the mapping.
Timing, all of it this model's own and all of it stated in docs/wifi.md: an
association takes between one and three seconds, decided from the SSID so a
lesson takes the same time twice; a wrong password becomes WL_CONNECT_FAILED
after five seconds; a request to a simulated origin comes back 60 ms later; a
request into the sketch's own server that the sketch never answers is closed by
the network with a 504 after ten seconds.
4. Host channel
host_write puts bytes in UART0's receive FIFO; host_read takes the bytes that have left its transmit FIFO. That is Serial in a sketch and the serial monitor in the UI. Bytes are UTF-8 as far as the UI is concerned and opaque to the model.
A DevKitC-1 has two USB-C sockets: one to a USB-to-UART bridge, which is this channel, and one to the chip's own USB, which is not modeled at all.
5. Time model
As docs/esp32c3.md section 5. The HP core runs at 160 MHz, one instruction per cycle, so an instruction is exactly 6250 ps and a 10 us slice is exactly 1600 instructions. Inside a slice the instruction count is the clock: a SYSTIMER read, a UART byte and a pin change all land at the instruction that caused them.
probe is 1 while the firmware runs, 0.5 parked in wfi, 0 halted (no program, held in reset, or crashed).
poke: 1 holds BOOT (GPIO 9 low), 0 releases it; 2 holds RST, 3 releases it.
6. Firmware runtime (firmware/c6/)
An Arduino-shaped runtime derived from the C3's: setup() and loop(), pinMode, digitalWrite, digitalRead, analogRead (with analogReadResolution, analogSetAttenuation and analogSetPinAttenuation), analogWrite and the ledcAttach/ledcWrite pair, millis, micros, delay, delayMicroseconds, attachInterrupt, Serial. No Arduino core, no ESP-IDF, no libc.
analogRead is ADC1 one-shot on GPIO 0 to GPIO 6 (section 3.6), twelve bits at 11 dB by default, so firmware/lib/mokxi_joystick.h compiles and works here as it does on the C3. analogWrite(pin, 0..255) is eight bits at 1 kHz on any GPIO and attaches a free LEDC channel the first time it is called; ledcAttach(pin, frequency, bits) is what a sketch wants when the frequency matters, and channels asking for the same frequency and resolution share one of the four timers.
Built with clang --target=riscv32-unknown-elf -march=rv32imc -mabi=ilp32, linked against firmware/c6/link.ld. Three of the runtime's files are the C3's, compiled again for this board rather than copied: the number formatting, the math helpers and the freestanding floor, plus Print and main. This board's own are the register header, the pin count, the startup code, the linker script, and the GPIO, ADC, PWM, time, UART and interrupt drivers.
The catalog program string is elf32 riscv32imc esp32c6. It differs from the C3's because the ELF really does: same instruction set, different memory map, and an image linked for one will not run on the other.
LED_BUILTIN is 8, which on this board is the addressable RGB LED. Driving it high and low still does the useful thing on a breadboard; the rainbow example is what the pin is really for.
Six examples: blink, button (the BOOT button on GPIO 9), serial, fade (an LED on GPIO 10 breathing on hardware PWM), stick (a KY-023 on ADC1 channels 0 and 1, with the brightness of two LEDs going back out through LEDC) and rainbow (a hue wheel bit-banged onto the RGB LED and seven more on GPIO 8). An example is budgeted at 16 KB.
7. What this model is sure of, and what it is not
Three groups. This section exists because a board model that quietly guesses an address is worse than one that says it did not know.
Taken from the technical reference manual. Every base address in section 2 (the ESP32-C6 Technical Reference Manual v1.2, table 5.3-2): UART0_BASE = 0x6000_0000, LEDC_BASE = 0x6000_7000, SYSTIMER_BASE = 0x6000_A000, APB_SARADC_BASE = 0x6000_E000, IOMUX_BASE = 0x6009_0000 and GPIO_BASE = 0x6009_1000. Every register offset inside the UART, GPIO and IO_MUX blocks. The UART's FIFO-reset bits and CONF1 thresholds (registers 27.9 and 27.10), the SYSTIMER comparator (chapter 13) and every LEDC register in section 3.7 (chapter 35), all checked against that manual while the ESP32-S3 was being added; the C6 differs from the C3 in each of them, and each was wrong here before that check and has been corrected, with the runtime's images rebuilt. The GPIO range, 0 to 30. The HP SRAM's 512 KB at 0x4080_0000, and the fact that it is one window rather than two. The RGB LED on GPIO 8 and the BOOT button on GPIO 9. ADC1's channel map: channel n is GPIO n for n in 0 to 6.
Believed, not verified. The register offsets inside APB_SARADC, which are the C3's (section 3.6), and the GPIO matrix signal indices 45 to 50 for ledc_ls_sig_out0 to ledc_ls_sig_out5. Both are named in one place, firmware/c6/include/esp32c6.h, so an image being taken to real silicon has a short list of #defines to check. Firmware built here agrees with the model whether or not it agrees with silicon.
Deliberately absent. Each of these was left out rather than invented, because its register layout is not something this model can reproduce honestly:
- The timer groups (TIMG0 and TIMG1). The C6 has two, with watchdogs. Time here comes from SYSTIMER, which is enough for
millis,microsand a parkeddelay. - RMT, which is what would normally drive the RGB LED. The
rainbowexample bit-bangs it instead, and that is not a shortcut: it is the only way here. - The LP core and its peripherals, including LP_UART and LP_I2C. The C6's low-power domain is half of what makes the part interesting and none of it is here.
- The interrupt matrix, the PLIC-shaped routing that decides which peripheral reaches which
mcause. Section 3.5 says what stands in for it. - ADC2, the SAR ADC's DMA and digital controller, its filters and threshold monitors and its calibration eFuses. Section 3.6 is ADC1 one-shot and nothing else.
- LEDC's duty-fade hardware and its interrupt line: section 3.7 is the timers, the channels and the output, and
LEDC_INT_RAW_REGis polled rather than delivered. - The PMU and the clock tree, the cache, the ROM, flash, SPI, I2C, I2S, USB Serial/JTAG, WiFi 6, Bluetooth LE and 802.15.4.
That is why vendor ESP-IDF and Arduino core binaries do not run here, and firmware built for this document does.