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