aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2020-02-09 22:42:47 +0100
committerRon Evans <[email protected]>2020-09-09 19:17:11 +0200
commit2ce17a1892da9f042394c8e857b000d5e7637b05 (patch)
treed699d6c1dfef370580b53abc7d8d421e57f6d2fe
parent0b9b2936518603b8bfa41b31f6ba95f3d511a018 (diff)
downloadtinygo-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--Makefile3
-rw-r--r--builder/build.go7
-rw-r--r--builder/esp.go79
m---------lib/cmsis-svd0
-rw-r--r--src/device/esp/esp8266.S6
-rw-r--r--src/machine/board_nodemcu.go21
-rw-r--r--src/machine/machine_esp8266.go159
-rw-r--r--src/runtime/runtime_esp8266.go115
-rw-r--r--targets/esp8266.json15
-rw-r--r--targets/esp8266.ld109
-rw-r--r--targets/nodemcu.json4
11 files changed, 488 insertions, 30 deletions
diff --git a/Makefile b/Makefile
index 17c6591c4..29bf8e021 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 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"]
+}