Browse Source

esp8266: add support for this chip

Many thanks to cnlohr for the nosdk8266 project:
    https://github.com/cnlohr/nosdk8266
pull/1372/head
Ayke van Laethem 5 years ago
committed by Ron Evans
parent
commit
2ce17a1892
  1. 3
      Makefile
  2. 7
      builder/build.go
  3. 79
      builder/esp.go
  4. 2
      lib/cmsis-svd
  5. 6
      src/device/esp/esp8266.S
  6. 21
      src/machine/board_nodemcu.go
  7. 159
      src/machine/machine_esp8266.go
  8. 115
      src/runtime/runtime_esp8266.go
  9. 15
      targets/esp8266.json
  10. 109
      targets/esp8266.ld
  11. 4
      targets/nodemcu.json

3
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

7
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
}

79
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)

2
lib/cmsis-svd

@ -1 +1 @@
Subproject commit 2fc335802cf97309ec4035caf276746b53efbd5b
Subproject commit d9b58694cef35b39ddf61c07ef7e6347d6ec3cbd

6
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

21
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

159
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
}

115
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")
}
}

15
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"
}

109
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;

4
targets/nodemcu.json

@ -0,0 +1,4 @@
{
"inherits": ["esp8266"],
"build-tags": ["nodemcu"]
}
Loading…
Cancel
Save