Skip to content

Commit cb147b9

Browse files
aykevldeadprogram
authored andcommitted
esp32c3: add support for this chip
This change adds support for the ESP32-C3, a new chip from Espressif. It is a RISC-V core so porting was comparatively easy. Most peripherals are shared with the (original) ESP32 chip, but with subtle differences. Also, the SVD file I've used gives some peripherals/registers a different name which makes sharing code harder. Eventually, when an official SVD file for the ESP32 is released, I expect that a lot of code can be shared between the two chips. More information: https://www.espressif.com/en/products/socs/esp32-c3 TODO: - stack scheduler - interrupts - most peripherals (SPI, I2C, PWM, etc)
1 parent c830f87 commit cb147b9

File tree

12 files changed

+660
-70
lines changed

12 files changed

+660
-70
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
124124

125125
gen-device-esp: build/gen-device-svd
126126
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
127+
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
127128
GO111MODULE=off $(GO) fmt ./src/device/esp
128129

129130
gen-device-nrf: build/gen-device-svd
@@ -432,6 +433,8 @@ ifneq ($(XTENSA), 0)
432433
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
433434
@$(MD5SUM) test.bin
434435
endif
436+
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
437+
@$(MD5SUM) test.bin
435438
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
436439
@$(MD5SUM) test.hex
437440
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial

builder/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
677677
if err != nil {
678678
return err
679679
}
680-
case "esp32", "esp8266":
680+
case "esp32", "esp32c3", "esp8266":
681681
// Special format for the ESP family of chips (parsed by the ROM
682682
// bootloader).
683683
tmppath = filepath.Join(dir, "main"+outext)

builder/esp.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error {
7878
// An added benefit is that we don't need to check for errors all the time.
7979
outf := &bytes.Buffer{}
8080

81+
// Chip IDs. Source:
82+
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
83+
chip_id := map[string]uint16{
84+
"esp32": 0x0000,
85+
"esp32c3": 0x0005,
86+
}[format]
87+
8188
// Image header.
8289
switch format {
83-
case "esp32":
90+
case "esp32", "esp32c3":
8491
// Header format:
85-
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
92+
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
93+
// Note: not adding a SHA256 hash as the binary is modified by
94+
// esptool.py while flashing and therefore the hash won't be valid
95+
// anymore.
8696
binary.Write(outf, binary.LittleEndian, struct {
8797
magic uint8
8898
segment_count uint8
@@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error {
91101
entry_addr uint32
92102
wp_pin uint8
93103
spi_pin_drv [3]uint8
94-
reserved [11]uint8
104+
chip_id uint16
105+
min_chip_rev uint8
106+
reserved [8]uint8
95107
hash_appended bool
96108
}{
97109
magic: 0xE9,
98110
segment_count: byte(len(segments)),
99-
spi_mode: 0, // irrelevant, replaced by esptool when flashing
100-
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
111+
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
112+
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
101113
entry_addr: uint32(inf.Entry),
102114
wp_pin: 0xEE, // disable WP pin
115+
chip_id: chip_id,
103116
hash_appended: true, // add a SHA256 hash
104117
})
105118
case "esp8266":
@@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
142155
outf.Write(make([]byte, 15-outf.Len()%16))
143156
outf.WriteByte(checksum)
144157

145-
if format == "esp32" {
158+
if format != "esp8266" {
146159
// SHA256 hash (to protect against image corruption, not for security).
147160
hash := sha256.Sum256(outf.Bytes())
148161
outf.Write(hash[:])

src/device/esp/esp32c3.S

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// This is a very minimal bootloader for the ESP32-C3. It only initializes the
2+
// flash and then continues with the generic RISC-V initialization code, which
3+
// in turn will call runtime.main.
4+
// It is written in assembly (and not in a higher level language) to make sure
5+
// it is entirely loaded into IRAM and doesn't accidentally call functions
6+
// stored in IROM.
7+
//
8+
// For reference, here is a nice introduction into RISC-V assembly:
9+
// https://www.imperialviolet.org/2016/12/31/riscv.html
10+
11+
.section .init
12+
.global call_start_cpu0
13+
.type call_start_cpu0,@function
14+
call_start_cpu0:
15+
// At this point:
16+
// - The ROM bootloader is finished and has jumped to here.
17+
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
18+
// by the ROM bootloader.
19+
// - We have a usable stack (but not the one we would like to use).
20+
// - No flash mappings (MMU) are set up yet.
21+
22+
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
23+
call Cache_Suspend_ICache
24+
mv s0, a0 // autoload value
25+
call Cache_Invalidate_ICache_All
26+
call Cache_MMU_Init
27+
28+
// Set up DROM from flash.
29+
// Somehow, this also sets up IROM from flash. Not sure why, but it avoids
30+
// the need for another such call.
31+
// C equivalent:
32+
// Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0)
33+
li a0, 0 // ext_ram: MMU_ACCESS_FLASH
34+
li a1, 0x3C000000 // vaddr: address in the data bus
35+
li a2, 0 // paddr: physical address in the flash chip
36+
li a3, 64 // psize: always 64 (kilobytes)
37+
li a4, 128 // num: pages to be set (8192K / 64K = 128)
38+
li a5, 0 // fixed
39+
call Cache_Dbus_MMU_Set
40+
41+
// Enable the flash cache.
42+
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
43+
call Cache_Resume_ICache
44+
45+
// Jump to generic RISC-V initialization, which initializes the stack
46+
// pointer and globals register. It should not return.
47+
// (It appears that the linker relaxes this jump and instead inserts the
48+
// _start function right after here).
49+
j _start

src/machine/machine_esp32c3.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// +build esp32c3
2+
3+
package machine
4+
5+
import (
6+
"device/esp"
7+
"runtime/volatile"
8+
"unsafe"
9+
)
10+
11+
// CPUFrequency returns the current CPU frequency of the chip.
12+
// Currently it is a fixed frequency but it may allow changing in the future.
13+
func CPUFrequency() uint32 {
14+
return 160e6 // 160MHz
15+
}
16+
17+
const (
18+
PinOutput PinMode = iota
19+
PinInput
20+
PinInputPullup
21+
PinInputPulldown
22+
)
23+
24+
// Configure this pin with the given configuration.
25+
func (p Pin) Configure(config PinConfig) {
26+
if p == NoPin {
27+
// This simplifies pin configuration in peripherals such as SPI.
28+
return
29+
}
30+
31+
var muxConfig uint32
32+
33+
// Configure this pin as a GPIO pin.
34+
const function = 1 // function 1 is GPIO for every pin
35+
muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos
36+
37+
// Make this pin an input pin (always).
38+
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
39+
40+
// Set drive strength: 0 is lowest, 3 is highest.
41+
muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos
42+
43+
// Select pull mode.
44+
if config.Mode == PinInputPullup {
45+
muxConfig |= esp.IO_MUX_GPIO_FUN_WPU
46+
} else if config.Mode == PinInputPulldown {
47+
muxConfig |= esp.IO_MUX_GPIO_FUN_WPD
48+
}
49+
50+
// Configure the pad with the given IO mux configuration.
51+
p.mux().Set(muxConfig)
52+
53+
// Set the output signal to the simple GPIO output.
54+
p.outFunc().Set(0x80)
55+
56+
switch config.Mode {
57+
case PinOutput:
58+
// Set the 'output enable' bit.
59+
esp.GPIO.ENABLE_W1TS.Set(1 << p)
60+
case PinInput, PinInputPullup, PinInputPulldown:
61+
// Clear the 'output enable' bit.
62+
esp.GPIO.ENABLE_W1TC.Set(1 << p)
63+
}
64+
}
65+
66+
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
67+
// output function selection.
68+
func (p Pin) outFunc() *volatile.Register32 {
69+
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG)) + uintptr(p)*4)))
70+
}
71+
72+
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
73+
// function selection.
74+
func inFunc(signal uint32) *volatile.Register32 {
75+
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG)) + uintptr(signal)*4)))
76+
}
77+
78+
// mux returns the I/O mux configuration register corresponding to the given
79+
// GPIO pin.
80+
func (p Pin) mux() *volatile.Register32 {
81+
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.IO_MUX.GPIO0)) + uintptr(p)*4)))
82+
}
83+
84+
// Set the pin to high or low.
85+
// Warning: only use this on an output pin!
86+
func (p Pin) Set(value bool) {
87+
if value {
88+
reg, mask := p.portMaskSet()
89+
reg.Set(mask)
90+
} else {
91+
reg, mask := p.portMaskClear()
92+
reg.Set(mask)
93+
}
94+
}
95+
96+
// Return the register and mask to enable a given GPIO pin. This can be used to
97+
// implement bit-banged drivers.
98+
//
99+
// Warning: only use this on an output pin!
100+
func (p Pin) PortMaskSet() (*uint32, uint32) {
101+
reg, mask := p.portMaskSet()
102+
return &reg.Reg, mask
103+
}
104+
105+
// Return the register and mask to disable a given GPIO pin. This can be used to
106+
// implement bit-banged drivers.
107+
//
108+
// Warning: only use this on an output pin!
109+
func (p Pin) PortMaskClear() (*uint32, uint32) {
110+
reg, mask := p.portMaskClear()
111+
return &reg.Reg, mask
112+
}
113+
114+
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
115+
return &esp.GPIO.OUT_W1TS, 1 << p
116+
}
117+
118+
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
119+
return &esp.GPIO.OUT_W1TC, 1 << p
120+
}
121+
122+
var DefaultUART = UART0
123+
124+
var (
125+
UART0 = &_UART0
126+
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
127+
UART1 = &_UART1
128+
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
129+
)
130+
131+
type UART struct {
132+
Bus *esp.UART_Type
133+
Buffer *RingBuffer
134+
}
135+
136+
func (uart *UART) WriteByte(b byte) error {
137+
for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 {
138+
// Read UART_TXFIFO_CNT from the status register, which indicates how
139+
// many bytes there are in the transmit buffer. Wait until there are
140+
// less than 128 bytes in this buffer (the default buffer size).
141+
}
142+
uart.Bus.FIFO.Set(uint32(b))
143+
return nil
144+
}

src/runtime/runtime_esp32.go

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,8 @@ import (
66
"device"
77
"device/esp"
88
"machine"
9-
"unsafe"
109
)
1110

12-
type timeUnit int64
13-
14-
var currentTime timeUnit
15-
16-
func putchar(c byte) {
17-
machine.Serial.WriteByte(c)
18-
}
19-
20-
func postinit() {}
21-
2211
// This is the function called on startup right after the stack pointer has been
2312
// set.
2413
//export main
@@ -50,23 +39,15 @@ func main() {
5039
// Clear .bss section. .data has already been loaded by the ROM bootloader.
5140
// Do this after increasing the CPU clock to possibly make startup slightly
5241
// faster.
53-
preinit()
42+
clearbss()
5443

5544
// Initialize UART.
5645
machine.Serial.Configure(machine.UARTConfig{})
5746

58-
// Configure timer 0 in timer group 0, for timekeeping.
59-
// EN: Enable the timer.
60-
// INCREASE: Count up every tick (as opposed to counting down).
61-
// DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two
62-
// (40MHz).
63-
esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<<esp.TIMG_T0CONFIG_T0_DIVIDER_Pos)
64-
65-
// Set the timer counter value to 0.
66-
esp.TIMG0.T0LOADLO.Set(0)
67-
esp.TIMG0.T0LOADHI.Set(0)
68-
esp.TIMG0.T0LOAD.Set(0) // value doesn't matter.
47+
// Initialize main system timer used for time.Now.
48+
initTimer()
6949

50+
// Initialize the heap, call main.main, etc.
7051
run()
7152

7253
// Fallback: if main ever returns, hang the CPU.
@@ -79,44 +60,6 @@ var _sbss [0]byte
7960
//go:extern _ebss
8061
var _ebss [0]byte
8162

82-
func preinit() {
83-
// Initialize .bss: zero-initialized global variables.
84-
// The .data section has already been loaded by the ROM bootloader.
85-
ptr := unsafe.Pointer(&_sbss)
86-
for ptr != unsafe.Pointer(&_ebss) {
87-
*(*uint32)(ptr) = 0
88-
ptr = unsafe.Pointer(uintptr(ptr) + 4)
89-
}
90-
}
91-
92-
func ticks() timeUnit {
93-
// First, update the LO and HI register pair by writing any value to the
94-
// register. This allows reading the pair atomically.
95-
esp.TIMG0.T0UPDATE.Set(0)
96-
// Then read the two 32-bit parts of the timer.
97-
return timeUnit(uint64(esp.TIMG0.T0LO.Get()) | uint64(esp.TIMG0.T0HI.Get())<<32)
98-
}
99-
100-
func nanosecondsToTicks(ns int64) timeUnit {
101-
// Calculate the number of ticks from the number of nanoseconds. At a 80MHz
102-
// APB clock, that's 25 nanoseconds per tick with a timer prescaler of 2:
103-
// 25 = 1e9 / (80MHz / 2)
104-
return timeUnit(ns / 25)
105-
}
106-
107-
func ticksToNanoseconds(ticks timeUnit) int64 {
108-
// See nanosecondsToTicks.
109-
return int64(ticks) * 25
110-
}
111-
112-
// sleepTicks busy-waits until the given number of ticks have passed.
113-
func sleepTicks(d timeUnit) {
114-
sleepUntil := ticks() + d
115-
for ticks() < sleepUntil {
116-
// TODO: suspend the CPU to not burn power here unnecessarily.
117-
}
118-
}
119-
12063
func abort() {
12164
for {
12265
device.Asm("waiti 0")

0 commit comments

Comments
 (0)