Skip to content

Commit 1c1cea0

Browse files
authored
Add better support for ESP32-C3 (#415)
* Add elf2image tool to create esp format binaries * Add esp format target to esp port * Add documentation on how to flash esp32-c3 target with both esp image and direct boot.
1 parent a371a1f commit 1c1cea0

File tree

23 files changed

+1016
-186
lines changed

23 files changed

+1016
-186
lines changed

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ jobs:
4646
run: zig build test
4747
working-directory: tools/uf2
4848

49+
unit-test-esp-image:
50+
name: Unit Test ESP Image
51+
continue-on-error: true
52+
strategy:
53+
matrix:
54+
os: [ubuntu-latest, windows-latest, macos-latest]
55+
runs-on: ${{ matrix.os }}
56+
steps:
57+
- name: Checkout
58+
uses: actions/checkout@v4
59+
- name: Setup Zig
60+
uses: mlugg/setup-zig@v1
61+
with:
62+
version: ${{ env.ZIG_VERSION }}
63+
- name: Unit Test ESP Image
64+
run: zig build test
65+
working-directory: tools/esp_image
66+
4967
stm32-gen-check:
5068
name: Check that stm32 generated code is up to date
5169
continue-on-error: true

build-internals/build.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const Module = Build.Module;
66
const regz = @import("regz");
77
pub const Patch = regz.patch.Patch;
88
const uf2 = @import("uf2");
9-
const FamilyId = uf2.FamilyId;
9+
pub const FamilyId = uf2.FamilyId;
10+
const esp_image = @import("esp_image");
1011

1112
pub fn build(b: *Build) void {
1213
_ = b.addModule("build-internals", .{
@@ -178,7 +179,7 @@ pub const BinaryFormat = union(enum) {
178179
uf2: uf2.FamilyId,
179180

180181
/// The [firmware format](https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/firmware-image-format.html) used by the [esptool](https://github.com/espressif/esptool) bootloader.
181-
esp,
182+
esp: esp_image.Options,
182183

183184
/// Custom option for non-standard formats.
184185
custom: *Custom,

build-internals/build.zig.zon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
.dependencies = .{
66
.regz = .{ .path = "../tools/regz" },
77
.uf2 = .{ .path = "../tools/uf2" },
8+
.esp_image = .{ .path = "../tools/esp_image" },
89
},
910
.paths = .{
1011
"LICENSE",

build.zig

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub fn build(b: *Build) void {
7070
fn generate_release_steps(b: *Build) void {
7171
const release_regz_step = b.step("release-regz", "Generate the release binaries for regz");
7272
const release_uf2_step = b.step("release-uf2", "Generate the release binaries for uf2");
73+
const release_esp_image_step = b.step("release-esp-image", "Generate the release binaries for esp image");
7374

7475
for (exe_targets) |t| {
7576
const release_target = b.resolveTargetQuery(t);
@@ -108,6 +109,25 @@ fn generate_release_steps(b: *Build) void {
108109
});
109110
release_uf2_step.dependOn(&uf2_target_output.step);
110111
}
112+
113+
for (exe_targets) |t| {
114+
const release_target = b.resolveTargetQuery(t);
115+
116+
const esp_image_dep = b.dependency("tools/esp_image", .{
117+
.optimize = .ReleaseSafe,
118+
.target = release_target,
119+
});
120+
121+
const elf2image_artifact = esp_image_dep.artifact("elf2image");
122+
const elf2image_target_output = b.addInstallArtifact(elf2image_artifact, .{
123+
.dest_dir = .{
124+
.override = .{
125+
.custom = t.zigTriple(b.allocator) catch unreachable,
126+
},
127+
},
128+
});
129+
release_esp_image_step.dependOn(&elf2image_target_output.step);
130+
}
111131
}
112132

113133
pub const PortSelect = blk: {
@@ -632,7 +652,12 @@ pub fn MicroBuild(port_select: PortSelect) type {
632652
},
633653

634654
.dfu => @panic("DFU is not implemented yet. See https://github.com/ZigEmbeddedGroup/microzig/issues/145 for more details!"),
635-
.esp => @panic("ESP firmware image is not implemented yet. See https://github.com/ZigEmbeddedGroup/microzig/issues/146 for more details!"),
655+
656+
.esp => |options| @import("tools/esp_image").from_elf(
657+
fw.mb.dep.builder.dependency("tools/esp_image", .{}),
658+
elf_file,
659+
options,
660+
),
636661

637662
.custom => |generator| generator.convert(fw.target.dep, elf_file),
638663
};

build.zig.zon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// tools
1313
.@"tools/regz" = .{ .path = "tools/regz" },
1414
.@"tools/uf2" = .{ .path = "tools/uf2" },
15+
.@"tools/esp_image" = .{ .path = "tools/esp_image" },
1516

1617
// modules
1718
.@"modules/foundation-libc" = .{ .path = "modules/foundation-libc" },

examples/espressif/esp/README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
# Examples for the Port `espressif-esp`
22

3-
- [Blinky](src/blinky.zig) on [ESP32-C3-32S-Kit](https://www.waveshare.com/wiki/ESP-C3-32S-Kit)
3+
- [Blinky](src/blinky.zig) on [ESP32-C3-32S-Kit](https://www.waveshare.com/wiki/ESP-C3-32S-Kit)
44
Showcases how to do a simple RGB cycling.
5+
6+
## How to flash the image onto the device
7+
8+
- esp image
9+
10+
```sh
11+
esptool.py --chip esp32c3 --baud 460800 --before default_reset --after hard_reset write_flash \
12+
0x0 bootloader.bin 0x8000 partition_table.bin 0x10000 zig-out/firmware/esp32_c3_blinky.bin
13+
```
14+
NOTE: you have to provide a [bootloader](https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-guides/bootloader.html) and a [partition table](https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-guides/partition-tables.html).
15+
16+
- [direct boot image](https://github.com/espressif/esp32c3-direct-boot-example)
17+
18+
```sh
19+
esptool.py --chip esp32c3 --baud 460800 --before default_reset --after hard_reset write_flash 0x0 \
20+
zig-out/firmware/esp32_c3_direct_boot_blinky.bin
21+
```

examples/espressif/esp/build.zig

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,48 @@ pub fn build(b: *std.Build) void {
1010

1111
const mz_dep = b.dependency("microzig", .{});
1212
const mb = MicroBuild.init(b, mz_dep) orelse return;
13+
14+
const targets = [_]TargetDescription{
15+
.{ .prefix = "esp32_c3", .target = mb.ports.esp.chips.esp32_c3 },
16+
.{ .prefix = "esp32_c3_direct_boot", .target = mb.ports.esp.chips.esp32_c3_direct_boot },
17+
};
18+
1319
const available_examples = [_]Example{
14-
.{ .target = mb.ports.esp.chips.esp32_c3, .name = "esp32-c3_blinky", .file = "src/blinky.zig" },
20+
.{ .name = "blinky", .file = "src/blinky.zig" },
1521
};
1622

1723
for (available_examples) |example| {
18-
// `add_firmware` basically works like addExecutable, but takes a
19-
// `microzig.Target` for target instead of a `std.zig.CrossTarget`.
20-
//
21-
// The target will convey all necessary information on the chip,
22-
// cpu and potentially the board as well.
23-
const firmware = mb.add_firmware(.{
24-
.name = example.name,
25-
.target = example.target,
26-
.optimize = optimize,
27-
.root_source_file = b.path(example.file),
28-
});
29-
30-
// `installFirmware()` is the MicroZig pendant to `Build.installArtifact()`
31-
// and allows installing the firmware as a typical firmware file.
32-
//
33-
// This will also install into `$prefix/firmware` instead of `$prefix/bin`.
34-
mb.install_firmware(firmware, .{});
35-
36-
// For debugging, we also always install the firmware as an ELF file
37-
mb.install_firmware(firmware, .{ .format = .elf });
24+
for (targets) |target_desc| {
25+
// `add_firmware` basically works like addExecutable, but takes a
26+
// `microzig.Target` for target instead of a `std.zig.CrossTarget`.
27+
//
28+
// The target will convey all necessary information on the chip,
29+
// cpu and potentially the board as well.
30+
const firmware = mb.add_firmware(.{
31+
.name = b.fmt("{s}_{s}", .{ target_desc.prefix, example.name }),
32+
.target = target_desc.target,
33+
.optimize = optimize,
34+
.root_source_file = b.path(example.file),
35+
});
36+
37+
// `installFirmware()` is the MicroZig pendant to `Build.installArtifact()`
38+
// and allows installing the firmware as a typical firmware file.
39+
//
40+
// This will also install into `$prefix/firmware` instead of `$prefix/bin`.
41+
mb.install_firmware(firmware, .{});
42+
43+
// For debugging, we also always install the firmware as an ELF file
44+
mb.install_firmware(firmware, .{ .format = .elf });
45+
}
3846
}
3947
}
4048

41-
const Example = struct {
49+
const TargetDescription = struct {
50+
prefix: []const u8,
4251
target: *const microzig.Target,
52+
};
53+
54+
const Example = struct {
4355
name: []const u8,
4456
file: []const u8,
4557
};

port/espressif/esp/build.zig

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const Self = @This();
55

66
chips: struct {
77
esp32_c3: *const microzig.Target,
8+
esp32_c3_direct_boot: *const microzig.Target,
89
},
910

1011
boards: struct {},
@@ -13,26 +14,60 @@ pub fn init(dep: *std.Build.Dependency) Self {
1314
const b = dep.builder;
1415

1516
const hal: microzig.HardwareAbstractionLayer = .{
16-
.root_source_file = b.path("src/hals/ESP32_C3.zig"),
17+
.root_source_file = b.path("src/hal.zig"),
18+
};
19+
20+
const chip_esp32_c3: microzig.Target = .{
21+
.dep = dep,
22+
.preferred_binary_format = .{ .esp = .{
23+
.chip_id = .esp32_c3,
24+
.flash_mode = .dio,
25+
.flash_size = .@"4mb",
26+
.flash_freq = .@"40m",
27+
} },
28+
.chip = .{
29+
.name = "ESP32-C3",
30+
.url = "https://www.espressif.com/en/products/socs/esp32-c3",
31+
.cpu = .{
32+
.cpu_arch = .riscv32,
33+
.cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 },
34+
.cpu_features_add = std.Target.riscv.featureSet(&.{
35+
.c,
36+
.m,
37+
}),
38+
.os_tag = .freestanding,
39+
.abi = .eabi,
40+
},
41+
.cpu_module_file = b.path("src/cpus/esp-riscv-image.zig"),
42+
.register_definition = .{ .svd = b.path("src/chips/ESP32-C3.svd") },
43+
.memory_regions = &.{
44+
// external memory, ibus
45+
.{ .kind = .flash, .offset = 0x4200_0000, .length = 0x0080_0000 },
46+
// sram 1, data bus
47+
.{ .kind = .ram, .offset = 0x3FC8_0000, .length = 0x0006_0000 },
48+
},
49+
},
50+
.hal = hal,
51+
.linker_script = b.path("esp32_c3.ld"),
1752
};
18-
const chip_esp32c3: microzig.Target = .{
53+
54+
const chip_esp32_c3_direct_boot: microzig.Target = .{
1955
.dep = dep,
20-
// TODO: Exchange FLAT format with .esp format
2156
.preferred_binary_format = .bin,
2257
.chip = .{
2358
.name = "ESP32-C3",
2459
.url = "https://www.espressif.com/en/products/socs/esp32-c3",
25-
.cpu = std.Target.Query{
60+
.cpu = .{
2661
.cpu_arch = .riscv32,
2762
.cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 },
2863
.cpu_features_add = std.Target.riscv.featureSet(&.{
29-
std.Target.riscv.Feature.c,
30-
std.Target.riscv.Feature.m,
64+
.c,
65+
.m,
3166
}),
3267
.os_tag = .freestanding,
3368
.abi = .eabi,
3469
},
35-
.cpu_module_file = b.path("src/cpus/espressif-riscv.zig"),
70+
.cpu_module_file = b.path("src/cpus/esp-riscv-direct-boot.zig"),
3671
.register_definition = .{ .svd = b.path("src/chips/ESP32-C3.svd") },
3772
.memory_regions = &.{
3873
// external memory, ibus
@@ -46,7 +81,8 @@ pub fn init(dep: *std.Build.Dependency) Self {
4681

4782
return .{
4883
.chips = .{
49-
.esp32_c3 = chip_esp32c3.derive(.{}),
84+
.esp32_c3 = chip_esp32_c3.derive(.{}),
85+
.esp32_c3_direct_boot = chip_esp32_c3_direct_boot.derive(.{}),
5086
},
5187
.boards = .{},
5288
};

port/espressif/esp/esp32_c3.ld

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* This file was auto-generated by microzig
3+
*
4+
* Target CPU: generic_rv32
5+
* Target Chip: ESP32-C3
6+
*/
7+
8+
ENTRY(_start);
9+
10+
MEMORY
11+
{
12+
irom (rx) : ORIGIN = 0x42000020, LENGTH = 0x800000 - 0x20
13+
drom (r) : ORIGIN = 0x3C000020, LENGTH = 0x800000 - 0x20
14+
iram (rwx) : ORIGIN = 0x40380000, LENGTH = 313K
15+
dram (rw) : ORIGIN = 0x3FC80000, LENGTH = 313K
16+
}
17+
18+
SECTIONS
19+
{
20+
.irom.text :
21+
{
22+
KEEP(*(microzig_flash_start))
23+
*(.text*)
24+
. += 16;
25+
} > irom
26+
27+
.drom.dummy (NOLOAD) :
28+
{
29+
. = ALIGN(ALIGNOF(.irom.text)) + SIZEOF(.irom.text);
30+
. = ALIGN(0x10000) + 0x20;
31+
} > drom
32+
33+
.drom.rodata : ALIGN(0x10)
34+
{
35+
KEEP(*(.app_desc))
36+
*(.rodata*) /* should we leave this here? */
37+
. = ALIGN(0x10);
38+
} > drom
39+
40+
/* .iram.rwtext : */
41+
/* { */
42+
/* *(.rwtext*) */
43+
/* } > irom */
44+
/**/
45+
/* .dram.dummy (NOLOAD) : */
46+
/* { */
47+
/* . = ALIGN(ALIGNOF(.iram.rwtext)) + SIZEOF(.iram.rwtext); */
48+
/* } > drom */
49+
50+
.dram.data :
51+
{
52+
microzig_data_start = .;
53+
*(.sdata*)
54+
*(.data*)
55+
} > dram
56+
57+
.dram.bss (NOLOAD) :
58+
{
59+
microzig_bss_start = .;
60+
*(.bss*)
61+
*(.sbss*)
62+
microzig_bss_end = .;
63+
} > dram
64+
65+
PROVIDE(__global_pointer$ = microzig_data_start + 0x800);
66+
}

0 commit comments

Comments
 (0)