From 2ce17a1892da9f042394c8e857b000d5e7637b05 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 9 Feb 2020 22:42:47 +0100 Subject: [PATCH] esp8266: add support for this chip Many thanks to cnlohr for the nosdk8266 project: https://github.com/cnlohr/nosdk8266 --- Makefile | 3 + builder/build.go | 7 +- builder/esp.go | 79 ++++++++++------ lib/cmsis-svd | 2 +- src/device/esp/esp8266.S | 6 ++ src/machine/board_nodemcu.go | 21 +++++ src/machine/machine_esp8266.go | 159 +++++++++++++++++++++++++++++++++ src/runtime/runtime_esp8266.go | 115 ++++++++++++++++++++++++ targets/esp8266.json | 15 ++++ targets/esp8266.ld | 109 ++++++++++++++++++++++ targets/nodemcu.json | 4 + 11 files changed, 489 insertions(+), 31 deletions(-) create mode 100644 src/device/esp/esp8266.S create mode 100644 src/machine/board_nodemcu.go create mode 100644 src/machine/machine_esp8266.go create mode 100644 src/runtime/runtime_esp8266.go create mode 100644 targets/esp8266.json create mode 100644 targets/esp8266.ld create mode 100644 targets/nodemcu.json diff --git a/Makefile b/Makefile index 17c6591c..29bf8e02 100644 --- a/Makefile +++ b/Makefile @@ -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 b26fff25..67c09df0 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 cac73e20..9a9c37f2 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 index 2fc33580..d9b58694 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit 2fc335802cf97309ec4035caf276746b53efbd5b +Subproject commit d9b58694cef35b39ddf61c07ef7e6347d6ec3cbd diff --git a/src/device/esp/esp8266.S b/src/device/esp/esp8266.S new file mode 100644 index 00000000..cffa5037 --- /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 00000000..30c16acf --- /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 00000000..78eccc05 --- /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 00000000..2ad10963 --- /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<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 00000000..c1fdbec0 --- /dev/null +++ b/targets/nodemcu.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp8266"], + "build-tags": ["nodemcu"] +}