diff --git a/builder/build.go b/builder/build.go index 1fffbca073..1e282aac3e 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1094,7 +1094,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return result, err } - case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp8266": + case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp32h2", "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/builder_test.go b/builder/builder_test.go index f10e66e636..194994b8a8 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -30,6 +30,7 @@ func TestClangAttributes(t *testing.T) { "cortex-m7", "esp32c3", "esp32c6", + "esp32h2", "esp32s3", "fe310", "gameboy-advance", diff --git a/builder/esp.go b/builder/esp.go index 9b59047a24..9f6545623e 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -169,6 +169,7 @@ func makeESPFirmwareImage(infile, outfile, format string) error { "esp32": 0x0000, "esp32c3": 0x0005, "esp32c6": 0x000d, + "esp32h2": 0x0010, "esp32s3": 0x0009, }[chip] @@ -180,12 +181,13 @@ func makeESPFirmwareImage(infile, outfile, format string) error { "esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10 "esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10 "esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding) + "esp32h2": 0x1f, // 48MHz=0x0F, 2MB=0x10 "esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10 }[chip] // Image header. switch chip { - case "esp32", "esp32c3", "esp32s3", "esp32c6": + case "esp32", "esp32c3", "esp32s3", "esp32c6", "esp32h2": // 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/make/smoketest.mk b/make/smoketest.mk index 3860dc1771..411c7be43a 100644 --- a/make/smoketest.mk +++ b/make/smoketest.mk @@ -510,6 +510,11 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) $(SMOKE_OUT).bin $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32c6 examples/blinkm @$(MD5SUM) $(SMOKE_OUT).bin + # esp32h2-devkitm-1 + $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32h2-devkitm-1 examples/machinetest + @$(MD5SUM) $(SMOKE_OUT).bin + $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32h2-devkitm-1 examples/serial + @$(MD5SUM) $(SMOKE_OUT).bin # xiao-esp32s3 $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32s3 examples/blinky1 @$(MD5SUM) $(SMOKE_OUT).bin diff --git a/src/device/esp/esp32c6.S b/src/device/esp/esp32c6.S index 7afd39f681..eb1007bbbc 100644 --- a/src/device/esp/esp32c6.S +++ b/src/device/esp/esp32c6.S @@ -1,12 +1,13 @@ -// This is a very minimal bootloader for the ESP32-C6. It only initializes the -// flash and then continues with the generic RISC-V initialization code, which -// in turn will call runtime.main. +// This is a very minimal bootloader for the ESP32-C6 and ESP32-H2. 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. // -// The ESP32-C6 has a unified IRAM/DRAM address space at 0x40800000, and -// separate DROM (0x42800000) / IROM (0x42000000) flash-mapped regions. +// The ESP32-C6 and ESP32-H2 have a unified IRAM/DRAM address space at +// 0x40800000, and separate DROM (0x42800000) / IROM (0x42000000) flash-mapped +// regions. .section .init .global call_start_cpu0 @@ -26,8 +27,8 @@ call_start_cpu0: call Cache_MMU_Init // Set up flash mapping (both IROM and DROM). - // On ESP32-C6, Cache_Dbus_MMU_Set is replaced by Cache_MSPI_MMU_Set - // which has an extra "sensitive" parameter. + // On ESP32-C6 and ESP32-H2, Cache_Dbus_MMU_Set is replaced by + // Cache_MSPI_MMU_Set which has an extra "sensitive" parameter. // C equivalent: // Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0) // Maps 16MB starting at 0x42000000, covering both IROM and DROM. diff --git a/src/machine/board_esp32h2-devkitm-1.go b/src/machine/board_esp32h2-devkitm-1.go new file mode 100644 index 0000000000..c1f36f94c9 --- /dev/null +++ b/src/machine/board_esp32h2-devkitm-1.go @@ -0,0 +1,23 @@ +//go:build esp32h2_devkitm_1 + +// This file contains the pin mappings for the Espressif ESP32-H2-DevKitM-1 board. +// +// - https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32h2/esp32-h2-devkitm-1/user_guide.html + +package machine + +// Data pin of the onboard addressable RGB LED. The board has no plain LED. +const ( + WS2812 = GPIO8 +) + +// BOOT button +const ( + BUTTON = GPIO9 +) + +// UART pins +const ( + UART_TX_PIN = GPIO24 + UART_RX_PIN = GPIO23 +) diff --git a/src/machine/machine_esp32c6_usb.go b/src/machine/machine_esp32c6_usb.go index f05d54c5ae..500bad0112 100644 --- a/src/machine/machine_esp32c6_usb.go +++ b/src/machine/machine_esp32c6_usb.go @@ -1,4 +1,4 @@ -//go:build esp32c6 +//go:build esp32c6 || esp32h2 package machine @@ -11,10 +11,10 @@ import ( ) // USB Serial/JTAG Controller -// The ESP32-C6 has the same built-in USB Serial/JTAG hardware IP as the -// ESP32-C3. The only difference at the machine level is how the peripheral -// clock is enabled: C3 uses SYSTEM.PERIP_CLK_EN0, while C6 uses the PCR -// (Peripheral Clock Reset) block. +// The ESP32-C6 and ESP32-H2 have the same built-in USB Serial/JTAG hardware IP +// as the ESP32-C3. The only difference at the machine level is how the +// peripheral clock is enabled: C3 uses SYSTEM.PERIP_CLK_EN0, while C6 and H2 +// use the PCR (Peripheral Clock Reset) block. const cpuInterruptFromUSB = 10 @@ -51,9 +51,9 @@ type Serialer interface { var usbConfigured bool -// USBDevice provides a stub USB device for the ESP32-C6. The hardware -// only supports a fixed-function CDC-ACM serial port, so the programmable -// USB device features are no-ops. +// USBDevice provides a stub USB device for the ESP32-C6 and ESP32-H2. The +// hardware only supports a fixed-function CDC-ACM serial port, so the +// programmable USB device features are no-ops. type USBDevice struct { initcomplete bool InitEndpointComplete bool @@ -84,8 +84,8 @@ func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { } usbConfigured = true - // Enable the USB_DEVICE peripheral clock via PCR (C6 uses PCR instead - // of SYSTEM.PERIP_CLK_EN0 that C3 used). + // Enable the USB_DEVICE peripheral clock via PCR (C6 and H2 use PCR + // instead of SYSTEM.PERIP_CLK_EN0 that C3 used). esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_CLK_EN(1) esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_RST_EN(0) @@ -240,16 +240,16 @@ func FlushSerial() { } } -// ConfigureUSBEndpoint is a no-op on ESP32-C6 — the hardware does not -// support programmable USB endpoints. +// ConfigureUSBEndpoint is a no-op on ESP32-C6 and ESP32-H2. The hardware does +// not support programmable USB endpoints. func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { } -// SendZlp is a no-op on ESP32-C6. +// SendZlp is a no-op on ESP32-C6 and ESP32-H2. func SendZlp() { } -// SendUSBInPacket is a no-op on ESP32-C6. +// SendUSBInPacket is a no-op on ESP32-C6 and ESP32-H2. func SendUSBInPacket(ep uint32, data []byte) bool { return false } diff --git a/src/machine/machine_esp32h2.go b/src/machine/machine_esp32h2.go new file mode 100644 index 0000000000..e3362a7482 --- /dev/null +++ b/src/machine/machine_esp32h2.go @@ -0,0 +1,544 @@ +//go:build esp32h2 + +package machine + +import ( + "device/esp" + "device/riscv" + "errors" + "runtime/interrupt" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = esp.Device +const maxPin = 28 +const cpuInterruptFromPin = 6 + +// CPU interrupts 3, 4 and 7 are bound to the CLINT and cannot be used. +// See ESP-IDF esp_hw_support/port/esp32h2/esp_cpu_intr.c. +const cpuInterruptFromUART = 13 + +// 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 32e6 // 32MHz +} + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown + PinAnalog +) + +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 + GPIO20 Pin = 20 + GPIO21 Pin = 21 + GPIO22 Pin = 22 + GPIO23 Pin = 23 + GPIO24 Pin = 24 + GPIO25 Pin = 25 + GPIO26 Pin = 26 + GPIO27 Pin = 27 +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = iota + 1 + PinFalling + PinToggle +) + +// 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 + + // FUN_IE: disable for PinAnalog (high-Z for ADC) + if config.Mode != PinAnalog { + 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 (no pulls for PinAnalog). + 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, PinAnalog: + // Clear the 'output enable' bit. + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } +} + +// configure is the same as Configure, but allows setting a specific GPIO matrix signal. +func (p Pin) configure(config PinConfig, signal uint32) { + p.Configure(config) + if signal == 256 { + return + } + if config.Mode == PinOutput { + p.outFunc().Set(signal) + } else { + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(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.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4)) +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + 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)) +} + +// mux returns the I/O mux configuration register corresponding to the given +// GPIO pin. +func (p Pin) mux() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4)) +} + +// pin returns the PIN register corresponding to the given GPIO pin. +func (p Pin) pin() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), 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) + } +} + +// Get returns the current value of a GPIO pin when configured as an input or as +// an output. +func (p Pin) Get() bool { + reg := &esp.GPIO.IN + return (reg.Get()>>p)&1 > 0 +} + +// 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 +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { + if p >= maxPin { + return ErrInvalidInputPin + } + + if callback == nil { + // Disable this pin interrupt + p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) + + if pinCallbacks[p] != nil { + pinCallbacks[p] = nil + } + return nil + } + + if pinCallbacks[p] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[p] = callback + + onceSetupPinInterrupt.Do(func() { + err = setupPinInterrupt() + }) + if err != nil { + return err + } + + p.pin().Set( + (p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) | + uint32(change)< 1 { + return errWrongStopBitSize + } + // - data length + uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5)) + // - stop bit + uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits)) + // - parity check + switch parity { + case ParityNone: + uart.Bus.SetCONF0_PARITY_EN(0) + case ParityEven: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(0) + case ParityOdd: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(1) + } + return nil +} + +func initUARTClock(bus *esp.UART_Type) { + // The UART clock is in PCR. SCLK_SEL 3 is the 32MHz XTAL. + // See ESP-IDF esp_hal_uart/esp32h2/include/hal/uart_ll.h uart_ll_set_sclk. + switch bus { + case esp.UART0: + esp.PCR.SetUART0_CONF_UART0_CLK_EN(1) + esp.PCR.SetUART0_CONF_UART0_RST_EN(0) // ensure not in reset + bus.SetCLK_CONF_RST_CORE(1) + esp.PCR.SetUART0_CONF_UART0_RST_EN(1) + esp.PCR.SetUART0_CONF_UART0_RST_EN(0) + bus.SetCLK_CONF_RST_CORE(0) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_SEL(3) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_EN(1) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(0) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_A(0) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_B(0) + case esp.UART1: + esp.PCR.SetUART1_CONF_UART1_CLK_EN(1) + esp.PCR.SetUART1_CONF_UART1_RST_EN(0) + bus.SetCLK_CONF_RST_CORE(1) + esp.PCR.SetUART1_CONF_UART1_RST_EN(1) + esp.PCR.SetUART1_CONF_UART1_RST_EN(0) + bus.SetCLK_CONF_RST_CORE(0) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_SEL(3) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_EN(1) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(0) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_A(0) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_B(0) + } + // wait for Core Clock to ready for configuration + for bus.GetREG_UPDATE() > 0 { + riscv.Asm("nop") + } +} + +func (uart *UART) SetBaudRate(baudRate uint32) { + // based on esp-idf + max_div := uint64((1 << 12) - 1) + baud := uint64(baudRate) + sclk_div := (uartClockFreq + (max_div * baud) - 1) / (max_div * baud) + clk_div := (uartClockFreq << 4) / (baud * sclk_div) + uart.Bus.SetCLKDIV(uint32(clk_div >> 4)) + uart.Bus.SetCLKDIV_FRAG(uint32(clk_div & 0xf)) + // The SCLK divisor is in the PCR register. + switch uart.Bus { + case esp.UART0: + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(uint32(sclk_div - 1)) + case esp.UART1: + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(uint32(sclk_div - 1)) + } +} + +func (uart *UART) setupPins(config UARTConfig, regs registerSet) { + config.RX.Configure(PinConfig{Mode: PinInputPullup}) + config.TX.Configure(PinConfig{Mode: PinInputPullup}) + + // link TX with GPIO signal X (technical reference manual, GPIO matrix) + if config.InvertTX { + config.TX.outFunc().Set(regs.gpioMatrixSignal | esp.GPIO_FUNC_OUT_SEL_CFG_INV_SEL) + } else { + config.TX.outFunc().Set(regs.gpioMatrixSignal) + } + // link RX with GPIO signal X and route signals via GPIO matrix + if config.InvertRX { + inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX) | esp.GPIO_FUNC_IN_SEL_CFG_IN_INV_SEL) + } else { + inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)) + } +} + +func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { + // Disable all UART interrupts + uart.Bus.INT_ENA.ClearBits(0x0ffff) + + intrMapReg.Set(cpuInterruptFromUART) + onceUart.Do(func() { + _ = interrupt.New(cpuInterruptFromUART, func(i interrupt.Interrupt) { + UART0.serveInterrupt(0) + UART1.serveInterrupt(1) + }).Enable() + }) +} + +func (uart *UART) serveInterrupt(num int) { + // get interrupt status + interrutFlag := uart.Bus.INT_ST.Get() + if (interrutFlag & uartInterrupts) == 0 { + return + } + + // block UART interrupts while processing + uart.Bus.INT_ENA.ClearBits(uartInterrupts) + + if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 { + for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 { + b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE() + if !uart.Buffer.Put(byte(b & 0xff)) { + uart.DataOverflowDetected = true + } + } + } + if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 { + uart.ParityErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA { + uart.DataErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA { + uart.DataOverflowDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA { + uart.DataErrorDetected = true + } + + // Clear the UART interrupt status + uart.Bus.INT_CLR.SetBits(interrutFlag) + uart.Bus.INT_CLR.ClearBits(interrutFlag) + // Enable interrupts + uart.Bus.INT_ENA.Set(uartInterrupts) +} + +const uart_empty_thresh_default = 10 + +func (uart *UART) enableTransmitter() { + uart.Bus.SetCONF0_TXFIFO_RST(1) + uart.Bus.SetCONF0_TXFIFO_RST(0) + uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) +} + +func (uart *UART) enableReceiver() { + uart.Bus.SetCONF0_RXFIFO_RST(1) + uart.Bus.SetCONF0_RXFIFO_RST(0) + uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1) + uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1) + uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1) + uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) +} + +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 { + // Wait until there is space in the transmit buffer. + } + uart.Bus.FIFO.Set(uint32(b)) + return nil +} + +func (uart *UART) flush() {} diff --git a/src/runtime/interrupt/interrupt_esp32c6.go b/src/runtime/interrupt/interrupt_esp32c6.go index 83cc5e54df..590f956aa3 100644 --- a/src/runtime/interrupt/interrupt_esp32c6.go +++ b/src/runtime/interrupt/interrupt_esp32c6.go @@ -1,4 +1,4 @@ -//go:build esp32c6 +//go:build esp32c6 || esp32h2 package interrupt @@ -12,12 +12,11 @@ import ( //go:extern tinygo_saved_ra var tinygo_saved_ra uintptr -// plicType maps the ESP32-C6 PLIC (Platform-Level Interrupt Controller) -// machine-mode registers. The C6 uses the PLIC — not the INTPRI/INTC block -// that the ESP32-C3 uses — as its CPU interrupt controller -// (SOC_INT_PLIC_SUPPORTED). The INTPRI registers at 0x600c5000 are vestigial -// backward-compatibility aliases on the C6 and are not wired to the CPU, so -// writing them never delivers an interrupt. +// plicType maps the PLIC (Platform-Level Interrupt Controller) machine-mode +// registers. The ESP32-C6 and ESP32-H2 use the PLIC, not the INTPRI/INTC +// block of the ESP32-C3, as the CPU interrupt controller +// (SOC_INT_PLIC_SUPPORTED). +// The INTPRI registers at 0x600c5000 are not wired to the CPU. type plicType struct { MXINT_ENABLE volatile.Register32 // 0x00 bit N enables CPU interrupt line N MXINT_TYPE volatile.Register32 // 0x04 bit N: 1=edge, 0=level @@ -31,10 +30,10 @@ type plicType struct { var plic = (*plicType)(unsafe.Pointer(uintptr(0x20001000))) // Enable registers a CPU interrupt. -// The ESP32-C6 has 31 CPU independent interrupts (1..31). +// The ESP32-C6 and ESP32-H2 have 31 CPU independent interrupts (1..31). func (i Interrupt) Enable() error { if i.num < 1 || i.num > 31 { - return errors.New("interrupt for ESP32-C6 must be in range of 1 through 31") + return errors.New("interrupt must be in range of 1 through 31") } mask := riscv.DisableInterrupts() defer riscv.EnableInterrupts(mask) @@ -101,7 +100,7 @@ const ( const ( defaultThreshold = 5 - // Priority 0 disables an interrupt on ESP32-C6. + // Priority 0 disables an interrupt on ESP32-C6 and ESP32-H2. disableThreshold = 0 ) diff --git a/src/runtime/runtime_esp32h2.go b/src/runtime/runtime_esp32h2.go new file mode 100644 index 0000000000..f8439d81ce --- /dev/null +++ b/src/runtime/runtime_esp32h2.go @@ -0,0 +1,74 @@ +//go:build esp32h2 + +package runtime + +import ( + "device/esp" +) + +// TIMG0 counts at 8MHz. The 32MHz XTAL is divided by 4. +const ( + timerDivider = 4 + nsPerTick = 125 +) + +// 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 32MHz from the XTAL. + + // Disable Timer Group 0 watchdog (unlock first). + esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) + esp.TIMG0.WDTCONFIG0.Set(0) + + // Disable Timer Group 1 watchdog (unlock first). + esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) + esp.TIMG1.WDTCONFIG0.Set(0) + + // Disable LP watchdog (write-protect key first). + esp.LP_WDT.WDTWPROTECT.Set(0x50D83AA1) + esp.LP_WDT.WDTCONFIG0.Set(0) + + // Disable super watchdog. + esp.LP_WDT.SWD_WPROTECT.Set(0x50D83AA1) + esp.LP_WDT.SWD_CONF.SetBits(1 << 30) // SWD_DISABLE bit + + // Run CPU, AHB and APB at 32MHz from the XTAL. The field values are divider-1. + // See ESP-IDF esp_hw_support/port/esp32h2/rtc_clk.c rtc_clk_cpu_freq_to_xtal. + esp.PCR.SetCPU_FREQ_CONF_CPU_DIV_NUM(0) + esp.PCR.SetAHB_FREQ_CONF_AHB_DIV_NUM(0) + esp.PCR.SetAPB_FREQ_CONF_APB_DIV_NUM(0) + esp.PCR.SetSYSCLK_CONF_SOC_CLK_SEL(0) + esp.PCR.SetBUS_CLK_UPDATE_BUS_CLOCK_UPDATE(1) + for esp.PCR.GetBUS_CLK_UPDATE_BUS_CLOCK_UPDATE() != 0 { + } + + // Use the 32MHz XTAL as the TIMG0 clock. 0 = XTAL, 1 = RC_FAST, 2 = PLL_F48M. + // See ESP-IDF esp_hal_timg/esp32h2/include/hal/timer_ll.h. + esp.PCR.SetTIMERGROUP0_CONF_TG0_CLK_EN(1) + esp.PCR.SetTIMERGROUP0_TIMER_CLK_CONF_TG0_TIMER_CLK_SEL(0) + esp.PCR.SetTIMERGROUP0_TIMER_CLK_CONF_TG0_TIMER_CLK_EN(1) + + clearbss() + + // Configure interrupt handler + interruptInit() + + // Initialize main system timer used for time.Now. + initTimer() + + // Initialize timer alarm interrupt for the scheduler. + initTimerInterrupt() + + // Initialize the heap, call main.main, etc. + run() + + // Fallback: if main ever returns, hang the CPU. + exit(0) +} diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go index 6737317ded..8d94ec78c0 100644 --- a/src/runtime/runtime_esp32xx.go +++ b/src/runtime/runtime_esp32xx.go @@ -1,4 +1,4 @@ -//go:build esp32 || esp32c3 || esp32c6 +//go:build esp32 || esp32c3 || esp32c6 || esp32h2 package runtime diff --git a/src/runtime/runtime_esp32xx_exit.go b/src/runtime/runtime_esp32xx_exit.go index 25a2010ca5..004d84ab4a 100644 --- a/src/runtime/runtime_esp32xx_exit.go +++ b/src/runtime/runtime_esp32xx_exit.go @@ -1,4 +1,4 @@ -//go:build (esp32 && !qemu) || esp32c3 || esp32c6 +//go:build (esp32 && !qemu) || esp32c3 || esp32c6 || esp32h2 package runtime diff --git a/src/runtime/runtime_esp32xx_plic.go b/src/runtime/runtime_esp32xx_plic.go index a3c3093f61..9b307bbbf6 100644 --- a/src/runtime/runtime_esp32xx_plic.go +++ b/src/runtime/runtime_esp32xx_plic.go @@ -1,4 +1,4 @@ -//go:build esp32c6 +//go:build esp32c6 || esp32h2 package runtime diff --git a/targets/esp32h2-devkitm-1.json b/targets/esp32h2-devkitm-1.json new file mode 100644 index 0000000000..c6eed37472 --- /dev/null +++ b/targets/esp32h2-devkitm-1.json @@ -0,0 +1,7 @@ +{ + "inherits": ["esp32h2"], + "build-tags": ["esp32h2_devkitm_1"], + "serial": "uart", + "flash-method": "esp32flash", + "serial-port": ["10c4:ea60"] +} diff --git a/targets/esp32h2.json b/targets/esp32h2.json new file mode 100644 index 0000000000..465ad319a2 --- /dev/null +++ b/targets/esp32h2.json @@ -0,0 +1,32 @@ +{ + "inherits": [ + "riscv32" + ], + "inheritable-only": true, + "features": "+32bit,+a,+c,+m,+zaamo,+zalrsc,+zmmul,-relax", + "build-tags": [ + "esp32h2", + "esp" + ], + "serial": "usb", + "rtlib": "compiler-rt", + "libc": "picolibc", + "default-stack-size": 8192, + "linkerscript": "targets/esp32h2.ld", + "extra-files": [ + "src/device/esp/esp32c6.S" + ], + "binary-format": "esp32h2", + "flash-method": "esp32jtag", + "serial-port": [ + "303a:1001" + ], + "openocd-interface": "esp_usb_jtag", + "openocd-target": "esp32h2", + "openocd-commands": [ + "gdb_memory_map disable" + ], + "gdb": [ + "riscv32-esp-elf-gdb" + ] +} diff --git a/targets/esp32h2.ld b/targets/esp32h2.ld new file mode 100644 index 0000000000..871fc43478 --- /dev/null +++ b/targets/esp32h2.ld @@ -0,0 +1,172 @@ +/* Linker script for the ESP32-H2. + * The memory layout is the same as the ESP32-C6. Only the SRAM size (320kB) + * and the ROM function addresses are different. + * See ESP-IDF components/soc/esp32h2/include/soc/soc.h. + * + * Keep .data and .iram before the dummy sections. lld can calculate a wrong + * value for a forward SIZEOF() reference, and then the image is corrupt. + */ +MEMORY +{ + /* HP-SRAM. IRAM and DRAM use the same address space. */ + SRAM (rwx) : ORIGIN = 0x40800000, LENGTH = 320K + + /* DROM and IROM are in separate, non-overlapping address ranges. */ + DROM (r) : ORIGIN = 0x42800000, LENGTH = 8M /* Data bus (read-only, flash-mapped) */ + IROM (rx) : ORIGIN = 0x42000000, LENGTH = 8M /* Instruction bus (flash-mapped) */ +} + +/* The entry point. It is set in the image flashed to the chip, so must be + * defined. + */ +ENTRY(call_start_cpu0) + +SECTIONS +{ + /* === SRAM sections (loaded by ROM bootloader) === */ + + /* Put the stack at the bottom of SRAM, 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 = .; + } >SRAM + + /* 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(.); + } >SRAM + + /* Mutable global variables. This data (in the SRAM segment) is initialized + * by the ROM bootloader. + */ + .data : ALIGN(4) + { + . = ALIGN (4); + _sdata = ABSOLUTE(.); + *(.sdata) + *(.data .data.*) + *(.dram*) + . = ALIGN (4); + _edata = ABSOLUTE(.); + } >SRAM + + /* Code that must run from IRAM (not flash-mapped), for example interrupt + * vectors and code that runs before the flash has been mapped. + * Since IRAM and DRAM share the same address space, this is placed + * sequentially after .data in the same SRAM region. + */ + .iram : ALIGN(4) + { + *(.init) /* call_start_cpu0 (before flash is mapped) */ + *(.iram*) /* code that must run from IRAM */ + *(.text.handleInterruptASM) /* must be in SRAM, within JAL range of _vector_table */ + . = ALIGN(256); + *(.text.exception_vectors) + . = ALIGN(4); + } > SRAM + + /* Heap starts after IRAM. */ + . = ALIGN(4); + _heap_start = ABSOLUTE(.); + _heap_end = ORIGIN(SRAM) + LENGTH(SRAM); + + /* === IROM sections (flash-mapped code) === */ + + /* Dummy section to align .text virtual address with its flash offset. + * In the firmware image, .data and .iram (SRAM) come before .text (IROM). + * SIZEOF(.data) and SIZEOF(.iram) are backward references (defined above). + * .data may be empty (esp.go skips empty sections), so use a ternary. + */ + .irom_dummy (NOLOAD) : ALIGN(0x10000) + { + . += 0x18; /* image header */ + . += (SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0; /* data segment + header (if non-empty) */ + . += SIZEOF(.iram) + 0x8; /* iram segment + header */ + . += 0x8; /* text segment header */ + } > IROM + + /* Code stored in IROM (flash-mapped). + * This is the main program code. + */ + .text : ALIGN(4) + { + *(.text .text.*) + /* Put the .rodata data at a 16-byte aligned flash offset, the same as its address. */ + . = ALIGN(. + 0x8, 16) - 0x8; + } > IROM + + /* === DROM sections (flash-mapped read-only data) === */ + + /* Dummy section to align .rodata virtual address with its flash offset. + * Segments in the image are sorted by address: SRAM (.data, .iram), + * IROM (.text), then DROM (.rodata). So .rodata comes last, and we must + * account for all preceding segments. + * All SIZEOF() references are backward (sections defined above). + */ + .rodata_dummy (NOLOAD): ALIGN(4) + { + . += 0x18; /* image header (24 bytes) */ + . += (SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0; /* data segment + header (if non-empty) */ + . += SIZEOF(.iram) + 0x8; /* iram segment + header */ + . += SIZEOF(.text) + 0x8; /* text segment + header */ + . += 0x8; /* rodata segment header */ + } > DROM + + /* Constant global variables, stored in DROM. */ + .rodata : ALIGN(4) + { + *(.rodata*) + . = ALIGN (4); + } >DROM + + ASSERT(ADDR(.text) == ADDR(.irom_dummy) + SIZEOF(.irom_dummy), ".text address does not match its flash offset") + ASSERT(ADDR(.rodata) == ADDR(.rodata_dummy) + SIZEOF(.rodata_dummy), ".rodata address does not match its flash offset") + + /DISCARD/ : + { + *(.eh_frame) /* we don't do exception handling in C */ + } + + /* For the garbage collector. */ + .tinygo_stacksizes (INFO) : + { + *(.tinygo_stacksizes) + } + .tinygo_arm_entries (INFO) : + { + *(.tinygo_arm_entries) + } +} + +/* For the GC and scheduler. */ +_globals_start = _sbss; +_globals_end = _edata; + +/* Default stack size. */ +_stack_size = 4K; + +/* ROM function addresses for ESP32-H2. + * See ESP-IDF components/esp_rom/esp32h2/ld/esp32h2.rom.ld. + */ +PROVIDE( ets_delay_us = 0x40000040 ); + +/* Cache ROM functions */ +Cache_Invalidate_ICache_All = 0x40000620; +Cache_Suspend_ICache = 0x4000066c; +Cache_Resume_ICache = 0x40000670; +Cache_MMU_Init = 0x40000688; +Cache_MSPI_MMU_Set = 0x4000068c;