From e498e7705f876640164965aab68f01d7b99c9e7b Mon Sep 17 00:00:00 2001 From: Denys Vitali Date: Tue, 3 Oct 2023 00:46:38 +0200 Subject: [PATCH 01/12] feat: add initial support for ESP32-S3 (#3442) --- lib/cmsis-svd | 2 +- src/device/esp/esp32s3.S | 57 +++++++ src/internal/task/task_stack_esp32.go | 2 +- src/machine/machine_esp32s3.go | 204 ++++++++++++++++++++++++++ src/runtime/runtime_esp32s3.go | 155 +++++++++++++++++++ targets/esp32s3.json | 21 +++ targets/esp32s3.ld | 203 +++++++++++++++++++++++++ 7 files changed, 642 insertions(+), 2 deletions(-) create mode 100644 src/device/esp/esp32s3.S create mode 100644 src/machine/machine_esp32s3.go create mode 100644 src/runtime/runtime_esp32s3.go create mode 100644 targets/esp32s3.json create mode 100644 targets/esp32s3.ld diff --git a/lib/cmsis-svd b/lib/cmsis-svd index 05a9562ec5..c65f8551e5 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit 05a9562ec59b87945a8d7177a4b08b7aa2f2fd58 +Subproject commit c65f8551e57c770344d229dcaa0bf838fa29aff4 diff --git a/src/device/esp/esp32s3.S b/src/device/esp/esp32s3.S new file mode 100644 index 0000000000..1179a2daa1 --- /dev/null +++ b/src/device/esp/esp32s3.S @@ -0,0 +1,57 @@ + +// The following definitions were copied from: +// esp-idf/components/xtensa/include/xtensa/corebits.h +#define PS_WOE_MASK 0x00040000 +#define PS_OWB_MASK 0x00000F00 +#define PS_CALLINC_MASK 0x00030000 +#define PS_WOE PS_WOE_MASK + +// Only calling it call_start_cpu0 for consistency with ESP-IDF. +.section .text.call_start_cpu0 +1: + .long _stack_top +.global call_start_cpu0 +call_start_cpu0: + // We need to set the stack pointer to a different value. This is somewhat + // complicated in the Xtensa architecture. The code below is a modified + // version of the following code: + // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 + + // Disable WOE. + rsr.ps a2 + movi a3, ~(PS_WOE_MASK) + and a2, a2, a3 + wsr.ps a2 + rsync + + // Set WINDOWSTART to 1 << WINDOWBASE. + rsr.windowbase a2 + ssl a2 + movi a2, 1 + sll a2, a2 + wsr.windowstart a2 + rsync + + // Load new stack pointer. + l32r sp, 1b + + // Re-enable WOE. + rsr.ps a2 + movi a3, PS_WOE + or a2, a2, a3 + wsr.ps a2 + rsync + + // Enable the FPU (coprocessor 0 so the lowest bit). + movi a2, 1 + wsr.cpenable a2 + rsync + + // Jump to the runtime start function written in Go. + call4 main + +.section .text.tinygo_scanCurrentStack +.global tinygo_scanCurrentStack +tinygo_scanCurrentStack: + // TODO: save callee saved registers on the stack + j tinygo_scanstack diff --git a/src/internal/task/task_stack_esp32.go b/src/internal/task/task_stack_esp32.go index e0bfa5b447..e32df88e39 100644 --- a/src/internal/task/task_stack_esp32.go +++ b/src/internal/task/task_stack_esp32.go @@ -1,4 +1,4 @@ -//go:build scheduler.tasks && esp32 +//go:build scheduler.tasks && (esp32 || esp32s3) package task diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go new file mode 100644 index 0000000000..0e488deb61 --- /dev/null +++ b/src/machine/machine_esp32s3.go @@ -0,0 +1,204 @@ +//go:build esp32s3 + +package machine + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +const deviceName = esp.Device + +const peripheralClock = 80000000 // 80MHz + +// 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 +} + +var ( + ErrInvalidSPIBus = errors.New("machine: invalid SPI bus") +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +// Hardware pin numbers +const ( + GPIO0 Pin = 0 + GPIO1 Pin = 1 + GPIO2 Pin = 2 + GPIO3 Pin = 3 + GPIO4 Pin = 4 + GPIO5 Pin = 5 + GPIO6 Pin = 6 + GPIO7 Pin = 7 + GPIO8 Pin = 8 + GPIO9 Pin = 9 + GPIO10 Pin = 10 + GPIO11 Pin = 11 + GPIO12 Pin = 12 + GPIO13 Pin = 13 + GPIO14 Pin = 14 + GPIO15 Pin = 15 + GPIO16 Pin = 16 + GPIO17 Pin = 17 + GPIO18 Pin = 18 + GPIO19 Pin = 19 + GPIO21 Pin = 21 + GPIO22 Pin = 22 + GPIO23 Pin = 23 + GPIO25 Pin = 25 + GPIO26 Pin = 26 + GPIO27 Pin = 27 + GPIO32 Pin = 32 + GPIO33 Pin = 33 + GPIO34 Pin = 34 + GPIO35 Pin = 35 + GPIO36 Pin = 36 + GPIO37 Pin = 37 + GPIO38 Pin = 38 + GPIO39 Pin = 39 + GPIO40 Pin = 40 + GPIO41 Pin = 41 + GPIO42 Pin = 42 + GPIO43 Pin = 43 + GPIO44 Pin = 44 +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + // Output function 256 is a special value reserved for use as a regular GPIO + // pin. Peripherals (SPI etc) can set a custom output function by calling + // lowercase configure() instead with a signal name. + p.configure(config, 256) +} + +// configure is the same as Configure, but allows for setting a specific input +// or output signal. +// Signals are always routed through the GPIO matrix for simplicity. Output +// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal +// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG, +// which sets the pin to use for a particular input signal. +func (p Pin) configure(config PinConfig, signal uint32) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + // TODO: Mux config +} + +// 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.Add(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.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*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) { + if p < 32 { + return &esp.GPIO.OUT_W1TS, 1 << p + } else { + return &esp.GPIO.OUT1_W1TS, 1 << (p - 32) + } +} + +func (p Pin) portMaskClear() (*volatile.Register32, uint32) { + if p < 32 { + return &esp.GPIO.OUT_W1TC, 1 << p + } else { + return &esp.GPIO.OUT1_W1TC, 1 << (p - 32) + } +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + if p < 32 { + return esp.GPIO.IN.Get()&(1<>16)&0xff >= 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 +} + +func (uart *UART) flush() {} + +// TODO: SPI diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go new file mode 100644 index 0000000000..fba430751f --- /dev/null +++ b/src/runtime/runtime_esp32s3.go @@ -0,0 +1,155 @@ +//go:build esp32s3 + +package runtime + +import ( + "device" + "device/esp" + "machine" + "unsafe" +) + +type timeUnit int64 + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +// Initialize .bss: zero-initialized global variables. +// The .data section has already been loaded by the ROM bootloader. +func clearbss() { + ptr := unsafe.Pointer(&_sbss) + for ptr != unsafe.Pointer(&_ebss) { + *(*uint32)(ptr) = 0 + ptr = unsafe.Add(ptr, 4) + } +} + +func initTimer() { + // 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(0 << esp.TIMG_T0CONFIG_T0_EN_Pos) + // TODO: Add support for esp32s3 + // esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<IRAM + + /* 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/ */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >DRAM + + /* Constant global variables. + * They are loaded in DRAM for ease of use. Eventually they should be stored + * in flash and loaded directly from there but they're kept in RAM to make + * sure they can always be accessed (even in interrupts). + */ + .rodata : ALIGN(4) + { + *(.rodata) + *(.rodata.*) + } >DRAM + + /* Mutable global variables. + */ + .data : ALIGN(4) + { + _sdata = ABSOLUTE(.); + *(.data) + *(.data.*) + _edata = ABSOLUTE(.); + } >DRAM + + /* Check that the boot ROM stack (for the APP CPU) does not overlap with the + * data that is loaded by the boot ROM. There may be ways to avoid this + * issue if it occurs in practice. + * The magic value here is _stack_sentry in the boot ROM ELF file. + */ + ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") + + /* 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(.); + *(.bss) + *(.bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM +} + +/* For the garbage collector. + */ +_globals_start = _sdata; +_globals_end = _ebss; +_heap_start = _ebss; +_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); + +_stack_size = 4K; + +/* From ESP-IDF: + * components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld + * This is the subset that is sometimes used by LLVM during codegen, and thus + * must always be present. + */ +memset = 0x400011e8; +memcpy = 0x400011f4; +memmove = 0x40001200; +memcmp = 0x4000120c; + +/* From ESP-IDF: + * components/esp_rom/esp32/ld/esp32.rom.libgcc.ld + * These are called from LLVM during codegen. The original license is Apache + * 2.0, but I believe that a list of function names and addresses can't really + * be copyrighted. + */ +__absvdi2 = 0x4000216c; +__absvsi2 = 0x40002178; +__adddf3 = 0x40002184; +__addsf3 = 0x40002190; +__addvdi3 = 0x4000219c; +__addvsi3 = 0x400021a8; +__ashldi3 = 0x400021b4; +__ashrdi3 = 0x400021c0; +__bswapdi2 = 0x400021cc; +__bswapsi2 = 0x400021d8; +__clear_cache = 0x400021e4; +__clrsbdi2 = 0x400021f0; +__clrsbsi2 = 0x400021fc; +__clzdi2 = 0x40002208; +__clzsi2 = 0x40002214; +__cmpdi2 = 0x40002220; +__ctzdi2 = 0x4000222c; +__ctzsi2 = 0x40002238; +__divdc3 = 0x40002244; +__divdf3 = 0x40002250; +__divdi3 = 0x4000225c; +__divsc3 = 0x40002268; +__divsf3 = 0x40002274; +__divsi3 = 0x40002280; +__eqdf2 = 0x4000228c; +__eqsf2 = 0x40002298; +__extendsfdf2 = 0x400022a4; +__ffsdi2 = 0x400022b0; +__ffssi2 = 0x400022bc; +__fixdfdi = 0x400022c8; +__fixdfsi = 0x400022d4; +__fixsfdi = 0x400022e0; +__fixsfsi = 0x400022ec; +__fixunsdfsi = 0x400022f8; +__fixunssfdi = 0x40002304; +__fixunssfsi = 0x40002310; +__floatdidf = 0x4000231c; +__floatdisf = 0x40002328; +__floatsidf = 0x40002334; +__floatsisf = 0x40002340; +__floatundidf = 0x4000234c; +__floatundisf = 0x40002358; +__floatunsidf = 0x40002364; +__floatunsisf = 0x40002370; +__gcc_bcmp = 0x4000237c; +__gedf2 = 0x40002388; +__gesf2 = 0x40002394; +__gtdf2 = 0x400023a0; +__gtsf2 = 0x400023ac; +__ledf2 = 0x400023b8; +__lesf2 = 0x400023c4; +__lshrdi3 = 0x400023d0; +__ltdf2 = 0x400023dc; +__ltsf2 = 0x400023e8; +__moddi3 = 0x400023f4; +__modsi3 = 0x40002400; +__muldc3 = 0x4000240c; +__muldf3 = 0x40002418; +__muldi3 = 0x40002424; +__mulsc3 = 0x40002430; +__mulsf3 = 0x4000243c; +__mulsi3 = 0x40002448; +__mulvdi3 = 0x40002454; +__mulvsi3 = 0x40002460; +__nedf2 = 0x4000246c; +__negdf2 = 0x40002478; +__negdi2 = 0x40002484; +__negsf2 = 0x40002490; +__negvdi2 = 0x4000249c; +__negvsi2 = 0x400024a8; +__nesf2 = 0x400024b4; +__paritysi2 = 0x400024c0; +__popcountdi2 = 0x400024cc; +__popcountsi2 = 0x400024d8; +__powidf2 = 0x400024e4; +__powisf2 = 0x400024f0; +__subdf3 = 0x400024fc; +__subsf3 = 0x40002508; +__subvdi3 = 0x40002514; +__subvsi3 = 0x40002520; +__truncdfsf2 = 0x4000252c; +__ucmpdi2 = 0x40002538; +__udivdi3 = 0x40002544; +__udivmoddi4 = 0x40002550; +__udivsi3 = 0x4000255c; +__udiv_w_sdiv = 0x40002568; +__umoddi3 = 0x40002574; +__umodsi3 = 0x40002580; +__unorddf2 = 0x4000258c; +__unordsf2 = 0x40002598; From 6a29ea21f558a3da8634babc1c3bd856974baa12 Mon Sep 17 00:00:00 2001 From: Denys Vitali Date: Wed, 13 Dec 2023 01:01:25 +0100 Subject: [PATCH 02/12] feat: add initial support for esp32-s3 --- src/device/esp/esp32s3.S | 2 +- src/machine/machine_esp32s3.go | 8 +- src/runtime/runtime_esp32s3.go | 183 ++++++++++----------------------- src/runtime/runtime_esp32sx.go | 86 ++++++++++++++++ targets/esp32s3.json | 2 +- targets/esp32s3.ld | 18 ++-- 6 files changed, 155 insertions(+), 144 deletions(-) create mode 100644 src/runtime/runtime_esp32sx.go diff --git a/src/device/esp/esp32s3.S b/src/device/esp/esp32s3.S index 1179a2daa1..7eb2bf0754 100644 --- a/src/device/esp/esp32s3.S +++ b/src/device/esp/esp32s3.S @@ -2,7 +2,7 @@ // The following definitions were copied from: // esp-idf/components/xtensa/include/xtensa/corebits.h #define PS_WOE_MASK 0x00040000 -#define PS_OWB_MASK 0x00000F00 +#define PS_OWB_MASK 0x00000F01 #define PS_CALLINC_MASK 0x00030000 #define PS_WOE PS_WOE_MASK diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go index 0e488deb61..c9f6220053 100644 --- a/src/machine/machine_esp32s3.go +++ b/src/machine/machine_esp32s3.go @@ -11,12 +11,12 @@ import ( const deviceName = esp.Device -const peripheralClock = 80000000 // 80MHz +const peripheralClock = 40_000000 // 80MHz // 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 + return 160e6 // 80 MHz } var ( @@ -71,6 +71,8 @@ const ( GPIO42 Pin = 42 GPIO43 Pin = 43 GPIO44 Pin = 44 + GPIO45 Pin = 45 + GPIO46 Pin = 46 ) // Configure this pin with the given configuration. @@ -170,7 +172,7 @@ var DefaultUART = UART0 var ( UART0 = &_UART0 - _UART0 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} + _UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()} UART1 = &_UART1 _UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} UART2 = &_UART2 diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go index fba430751f..c0a4f6c315 100644 --- a/src/runtime/runtime_esp32s3.go +++ b/src/runtime/runtime_esp32s3.go @@ -3,153 +3,78 @@ package runtime import ( - "device" "device/esp" - "machine" - "unsafe" ) -type timeUnit int64 +// This is the function called on startup after the flash (IROM/DROM) is +// initialized and the stack pointer has been set. +// +//export main +func main() { + // This initialization configures the following things: + // * It disables all watchdog timers. They might be useful at some point in + // the future, but will need integration into the scheduler. For now, + // they're all disabled. + // * It sets the CPU frequency to 160MHz, which is the maximum speed allowed + // for this CPU. Lower frequencies might be possible in the future, but + // running fast and sleeping quickly is often also a good strategy to save + // power. + // TODO: protect certain memory regions, especially the area below the stack + // to protect against stack overflows. See + // esp_cpu_configure_region_protection in ESP-IDF. + + // Disable RTC watchdog. + esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x50D83AA1) + esp.RTC_CNTL.RTC_WDTCONFIG0.Set(0) + esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x0) // Re-enable write protect -func putchar(c byte) { - machine.Serial.WriteByte(c) -} + // Disable Timer 0 watchdog. + esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) // write protect + esp.TIMG1.WDTCONFIG0.Set(0) // disable TG0 WDT + esp.TIMG1.WDTWPROTECT.Set(0x0) // Re-enable write protect -func getchar() byte { - for machine.Serial.Buffered() == 0 { - Gosched() - } - v, _ := machine.Serial.ReadByte() - return v -} + esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) // write protect + esp.TIMG0.WDTCONFIG0.Set(0) // disable TG0 WDT + esp.TIMG0.WDTWPROTECT.Set(0x0) // Re-enable write protect -func buffered() int { - return machine.Serial.Buffered() -} + // Disable super watchdog. + esp.RTC_CNTL.RTC_SWD_WPROTECT.Set(0x8F1D312A) + esp.RTC_CNTL.RTC_SWD_CONF.Set(esp.RTC_CNTL_RTC_SWD_CONF_SWD_DISABLE) + esp.RTC_CNTL.RTC_SWD_WPROTECT.Set(0x0) // Re-enable write protect -// Initialize .bss: zero-initialized global variables. -// The .data section has already been loaded by the ROM bootloader. -func clearbss() { - ptr := unsafe.Pointer(&_sbss) - for ptr != unsafe.Pointer(&_ebss) { - *(*uint32)(ptr) = 0 - ptr = unsafe.Add(ptr, 4) - } -} + // // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to + // // the PLL clock source (see table "CPU Clock Frequency" in the reference + // // manual). + // esp.SYSTEM.SYSCLK_CONF.Set(1 << esp.SYSTEM_SYSCLK_CONF_SOC_CLK_SEL_Pos) -func initTimer() { - // 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(0 << esp.TIMG_T0CONFIG_T0_EN_Pos) - // TODO: Add support for esp32s3 - // esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2< Date: Sat, 19 Apr 2025 22:52:22 +0200 Subject: [PATCH 03/12] esp32s3: fix merge errors --- lib/cmsis-svd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cmsis-svd b/lib/cmsis-svd index c65f8551e5..05a9562ec5 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit c65f8551e57c770344d229dcaa0bf838fa29aff4 +Subproject commit 05a9562ec59b87945a8d7177a4b08b7aa2f2fd58 From cf85cca174468029d6d6a9f0cf26fc07ce2803dc Mon Sep 17 00:00:00 2001 From: Olivier Fauchon Date: Wed, 23 Apr 2025 14:34:23 +0200 Subject: [PATCH 04/12] esp32s3: Fix Watchdog registers bad names --- src/runtime/runtime_esp32s3.go | 12 ++++++------ src/runtime/runtime_esp32sx.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go index c0a4f6c315..3a67764b76 100644 --- a/src/runtime/runtime_esp32s3.go +++ b/src/runtime/runtime_esp32s3.go @@ -24,9 +24,9 @@ func main() { // esp_cpu_configure_region_protection in ESP-IDF. // Disable RTC watchdog. - esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x50D83AA1) - esp.RTC_CNTL.RTC_WDTCONFIG0.Set(0) - esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x0) // Re-enable write protect + esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1) + esp.RTC_CNTL.WDTCONFIG0.Set(0) + esp.RTC_CNTL.WDTWPROTECT.Set(0x0) // Re-enable write protect // Disable Timer 0 watchdog. esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) // write protect @@ -38,9 +38,9 @@ func main() { esp.TIMG0.WDTWPROTECT.Set(0x0) // Re-enable write protect // Disable super watchdog. - esp.RTC_CNTL.RTC_SWD_WPROTECT.Set(0x8F1D312A) - esp.RTC_CNTL.RTC_SWD_CONF.Set(esp.RTC_CNTL_RTC_SWD_CONF_SWD_DISABLE) - esp.RTC_CNTL.RTC_SWD_WPROTECT.Set(0x0) // Re-enable write protect + esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A) + esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE) + esp.RTC_CNTL.SWD_WPROTECT.Set(0x0) // Re-enable write protect // // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to // // the PLL clock source (see table "CPU Clock Frequency" in the reference diff --git a/src/runtime/runtime_esp32sx.go b/src/runtime/runtime_esp32sx.go index fc087dc64d..b30dc37e97 100644 --- a/src/runtime/runtime_esp32sx.go +++ b/src/runtime/runtime_esp32sx.go @@ -8,7 +8,7 @@ import ( "unsafe" ) -type timeUnit int64 +//type timeUnit int64 func putchar(c byte) { machine.Serial.WriteByte(c) @@ -43,7 +43,7 @@ func initTimer() { // DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two // (40MHz). // esp.TIMG0.T0CONFIG.Set(0 << esp.TIMG_T0CONFIG_T0_EN_Pos) - esp.TIMG0.T0CONFIG.Set(esp.TIMG_TCONFIG_T0_EN | esp.TIMG_TCONFIG_T0_INCREASE | 2< Date: Thu, 24 Apr 2025 17:45:16 +0200 Subject: [PATCH 05/12] esp32s3: fix linker relocation errors and support for ESP binary --- builder/build.go | 2 +- builder/esp.go | 3 ++- targets/esp32s3.ld | 21 ++++++++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/builder/build.go b/builder/build.go index e1634a3b06..a598f01965 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1042,7 +1042,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return result, err } - case "esp32", "esp32-img", "esp32c3", "esp8266": + case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). result.Binary = filepath.Join(tmpdir, "main"+outext) diff --git a/builder/esp.go b/builder/esp.go index 67f9384d43..c866a5c316 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -100,11 +100,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error { chip_id := map[string]uint16{ "esp32": 0x0000, "esp32c3": 0x0005, + "esp32s3": 0x0009, }[chip] // Image header. switch chip { - case "esp32", "esp32c3": + case "esp32", "esp32c3", "esp32s3": // Header format: // 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 diff --git a/targets/esp32s3.ld b/targets/esp32s3.ld index 048cdebdee..65b0a99466 100644 --- a/targets/esp32s3.ld +++ b/targets/esp32s3.ld @@ -23,13 +23,20 @@ SECTIONS * code should be executed directly from flash. * Note that literals must be before code for the l32r instruction to work. */ - .text : ALIGN(4) - { - *(.literal.text.call_start_cpu0) - *(.text.call_start_cpu0) - *(.literal .text) - *(.literal.* .text.*) - } >IRAM +.text.call_start_cpu0 : ALIGN(4) +{ + *(.literal.call_start_cpu0) + *(.text.call_start_cpu0) +} >IRAM + +/* All other code and literals */ +.text : ALIGN(4) +{ + *(.literal .text) + *(.literal.* .text.*) + *(.text) + *(.text.*) +} >IRAM /* Put the stack at the bottom of DRAM, so that the application will * crash on stack overflow instead of silently corrupting memory. From 4e63a7159c421d182ce0049252f98a86feb3e23a Mon Sep 17 00:00:00 2001 From: cjpeterson Date: Wed, 12 Nov 2025 14:00:40 -0700 Subject: [PATCH 06/12] esp32s3: fix memory section overlap --- targets/esp32s3.ld | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/targets/esp32s3.ld b/targets/esp32s3.ld index 65b0a99466..9e4156ea13 100644 --- a/targets/esp32s3.ld +++ b/targets/esp32s3.ld @@ -19,6 +19,16 @@ ENTRY(call_start_cpu0) SECTIONS { + /* 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/ */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >DRAM + /* Constant literals and code. Loaded into IRAM for now. Eventually, most * code should be executed directly from flash. * Note that literals must be before code for the l32r instruction to work. @@ -27,7 +37,7 @@ SECTIONS { *(.literal.call_start_cpu0) *(.text.call_start_cpu0) -} >IRAM +} >IRAM AT >DRAM /* All other code and literals */ .text : ALIGN(4) @@ -36,17 +46,7 @@ SECTIONS *(.literal.* .text.*) *(.text) *(.text.*) -} >IRAM - - /* 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/ */ - .stack (NOLOAD) : - { - . = ALIGN(16); - . += _stack_size; - _stack_top = .; - } >DRAM +} >IRAM AT >DRAM /* Constant global variables. * They are loaded in DRAM for ease of use. Eventually they should be stored From 505b8740611c9a91cfe4cd54c526149afd47e7a1 Mon Sep 17 00:00:00 2001 From: cjpeterson Date: Wed, 12 Nov 2025 21:17:13 -0700 Subject: [PATCH 07/12] esp32s3: correct clock frequencies --- src/machine/machine_esp32s3.go | 55 ++++++++++++++++++++++++++++++---- src/runtime/runtime_esp32s3.go | 19 ++++++------ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go index c9f6220053..fe1928f6bc 100644 --- a/src/machine/machine_esp32s3.go +++ b/src/machine/machine_esp32s3.go @@ -11,12 +11,54 @@ import ( const deviceName = esp.Device -const peripheralClock = 40_000000 // 80MHz +const xtalClock = 40_000000 // 40MHz +const apbClock = 80_000000 // 80MHz +const cryptoPWMClock = 160_000000 // 160MHz + +// GetCPUFrequency returns the current CPU frequency of the chip. +func GetCPUFrequency() (uint32, error) { + switch esp.SYSTEM.GetSYSCLK_CONF_SOC_CLK_SEL() { + case 0: + return xtalClock / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil + case 1: + switch esp.SYSTEM.GetCPU_PER_CONF_CPUPERIOD_SEL() { + case 0: + return 80e6, nil + case 1: + return 160e6, nil + case 2: + // If esp.SYSTEM.GetCPU_PER_CONF_PLL_FREQ_SEL() == 1, this is undefined + return 240e6, nil + } + case 2: + //RC Fast Clock + return (175e5) / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil + } + return 0, errors.New("machine: Unable to determine current cpu frequency") +} -// 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 // 80 MHz +// SetCPUFrequency sets the frequency of the CPU to one of several targets +func SetCPUFrequency(frequency uint32) error { + // Always assume we are on PLL. Lower frequencies can be set with a different + // clock source, but this will change the behavior of APB clock and Crypto PWM + // clock + //esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1) + + switch frequency { + case 80_000000: + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0) + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) // Reduce PLL freq when possible + return nil + case 160_000000: + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1) + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) + return nil + case 240_000000: + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) // Increase PLL freq when needed + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) + return nil + } + return errors.New("machine: Unsupported CPU frequency selected. Supported: 80, 160, 240 MHz") } var ( @@ -188,7 +230,8 @@ func (uart *UART) Configure(config UARTConfig) { if config.BaudRate == 0 { config.BaudRate = 115200 } - uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate) + // Crystal clock source is selected by default + uart.Bus.CLKDIV.Set(xtalClock / config.BaudRate) } func (uart *UART) writeByte(b byte) error { diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go index 3a67764b76..e092da40b2 100644 --- a/src/runtime/runtime_esp32s3.go +++ b/src/runtime/runtime_esp32s3.go @@ -15,7 +15,7 @@ func main() { // * It disables all watchdog timers. They might be useful at some point in // the future, but will need integration into the scheduler. For now, // they're all disabled. - // * It sets the CPU frequency to 160MHz, which is the maximum speed allowed + // * It sets the CPU frequency to 240MHz, which is the maximum speed allowed // for this CPU. Lower frequencies might be possible in the future, but // running fast and sleeping quickly is often also a good strategy to save // power. @@ -42,16 +42,15 @@ func main() { esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE) esp.RTC_CNTL.SWD_WPROTECT.Set(0x0) // Re-enable write protect - // // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to - // // the PLL clock source (see table "CPU Clock Frequency" in the reference - // // manual). - // esp.SYSTEM.SYSCLK_CONF.Set(1 << esp.SYSTEM_SYSCLK_CONF_SOC_CLK_SEL_Pos) + // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to the + // PLL clock source (see table "CPU Clock Frequency" in the reference manual). + esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1) - // // Change CPU frequency from 80MHz to 160MHz by setting SYSTEM_CPUPERIOD_SEL - // // to 1 (see table "CPU Clock Frequency" in the reference manual). - // // Note: we might not want to set SYSTEM_CPU_WAIT_MODE_FORCE_ON to save - // // power. It is set here to keep the default on reset. - // esp.SYSTEM.CPU_PER_CONF.Set(esp.SYSTEM_CPU_PER_CONF_CPU_WAIT_MODE_FORCE_ON | esp.SYSTEM_CPU_PER_CONF_PLL_FREQ_SEL | 1< Date: Fri, 14 Nov 2025 16:37:56 -0700 Subject: [PATCH 08/12] esp32s3: more stable cpu --- src/runtime/runtime_esp32s3.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go index e092da40b2..35cd26da85 100644 --- a/src/runtime/runtime_esp32s3.go +++ b/src/runtime/runtime_esp32s3.go @@ -52,7 +52,10 @@ func main() { esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) - clearbss() + // Clear bss. Repeat many times while we wait for cpu/clock to stabilize + for x := 0; x < 30; x++ { + clearbss() + } // Initialize main system timer used for time.Now. initTimer() From d2971bfa094778315532a406f65ef916a61a3c5c Mon Sep 17 00:00:00 2001 From: cjpeterson Date: Fri, 14 Nov 2025 16:40:05 -0700 Subject: [PATCH 09/12] esp32s3: enable basic gpio support --- src/machine/machine_esp32s3.go | 77 ++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go index fe1928f6bc..65261bd3c8 100644 --- a/src/machine/machine_esp32s3.go +++ b/src/machine/machine_esp32s3.go @@ -43,7 +43,7 @@ func SetCPUFrequency(frequency uint32) error { // clock source, but this will change the behavior of APB clock and Crypto PWM // clock //esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1) - + switch frequency { case 80_000000: esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0) @@ -94,12 +94,14 @@ const ( GPIO17 Pin = 17 GPIO18 Pin = 18 GPIO19 Pin = 19 + GPIO20 Pin = 20 GPIO21 Pin = 21 - GPIO22 Pin = 22 - GPIO23 Pin = 23 - GPIO25 Pin = 25 GPIO26 Pin = 26 GPIO27 Pin = 27 + GPIO28 Pin = 28 + GPIO29 Pin = 29 + GPIO30 Pin = 30 + GPIO31 Pin = 31 GPIO32 Pin = 32 GPIO33 Pin = 33 GPIO34 Pin = 34 @@ -115,6 +117,8 @@ const ( GPIO44 Pin = 44 GPIO45 Pin = 45 GPIO46 Pin = 46 + GPIO47 Pin = 47 + GPIO48 Pin = 48 ) // Configure this pin with the given configuration. @@ -137,7 +141,68 @@ func (p Pin) configure(config PinConfig, signal uint32) { return } - // TODO: Mux config + ioConfig := uint32(0) + + // MCU_SEL: Function 1 is always GPIO + ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos) + + // FUN_IE: Make this pin an input pin (always set for GPIO operation) + ioConfig |= esp.IO_MUX_GPIO_FUN_IE + + // DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special + var drive uint32 + if p == GPIO17 || p == GPIO18 { + drive = 1 // 20 mA + } else { + drive = 2 // 20 mA + } + ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos) + + // WPU/WPD: Select pull mode. + if config.Mode == PinInputPullup { + ioConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + ioConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Set configuration + ioRegister := p.ioMuxReg() + ioRegister.Set(ioConfig) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + if p < 32 { + esp.GPIO.ENABLE_W1TS.Set(1 << p) + } else { + esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32)) + } + // Set the signal to read the output value from. It can be a peripheral + // output signal, or the special value 256 which indicates regular GPIO + // usage. + p.outFunc().Set(signal) + case PinInput, PinInputPullup, PinInputPulldown: + // Clear the 'output enable' bit. + if p < 32 { + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } else { + esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) + } + if signal != 256 { + // Signal is a peripheral function (not a simple GPIO). Connect this + // signal to the pin. + // Note that outFunc and inFunc work in the opposite direction. + // outFunc configures a pin to use a given output signal, while + // inFunc specifies a pin to use to read the signal from. + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)< Date: Sat, 15 Nov 2025 14:54:08 -0700 Subject: [PATCH 10/12] esp32s3: simplify loading and check extensions --- builder/builder_test.go | 1 + src/device/esp/esp32s3.S | 57 ---------------------------------------- targets/esp32s3.json | 2 +- 3 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 src/device/esp/esp32s3.S diff --git a/builder/builder_test.go b/builder/builder_test.go index 6b84b10070..8e62d9183c 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) { "cortex-m4", "cortex-m7", "esp32c3", + "esp32s3", "fe310", "gameboy-advance", "k210", diff --git a/src/device/esp/esp32s3.S b/src/device/esp/esp32s3.S deleted file mode 100644 index 7eb2bf0754..0000000000 --- a/src/device/esp/esp32s3.S +++ /dev/null @@ -1,57 +0,0 @@ - -// The following definitions were copied from: -// esp-idf/components/xtensa/include/xtensa/corebits.h -#define PS_WOE_MASK 0x00040000 -#define PS_OWB_MASK 0x00000F01 -#define PS_CALLINC_MASK 0x00030000 -#define PS_WOE PS_WOE_MASK - -// Only calling it call_start_cpu0 for consistency with ESP-IDF. -.section .text.call_start_cpu0 -1: - .long _stack_top -.global call_start_cpu0 -call_start_cpu0: - // We need to set the stack pointer to a different value. This is somewhat - // complicated in the Xtensa architecture. The code below is a modified - // version of the following code: - // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 - - // Disable WOE. - rsr.ps a2 - movi a3, ~(PS_WOE_MASK) - and a2, a2, a3 - wsr.ps a2 - rsync - - // Set WINDOWSTART to 1 << WINDOWBASE. - rsr.windowbase a2 - ssl a2 - movi a2, 1 - sll a2, a2 - wsr.windowstart a2 - rsync - - // Load new stack pointer. - l32r sp, 1b - - // Re-enable WOE. - rsr.ps a2 - movi a3, PS_WOE - or a2, a2, a3 - wsr.ps a2 - rsync - - // Enable the FPU (coprocessor 0 so the lowest bit). - movi a2, 1 - wsr.cpenable a2 - rsync - - // Jump to the runtime start function written in Go. - call4 main - -.section .text.tinygo_scanCurrentStack -.global tinygo_scanCurrentStack -tinygo_scanCurrentStack: - // TODO: save callee saved registers on the stack - j tinygo_scanstack diff --git a/targets/esp32s3.json b/targets/esp32s3.json index d2d8776d26..3192dd090e 100644 --- a/targets/esp32s3.json +++ b/targets/esp32s3.json @@ -11,7 +11,7 @@ "libc": "picolibc", "linkerscript": "targets/esp32s3.ld", "extra-files": [ - "src/device/esp/esp32s3.S", + "src/device/esp/esp32.S", "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32s3", From a851febf253943516509fc2fd18197eb71f5344f Mon Sep 17 00:00:00 2001 From: cjpeterson Date: Sun, 16 Nov 2025 15:41:25 -0700 Subject: [PATCH 11/12] esp32s3: synchronize cpu features with clang --- targets/esp32s3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/esp32s3.json b/targets/esp32s3.json index 3192dd090e..f245b82ab8 100644 --- a/targets/esp32s3.json +++ b/targets/esp32s3.json @@ -1,7 +1,7 @@ { "inherits": ["xtensa"], "cpu": "esp32s3", - "features": "+atomctl,+bool,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", + "features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+div32,+esp32s3,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", "build-tags": ["esp32s3", "esp"], "scheduler": "tasks", "serial": "uart", From 190b99fa0dd29a09373e2d677f49b54398befa1d Mon Sep 17 00:00:00 2001 From: cjpeterson Date: Tue, 18 Nov 2025 17:25:28 -0700 Subject: [PATCH 12/12] esp32s3: correct iram origin --- targets/esp32s3.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/esp32s3.ld b/targets/esp32s3.ld index 9e4156ea13..3d23174a04 100644 --- a/targets/esp32s3.ld +++ b/targets/esp32s3.ld @@ -5,7 +5,7 @@ MEMORY { /* Note: DRAM and IRAM below are actually in the same 416K address space. */ DRAM (rw) : ORIGIN = 0x3FC88000, LENGTH = 416K /* Internal SRAM 1 (data bus) */ - IRAM (x) : ORIGIN = 0x40378000, LENGTH = 416K /* Internal SRAM 1 (instruction bus) */ + IRAM (x) : ORIGIN = 0x40370000, LENGTH = 416K /* Internal SRAM 1 (instruction bus) */ /* Note: DROM and IROM below are actually in the same 32M address space. */ DROM (r) : ORIGIN = 0x3C000000, LENGTH = 32M /* Data bus (read-only) */