mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 19:43:44 +00:00
machine,targets: add minimal esp32c6 implementation
This adds a minimal esp32c6 implementation, currently only supporting the examples/serial and examples/blinky1 programs. It does correctly output the expected "Hello, World" via the serial port, as well as blink the onboard LED. In addition, it adds support for the PLIC based IRQ handling as used on the ESP32C6 processor. Some parts of this code are loosely based on PR #5252 and #5248 Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -960,6 +960,9 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
# xiao-esp32c6
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
# xiao-esp32s3
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
|
||||
+1
-1
@@ -1076,7 +1076,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
|
||||
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp8266":
|
||||
// Special format for the ESP family of chips (parsed by the ROM
|
||||
// bootloader).
|
||||
result.Binary = filepath.Join(tmpdir, "main"+outext)
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) {
|
||||
"cortex-m4",
|
||||
"cortex-m7",
|
||||
"esp32c3",
|
||||
"esp32c6",
|
||||
"esp32s3",
|
||||
"fe310",
|
||||
"gameboy-advance",
|
||||
|
||||
+15
-3
@@ -100,12 +100,24 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
chip_id := map[string]uint16{
|
||||
"esp32": 0x0000,
|
||||
"esp32c3": 0x0005,
|
||||
"esp32c6": 0x000d,
|
||||
"esp32s3": 0x0009,
|
||||
}[chip]
|
||||
|
||||
// SPI flash speed/size byte (byte 3 of header):
|
||||
// Upper nibble = flash size, lower nibble = flash frequency.
|
||||
// The espflasher auto-detects and patches the flash size (upper nibble),
|
||||
// but the frequency (lower nibble) must be correct per chip.
|
||||
spiSpeedSize := map[string]uint8{
|
||||
"esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
"esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
"esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding)
|
||||
"esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
}[chip]
|
||||
|
||||
// Image header.
|
||||
switch chip {
|
||||
case "esp32", "esp32c3", "esp32s3":
|
||||
case "esp32", "esp32c3", "esp32s3", "esp32c6":
|
||||
// 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
|
||||
@@ -126,8 +138,8 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
}{
|
||||
magic: 0xE9,
|
||||
segment_count: byte(len(segments)),
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: spiSpeedSize,
|
||||
entry_addr: uint32(inf.Entry),
|
||||
wp_pin: 0xEE, // disable WP pin
|
||||
chip_id: chip_id,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
// 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.
|
||||
|
||||
.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 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.
|
||||
// C equivalent:
|
||||
// Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0)
|
||||
// Maps 16MB starting at 0x42000000, covering both IROM and DROM.
|
||||
li a0, 0 // sensitive: no flash encryption
|
||||
li a1, 0 // ext_ram: MMU_ACCESS_FLASH
|
||||
li a2, 0x42000000 // vaddr: start of flash-mapped region
|
||||
li a3, 0 // paddr: physical address in the flash chip
|
||||
li a4, 64 // psize: always 64 (kilobytes)
|
||||
li a5, 256 // num: pages (16MB / 64K = 256, covers IROM+DROM)
|
||||
li a6, 0 // fixed
|
||||
call Cache_MSPI_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.
|
||||
j _start
|
||||
|
||||
.section .text.exception_vectors
|
||||
.global _vector_table
|
||||
.type _vector_table,@function
|
||||
|
||||
_vector_table:
|
||||
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
.rept 32
|
||||
j handleInterruptASM /* interrupt handler */
|
||||
.endr
|
||||
|
||||
.option pop
|
||||
|
||||
.size _vector_table, .-_vector_table
|
||||
@@ -0,0 +1,57 @@
|
||||
//go:build xiao_esp32c6
|
||||
|
||||
// This file contains the pin mappings for the Seeed XIAO ESP32C6 board.
|
||||
//
|
||||
// Seeed Studio XIAO ESP32C6 is an IoT mini development board based on
|
||||
// the Espressif ESP32-C6 WiFi 6 / Bluetooth 5 / 802.15.4 chip.
|
||||
//
|
||||
// - https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html
|
||||
// - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/
|
||||
|
||||
package machine
|
||||
|
||||
// Digital Pins
|
||||
const (
|
||||
D0 = GPIO0
|
||||
D1 = GPIO1
|
||||
D2 = GPIO2
|
||||
D3 = GPIO21
|
||||
D4 = GPIO22
|
||||
D5 = GPIO23
|
||||
D6 = GPIO16
|
||||
D7 = GPIO17
|
||||
D8 = GPIO19
|
||||
D9 = GPIO20
|
||||
D10 = GPIO18
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 = GPIO0
|
||||
A1 = GPIO1
|
||||
A2 = GPIO2
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = GPIO16
|
||||
UART_RX_PIN = GPIO17
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = GPIO22
|
||||
SCL_PIN = GPIO23
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI_SCK_PIN = GPIO19
|
||||
SPI_SDI_PIN = GPIO20
|
||||
SPI_SDO_PIN = GPIO18
|
||||
)
|
||||
|
||||
// Onboard LEDs
|
||||
const (
|
||||
LED = GPIO15
|
||||
)
|
||||
@@ -0,0 +1,546 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"device/riscv"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const deviceName = esp.Device
|
||||
const maxPin = 31
|
||||
const cpuInterruptFromPin = 6
|
||||
|
||||
// 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
|
||||
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
|
||||
GPIO28 Pin = 28
|
||||
GPIO29 Pin = 29
|
||||
GPIO30 Pin = 30
|
||||
)
|
||||
|
||||
const (
|
||||
ADC0 Pin = GPIO0
|
||||
ADC1 Pin = GPIO1
|
||||
ADC2 Pin = GPIO2
|
||||
ADC3 Pin = GPIO3
|
||||
ADC4 Pin = GPIO4
|
||||
ADC5 Pin = GPIO5
|
||||
ADC6 Pin = GPIO6
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func initI2CExt1Clock() {}
|
||||
|
||||
// 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)<<esp.GPIO_PIN_INT_TYPE_Pos | uint32(1)<<esp.GPIO_PIN_INT_ENA_Pos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pinCallbacks [maxPin]func(Pin)
|
||||
onceSetupPinInterrupt sync.Once
|
||||
)
|
||||
|
||||
func setupPinInterrupt() error {
|
||||
esp.INTERRUPT_CORE0.GPIO_INTERRUPT_PRO_MAP.Set(cpuInterruptFromPin)
|
||||
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
|
||||
status := esp.GPIO.STATUS.Get()
|
||||
// Clear before processing so new edges during callbacks are not lost.
|
||||
esp.GPIO.STATUS_W1TC.Set(status)
|
||||
for i, mask := 0, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
|
||||
if (status&mask) != 0 && pinCallbacks[i] != nil {
|
||||
pinCallbacks[i](Pin(i))
|
||||
}
|
||||
}
|
||||
}).Enable()
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultUART = UART0
|
||||
|
||||
UART0 = &_UART0
|
||||
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
|
||||
|
||||
onceUart = sync.Once{}
|
||||
errSamePins = errors.New("UART: invalid pin combination")
|
||||
errWrongUART = errors.New("UART: unsupported UARTn")
|
||||
errWrongBitSize = errors.New("UART: invalid data size")
|
||||
errWrongStopBitSize = errors.New("UART: invalid bit size")
|
||||
)
|
||||
|
||||
type UART struct {
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
ParityErrorDetected bool // set when parity error detected
|
||||
DataErrorDetected bool // set when data corruption detected
|
||||
DataOverflowDetected bool // set when data overflow detected in UART FIFO buffer or RingBuffer
|
||||
}
|
||||
|
||||
const (
|
||||
defaultDataBits = 8
|
||||
defaultStopBit = 1
|
||||
defaultParity = ParityNone
|
||||
|
||||
uartInterrupts = esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA |
|
||||
esp.UART_INT_ENA_PARITY_ERR_INT_ENA |
|
||||
esp.UART_INT_ENA_FRM_ERR_INT_ENA |
|
||||
esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA |
|
||||
esp.UART_INT_ENA_GLITCH_DET_INT_ENA
|
||||
|
||||
pplClockFreq = 80e6
|
||||
)
|
||||
|
||||
type registerSet struct {
|
||||
interruptMapReg *volatile.Register32
|
||||
gpioMatrixSignal uint32
|
||||
}
|
||||
|
||||
func (uart *UART) Configure(config UARTConfig) error {
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
if config.TX == config.RX {
|
||||
return errSamePins
|
||||
}
|
||||
switch {
|
||||
case uart.Bus == esp.UART0:
|
||||
return uart.configure(config, registerSet{
|
||||
interruptMapReg: &esp.INTERRUPT_CORE0.UART0_INTR_MAP,
|
||||
gpioMatrixSignal: 6,
|
||||
})
|
||||
case uart.Bus == esp.UART1:
|
||||
return uart.configure(config, registerSet{
|
||||
interruptMapReg: &esp.INTERRUPT_CORE0.UART1_INTR_MAP,
|
||||
gpioMatrixSignal: 9,
|
||||
})
|
||||
}
|
||||
return errWrongUART
|
||||
}
|
||||
|
||||
func (uart *UART) configure(config UARTConfig, regs registerSet) error {
|
||||
initUARTClock(uart.Bus)
|
||||
|
||||
// - disable TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration
|
||||
uart.Bus.SetCLK_CONF_TX_SCLK_EN(0)
|
||||
uart.Bus.SetCLK_CONF_RX_SCLK_EN(0)
|
||||
|
||||
// - default clock source: 1=XTAL_CLK, 2=RC_FAST_CLK, 3=FOSC_CLK
|
||||
// On C6, SCLK_SEL=1 selects XTAL (40 MHz). Use FOSC (80 MHz) for
|
||||
// better baud-rate accuracy.
|
||||
uart.Bus.SetCLK_CONF_SCLK_SEL(3) // FOSC_CLK (80 MHz)
|
||||
// reset divisor of the divider via UART_SCLK_DIV_NUM, UART_SCLK_DIV_A, and UART_SCLK_DIV_B
|
||||
uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(0)
|
||||
uart.Bus.SetCLK_CONF_SCLK_DIV_A(0)
|
||||
uart.Bus.SetCLK_CONF_SCLK_DIV_B(0)
|
||||
|
||||
uart.SetBaudRate(config.BaudRate)
|
||||
uart.SetFormat(defaultDataBits, defaultStopBit, defaultParity)
|
||||
|
||||
// - set UART mode
|
||||
uart.Bus.SetRS485_CONF_RS485_EN(0)
|
||||
uart.Bus.SetRS485_CONF_RS485TX_RX_EN(0)
|
||||
uart.Bus.SetRS485_CONF_RS485RXBY_TX_EN(0)
|
||||
uart.Bus.SetCONF0_IRDA_EN(0)
|
||||
// - disable hw-flow control
|
||||
uart.Bus.SetCONF0_TX_FLOW_EN(0)
|
||||
|
||||
// synchronize values into Core Clock
|
||||
uart.Bus.SetREG_UPDATE(1)
|
||||
|
||||
uart.setupPins(config, regs)
|
||||
uart.configureInterrupt(regs.interruptMapReg)
|
||||
uart.enableTransmitter()
|
||||
uart.enableReceiver()
|
||||
|
||||
// Start TX/RX
|
||||
uart.Bus.SetCLK_CONF_TX_SCLK_EN(1)
|
||||
uart.Bus.SetCLK_CONF_RX_SCLK_EN(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uart *UART) SetFormat(dataBits, stopBits int, parity UARTParity) error {
|
||||
if dataBits < 5 {
|
||||
return errWrongBitSize
|
||||
}
|
||||
if stopBits > 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) {
|
||||
// On ESP32-C6, UART clock is controlled via PCR (Peripheral Clock Reset).
|
||||
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)
|
||||
// Configure SCLK divisor in PCR
|
||||
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_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 := uint32((1 << 12) - 1)
|
||||
sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate)
|
||||
clk_div := (pplClockFreq << 4) / (baudRate * sclk_div)
|
||||
uart.Bus.SetCLKDIV(clk_div >> 4)
|
||||
uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf)
|
||||
// On C6, the SCLK divisor is in the PCR register.
|
||||
switch uart.Bus {
|
||||
case esp.UART0:
|
||||
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(sclk_div - 1)
|
||||
case esp.UART1:
|
||||
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(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)
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal)
|
||||
// link RX with GPIO signal X and route signals via GPIO matrix
|
||||
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(7)
|
||||
onceUart.Do(func() {
|
||||
_ = interrupt.New(7, 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() {}
|
||||
@@ -0,0 +1,250 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"errors"
|
||||
"machine/usb"
|
||||
"machine/usb/descriptor"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
||||
const cpuInterruptFromUSB = 10
|
||||
|
||||
type USB_DEVICE struct {
|
||||
Bus *esp.USB_DEVICE_Type
|
||||
Buffer *RingBuffer
|
||||
txPending bool // unflushed data in the EP1 TX FIFO
|
||||
txStalled bool // set when flushAndWait fails (no host reading); cleared when FIFO becomes writable
|
||||
}
|
||||
|
||||
var (
|
||||
_USBCDC = &USB_DEVICE{
|
||||
Bus: esp.USB_DEVICE,
|
||||
Buffer: NewRingBuffer(),
|
||||
}
|
||||
|
||||
USBCDC Serialer = _USBCDC
|
||||
)
|
||||
|
||||
var (
|
||||
errUSBWrongSize = errors.New("USB: invalid write size")
|
||||
errUSBCouldNotWriteAllData = errors.New("USB: could not write all data")
|
||||
)
|
||||
|
||||
type Serialer interface {
|
||||
WriteByte(c byte) error
|
||||
Write(data []byte) (n int, err error)
|
||||
Configure(config UARTConfig) error
|
||||
Buffered() int
|
||||
ReadByte() (byte, error)
|
||||
DTR() bool
|
||||
RTS() bool
|
||||
}
|
||||
|
||||
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.
|
||||
type USBDevice struct {
|
||||
initcomplete bool
|
||||
InitEndpointComplete bool
|
||||
}
|
||||
|
||||
var USBDev = &USBDevice{}
|
||||
|
||||
func (dev *USBDevice) SetStallEPIn(ep uint32) {}
|
||||
func (dev *USBDevice) SetStallEPOut(ep uint32) {}
|
||||
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
|
||||
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
|
||||
|
||||
// initUSB is intentionally empty — the interp phase evaluates init()
|
||||
// functions at compile time and cannot access hardware registers.
|
||||
// Actual hardware setup is deferred to the first Configure() call.
|
||||
func initUSB() {}
|
||||
|
||||
// Configure initialises the USB Serial/JTAG controller clock, pads, and
|
||||
// interrupt so that received data is buffered automatically.
|
||||
func (usbdev *USB_DEVICE) Configure(config UARTConfig) error {
|
||||
if usbConfigured {
|
||||
return nil
|
||||
}
|
||||
usbConfigured = true
|
||||
|
||||
// Enable the USB_DEVICE peripheral clock via PCR (C6 uses 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)
|
||||
|
||||
// Ensure internal PHY is selected and USB pads are enabled.
|
||||
usbdev.Bus.SetCONF0_PHY_SEL(0)
|
||||
usbdev.Bus.SetCONF0_USB_PAD_ENABLE(1)
|
||||
usbdev.Bus.SetCONF0_DP_PULLUP(1)
|
||||
|
||||
// Clear any pending interrupts.
|
||||
usbdev.Bus.INT_CLR.Set(0xFFFFFFFF)
|
||||
|
||||
// Enable the RX-packet-received interrupt.
|
||||
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
|
||||
|
||||
// Map the USB peripheral interrupt to CPU interrupt cpuInterruptFromUSB.
|
||||
esp.INTERRUPT_CORE0.SetUSB_INTR_MAP(cpuInterruptFromUSB)
|
||||
|
||||
_ = interrupt.New(cpuInterruptFromUSB, func(interrupt.Interrupt) {
|
||||
_USBCDC.handleInterrupt()
|
||||
}).Enable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureConfigured triggers lazy initialization on first use.
|
||||
func (usbdev *USB_DEVICE) ensureConfigured() {
|
||||
if !usbConfigured {
|
||||
usbdev.Configure(UARTConfig{})
|
||||
}
|
||||
}
|
||||
|
||||
// handleInterrupt drains the hardware RX FIFO into the software ring buffer.
|
||||
func (usbdev *USB_DEVICE) handleInterrupt() {
|
||||
intStatus := usbdev.Bus.INT_ST.Get()
|
||||
|
||||
// Disable the RX interrupt to prevent re-triggering while we drain.
|
||||
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0)
|
||||
|
||||
if intStatus&esp.USB_DEVICE_INT_ST_SERIAL_OUT_RECV_PKT_INT_ST != 0 {
|
||||
for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 {
|
||||
b := byte(usbdev.Bus.EP1.Get())
|
||||
usbdev.Buffer.Put(b)
|
||||
}
|
||||
usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1)
|
||||
}
|
||||
|
||||
// Re-enable the RX interrupt.
|
||||
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
|
||||
usbdev.ensureConfigured()
|
||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||
if usbdev.txStalled {
|
||||
return errUSBCouldNotWriteAllData
|
||||
}
|
||||
if !usbdev.flushAndWait() {
|
||||
usbdev.txStalled = true
|
||||
return errUSBCouldNotWriteAllData
|
||||
}
|
||||
}
|
||||
usbdev.txStalled = false
|
||||
|
||||
usbdev.Bus.EP1.Set(uint32(c))
|
||||
|
||||
if c == '\n' {
|
||||
usbdev.flush()
|
||||
usbdev.txPending = false
|
||||
} else {
|
||||
usbdev.txPending = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) {
|
||||
usbdev.ensureConfigured()
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
for i, c := range data {
|
||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||
if usbdev.txStalled {
|
||||
return i, errUSBCouldNotWriteAllData
|
||||
}
|
||||
if !usbdev.flushAndWait() {
|
||||
usbdev.txStalled = true
|
||||
return i, errUSBCouldNotWriteAllData
|
||||
}
|
||||
}
|
||||
usbdev.txStalled = false
|
||||
usbdev.Bus.EP1.Set(uint32(c))
|
||||
}
|
||||
|
||||
usbdev.flush()
|
||||
usbdev.txPending = false
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// Buffered returns the number of bytes waiting in the receive ring buffer.
|
||||
func (usbdev *USB_DEVICE) Buffered() int {
|
||||
usbdev.ensureConfigured()
|
||||
if usbdev.txPending {
|
||||
usbdev.flush()
|
||||
usbdev.txPending = false
|
||||
}
|
||||
return int(usbdev.Buffer.Used())
|
||||
}
|
||||
|
||||
// ReadByte returns a byte from the receive ring buffer.
|
||||
func (usbdev *USB_DEVICE) ReadByte() (byte, error) {
|
||||
b, ok := usbdev.Buffer.Get()
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) DTR() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) RTS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// flush signals WR_DONE to tell the hardware to send the data that has
|
||||
// been written to the EP1 FIFO.
|
||||
func (usbdev *USB_DEVICE) flush() {
|
||||
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
||||
}
|
||||
|
||||
// flushAndWait signals WR_DONE and waits for the EP1 FIFO to become
|
||||
// writable again. Returns false if the FIFO is still locked after the
|
||||
// timeout (no host reading).
|
||||
func (usbdev *USB_DEVICE) flushAndWait() bool {
|
||||
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
||||
for i := 0; i < 50000; i++ {
|
||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FlushSerial flushes any pending USB serial TX data.
|
||||
func FlushSerial() {
|
||||
if _USBCDC.txPending {
|
||||
_USBCDC.flush()
|
||||
_USBCDC.txPending = false
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureUSBEndpoint is a no-op on ESP32-C6 — 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.
|
||||
func SendZlp() {
|
||||
}
|
||||
|
||||
// SendUSBInPacket is a no-op on ESP32-C6.
|
||||
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"device/riscv"
|
||||
"errors"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//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.
|
||||
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
|
||||
MXINT_CLEAR volatile.Register32 // 0x08 edge acknowledge
|
||||
MXINT_EIP_STATUS volatile.Register32 // 0x0C pending status (read-only)
|
||||
MXINT_PRI [32]volatile.Register32 // 0x10..0x8C per-line priority (4 bits)
|
||||
MXINT_THRESH volatile.Register32 // 0x90 priority threshold (8 bits)
|
||||
}
|
||||
|
||||
// plic points at the PLIC machine-mode register block (DR_REG_PLIC_MX_BASE).
|
||||
var plic = (*plicType)(unsafe.Pointer(uintptr(0x20001000)))
|
||||
|
||||
// Enable registers a CPU interrupt.
|
||||
// The ESP32-C6 has 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")
|
||||
}
|
||||
mask := riscv.DisableInterrupts()
|
||||
defer riscv.EnableInterrupts(mask)
|
||||
|
||||
// Enable CPU interrupt number i.num via the PLIC.
|
||||
plic.MXINT_ENABLE.SetBits(1 << i.num)
|
||||
|
||||
// Set pulse interrupt type (rising edge detection).
|
||||
plic.MXINT_TYPE.SetBits(1 << i.num)
|
||||
|
||||
// Set default priority (must be >= threshold to be delivered).
|
||||
plic.MXINT_PRI[i.num].Set(defaultThreshold)
|
||||
|
||||
// Reset interrupt before re-enabling.
|
||||
plic.MXINT_CLEAR.SetBits(1 << i.num)
|
||||
plic.MXINT_CLEAR.ClearBits(1 << i.num)
|
||||
|
||||
riscv.Asm("fence")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Adding pseudo function calls that is replaced by the compiler with the actual
|
||||
// functions registered through interrupt.New.
|
||||
//
|
||||
//go:linkname callHandlers runtime/interrupt.callHandlers
|
||||
func callHandlers(num int)
|
||||
|
||||
//go:linkname signalInterrupt runtime.signalInterrupt
|
||||
func signalInterrupt()
|
||||
|
||||
const (
|
||||
IRQNUM_1 = 1 + iota
|
||||
IRQNUM_2
|
||||
IRQNUM_3
|
||||
IRQNUM_4
|
||||
IRQNUM_5
|
||||
IRQNUM_6
|
||||
IRQNUM_7
|
||||
IRQNUM_8
|
||||
IRQNUM_9
|
||||
IRQNUM_10
|
||||
IRQNUM_11
|
||||
IRQNUM_12
|
||||
IRQNUM_13
|
||||
IRQNUM_14
|
||||
IRQNUM_15
|
||||
IRQNUM_16
|
||||
IRQNUM_17
|
||||
IRQNUM_18
|
||||
IRQNUM_19
|
||||
IRQNUM_20
|
||||
IRQNUM_21
|
||||
IRQNUM_22
|
||||
IRQNUM_23
|
||||
IRQNUM_24
|
||||
IRQNUM_25
|
||||
IRQNUM_26
|
||||
IRQNUM_27
|
||||
IRQNUM_28
|
||||
IRQNUM_29
|
||||
IRQNUM_30
|
||||
IRQNUM_31
|
||||
)
|
||||
|
||||
const (
|
||||
defaultThreshold = 5
|
||||
// Priority 0 disables an interrupt on ESP32-C6.
|
||||
disableThreshold = 0
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func callHandler(n int) {
|
||||
switch n {
|
||||
case IRQNUM_1:
|
||||
callHandlers(IRQNUM_1)
|
||||
case IRQNUM_2:
|
||||
callHandlers(IRQNUM_2)
|
||||
case IRQNUM_3:
|
||||
callHandlers(IRQNUM_3)
|
||||
case IRQNUM_4:
|
||||
callHandlers(IRQNUM_4)
|
||||
case IRQNUM_5:
|
||||
callHandlers(IRQNUM_5)
|
||||
case IRQNUM_6:
|
||||
callHandlers(IRQNUM_6)
|
||||
case IRQNUM_7:
|
||||
callHandlers(IRQNUM_7)
|
||||
case IRQNUM_8:
|
||||
callHandlers(IRQNUM_8)
|
||||
case IRQNUM_9:
|
||||
callHandlers(IRQNUM_9)
|
||||
case IRQNUM_10:
|
||||
callHandlers(IRQNUM_10)
|
||||
case IRQNUM_11:
|
||||
callHandlers(IRQNUM_11)
|
||||
case IRQNUM_12:
|
||||
callHandlers(IRQNUM_12)
|
||||
case IRQNUM_13:
|
||||
callHandlers(IRQNUM_13)
|
||||
case IRQNUM_14:
|
||||
callHandlers(IRQNUM_14)
|
||||
case IRQNUM_15:
|
||||
callHandlers(IRQNUM_15)
|
||||
case IRQNUM_16:
|
||||
callHandlers(IRQNUM_16)
|
||||
case IRQNUM_17:
|
||||
callHandlers(IRQNUM_17)
|
||||
case IRQNUM_18:
|
||||
callHandlers(IRQNUM_18)
|
||||
case IRQNUM_19:
|
||||
callHandlers(IRQNUM_19)
|
||||
case IRQNUM_20:
|
||||
callHandlers(IRQNUM_20)
|
||||
case IRQNUM_21:
|
||||
callHandlers(IRQNUM_21)
|
||||
case IRQNUM_22:
|
||||
callHandlers(IRQNUM_22)
|
||||
case IRQNUM_23:
|
||||
callHandlers(IRQNUM_23)
|
||||
case IRQNUM_24:
|
||||
callHandlers(IRQNUM_24)
|
||||
case IRQNUM_25:
|
||||
callHandlers(IRQNUM_25)
|
||||
case IRQNUM_26:
|
||||
callHandlers(IRQNUM_26)
|
||||
case IRQNUM_27:
|
||||
callHandlers(IRQNUM_27)
|
||||
case IRQNUM_28:
|
||||
callHandlers(IRQNUM_28)
|
||||
case IRQNUM_29:
|
||||
callHandlers(IRQNUM_29)
|
||||
case IRQNUM_30:
|
||||
callHandlers(IRQNUM_30)
|
||||
case IRQNUM_31:
|
||||
callHandlers(IRQNUM_31)
|
||||
}
|
||||
}
|
||||
|
||||
//export handleInterrupt
|
||||
func handleInterrupt() {
|
||||
mcause := riscv.MCAUSE.Get()
|
||||
exception := mcause&(1<<31) == 0
|
||||
interruptNumber := uint32(mcause & 0x1f)
|
||||
|
||||
if !exception && interruptNumber > 0 {
|
||||
// Save MSTATUS & MEPC, which could be overwritten by another CPU interrupt.
|
||||
mstatus := riscv.MSTATUS.Get()
|
||||
mepc := riscv.MEPC.Get()
|
||||
// Temporarily disable this interrupt by lowering its PLIC priority
|
||||
// below the threshold.
|
||||
thresholdSave := plic.MXINT_PRI[interruptNumber].Get()
|
||||
plic.MXINT_PRI[interruptNumber].Set(disableThreshold)
|
||||
riscv.Asm("fence")
|
||||
|
||||
interruptBit := uint32(1 << interruptNumber)
|
||||
|
||||
// Reset pending status interrupt.
|
||||
if plic.MXINT_TYPE.Get()&interruptBit != 0 {
|
||||
// Edge type interrupt.
|
||||
plic.MXINT_CLEAR.SetBits(interruptBit)
|
||||
plic.MXINT_CLEAR.ClearBits(interruptBit)
|
||||
} else {
|
||||
// Level type interrupt.
|
||||
plic.MXINT_CLEAR.ClearBits(interruptBit)
|
||||
}
|
||||
|
||||
// Enable CPU interrupts.
|
||||
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)
|
||||
|
||||
// Call registered interrupt handler(s).
|
||||
callHandler(int(interruptNumber))
|
||||
|
||||
// Signal to sleepTicks that an interrupt has occurred.
|
||||
signalInterrupt()
|
||||
|
||||
// Disable CPU interrupts.
|
||||
riscv.MSTATUS.ClearBits(riscv.MSTATUS_MIE)
|
||||
|
||||
// Restore interrupt priority to enable interrupt again.
|
||||
plic.MXINT_PRI[interruptNumber].Set(thresholdSave)
|
||||
riscv.Asm("fence")
|
||||
|
||||
// Zero MCAUSE so that interrupt.In() returns false once we
|
||||
// return to normal (non-interrupt) code.
|
||||
riscv.MCAUSE.Set(0)
|
||||
|
||||
// Restore MSTATUS & MEPC.
|
||||
riscv.MSTATUS.Set(mstatus)
|
||||
riscv.MEPC.Set(mepc)
|
||||
} else {
|
||||
handleException(mcause)
|
||||
}
|
||||
}
|
||||
|
||||
func handleException(mcause uintptr) {
|
||||
println("*** Exception: pc:", riscv.MEPC.Get())
|
||||
println("*** Exception: code:", uint32(mcause&0x1f))
|
||||
println("*** Exception: mcause:", mcause)
|
||||
println("*** Exception: ra:", tinygo_saved_ra)
|
||||
switch uint32(mcause & 0x1f) {
|
||||
case riscv.InstructionAccessFault:
|
||||
println("*** virtual address:", riscv.MTVAL.Get())
|
||||
case riscv.IllegalInstruction:
|
||||
println("*** opcode:", riscv.MTVAL.Get())
|
||||
case riscv.LoadAccessFault:
|
||||
println("*** read address:", riscv.MTVAL.Get())
|
||||
case riscv.StoreOrAMOAccessFault:
|
||||
println("*** write address:", riscv.MTVAL.Get())
|
||||
}
|
||||
for {
|
||||
riscv.Asm("wfi")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"device/riscv"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
||||
// 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
|
||||
|
||||
// Change CPU frequency to 160MHz from SPLL (480MHz).
|
||||
//
|
||||
// Clock tree: SPLL (480MHz) → HP root → CPU / AHB / APB
|
||||
//
|
||||
// Set dividers BEFORE switching the clock source so the first PLL
|
||||
// cycle already arrives divided:
|
||||
// HP root = SPLL / (HS_DIV_NUM+1) = 480 / 3 = 160 MHz
|
||||
// CPU = HP root / (CPU_HS_DIV_NUM+1) = 160 / 1 = 160 MHz
|
||||
// AHB = HP root / (AHB_HS_DIV_NUM+1) = 160 / 4 = 40 MHz
|
||||
// APB = AHB / (APB_HS_DIV_NUM+1) = 40 / 1 = 40 MHz
|
||||
esp.PCR.CPU_FREQ_CONF.Set(0 << 8) // CPU_HS_DIV_NUM = 0 (div1)
|
||||
esp.PCR.AHB_FREQ_CONF.Set(3 << 8) // AHB_HS_DIV_NUM = 3 (div4)
|
||||
esp.PCR.APB_FREQ_CONF.Set(0 << 8) // APB_HS_DIV_NUM = 0 (div1)
|
||||
|
||||
// Switch to PLL: SOC_CLK_SEL = 1 (SPLL), HS_DIV_NUM = 2 (div3).
|
||||
esp.PCR.SYSCLK_CONF.Set(1<<16 | 2<<8)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func init() {
|
||||
machine.InitSerial()
|
||||
}
|
||||
|
||||
func abort() {
|
||||
for {
|
||||
riscv.Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
// interruptInit initializes the interrupt controller.
|
||||
func interruptInit() {
|
||||
mie := riscv.DisableInterrupts()
|
||||
|
||||
// Reset all interrupt priorities to zero in the PLIC.
|
||||
for i := 1; i < 32; i++ {
|
||||
plic.MXINT_PRI[i].Set(0)
|
||||
}
|
||||
|
||||
// Default threshold for interrupts is 5.
|
||||
plic.MXINT_THRESH.Set(5)
|
||||
|
||||
// Set the interrupt address.
|
||||
// Set MODE field to 1 - a vector base address.
|
||||
// Note that this address must be aligned to 256 bytes.
|
||||
riscv.MTVEC.Set((uintptr(unsafe.Pointer(&_vector_table))) | 1)
|
||||
|
||||
// On the ESP32-C6 the CPU receives PLIC interrupts through the standard
|
||||
// RISC-V mie CSR (machine interrupt-enable, 0x304). Unlike the ESP32-C3's
|
||||
// INTC, the PLIC's per-line interrupts are gated by mie, so it must be
|
||||
// enabled for any interrupt to reach the CPU. esp-hal does the same thing
|
||||
// for the PLIC: `csrw mie, 0xffffffff`.
|
||||
riscv.MIE.Set(0xffffffff)
|
||||
|
||||
// Globally enable machine-mode interrupts (MSTATUS.MIE).
|
||||
//
|
||||
// Unlike the ESP32-C3, whose ROM bootloader hands control to the
|
||||
// application with MSTATUS.MIE already set, the ESP32-C6 ROM leaves it
|
||||
// cleared. Restoring the previous state (riscv.EnableInterrupts(mie))
|
||||
// would therefore leave interrupts globally disabled, so no interrupt is
|
||||
// ever delivered to the CPU: timer alarms silently fall back to the
|
||||
// polling loop in sleepTicks and peripheral RX interrupts (such as the
|
||||
// USB Serial/JTAG controller) never fire. Set the bit explicitly instead.
|
||||
_ = mie
|
||||
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)
|
||||
}
|
||||
|
||||
// CPU interrupt number used for the TIMG0 timer alarm.
|
||||
const timerAlarmCPUInterrupt = 9
|
||||
|
||||
var interruptPending volatile.Register8
|
||||
|
||||
func signalInterrupt() {
|
||||
interruptPending.Set(1)
|
||||
}
|
||||
|
||||
// initTimerInterrupt routes the TIMG0 timer 0 alarm interrupt to a CPU
|
||||
// interrupt and registers a handler.
|
||||
func initTimerInterrupt() {
|
||||
// Map the TIMG0 T0 peripheral interrupt to a CPU interrupt line.
|
||||
// On C6, INTERRUPT_CORE0 is for peripheral→CPU interrupt mapping.
|
||||
esp.INTERRUPT_CORE0.TG0_T0_INTR_MAP.Set(timerAlarmCPUInterrupt)
|
||||
|
||||
// Enable T0 interrupt at the timer group level.
|
||||
esp.TIMG0.INT_ENA_TIMERS.SetBits(1)
|
||||
|
||||
// Register the interrupt handler.
|
||||
interrupt.New(timerAlarmCPUInterrupt, func(interrupt.Interrupt) {
|
||||
esp.TIMG0.INT_CLR_TIMERS.Set(1)
|
||||
})
|
||||
|
||||
// Enable the CPU interrupt:
|
||||
mie := riscv.DisableInterrupts()
|
||||
|
||||
// Clear any stale pending bit.
|
||||
plic.MXINT_CLEAR.SetBits(1 << timerAlarmCPUInterrupt)
|
||||
plic.MXINT_CLEAR.ClearBits(1 << timerAlarmCPUInterrupt)
|
||||
|
||||
// Set edge-triggered.
|
||||
plic.MXINT_TYPE.SetBits(1 << timerAlarmCPUInterrupt)
|
||||
|
||||
// Set priority above threshold.
|
||||
plic.MXINT_PRI[timerAlarmCPUInterrupt].Set(10)
|
||||
|
||||
riscv.Asm("fence")
|
||||
|
||||
plic.MXINT_ENABLE.SetBits(1 << timerAlarmCPUInterrupt)
|
||||
|
||||
riscv.EnableInterrupts(mie)
|
||||
}
|
||||
|
||||
// sleepTicks spins until the given number of ticks have elapsed, using the
|
||||
// TIMG0 alarm interrupt to avoid busy-waiting for the entire duration.
|
||||
func sleepTicks(d timeUnit) {
|
||||
machine.FlushSerial()
|
||||
target := ticks() + d
|
||||
for ticks() < target {
|
||||
interruptPending.Set(0)
|
||||
|
||||
esp.TIMG0.T0ALARMLO.Set(uint32(target))
|
||||
esp.TIMG0.T0ALARMHI.Set(uint32(target >> 32))
|
||||
|
||||
// Enable the alarm (auto-clears when alarm fires).
|
||||
esp.TIMG0.T0CONFIG.SetBits(esp.TIMG_T0CONFIG_ALARM_EN)
|
||||
|
||||
for interruptPending.Get() == 0 {
|
||||
if ticks() >= target {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go:extern _vector_table
|
||||
var _vector_table [0]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.
|
||||
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
|
||||
MXINT_CLEAR volatile.Register32 // 0x08 edge acknowledge
|
||||
MXINT_EIP_STATUS volatile.Register32 // 0x0C pending status (read-only)
|
||||
MXINT_PRI [32]volatile.Register32 // 0x10..0x8C per-line priority (4 bits)
|
||||
MXINT_THRESH volatile.Register32 // 0x90 priority threshold (8 bits)
|
||||
}
|
||||
|
||||
// plic points at the PLIC machine-mode register block (DR_REG_PLIC_MX_BASE).
|
||||
var plic = (*plicType)(unsafe.Pointer(uintptr(0x20001000)))
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build esp32 || esp32c3
|
||||
//go:build esp32 || esp32c3 || esp32c6
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"inherits": ["riscv32"],
|
||||
"inheritable-only": true,
|
||||
"features": "+32bit,+a,+c,+m,+zaamo,+zalrsc,+zmmul,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zabha,-zacas,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b",
|
||||
"build-tags": ["esp32c6", "esp"],
|
||||
"serial": "usb",
|
||||
"rtlib": "compiler-rt",
|
||||
"libc": "picolibc",
|
||||
"default-stack-size": 8192,
|
||||
"linkerscript": "targets/esp32c6.ld",
|
||||
"extra-files": [
|
||||
"src/device/esp/esp32c6.S"
|
||||
],
|
||||
"binary-format": "esp32c6",
|
||||
"flash-method": "esp32jtag",
|
||||
"serial-port": ["303a:1001"],
|
||||
"openocd-interface": "esp_usb_jtag",
|
||||
"openocd-target": "esp32c6",
|
||||
"openocd-commands": ["gdb_memory_map disable"],
|
||||
"gdb": ["riscv32-esp-elf-gdb"]
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/* Linker script for the ESP32-C6
|
||||
*
|
||||
* The ESP32-C6 has a simpler memory layout than the ESP32-C3:
|
||||
* - It has 512kB of HP-SRAM. Unlike the C3, IRAM and DRAM share the same
|
||||
* address space at 0x40800000, so there is no need for separate DRAM/IRAM
|
||||
* regions.
|
||||
* - It has 16kB of LP-SRAM at 0x50000000 for low-power / RTC use.
|
||||
* - DROM and IROM are in separate, non-overlapping 8MB address ranges:
|
||||
* IROM at 0x42000000 and DROM at 0x42800000.
|
||||
* - The MMU works in pages of 64kB, which means the bottom 16 bits of the
|
||||
* address in flash and the address in DROM/IROM need to match.
|
||||
* - Memory in SRAM is loaded at reset by the ROM bootloader.
|
||||
*
|
||||
* The firmware image segments are sorted by virtual address (by esp.go):
|
||||
* 1. .data (SRAM 0x408...) - loaded by ROM bootloader
|
||||
* 2. .iram (SRAM 0x408...) - loaded by ROM bootloader
|
||||
* 3. .text (IROM 0x420...) - flash-mapped via MMU
|
||||
* 4. .rodata (DROM 0x428...) - flash-mapped via MMU
|
||||
*
|
||||
* IMPORTANT: SRAM sections (.data, .iram) are defined BEFORE the dummy
|
||||
* sections so that SIZEOF() references are backward (not forward). lld may
|
||||
* not correctly resolve forward SIZEOF() references, which would produce
|
||||
* wrong dummy sizes and corrupt the firmware image.
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
/* HP-SRAM: unified IRAM/DRAM address space (512K).
|
||||
* Unlike the C3, IRAM and DRAM share the same address space, so we use
|
||||
* a single memory region to avoid linker overlap errors.
|
||||
*/
|
||||
SRAM (rwx) : ORIGIN = 0x40800000, LENGTH = 512K
|
||||
|
||||
/* 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 on ESP32-C6, 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.*)
|
||||
} > 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
|
||||
|
||||
/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-C6.
|
||||
* Source: ESP-IDF components/esp_rom/esp32c6/ld/esp32c6.rom.ld
|
||||
*/
|
||||
PROVIDE( ets_delay_us = 0x40000040 );
|
||||
|
||||
/* Cache ROM functions */
|
||||
Cache_Invalidate_ICache_All = 0x4000064c;
|
||||
Cache_Suspend_ICache = 0x40000698;
|
||||
Cache_Resume_ICache = 0x4000069c;
|
||||
Cache_MMU_Init = 0x400006b4;
|
||||
Cache_MSPI_MMU_Set = 0x400006b8;
|
||||
|
||||
/* PHY ROM data */
|
||||
phy_param_rom = 0x4087fce8;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"inherits": ["esp32c6"],
|
||||
"build-tags": ["xiao_esp32c6"]
|
||||
}
|
||||
Reference in New Issue
Block a user