Skip to content

Commit 60d1bf6

Browse files
committed
esp8266: add support for this chip
To use this, use LLVM with Xtensa support: https://github.com/espressif/llvm-project Make sure you modify the CMake command (in the Makefile) to include the Xtensa backend: -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa Many thanks to cnlohr for the nosdk8266 project: https://github.com/cnlohr/nosdk8266
1 parent c721cae commit 60d1bf6

File tree

10 files changed

+476
-1
lines changed

10 files changed

+476
-1
lines changed

src/device/esp/esp8266.S

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
.section .text.runtime.getCurrentStackPointer
3+
.global runtime.getCurrentStackPointer
4+
runtime.getCurrentStackPointer:
5+
// The stack pointer is located in a1. Copy it over to the return register
6+
// in a2.
7+
mov a2, a1
8+
ret.n
9+
10+
.section .text.runtime.abort
11+
.global runtime.abort
12+
runtime.abort:
13+
1:
14+
waiti 0 // wait for interrupt
15+
j 1b

src/device/esp/esp8266.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// +build esp8266
2+
3+
// Most of the register information in here comes from the appendix in this
4+
// document:
5+
// https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
6+
// Some more information comes from the *_register.h files here:
7+
// https://github.com/esp8266/Arduino/tree/master/tools/sdk/include
8+
9+
package esp
10+
11+
import (
12+
"runtime/volatile"
13+
"unsafe"
14+
)
15+
16+
var (
17+
UART0 = (*UART_Type)(unsafe.Pointer(uintptr(0x60000000)))
18+
GPIO = (*GPIO_Type)(unsafe.Pointer(uintptr(0x60000300)))
19+
FRC1 = (*FRC_Type)(unsafe.Pointer(uintptr(0x60000600))) // timer 1
20+
FRC2 = (*FRC_Type)(unsafe.Pointer(uintptr(0x60000620))) // timer 2
21+
IO_MUX = (*IO_MUX_Type)(unsafe.Pointer(uintptr(0x60000800)))
22+
)
23+
24+
type UART_Type struct {
25+
FIFO volatile.Register32
26+
INT_RAW volatile.Register32
27+
INT_ST volatile.Register32
28+
INT_ENA volatile.Register32
29+
INT_CLR volatile.Register32
30+
CLKDIV volatile.Register32
31+
AUTOBAUD volatile.Register32
32+
STATUS volatile.Register32
33+
CONF0 volatile.Register32
34+
CONF1 volatile.Register32
35+
LOWPULSE volatile.Register32
36+
HIGHPULSE volatile.Register32
37+
RXD_CNT volatile.Register32
38+
DATE volatile.Register32
39+
ID volatile.Register32
40+
}
41+
42+
type GPIO_Type struct {
43+
OUT volatile.Register32
44+
OUT_W1TS volatile.Register32
45+
OUT_W1TC volatile.Register32
46+
ENABLE volatile.Register32
47+
ENABLE_W1TS volatile.Register32
48+
ENABLE_W1TC volatile.Register32
49+
IN volatile.Register32
50+
STATUS volatile.Register32
51+
STATUS_W1TS volatile.Register32
52+
STATUS_W1TC volatile.Register32
53+
PIN [16]volatile.Register32
54+
SIGMA_DELTA volatile.Register32
55+
RTC_CALIB_SYNC volatile.Register32
56+
RTC_CALIB_VALUE volatile.Register32
57+
}
58+
59+
type FRC_Type struct {
60+
LOAD volatile.Register32
61+
COUNT volatile.Register32
62+
CTRL volatile.Register32
63+
INT volatile.Register32
64+
ALARM volatile.Register32 // only available in FRC2
65+
}
66+
67+
type IO_MUX_Type struct {
68+
CONF volatile.Register32
69+
// The pins appear to have the following bits defined:
70+
// bit 0: output enable
71+
// bit 1: sleep output enable
72+
// bit 2: sleep pullup 2
73+
// bit 3: sleep pullup
74+
// bit 4-5: lower bits of the pin function
75+
// bit 6: pullup 2
76+
// bit 7: pullup
77+
// bit 8: upper bit of the pin function
78+
// Source:
79+
// https://github.com/esp8266/Arduino/blob/11ae243ecf00bd80c1d5aacde95ca20e92e2cb74/tools/sdk/include/eagle_soc.h#L217-L224
80+
PIN [17]volatile.Register32
81+
}

src/machine/board_nodemcu.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// +build nodemcu
2+
3+
// Pinout for the NodeMCU dev kit.
4+
5+
package machine
6+
7+
// GPIO pins on the NodeMCU board.
8+
const (
9+
D0 Pin = 16
10+
D1 Pin = 5
11+
D2 Pin = 4
12+
D3 Pin = 0
13+
D4 Pin = 2
14+
D5 Pin = 14
15+
D6 Pin = 12
16+
D7 Pin = 13
17+
D8 Pin = 15
18+
)
19+
20+
// Onboard blue LED (on the AI-Thinker module).
21+
const LED = D4

src/machine/machine_esp8266.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// +build esp8266
2+
3+
package machine
4+
5+
import (
6+
"device/esp"
7+
)
8+
9+
func CPUFrequency() uint32 {
10+
return 80000000 // 80MHz
11+
}
12+
13+
type PinMode uint8
14+
15+
const (
16+
PinOutput PinMode = iota
17+
PinInput
18+
)
19+
20+
// Pins that are fixed by the chip.
21+
const (
22+
UART_TX_PIN Pin = 1
23+
UART_RX_PIN Pin = 3
24+
)
25+
26+
// Pin functions are not trivial. The below array maps a pin number (GPIO
27+
// number) to the pad as used in the IO mux.
28+
// Tables with the mapping:
29+
// https://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations#pin_functions
30+
// https://www.espressif.com/sites/default/files/documentation/ESP8266_Pin_List_0.xls
31+
var pinPadMapping = [...]uint8{
32+
12: 0,
33+
13: 1,
34+
14: 2,
35+
15: 3,
36+
3: 4,
37+
1: 5,
38+
6: 6,
39+
7: 7,
40+
8: 8,
41+
9: 9,
42+
10: 10,
43+
11: 11,
44+
0: 12,
45+
2: 13,
46+
4: 14,
47+
5: 15,
48+
}
49+
50+
// Configure sets the given pin as output or input pin.
51+
func (p Pin) Configure(config PinConfig) {
52+
switch config.Mode {
53+
case PinInput, PinOutput:
54+
pad := pinPadMapping[p]
55+
if pad >= 12 { // pin 0, 2, 4, 5
56+
esp.IO_MUX.PIN[pad].Set(0 << 4) // function 0 at bit position 4
57+
} else {
58+
esp.IO_MUX.PIN[pad].Set(3 << 4) // function 3 at bit position 4
59+
}
60+
if config.Mode == PinOutput {
61+
esp.GPIO.ENABLE_W1TS.Set(1 << uint8(p))
62+
} else {
63+
esp.GPIO.ENABLE_W1TC.Set(1 << uint8(p))
64+
}
65+
}
66+
}
67+
68+
// Set sets the output value of this pin to high (true) or low (false).
69+
func (p Pin) Set(value bool) {
70+
if value {
71+
esp.GPIO.OUT_W1TS.Set(1 << uint8(p))
72+
} else {
73+
esp.GPIO.OUT_W1TC.Set(1 << uint8(p))
74+
}
75+
}
76+
77+
// UART0 is a hardware UART that supports both TX and RX.
78+
var UART0 = UART{Buffer: NewRingBuffer()}
79+
80+
type UART struct {
81+
Buffer *RingBuffer
82+
}
83+
84+
// Configure the UART baud rate. TX and RX pins are fixed by the hardware so
85+
// cannot be modified and will be ignored.
86+
func (uart UART) Configure(config UARTConfig) {
87+
if config.BaudRate == 0 {
88+
config.BaudRate = 115200
89+
}
90+
esp.UART0.CLKDIV.Set(CPUFrequency() / config.BaudRate)
91+
}
92+
93+
// WriteByte writes a single byte to the output buffer. Note that the hardware
94+
// includes a buffer of 128 bytes which will be used first.
95+
func (uart UART) WriteByte(c byte) {
96+
for (esp.UART0.STATUS.Get()>>16)&0xff >= 128 {
97+
// Wait until the TX buffer has room.
98+
}
99+
esp.UART0.FIFO.Set(uint32(c))
100+
}

src/machine/uart.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build avr nrf sam sifive stm32
1+
// +build avr esp8266 nrf sam sifive stm32
22

33
package machine
44

src/runtime/arch_xtensa.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// +build xtensa
2+
3+
package runtime
4+
5+
const GOARCH = "arm" // xtensa pretends to be arm
6+
7+
// The bitness of the CPU (e.g. 8, 32, 64).
8+
const TargetBits = 32
9+
10+
// Align on a word boundary.
11+
func align(ptr uintptr) uintptr {
12+
return (ptr + 3) &^ 3
13+
}
14+
15+
func getCurrentStackPointer() uintptr

src/runtime/runtime_esp8266.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// +build esp8266
2+
3+
package runtime
4+
5+
import (
6+
"device/esp"
7+
"machine"
8+
"unsafe"
9+
)
10+
11+
type timeUnit int64
12+
13+
const tickMicros = 3200 // time.Second / (80MHz / 256)
14+
15+
var currentTime timeUnit = 0
16+
17+
func putchar(c byte) {
18+
machine.UART0.WriteByte(c)
19+
}
20+
21+
// Write to the internal control bus (using I2C?).
22+
// Signature found here:
23+
// https://github.com/espressif/esp-idf/blob/64654c04447914610586e1ac44457510a5bf7191/components/soc/esp32/i2c_rtc_clk.h#L32
24+
//export rom_i2c_writeReg
25+
func rom_i2c_writeReg(block, host_id, reg_add, data uint8)
26+
27+
//export main
28+
func main() {
29+
// Clear .bss section. .data has already been loaded by the ROM bootloader.
30+
preinit()
31+
32+
// Initialize PLL.
33+
// I'm not quite sure what this magic incantation means, but it does set the
34+
// esp8266 to the right clock speed. Without this, it is running too slow.
35+
rom_i2c_writeReg(103, 4, 1, 136)
36+
rom_i2c_writeReg(103, 4, 2, 145)
37+
38+
// Initialize UART.
39+
machine.UART0.Configure(machine.UARTConfig{})
40+
41+
// Initialize timer. Bits:
42+
// 7: timer enable
43+
// 6: automatically reload when hitting 0
44+
// 3-2: divide by 256
45+
esp.FRC1.CTRL.Set(1<<7 | 1<<6 | 2<<2)
46+
esp.FRC1.LOAD.Set(0x3fffff) // set all 22 bits to 1
47+
esp.FRC1.COUNT.Set(0x3fffff) // set all 22 bits to 1
48+
49+
// Run init functions in all packages.
50+
initAll()
51+
52+
// Call main.main.
53+
callMain()
54+
55+
// Fallback: if main ever returns, hang the CPU.
56+
abort()
57+
}
58+
59+
//go:extern _sbss
60+
var _sbss [0]byte
61+
62+
//go:extern _ebss
63+
var _ebss [0]byte
64+
65+
func preinit() {
66+
// Initialize .bss: zero-initialized global variables.
67+
ptr := unsafe.Pointer(&_sbss)
68+
for ptr != unsafe.Pointer(&_ebss) {
69+
*(*uint32)(ptr) = 0
70+
ptr = unsafe.Pointer(uintptr(ptr) + 4)
71+
}
72+
}
73+
74+
func ticks() timeUnit {
75+
// Get the counter value of the timer. It is 22 bits and starts with all
76+
// ones (0x3fffff). To make it easier to work with, let it count upwards.
77+
count := 0x3fffff - esp.FRC1.COUNT.Get()
78+
79+
// Replace the lowest 22 bits of the current time with the counter.
80+
newTime := (currentTime &^ 0x3fffff) | timeUnit(count)
81+
82+
// If there was an overflow, the new time will be lower than the current
83+
// time, so will need to add (1<<22).
84+
if newTime < currentTime {
85+
newTime += 0x400000
86+
}
87+
88+
// Update the timestamp for the next call to ticks().
89+
currentTime = newTime
90+
91+
return currentTime
92+
}
93+
94+
const asyncScheduler = false
95+
96+
// sleepTicks busy-waits until the given number of ticks have passed.
97+
func sleepTicks(d timeUnit) {
98+
sleepUntil := ticks() + d
99+
for ticks() < sleepUntil {
100+
}
101+
}
102+
103+
func abort()
104+
105+
// Implement memset for LLVM and compiler-rt.
106+
//go:export memset
107+
func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) {
108+
for i := uintptr(0); i < size; i++ {
109+
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
110+
}
111+
}

targets/esp8266.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"llvm-target": "xtensa",
3+
"cpu": "esp8266",
4+
"goos": "linux",
5+
"goarch": "arm",
6+
"build-tags": ["esp8266", "xtensa", "baremetal", "linux", "arm"],
7+
"compiler": "clang",
8+
"linker": "xtensa-lx106-elf-ld",
9+
"rtlib": "compiler-rt",
10+
"cflags": [
11+
"-g",
12+
"--target=xtensa",
13+
"-mcpu=esp8266",
14+
"-Oz",
15+
"-Werror",
16+
"-fshort-enums",
17+
"-Wno-macro-redefined",
18+
"-Qunused-arguments",
19+
"-fno-exceptions", "-fno-unwind-tables",
20+
"-ffunction-sections", "-fdata-sections"
21+
],
22+
"ldflags": [
23+
"--gc-sections"
24+
],
25+
"linkerscript": "targets/esp8266.ld",
26+
"extra-files": [
27+
"src/device/esp/esp8266.S"
28+
]
29+
}

0 commit comments

Comments
 (0)