mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 11:37:46 +00:00
esp32c3: add support for this chip
This change adds support for the ESP32-C3, a new chip from Espressif. It is a RISC-V core so porting was comparatively easy. Most peripherals are shared with the (original) ESP32 chip, but with subtle differences. Also, the SVD file I've used gives some peripherals/registers a different name which makes sharing code harder. Eventually, when an official SVD file for the ESP32 is released, I expect that a lot of code can be shared between the two chips. More information: https://www.espressif.com/en/products/socs/esp32-c3 TODO: - stack scheduler - interrupts - most peripherals (SPI, I2C, PWM, etc)
This commit is contained in:
committed by
Ron Evans
parent
c830f878c6
commit
cb147b9475
@@ -0,0 +1,49 @@
|
||||
// This is a very minimal bootloader for the ESP32-C3. It only initializes the
|
||||
// flash and then continues with the generic RISC-V initialization code, which
|
||||
// in turn will call runtime.main.
|
||||
// It is written in assembly (and not in a higher level language) to make sure
|
||||
// it is entirely loaded into IRAM and doesn't accidentally call functions
|
||||
// stored in IROM.
|
||||
//
|
||||
// For reference, here is a nice introduction into RISC-V assembly:
|
||||
// https://www.imperialviolet.org/2016/12/31/riscv.html
|
||||
|
||||
.section .init
|
||||
.global call_start_cpu0
|
||||
.type call_start_cpu0,@function
|
||||
call_start_cpu0:
|
||||
// At this point:
|
||||
// - The ROM bootloader is finished and has jumped to here.
|
||||
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
|
||||
// by the ROM bootloader.
|
||||
// - We have a usable stack (but not the one we would like to use).
|
||||
// - No flash mappings (MMU) are set up yet.
|
||||
|
||||
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
|
||||
call Cache_Suspend_ICache
|
||||
mv s0, a0 // autoload value
|
||||
call Cache_Invalidate_ICache_All
|
||||
call Cache_MMU_Init
|
||||
|
||||
// Set up DROM from flash.
|
||||
// Somehow, this also sets up IROM from flash. Not sure why, but it avoids
|
||||
// the need for another such call.
|
||||
// C equivalent:
|
||||
// Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0)
|
||||
li a0, 0 // ext_ram: MMU_ACCESS_FLASH
|
||||
li a1, 0x3C000000 // vaddr: address in the data bus
|
||||
li a2, 0 // paddr: physical address in the flash chip
|
||||
li a3, 64 // psize: always 64 (kilobytes)
|
||||
li a4, 128 // num: pages to be set (8192K / 64K = 128)
|
||||
li a5, 0 // fixed
|
||||
call Cache_Dbus_MMU_Set
|
||||
|
||||
// Enable the flash cache.
|
||||
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
|
||||
call Cache_Resume_ICache
|
||||
|
||||
// Jump to generic RISC-V initialization, which initializes the stack
|
||||
// pointer and globals register. It should not return.
|
||||
// (It appears that the linker relaxes this jump and instead inserts the
|
||||
// _start function right after here).
|
||||
j _start
|
||||
@@ -0,0 +1,144 @@
|
||||
// +build esp32c3
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// CPUFrequency returns the current CPU frequency of the chip.
|
||||
// Currently it is a fixed frequency but it may allow changing in the future.
|
||||
func CPUFrequency() uint32 {
|
||||
return 160e6 // 160MHz
|
||||
}
|
||||
|
||||
const (
|
||||
PinOutput PinMode = iota
|
||||
PinInput
|
||||
PinInputPullup
|
||||
PinInputPulldown
|
||||
)
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
if p == NoPin {
|
||||
// This simplifies pin configuration in peripherals such as SPI.
|
||||
return
|
||||
}
|
||||
|
||||
var muxConfig uint32
|
||||
|
||||
// Configure this pin as a GPIO pin.
|
||||
const function = 1 // function 1 is GPIO for every pin
|
||||
muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos
|
||||
|
||||
// Make this pin an input pin (always).
|
||||
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
|
||||
|
||||
// Set drive strength: 0 is lowest, 3 is highest.
|
||||
muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos
|
||||
|
||||
// Select pull mode.
|
||||
if config.Mode == PinInputPullup {
|
||||
muxConfig |= esp.IO_MUX_GPIO_FUN_WPU
|
||||
} else if config.Mode == PinInputPulldown {
|
||||
muxConfig |= esp.IO_MUX_GPIO_FUN_WPD
|
||||
}
|
||||
|
||||
// Configure the pad with the given IO mux configuration.
|
||||
p.mux().Set(muxConfig)
|
||||
|
||||
// Set the output signal to the simple GPIO output.
|
||||
p.outFunc().Set(0x80)
|
||||
|
||||
switch config.Mode {
|
||||
case PinOutput:
|
||||
// Set the 'output enable' bit.
|
||||
esp.GPIO.ENABLE_W1TS.Set(1 << p)
|
||||
case PinInput, PinInputPullup, PinInputPulldown:
|
||||
// Clear the 'output enable' bit.
|
||||
esp.GPIO.ENABLE_W1TC.Set(1 << p)
|
||||
}
|
||||
}
|
||||
|
||||
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
|
||||
// output function selection.
|
||||
func (p Pin) outFunc() *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG)) + uintptr(p)*4)))
|
||||
}
|
||||
|
||||
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
|
||||
// function selection.
|
||||
func inFunc(signal uint32) *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG)) + uintptr(signal)*4)))
|
||||
}
|
||||
|
||||
// mux returns the I/O mux configuration register corresponding to the given
|
||||
// GPIO pin.
|
||||
func (p Pin) mux() *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.IO_MUX.GPIO0)) + uintptr(p)*4)))
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) Set(value bool) {
|
||||
if value {
|
||||
reg, mask := p.portMaskSet()
|
||||
reg.Set(mask)
|
||||
} else {
|
||||
reg, mask := p.portMaskClear()
|
||||
reg.Set(mask)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the register and mask to enable a given GPIO pin. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
//
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) PortMaskSet() (*uint32, uint32) {
|
||||
reg, mask := p.portMaskSet()
|
||||
return ®.Reg, mask
|
||||
}
|
||||
|
||||
// Return the register and mask to disable a given GPIO pin. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
//
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) PortMaskClear() (*uint32, uint32) {
|
||||
reg, mask := p.portMaskClear()
|
||||
return ®.Reg, mask
|
||||
}
|
||||
|
||||
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
|
||||
return &esp.GPIO.OUT_W1TS, 1 << p
|
||||
}
|
||||
|
||||
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
|
||||
return &esp.GPIO.OUT_W1TC, 1 << p
|
||||
}
|
||||
|
||||
var DefaultUART = UART0
|
||||
|
||||
var (
|
||||
UART0 = &_UART0
|
||||
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
type UART struct {
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
func (uart *UART) WriteByte(b byte) error {
|
||||
for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 {
|
||||
// Read UART_TXFIFO_CNT from the status register, which indicates how
|
||||
// many bytes there are in the transmit buffer. Wait until there are
|
||||
// less than 128 bytes in this buffer (the default buffer size).
|
||||
}
|
||||
uart.Bus.FIFO.Set(uint32(b))
|
||||
return nil
|
||||
}
|
||||
@@ -6,19 +6,8 @@ import (
|
||||
"device"
|
||||
"device/esp"
|
||||
"machine"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
var currentTime timeUnit
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.Serial.WriteByte(c)
|
||||
}
|
||||
|
||||
func postinit() {}
|
||||
|
||||
// This is the function called on startup right after the stack pointer has been
|
||||
// set.
|
||||
//export main
|
||||
@@ -50,23 +39,15 @@ func main() {
|
||||
// Clear .bss section. .data has already been loaded by the ROM bootloader.
|
||||
// Do this after increasing the CPU clock to possibly make startup slightly
|
||||
// faster.
|
||||
preinit()
|
||||
clearbss()
|
||||
|
||||
// Initialize UART.
|
||||
machine.Serial.Configure(machine.UARTConfig{})
|
||||
|
||||
// Configure timer 0 in timer group 0, for timekeeping.
|
||||
// EN: Enable the timer.
|
||||
// INCREASE: Count up every tick (as opposed to counting down).
|
||||
// DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two
|
||||
// (40MHz).
|
||||
esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<<esp.TIMG_T0CONFIG_T0_DIVIDER_Pos)
|
||||
|
||||
// Set the timer counter value to 0.
|
||||
esp.TIMG0.T0LOADLO.Set(0)
|
||||
esp.TIMG0.T0LOADHI.Set(0)
|
||||
esp.TIMG0.T0LOAD.Set(0) // value doesn't matter.
|
||||
// Initialize main system timer used for time.Now.
|
||||
initTimer()
|
||||
|
||||
// Initialize the heap, call main.main, etc.
|
||||
run()
|
||||
|
||||
// Fallback: if main ever returns, hang the CPU.
|
||||
@@ -79,44 +60,6 @@ var _sbss [0]byte
|
||||
//go:extern _ebss
|
||||
var _ebss [0]byte
|
||||
|
||||
func preinit() {
|
||||
// Initialize .bss: zero-initialized global variables.
|
||||
// The .data section has already been loaded by the ROM bootloader.
|
||||
ptr := unsafe.Pointer(&_sbss)
|
||||
for ptr != unsafe.Pointer(&_ebss) {
|
||||
*(*uint32)(ptr) = 0
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 4)
|
||||
}
|
||||
}
|
||||
|
||||
func ticks() timeUnit {
|
||||
// First, update the LO and HI register pair by writing any value to the
|
||||
// register. This allows reading the pair atomically.
|
||||
esp.TIMG0.T0UPDATE.Set(0)
|
||||
// Then read the two 32-bit parts of the timer.
|
||||
return timeUnit(uint64(esp.TIMG0.T0LO.Get()) | uint64(esp.TIMG0.T0HI.Get())<<32)
|
||||
}
|
||||
|
||||
func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// Calculate the number of ticks from the number of nanoseconds. At a 80MHz
|
||||
// APB clock, that's 25 nanoseconds per tick with a timer prescaler of 2:
|
||||
// 25 = 1e9 / (80MHz / 2)
|
||||
return timeUnit(ns / 25)
|
||||
}
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// See nanosecondsToTicks.
|
||||
return int64(ticks) * 25
|
||||
}
|
||||
|
||||
// sleepTicks busy-waits until the given number of ticks have passed.
|
||||
func sleepTicks(d timeUnit) {
|
||||
sleepUntil := ticks() + d
|
||||
for ticks() < sleepUntil {
|
||||
// TODO: suspend the CPU to not burn power here unnecessarily.
|
||||
}
|
||||
}
|
||||
|
||||
func abort() {
|
||||
for {
|
||||
device.Asm("waiti 0")
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// +build esp32c3
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"device/riscv"
|
||||
)
|
||||
|
||||
// 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 Timer 0 watchdog.
|
||||
esp.TIMG0.WDTCONFIG0.Set(0)
|
||||
|
||||
// Disable RTC watchdog.
|
||||
esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x50D83AA1)
|
||||
esp.RTC_CNTL.RTC_WDTCONFIG0.Set(0)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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 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<<esp.SYSTEM_CPU_PER_CONF_CPUPERIOD_SEL_Pos)
|
||||
|
||||
clearbss()
|
||||
|
||||
// Initialize main system timer used for time.Now.
|
||||
initTimer()
|
||||
|
||||
// Initialize the heap, call main.main, etc.
|
||||
run()
|
||||
|
||||
// Fallback: if main ever returns, hang the CPU.
|
||||
abort()
|
||||
}
|
||||
|
||||
func abort() {
|
||||
// lock up forever
|
||||
for {
|
||||
riscv.Asm("wfi")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// +build esp32 esp32c3
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"machine"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.Serial.WriteByte(c)
|
||||
}
|
||||
|
||||
func postinit() {}
|
||||
|
||||
// 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.Pointer(uintptr(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(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<<esp.TIMG_T0CONFIG_T0_DIVIDER_Pos)
|
||||
|
||||
// Set the timer counter value to 0.
|
||||
esp.TIMG0.T0LOADLO.Set(0)
|
||||
esp.TIMG0.T0LOADHI.Set(0)
|
||||
esp.TIMG0.T0LOAD.Set(0) // value doesn't matter.
|
||||
}
|
||||
|
||||
func ticks() timeUnit {
|
||||
// First, update the LO and HI register pair by writing any value to the
|
||||
// register. This allows reading the pair atomically.
|
||||
esp.TIMG0.T0UPDATE.Set(0)
|
||||
// Then read the two 32-bit parts of the timer.
|
||||
return timeUnit(uint64(esp.TIMG0.T0LO.Get()) | uint64(esp.TIMG0.T0HI.Get())<<32)
|
||||
}
|
||||
|
||||
func nanosecondsToTicks(ns int64) timeUnit {
|
||||
// Calculate the number of ticks from the number of nanoseconds. At a 80MHz
|
||||
// APB clock, that's 25 nanoseconds per tick with a timer prescaler of 2:
|
||||
// 25 = 1e9 / (80MHz / 2)
|
||||
return timeUnit(ns / 25)
|
||||
}
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
// See nanosecondsToTicks.
|
||||
return int64(ticks) * 25
|
||||
}
|
||||
|
||||
// sleepTicks busy-waits until the given number of ticks have passed.
|
||||
func sleepTicks(d timeUnit) {
|
||||
sleepUntil := ticks() + d
|
||||
for ticks() < sleepUntil {
|
||||
// TODO: suspend the CPU to not burn power here unnecessarily.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user