Skip to content

Commit 0df4a7a

Browse files
aykevldeadprogram
authored andcommitted
esp32: support flashing directly from tinygo
Right now this requires setting the -port parameter, but other than that it totally works (if esptool.py is installed). It works by converting the ELF file to the custom ESP32 image format and flashing that using esptool.py.
1 parent 9a17698 commit 0df4a7a

File tree

4 files changed

+139
-2
lines changed

4 files changed

+139
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ ifneq ($(AVR), 0)
347347
@$(MD5SUM) test.hex
348348
endif
349349
ifneq ($(XTENSA), 0)
350-
$(TINYGO) build -size short -o test.elf -target=esp32 examples/serial
350+
$(TINYGO) build -size short -o test.bin -target=esp32 examples/serial
351351
endif
352352
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
353353
@$(MD5SUM) test.hex

builder/build.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
279279
if err != nil {
280280
return err
281281
}
282+
case "esp32":
283+
// Special format for the ESP32 chip (parsed by the ROM bootloader).
284+
tmppath = filepath.Join(dir, "main"+outext)
285+
err := makeESP32FirmareImage(executable, tmppath)
286+
if err != nil {
287+
return err
288+
}
282289
default:
283290
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
284291
}

builder/esp.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package builder
2+
3+
// This file implements support for writing ESP image files. These image files
4+
// are read by the ROM bootloader so have to be in a particular format.
5+
//
6+
// In the future, it may be necessary to implement support for other image
7+
// formats, such as the ESP8266 image formats (again, used by the ROM bootloader
8+
// to load the firmware).
9+
10+
import (
11+
"bytes"
12+
"crypto/sha256"
13+
"debug/elf"
14+
"encoding/binary"
15+
"fmt"
16+
"io/ioutil"
17+
"sort"
18+
)
19+
20+
type espImageSegment struct {
21+
addr uint32
22+
data []byte
23+
}
24+
25+
// makeESP32Firmare converts an input ELF file to an image file for the ESP32
26+
// chip. This is a special purpose image format just for the ESP32 chip, and is
27+
// parsed by the on-chip mask ROM bootloader.
28+
//
29+
// The following documentation has been used:
30+
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
31+
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
32+
// https://github.com/espressif/esptool/blob/master/esptool.py
33+
func makeESP32FirmareImage(infile, outfile string) error {
34+
inf, err := elf.Open(infile)
35+
if err != nil {
36+
return err
37+
}
38+
defer inf.Close()
39+
40+
// Load all segments to be written to the image. These are actually ELF
41+
// sections, not true ELF segments (similar to how esptool does it).
42+
var segments []*espImageSegment
43+
for _, section := range inf.Sections {
44+
if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 {
45+
continue
46+
}
47+
data, err := section.Data()
48+
if err != nil {
49+
return fmt.Errorf("failed to read section data: %w", err)
50+
}
51+
for len(data)%4 != 0 {
52+
// Align segment to 4 bytes.
53+
data = append(data, 0)
54+
}
55+
if uint64(uint32(section.Addr)) != section.Addr {
56+
return fmt.Errorf("section address too big: 0x%x", section.Addr)
57+
}
58+
segments = append(segments, &espImageSegment{
59+
addr: uint32(section.Addr),
60+
data: data,
61+
})
62+
}
63+
64+
// Sort the segments by address. This is what esptool does too.
65+
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
66+
67+
// Calculate checksum over the segment data. This is used in the image
68+
// footer.
69+
checksum := uint8(0xef)
70+
for _, segment := range segments {
71+
for _, b := range segment.data {
72+
checksum ^= b
73+
}
74+
}
75+
76+
// Write first to an in-memory buffer, primarily so that we can easily
77+
// calculate a hash over the entire image.
78+
// An added benefit is that we don't need to check for errors all the time.
79+
outf := &bytes.Buffer{}
80+
81+
// Image header.
82+
// Details: https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
83+
binary.Write(outf, binary.LittleEndian, struct {
84+
magic uint8
85+
segment_count uint8
86+
spi_mode uint8
87+
spi_speed_size uint8
88+
entry_addr uint32
89+
wp_pin uint8
90+
spi_pin_drv [3]uint8
91+
reserved [11]uint8
92+
hash_appended bool
93+
}{
94+
magic: 0xE9,
95+
segment_count: byte(len(segments)),
96+
spi_mode: 0, // irrelevant, replaced by esptool when flashing
97+
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
98+
entry_addr: uint32(inf.Entry),
99+
wp_pin: 0xEE, // disable WP pin
100+
hash_appended: true, // add a SHA256 hash
101+
})
102+
103+
// Write all segments to the image.
104+
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment
105+
for _, segment := range segments {
106+
binary.Write(outf, binary.LittleEndian, struct {
107+
addr uint32
108+
length uint32
109+
}{
110+
addr: segment.addr,
111+
length: uint32(len(segment.data)),
112+
})
113+
outf.Write(segment.data)
114+
}
115+
116+
// Footer, including checksum.
117+
// The entire image size must be a multiple of 16, so pad the image to one
118+
// byte less than that before writing the checksum.
119+
outf.Write(make([]byte, 15-outf.Len()%16))
120+
outf.WriteByte(checksum)
121+
122+
// SHA256 hash (to protect against image corruption, not for security).
123+
hash := sha256.Sum256(outf.Bytes())
124+
outf.Write(hash[:])
125+
126+
// Write the image to the output file.
127+
return ioutil.WriteFile(outfile, outf.Bytes(), 0666)
128+
}

targets/esp32.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@
99
"linkerscript": "targets/esp32.ld",
1010
"extra-files": [
1111
"src/device/esp/esp32.S"
12-
]
12+
],
13+
"binary-format": "esp32",
14+
"flash-command": "esptool.py --chip=esp32 --port {port} write_flash 0x1000 {bin} -ff 80m -fm dout"
1315
}

0 commit comments

Comments
 (0)