From cb147b9475e843a83c2d3f4e7a19858b6cceb3c8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 4 Sep 2021 00:55:35 +0200 Subject: [PATCH] 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) --- Makefile | 3 + builder/build.go | 2 +- builder/esp.go | 25 ++- lib/cmsis-svd | 2 +- src/device/esp/esp32c3.S | 49 +++++ src/machine/machine_esp32c3.go | 144 +++++++++++++ src/runtime/runtime_esp32.go | 65 +----- src/runtime/runtime_esp32c3.go | 65 ++++++ src/runtime/runtime_esp32xx.go | 69 ++++++ targets/esp32c3.json | 16 ++ targets/esp32c3.ld | 285 +++++++++++++++++++++++++ tools/gen-device-svd/gen-device-svd.go | 5 +- 12 files changed, 660 insertions(+), 70 deletions(-) create mode 100644 src/device/esp/esp32c3.S create mode 100644 src/machine/machine_esp32c3.go create mode 100644 src/runtime/runtime_esp32c3.go create mode 100644 src/runtime/runtime_esp32xx.go create mode 100644 targets/esp32c3.json create mode 100644 targets/esp32c3.ld diff --git a/Makefile b/Makefile index 4f828430..fd3dd883 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go gen-device-esp: build/gen-device-svd ./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/ + ./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/ GO111MODULE=off $(GO) fmt ./src/device/esp gen-device-nrf: build/gen-device-svd @@ -432,6 +433,8 @@ ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin endif + $(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial diff --git a/builder/build.go b/builder/build.go index c1fd78e3..ebb1f86a 100644 --- a/builder/build.go +++ b/builder/build.go @@ -677,7 +677,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil if err != nil { return err } - case "esp32", "esp8266": + case "esp32", "esp32c3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). tmppath = filepath.Join(dir, "main"+outext) diff --git a/builder/esp.go b/builder/esp.go index 9a9c37f2..98e37542 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error { // An added benefit is that we don't need to check for errors all the time. outf := &bytes.Buffer{} + // Chip IDs. Source: + // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22 + chip_id := map[string]uint16{ + "esp32": 0x0000, + "esp32c3": 0x0005, + }[format] + // Image header. switch format { - case "esp32": + case "esp32", "esp32c3": // Header format: - // https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58 + // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 + // Note: not adding a SHA256 hash as the binary is modified by + // esptool.py while flashing and therefore the hash won't be valid + // anymore. binary.Write(outf, binary.LittleEndian, struct { magic uint8 segment_count uint8 @@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error { entry_addr uint32 wp_pin uint8 spi_pin_drv [3]uint8 - reserved [11]uint8 + chip_id uint16 + min_chip_rev uint8 + reserved [8]uint8 hash_appended bool }{ magic: 0xE9, segment_count: byte(len(segments)), - spi_mode: 0, // irrelevant, replaced by esptool when flashing - spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing + spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO + spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB entry_addr: uint32(inf.Entry), wp_pin: 0xEE, // disable WP pin + chip_id: chip_id, hash_appended: true, // add a SHA256 hash }) case "esp8266": @@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error { outf.Write(make([]byte, 15-outf.Len()%16)) outf.WriteByte(checksum) - if format == "esp32" { + if format != "esp8266" { // SHA256 hash (to protect against image corruption, not for security). hash := sha256.Sum256(outf.Bytes()) outf.Write(hash[:]) diff --git a/lib/cmsis-svd b/lib/cmsis-svd index 9c35b6d9..df75ff97 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit 9c35b6d9df1f9eeecfcc33fc6f98719dbaaa30ce +Subproject commit df75ff974c76a911fc2815e29807f5ecaae06fc2 diff --git a/src/device/esp/esp32c3.S b/src/device/esp/esp32c3.S new file mode 100644 index 00000000..70733982 --- /dev/null +++ b/src/device/esp/esp32c3.S @@ -0,0 +1,49 @@ +// This is a very minimal bootloader for the ESP32-C3. It only initializes the +// flash and then continues with the generic RISC-V initialization code, which +// in turn will call runtime.main. +// It is written in assembly (and not in a higher level language) to make sure +// it is entirely loaded into IRAM and doesn't accidentally call functions +// stored in IROM. +// +// For reference, here is a nice introduction into RISC-V assembly: +// https://www.imperialviolet.org/2016/12/31/riscv.html + +.section .init +.global call_start_cpu0 +.type call_start_cpu0,@function +call_start_cpu0: + // At this point: + // - The ROM bootloader is finished and has jumped to here. + // - We're running from IRAM: both IRAM and DRAM segments have been loaded + // by the ROM bootloader. + // - We have a usable stack (but not the one we would like to use). + // - No flash mappings (MMU) are set up yet. + + // Reset MMU, see bootloader_reset_mmu in the ESP-IDF. + call Cache_Suspend_ICache + mv s0, a0 // autoload value + call Cache_Invalidate_ICache_All + call Cache_MMU_Init + + // Set up DROM from flash. + // Somehow, this also sets up IROM from flash. Not sure why, but it avoids + // the need for another such call. + // C equivalent: + // Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0) + li a0, 0 // ext_ram: MMU_ACCESS_FLASH + li a1, 0x3C000000 // vaddr: address in the data bus + li a2, 0 // paddr: physical address in the flash chip + li a3, 64 // psize: always 64 (kilobytes) + li a4, 128 // num: pages to be set (8192K / 64K = 128) + li a5, 0 // fixed + call Cache_Dbus_MMU_Set + + // Enable the flash cache. + mv a0, s0 // restore autoload value from Cache_Suspend_ICache call + call Cache_Resume_ICache + + // Jump to generic RISC-V initialization, which initializes the stack + // pointer and globals register. It should not return. + // (It appears that the linker relaxes this jump and instead inserts the + // _start function right after here). + j _start diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go new file mode 100644 index 00000000..0e45ed46 --- /dev/null +++ b/src/machine/machine_esp32c3.go @@ -0,0 +1,144 @@ +// +build esp32c3 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +// CPUFrequency returns the current CPU frequency of the chip. +// Currently it is a fixed frequency but it may allow changing in the future. +func CPUFrequency() uint32 { + return 160e6 // 160MHz +} + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + var muxConfig uint32 + + // Configure this pin as a GPIO pin. + const function = 1 // function 1 is GPIO for every pin + muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos + + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos + + // Select pull mode. + if config.Mode == PinInputPullup { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Configure the pad with the given IO mux configuration. + p.mux().Set(muxConfig) + + // Set the output signal to the simple GPIO output. + p.outFunc().Set(0x80) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + esp.GPIO.ENABLE_W1TS.Set(1 << p) + case PinInput, PinInputPullup, PinInputPulldown: + // Clear the 'output enable' bit. + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } +} + +// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the +// output function selection. +func (p Pin) outFunc() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG)) + uintptr(p)*4))) +} + +// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input +// function selection. +func inFunc(signal uint32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG)) + uintptr(signal)*4))) +} + +// mux returns the I/O mux configuration register corresponding to the given +// GPIO pin. +func (p Pin) mux() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.IO_MUX.GPIO0)) + uintptr(p)*4))) +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(value bool) { + if value { + reg, mask := p.portMaskSet() + reg.Set(mask) + } else { + reg, mask := p.portMaskClear() + reg.Set(mask) + } +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskSet() (*uint32, uint32) { + reg, mask := p.portMaskSet() + return ®.Reg, mask +} + +// Return the register and mask to disable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskClear() (*uint32, uint32) { + reg, mask := p.portMaskClear() + return ®.Reg, mask +} + +func (p Pin) portMaskSet() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TS, 1 << p +} + +func (p Pin) portMaskClear() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TC, 1 << p +} + +var DefaultUART = UART0 + +var ( + UART0 = &_UART0 + _UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()} + UART1 = &_UART1 + _UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} +) + +type UART struct { + Bus *esp.UART_Type + Buffer *RingBuffer +} + +func (uart *UART) WriteByte(b byte) error { + for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { + // Read UART_TXFIFO_CNT from the status register, which indicates how + // many bytes there are in the transmit buffer. Wait until there are + // less than 128 bytes in this buffer (the default buffer size). + } + uart.Bus.FIFO.Set(uint32(b)) + return nil +} diff --git a/src/runtime/runtime_esp32.go b/src/runtime/runtime_esp32.go index 2fdeab90..79791ff2 100644 --- a/src/runtime/runtime_esp32.go +++ b/src/runtime/runtime_esp32.go @@ -6,19 +6,8 @@ import ( "device" "device/esp" "machine" - "unsafe" ) -type timeUnit int64 - -var currentTime timeUnit - -func putchar(c byte) { - machine.Serial.WriteByte(c) -} - -func postinit() {} - // This is the function called on startup right after the stack pointer has been // set. //export main @@ -50,23 +39,15 @@ func main() { // Clear .bss section. .data has already been loaded by the ROM bootloader. // Do this after increasing the CPU clock to possibly make startup slightly // faster. - preinit() + clearbss() // Initialize UART. machine.Serial.Configure(machine.UARTConfig{}) - // Configure timer 0 in timer group 0, for timekeeping. - // EN: Enable the timer. - // INCREASE: Count up every tick (as opposed to counting down). - // DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two - // (40MHz). - esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2< DROM + + /* Constant global variables, stored in DROM. + */ + .rodata : ALIGN(4) + { + *(.rodata .rodata.*) + . = ALIGN (4); + } >DROM + + /* Put the stack at the bottom of DRAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + * See: http://blog.japaric.io/stack-overflow-protection/ + * TODO: this might not actually work because memory protection hasn't been set up. + */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >DRAM + + /* Global variables that are mutable and zero-initialized. + * These must be zeroed at startup (unlike data, which is loaded by the + * bootloader). + */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN (4); + _sbss = ABSOLUTE(.); + *(.sbss) + *(.bss .bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM + + /* Mutable global variables. This data (in the DRAM segment) is initialized + * by the ROM bootloader. + */ + .data : ALIGN(4) + { + . = ALIGN (4); + _sdata = ABSOLUTE(.); + *(.sdata) + *(.data .data.*) + . = ALIGN (4); + _edata = ABSOLUTE(.); + } >DRAM + + /* Dummy section to make sure the .init section (in the IRAM segment) is just + * behind the DRAM segment. For IRAM and DRAM, we luckily don't have to + * worry about 64kB pages or image headers as they're loaded in RAM by the + * bootloader (not mapped from flash). + */ + .iram_dummy (NOLOAD): ALIGN(4) + { + . += SIZEOF(.stack); + . += SIZEOF(.bss); + . += SIZEOF(.data); + } > IRAM + + /* Initialization code is loaded into IRAM. This memory area is also used by + * the heap, so no RAM is wasted. + */ + .init : ALIGN(4) + { + *(.init) + } >IRAM + + /* Dummy section to put the IROM segment exactly behind the IRAM segment. + * This has to follow the app image format exactly. + */ + .text_dummy (NOLOAD): ALIGN(4) + { + /* Note: DRAM and DROM are not always present so the header should only + * be inserted if it actually exists. + */ + . += 0x18; /* esp_image_header_t */ + . += SIZEOF(.rodata) + ((SIZEOF(.rodata) != 0) ? 0x8 : 0); /* DROM segment (optional) */ + . += SIZEOF(.data) + ((SIZEOF(.data) != 0) ? 0x8 : 0); /* DRAM segment (optional) */ + . += SIZEOF(.init) + 0x8; /* IRAM segment */ + . += 0x8; /* IROM segment header */ + } > IROM + + /* IROM segment. This contains all the actual code and is placed right after + * the DROM segment. + */ + .text : ALIGN(4) + { + *(.text .text.*) + } >IROM + + /DISCARD/ : + { + *(.eh_frame) /* causes 'no memory region specified' error in lld */ + } + + /* Check that the boot ROM stack (for the APP CPU) does not overlap with the + * data that is loaded by the boot ROM. This is unlikely to happen in + * practice. + * The magic value comes from here: + * https://github.com/espressif/esp-idf/blob/61299f879e/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld#L191 + */ + ASSERT((_edata + SIZEOF(.init)) < 0x3FCDE710, "the .init section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") +} + +/* For the garbage collector. + * Note that _heap_start starts after _edata (without caring for the .init + * section), because the .init section isn't necessary anymore after startup and + * can thus be overwritten by the heap. + */ +_globals_start = _sbss; +_globals_end = _edata; +_heap_start = _edata; +_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); + +_stack_size = 4K; + +/* ROM functions used for setting up the flash mapping. + */ +Cache_Invalidate_ICache_All = 0x400004d8; +Cache_Suspend_ICache = 0x40000524; +Cache_Resume_ICache = 0x40000528; +Cache_MMU_Init = 0x4000055c; +Cache_Dbus_MMU_Set = 0x40000564; + +/* From ESP-IDF: + * components/esp_rom/esp32c3/ld/esp32c3.rom.libgcc.ld + * These are called from LLVM during codegen. The original license is Apache + * 2.0. + */ +__absvdi2 = 0x40000764; +__absvsi2 = 0x40000768; +__adddf3 = 0x4000076c; +__addsf3 = 0x40000770; +__addvdi3 = 0x40000774; +__addvsi3 = 0x40000778; +__ashldi3 = 0x4000077c; +__ashrdi3 = 0x40000780; +__bswapdi2 = 0x40000784; +__bswapsi2 = 0x40000788; +__clear_cache = 0x4000078c; +__clrsbdi2 = 0x40000790; +__clrsbsi2 = 0x40000794; +__clzdi2 = 0x40000798; +__clzsi2 = 0x4000079c; +__cmpdi2 = 0x400007a0; +__ctzdi2 = 0x400007a4; +__ctzsi2 = 0x400007a8; +__divdc3 = 0x400007ac; +__divdf3 = 0x400007b0; +__divdi3 = 0x400007b4; +__divsc3 = 0x400007b8; +__divsf3 = 0x400007bc; +__divsi3 = 0x400007c0; +__eqdf2 = 0x400007c4; +__eqsf2 = 0x400007c8; +__extendsfdf2 = 0x400007cc; +__ffsdi2 = 0x400007d0; +__ffssi2 = 0x400007d4; +__fixdfdi = 0x400007d8; +__fixdfsi = 0x400007dc; +__fixsfdi = 0x400007e0; +__fixsfsi = 0x400007e4; +__fixunsdfsi = 0x400007e8; +__fixunssfdi = 0x400007ec; +__fixunssfsi = 0x400007f0; +__floatdidf = 0x400007f4; +__floatdisf = 0x400007f8; +__floatsidf = 0x400007fc; +__floatsisf = 0x40000800; +__floatundidf = 0x40000804; +__floatundisf = 0x40000808; +__floatunsidf = 0x4000080c; +__floatunsisf = 0x40000810; +__gcc_bcmp = 0x40000814; +__gedf2 = 0x40000818; +__gesf2 = 0x4000081c; +__gtdf2 = 0x40000820; +__gtsf2 = 0x40000824; +__ledf2 = 0x40000828; +__lesf2 = 0x4000082c; +__lshrdi3 = 0x40000830; +__ltdf2 = 0x40000834; +__ltsf2 = 0x40000838; +__moddi3 = 0x4000083c; +__modsi3 = 0x40000840; +__muldc3 = 0x40000844; +__muldf3 = 0x40000848; +__muldi3 = 0x4000084c; +__mulsc3 = 0x40000850; +__mulsf3 = 0x40000854; +__mulsi3 = 0x40000858; +__mulvdi3 = 0x4000085c; +__mulvsi3 = 0x40000860; +__nedf2 = 0x40000864; +__negdf2 = 0x40000868; +__negdi2 = 0x4000086c; +__negsf2 = 0x40000870; +__negvdi2 = 0x40000874; +__negvsi2 = 0x40000878; +__nesf2 = 0x4000087c; +__paritysi2 = 0x40000880; +__popcountdi2 = 0x40000884; +__popcountsi2 = 0x40000888; +__powidf2 = 0x4000088c; +__powisf2 = 0x40000890; +__subdf3 = 0x40000894; +__subsf3 = 0x40000898; +__subvdi3 = 0x4000089c; +__subvsi3 = 0x400008a0; +__truncdfsf2 = 0x400008a4; +__ucmpdi2 = 0x400008a8; +__udivdi3 = 0x400008ac; +__udivmoddi4 = 0x400008b0; +__udivsi3 = 0x400008b4; +__udiv_w_sdiv = 0x400008b8; +__umoddi3 = 0x400008bc; +__umodsi3 = 0x400008c0; +__unorddf2 = 0x400008c4; +__unordsf2 = 0x400008c8; + +/* From ESP-IDF: + * components/esp_rom/esp32c3/ld/esp32c3.rom.newlib.ld + * These are called during codegen and thus it's a good idea to make them always + * available. ROM functions may also be faster than functions in IROM (that go + * through the flash cache) and are always available in interrupts. + */ +memset = 0x40000354; +memcpy = 0x40000358; +memmove = 0x4000035c; diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index e527be0e..4bb9e291 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -428,11 +428,14 @@ func readSVD(path, sourceURL string) (*Device, error) { licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n") } + // Remove "-" characters from the device name because such characters cannot + // be used in build tags. Necessary for the ESP32-C3 for example. + nameLower := strings.ReplaceAll(strings.ToLower(device.Name), "-", "") metadata := &Metadata{ File: filepath.Base(path), DescriptorSource: sourceURL, Name: device.Name, - NameLower: strings.ToLower(device.Name), + NameLower: nameLower, Description: strings.TrimSpace(device.Description), LicenseBlock: licenseBlock, }