diff options
author | Ayke van Laethem <[email protected]> | 2020-02-09 22:42:47 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2020-09-09 19:17:11 +0200 |
commit | 2ce17a1892da9f042394c8e857b000d5e7637b05 (patch) | |
tree | d699d6c1dfef370580b53abc7d8d421e57f6d2fe | |
parent | 0b9b2936518603b8bfa41b31f6ba95f3d511a018 (diff) | |
download | tinygo-2ce17a1892da9f042394c8e857b000d5e7637b05.tar.gz tinygo-2ce17a1892da9f042394c8e857b000d5e7637b05.zip |
esp8266: add support for this chip
Many thanks to cnlohr for the nosdk8266 project:
https://github.com/cnlohr/nosdk8266
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | builder/build.go | 7 | ||||
-rw-r--r-- | builder/esp.go | 79 | ||||
m--------- | lib/cmsis-svd | 0 | ||||
-rw-r--r-- | src/device/esp/esp8266.S | 6 | ||||
-rw-r--r-- | src/machine/board_nodemcu.go | 21 | ||||
-rw-r--r-- | src/machine/machine_esp8266.go | 159 | ||||
-rw-r--r-- | src/runtime/runtime_esp8266.go | 115 | ||||
-rw-r--r-- | targets/esp8266.json | 15 | ||||
-rw-r--r-- | targets/esp8266.ld | 109 | ||||
-rw-r--r-- | targets/nodemcu.json | 4 |
11 files changed, 488 insertions, 30 deletions
@@ -356,6 +356,9 @@ ifneq ($(AVR), 0) endif ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target=esp32-wroom-32 examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 + @$(MD5SUM) test.bin endif $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex diff --git a/builder/build.go b/builder/build.go index b26fff255..67c09df0a 100644 --- a/builder/build.go +++ b/builder/build.go @@ -292,10 +292,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil if err != nil { return err } - case "esp32": - // Special format for the ESP32 chip (parsed by the ROM bootloader). + case "esp32", "esp8266": + // Special format for the ESP family of chips (parsed by the ROM + // bootloader). tmppath = filepath.Join(dir, "main"+outext) - err := makeESP32FirmareImage(executable, tmppath) + err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat) if err != nil { return err } diff --git a/builder/esp.go b/builder/esp.go index cac73e20b..9a9c37f2e 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -22,15 +22,15 @@ type espImageSegment struct { data []byte } -// makeESP32Firmare converts an input ELF file to an image file for the ESP32 -// chip. This is a special purpose image format just for the ESP32 chip, and is -// parsed by the on-chip mask ROM bootloader. +// makeESPFirmare converts an input ELF file to an image file for an ESP32 or +// ESP8266 chip. This is a special purpose image format just for the ESP chip +// family, and is parsed by the on-chip mask ROM bootloader. // // The following documentation has been used: // https://github.com/espressif/esptool/wiki/Firmware-Image-Format // https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58 // https://github.com/espressif/esptool/blob/master/esptool.py -func makeESP32FirmareImage(infile, outfile string) error { +func makeESPFirmareImage(infile, outfile, format string) error { inf, err := elf.Open(infile) if err != nil { return err @@ -79,26 +79,49 @@ func makeESP32FirmareImage(infile, outfile string) error { outf := &bytes.Buffer{} // Image header. - // Details: https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58 - binary.Write(outf, binary.LittleEndian, struct { - magic uint8 - segment_count uint8 - spi_mode uint8 - spi_speed_size uint8 - entry_addr uint32 - wp_pin uint8 - spi_pin_drv [3]uint8 - reserved [11]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 - entry_addr: uint32(inf.Entry), - wp_pin: 0xEE, // disable WP pin - hash_appended: true, // add a SHA256 hash - }) + switch format { + case "esp32": + // Header format: + // https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58 + binary.Write(outf, binary.LittleEndian, struct { + magic uint8 + segment_count uint8 + spi_mode uint8 + spi_speed_size uint8 + entry_addr uint32 + wp_pin uint8 + spi_pin_drv [3]uint8 + reserved [11]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 + entry_addr: uint32(inf.Entry), + wp_pin: 0xEE, // disable WP pin + hash_appended: true, // add a SHA256 hash + }) + case "esp8266": + // Header format: + // https://github.com/espressif/esptool/wiki/Firmware-Image-Format + // Basically a truncated version of the ESP32 header. + binary.Write(outf, binary.LittleEndian, struct { + magic uint8 + segment_count uint8 + spi_mode uint8 + spi_speed_size uint8 + entry_addr uint32 + }{ + magic: 0xE9, + segment_count: byte(len(segments)), + spi_mode: 0, // irrelevant, replaced by esptool when flashing + spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing + entry_addr: uint32(inf.Entry), + }) + default: + return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format) + } // Write all segments to the image. // https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment @@ -119,9 +142,11 @@ func makeESP32FirmareImage(infile, outfile string) error { outf.Write(make([]byte, 15-outf.Len()%16)) outf.WriteByte(checksum) - // SHA256 hash (to protect against image corruption, not for security). - hash := sha256.Sum256(outf.Bytes()) - outf.Write(hash[:]) + if format == "esp32" { + // SHA256 hash (to protect against image corruption, not for security). + hash := sha256.Sum256(outf.Bytes()) + outf.Write(hash[:]) + } // Write the image to the output file. return ioutil.WriteFile(outfile, outf.Bytes(), 0666) diff --git a/lib/cmsis-svd b/lib/cmsis-svd -Subproject 2fc335802cf97309ec4035caf276746b53efbd5 +Subproject d9b58694cef35b39ddf61c07ef7e6347d6ec3cb diff --git a/src/device/esp/esp8266.S b/src/device/esp/esp8266.S new file mode 100644 index 000000000..cffa5037a --- /dev/null +++ b/src/device/esp/esp8266.S @@ -0,0 +1,6 @@ + +.section .text.tinygo_scanCurrentStack +.global tinygo_scanCurrentStack +tinygo_scanCurrentStack: + // TODO: save callee saved registers on the stack + j tinygo_scanstack diff --git a/src/machine/board_nodemcu.go b/src/machine/board_nodemcu.go new file mode 100644 index 000000000..30c16acf0 --- /dev/null +++ b/src/machine/board_nodemcu.go @@ -0,0 +1,21 @@ +// +build nodemcu + +// Pinout for the NodeMCU dev kit. + +package machine + +// GPIO pins on the NodeMCU board. +const ( + D0 Pin = 16 + D1 Pin = 5 + D2 Pin = 4 + D3 Pin = 0 + D4 Pin = 2 + D5 Pin = 14 + D6 Pin = 12 + D7 Pin = 13 + D8 Pin = 15 +) + +// Onboard blue LED (on the AI-Thinker module). +const LED = D4 diff --git a/src/machine/machine_esp8266.go b/src/machine/machine_esp8266.go new file mode 100644 index 000000000..78eccc059 --- /dev/null +++ b/src/machine/machine_esp8266.go @@ -0,0 +1,159 @@ +// +build esp8266 + +package machine + +import ( + "device/esp" + "runtime/volatile" +) + +func CPUFrequency() uint32 { + return 80000000 // 80MHz +} + +type PinMode uint8 + +const ( + PinOutput PinMode = iota + PinInput +) + +// Pins that are fixed by the chip. +const ( + UART_TX_PIN Pin = 1 + UART_RX_PIN Pin = 3 +) + +// Pin functions are not trivial. The below array maps a pin number (GPIO +// number) to the pad as used in the IO mux. +// Tables with the mapping: +// https://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations#pin_functions +// https://www.espressif.com/sites/default/files/documentation/ESP8266_Pin_List_0.xls +var pinPadMapping = [...]uint8{ + 12: 0, + 13: 1, + 14: 2, + 15: 3, + 3: 4, + 1: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 11: 11, + 0: 12, + 2: 13, + 4: 14, + 5: 15, +} + +// getPad returns the pad number and the register to configure this pad. +func (p Pin) getPad() (uint8, *volatile.Register32) { + pad := pinPadMapping[p] + var reg *volatile.Register32 + switch pad { + case 0: + reg = &esp.IO_MUX.IO_MUX_MTDI + case 1: + reg = &esp.IO_MUX.IO_MUX_MTCK + case 2: + reg = &esp.IO_MUX.IO_MUX_MTMS + case 3: + reg = &esp.IO_MUX.IO_MUX_MTDO + case 4: + reg = &esp.IO_MUX.IO_MUX_U0RXD + case 5: + reg = &esp.IO_MUX.IO_MUX_U0TXD + case 6: + reg = &esp.IO_MUX.IO_MUX_SD_CLK + case 7: + reg = &esp.IO_MUX.IO_MUX_SD_DATA0 + case 8: + reg = &esp.IO_MUX.IO_MUX_SD_DATA1 + case 9: + reg = &esp.IO_MUX.IO_MUX_SD_DATA2 + case 10: + reg = &esp.IO_MUX.IO_MUX_SD_DATA3 + case 11: + reg = &esp.IO_MUX.IO_MUX_SD_CMD + case 12: + reg = &esp.IO_MUX.IO_MUX_GPIO0 + case 13: + reg = &esp.IO_MUX.IO_MUX_GPIO2 + case 14: + reg = &esp.IO_MUX.IO_MUX_GPIO4 + case 15: + reg = &esp.IO_MUX.IO_MUX_GPIO5 + } + return pad, reg +} + +// Configure sets the given pin as output or input pin. +func (p Pin) Configure(config PinConfig) { + switch config.Mode { + case PinInput, PinOutput: + pad, reg := p.getPad() + if pad >= 12 { // pin 0, 2, 4, 5 + reg.Set(0 << 4) // function 0 at bit position 4 + } else { + reg.Set(3 << 4) // function 3 at bit position 4 + } + if config.Mode == PinOutput { + esp.GPIO.GPIO_ENABLE_W1TS.Set(1 << p) + } else { + esp.GPIO.GPIO_ENABLE_W1TC.Set(1 << p) + } + } +} + +// Set sets the output value of this pin to high (true) or low (false). +func (p Pin) Set(value bool) { + if value { + esp.GPIO.GPIO_OUT_W1TS.Set(1 << p) + } else { + esp.GPIO.GPIO_OUT_W1TC.Set(1 << p) + } +} + +// 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) { + return &esp.GPIO.GPIO_OUT_W1TS.Reg, 1 << p +} + +// 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) { + return &esp.GPIO.GPIO_OUT_W1TC.Reg, 1 << p +} + +// UART0 is a hardware UART that supports both TX and RX. +var UART0 = UART{Buffer: NewRingBuffer()} + +type UART struct { + Buffer *RingBuffer +} + +// Configure the UART baud rate. TX and RX pins are fixed by the hardware so +// cannot be modified and will be ignored. +func (uart UART) Configure(config UARTConfig) { + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + esp.UART0.UART_CLKDIV.Set(CPUFrequency() / config.BaudRate) +} + +// WriteByte writes a single byte to the output buffer. Note that the hardware +// includes a buffer of 128 bytes which will be used first. +func (uart UART) WriteByte(c byte) error { + for (esp.UART0.UART_STATUS.Get()>>16)&0xff >= 128 { + // Wait until the TX buffer has room. + } + esp.UART0.UART_FIFO.Set(uint32(c)) + return nil +} diff --git a/src/runtime/runtime_esp8266.go b/src/runtime/runtime_esp8266.go new file mode 100644 index 000000000..2ad109639 --- /dev/null +++ b/src/runtime/runtime_esp8266.go @@ -0,0 +1,115 @@ +// +build esp8266 + +package runtime + +import ( + "device" + "device/esp" + "machine" + "unsafe" +) + +type timeUnit int64 + +var currentTime timeUnit = 0 + +func putchar(c byte) { + machine.UART0.WriteByte(c) +} + +// Write to the internal control bus (using I2C?). +// Signature found here: +// https://github.com/espressif/ESP8266_RTOS_SDK/blob/14171de0/components/esp8266/include/esp8266/rom_functions.h#L54 +//export rom_i2c_writeReg +func rom_i2c_writeReg(block, host_id, reg_add, data uint8) + +func postinit() {} + +//export main +func main() { + // Clear .bss section. .data has already been loaded by the ROM bootloader. + preinit() + + // Initialize PLL. + // I'm not quite sure what this magic incantation means, but it does set the + // esp8266 to the right clock speed. Without this, it is running too slow. + rom_i2c_writeReg(103, 4, 1, 136) + rom_i2c_writeReg(103, 4, 2, 145) + + // Initialize UART. + machine.UART0.Configure(machine.UARTConfig{}) + + // Initialize timer. Bits: + // ENABLE: timer enable + // ROLLOVER: automatically reload when hitting 0 + // PRESCALE: divide by 256 + esp.TIMER.FRC1_CTRL.Set( + esp.TIMER_FRC1_CTRL_TIMER_ENABLE | esp.TIMER_FRC1_CTRL_ROLLOVER | esp.TIMER_FRC1_CTRL_PRESCALE_DIVIDER_DEVIDED_BY_256<<esp.TIMER_FRC1_CTRL_PRESCALE_DIVIDER_Pos) + esp.TIMER.FRC1_LOAD.Set(0x3fffff) // set all 22 bits to 1 + esp.TIMER.FRC1_COUNT.Set(0x3fffff) // set all 22 bits to 1 + + run() + + // Fallback: if main ever returns, hang the CPU. + abort() +} + +//go:extern _sbss +var _sbss [0]byte + +//go:extern _ebss +var _ebss [0]byte + +func preinit() { + // Initialize .bss: zero-initialized global variables. + ptr := unsafe.Pointer(&_sbss) + for ptr != unsafe.Pointer(&_ebss) { + *(*uint32)(ptr) = 0 + ptr = unsafe.Pointer(uintptr(ptr) + 4) + } +} + +func ticks() timeUnit { + // Get the counter value of the timer. It is 22 bits and starts with all + // ones (0x3fffff). To make it easier to work with, let it count upwards. + count := 0x3fffff - esp.TIMER.FRC1_COUNT.Get() + + // Replace the lowest 22 bits of the current time with the counter. + newTime := (currentTime &^ 0x3fffff) | timeUnit(count) + + // If there was an overflow, the new time will be lower than the current + // time, so will need to add (1<<22). + if newTime < currentTime { + newTime += 0x400000 + } + + // Update the timestamp for the next call to ticks(). + currentTime = newTime + + return currentTime +} + +const asyncScheduler = false + +const tickNanos = 3200 // time.Second / (80MHz / 256) + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * tickNanos +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / tickNanos) +} + +// sleepTicks busy-waits until the given number of ticks have passed. +func sleepTicks(d timeUnit) { + sleepUntil := ticks() + d + for ticks() < sleepUntil { + } +} + +func abort() { + for { + device.Asm("waiti 0") + } +} diff --git a/targets/esp8266.json b/targets/esp8266.json new file mode 100644 index 000000000..8e4c9aad7 --- /dev/null +++ b/targets/esp8266.json @@ -0,0 +1,15 @@ +{ + "inherits": ["xtensa"], + "cpu": "esp8266", + "build-tags": ["esp8266", "esp"], + "linker": "xtensa-esp32-elf-ld", + "cflags": [ + "-mcpu=esp8266" + ], + "linkerscript": "targets/esp8266.ld", + "extra-files": [ + "src/device/esp/esp8266.S" + ], + "binary-format": "esp8266", + "flash-command": "esptool.py --chip=esp8266 --port {port} write_flash 0x00000 {bin} -fm qio" +} diff --git a/targets/esp8266.ld b/targets/esp8266.ld new file mode 100644 index 000000000..9cbc42c14 --- /dev/null +++ b/targets/esp8266.ld @@ -0,0 +1,109 @@ +/* Linker script for the ESP8266 */ + +MEMORY +{ + /* Data RAM. Allows byte access. */ + DRAM (rw) : ORIGIN = 0x3FFE8000, LENGTH = 80K + /* Instruction RAM. */ + IRAM (x) : ORIGIN = 0x40100000, LENGTH = 32K +} + +/* The entry point. It is set in the image flashed to the chip, so must be + * defined. + */ +ENTRY(main) + +SECTIONS +{ + /* Mutable global variables. + */ + .data : ALIGN(4) + { + _sdata = ABSOLUTE(.); + *(.data) + *(.data.*) + } >DRAM + + /* Constant global variables. + * Note that they still need to be loaded in RAM because the ESP8266 doesn't + * allow byte access to flash. + */ + .rodata : ALIGN(4) + { + *(.rodata) + *(.rodata.*) + } >DRAM + + /* Global variables that are mutable and zero-initialized. + */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN (4); + _sbss = ABSOLUTE(.); + *(.bss) + *(.bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM + + /* Constant literals and code. Loaded into IRAM for now. Eventually, most + * code should be executed directly from flash. + * Note that literals must be before code for the l32r instruction to work. + */ + .text : ALIGN(4) + { + *(.literal .text) + *(.literal.* .text.*) + } >IRAM +} + +_globals_start = _sdata; +_globals_end = _ebss; +_heap_start = _ebss; +_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); + +/* It appears that the stack is set to 0x3ffffff0 when main is called. + * Be conservative and scan all the way up to the end of the RAM. + */ +_stack_top = 0x40000000; + +/* Functions normally provided by a libc. + * Source: + * https://github.com/espressif/ESP8266_NONOS_SDK/blob/master/ld/eagle.rom.addr.v6.ld + */ +memcpy = 0x4000df48; +memmove = 0x4000e04c; +memset = 0x4000e190; + +/* Compiler runtime functions provided by the ROM. + * Source: + * https://github.com/espressif/ESP8266_NONOS_SDK/blob/master/ld/eagle.rom.addr.v6.ld + */ +__adddf3 = 0x4000c538; +__addsf3 = 0x4000c180; +__divdf3 = 0x4000cb94; +__divdi3 = 0x4000ce60; +__divsi3 = 0x4000dc88; +__extendsfdf2 = 0x4000cdfc; +__fixdfsi = 0x4000ccb8; +__fixunsdfsi = 0x4000cd00; +__fixunssfsi = 0x4000c4c4; +__floatsidf = 0x4000e2f0; +__floatsisf = 0x4000e2ac; +__floatunsidf = 0x4000e2e8; +__floatunsisf = 0x4000e2a4; +__muldf3 = 0x4000c8f0; +__muldi3 = 0x40000650; +__mulsf3 = 0x4000c3dc; +__subdf3 = 0x4000c688; +__subsf3 = 0x4000c268; +__truncdfsf2 = 0x4000cd5c; +__udivdi3 = 0x4000d310; +__udivsi3 = 0x4000e21c; +__umoddi3 = 0x4000d770; +__umodsi3 = 0x4000e268; +__umulsidi3 = 0x4000dcf0; + +/* Proprietary ROM function needed for proper clock configuration. + */ +rom_i2c_writeReg = 0x400072d8; diff --git a/targets/nodemcu.json b/targets/nodemcu.json new file mode 100644 index 000000000..c1fdbec06 --- /dev/null +++ b/targets/nodemcu.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp8266"], + "build-tags": ["nodemcu"] +} |