Skip to content

Commit 9f89fee

Browse files
committed
feat: add Arduino target and trim ESP32 image
1 parent 09fc023 commit 9f89fee

11 files changed

Lines changed: 364 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
- uses: dtolnay/rust-toolchain@1.94.1
5252
with:
5353
components: llvm-tools-preview
54-
targets: riscv32imc-unknown-none-elf
54+
targets: riscv32imc-unknown-none-elf,thumbv6m-none-eabi
5555
- uses: actions/setup-python@v5
5656
with:
5757
python-version: "3.12"
@@ -60,21 +60,33 @@ jobs:
6060
- name: Build complete firmware
6161
run: pio run
6262
working-directory: app
63+
- name: Build Arduino RP2040 target
64+
run: pio run -e arduino-pico
65+
working-directory: app
66+
- name: Run native simulator
67+
run: |
68+
pio run -e native-sim
69+
.pio/build/native-sim/program | tee /tmp/native-sim.log
70+
grep 'micro-rustscript:gpio=low' /tmp/native-sim.log
71+
grep 'rss:status=0' /tmp/native-sim.log
72+
working-directory: app
6373
- name: Verify firmware artifacts
6474
run: |
6575
test -s .pio/build/esp32-c3-devkitm-1/firmware.elf
6676
test -s .pio/build/esp32-c3-devkitm-1/firmware.bin
6777
test -s dist/micro-rustscript-esp32-c3.factory.bin
6878
test -s .pio/generated/rustscript.partition.bin
79+
test -s .pio/build/arduino-pico/firmware.uf2
6980
nm -C .pio/build/esp32-c3-devkitm-1/firmware.elf | grep rustscript_run_vmbc
7081
nm -C .pio/build/esp32-c3-devkitm-1/firmware.elf | grep rustscript_dispatch_host
71-
grep 'rustscript.*0x210000.*0x100000' partitions.csv
82+
grep 'rustscript.*0x0b0000.*0x010000' partitions.csv
7283
stat --format='%n %s bytes' \
7384
.pio/build/esp32-c3-devkitm-1/firmware.elf \
7485
.pio/build/esp32-c3-devkitm-1/firmware.bin \
7586
dist/micro-rustscript-esp32-c3.factory.bin \
7687
.pio/generated/esp32-blinky.vmbc \
7788
.pio/generated/rustscript.partition.bin
89+
test "$(stat --format=%s dist/micro-rustscript-esp32-c3.factory.bin)" -lt 1000000
7890
working-directory: app
7991
- uses: actions/upload-artifact@v4
8092
with:
@@ -84,6 +96,7 @@ jobs:
8496
app/.pio/build/esp32-c3-devkitm-1/firmware.elf
8597
app/.pio/generated/esp32-blinky.vmbc
8698
app/.pio/generated/rustscript.partition.bin
99+
app/.pio/build/arduino-pico/firmware.uf2
87100
app/scripts/flash_vmbc.py
88101
app/scripts/vmbc_image.py
89102
app/scripts/repl_vmbc.py

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ required-features = ["host"]
1919
default = ["host"]
2020
host = ["dep:vm", "vm/runtime"]
2121
esp32 = ["dep:pd_vm_nostd"]
22+
arduino = ["dep:pd_vm_nostd"]
2223

2324
[dependencies]
2425
pd_vm_nostd = { package = "pd-vm-nostd", git = "https://github.com/rustscript-lang/rustscript.git", rev = "ab04637a9b6192dd4f37b7e6abe4dde83f2fc710", version = "0.1.0", optional = true }

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ python -m esptool --chip esp32c3 write_flash 0x0 micro-rustscript-esp32-c3.facto
1919
The boot order is fixed:
2020

2121
1. `/rustscript/main.vmbc` on an SD card connected with CS on GPIO 7.
22-
2. The dedicated `rustscript` flash partition at `0x210000`.
22+
2. The dedicated 64 KiB `rustscript` flash partition at `0x0b0000`.
2323
3. The serial VMBC REPL at 115200 baud.
2424

2525
An absent, unreadable, or missing SD script automatically falls through to the flash partition.
@@ -123,15 +123,26 @@ interpreter, without the desktop compiler.
123123
The repository root is a complete PlatformIO project:
124124

125125
```bash
126-
uv tool install platformio
127-
pio run
126+
export UV_TOOL_DIR=/mnt/TEMP/platformio/tools
127+
export UV_TOOL_BIN_DIR=/mnt/TEMP/platformio/bin
128+
export UV_CACHE_DIR=/mnt/TEMP/platformio/uv-cache
129+
export PLATFORMIO_CORE_DIR=/mnt/TEMP/platformio/core
130+
uv tool install platformio==6.1.19
131+
export PATH=/mnt/TEMP/platformio/bin:$PATH
132+
133+
pio run -e esp32-c3-devkitm-1
134+
pio run -e arduino-pico
135+
pio run -e native-sim
136+
.pio/build/native-sim/program
128137
```
129138

130139
Outputs:
131140

132141
```text
133142
.pio/build/esp32-c3-devkitm-1/firmware.elf
134143
.pio/build/esp32-c3-devkitm-1/firmware.bin
144+
.pio/build/arduino-pico/firmware.uf2
145+
.pio/build/native-sim/program
135146
.pio/generated/esp32-blinky.vmbc
136147
.pio/generated/rustscript.partition.bin
137148
dist/micro-rustscript-esp32-c3.factory.bin
@@ -140,3 +151,15 @@ dist/micro-rustscript-esp32-c3.factory.bin
140151
The factory image merges the ESP32 boot components, application, and default script partition. The
141152
release includes the factory image, ELF, VMBC, packed script partition, flash helpers, partition CSV,
142153
and SHA-256 checksums.
154+
155+
The `arduino-pico` environment is the generic Arduino SoC target. It links the same
156+
`pd-vm-nostd` VM through the Arduino GPIO, delay, serial, and allocator bridge. The
157+
`native-sim` environment runs that bridge and the compiled VMBC program on the host before a board
158+
is connected. A successful simulation ends with `rss:status=0`.
159+
160+
## ESP32 image size
161+
162+
The ESP32 partition table uses a 640 KiB factory application slot and a 64 KiB VMBC slot. OTA data
163+
and SPIFFS partitions are omitted because this image is flashed directly and script updates use the
164+
dedicated VMBC partition. The measured factory image is 722,391 bytes, down from 2,164,183 bytes
165+
(66.62%), while retaining SD boot, the flash script, and the serial VMBC REPL.

firmware/arduino/main.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#include <Arduino.h>
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <cstdlib>
6+
#include <cstring>
7+
#include <limits>
8+
9+
#include "program_vmbc.h"
10+
#include "rustscript_embedded.h"
11+
12+
namespace {
13+
14+
bool host_name_equals(const uint8_t *name, size_t name_len, const char *expected) {
15+
const size_t expected_len = std::strlen(expected);
16+
return name_len == expected_len && std::memcmp(name, expected, expected_len) == 0;
17+
}
18+
19+
int32_t return_bool(rustscript_value *result, bool value) {
20+
if (result == nullptr) {
21+
return -1;
22+
}
23+
*result = {};
24+
result->tag = RUSTSCRIPT_VALUE_BOOL;
25+
result->boolean = value ? 1 : 0;
26+
return 1;
27+
}
28+
29+
int32_t dispatch_host(
30+
void *,
31+
const uint8_t *name,
32+
size_t name_len,
33+
const rustscript_value *args,
34+
size_t arg_count,
35+
rustscript_value *result
36+
) {
37+
if (host_name_equals(name, name_len, "gpio::configure") && arg_count == 2 &&
38+
args[0].tag == RUSTSCRIPT_VALUE_INT && args[1].tag == RUSTSCRIPT_VALUE_INT) {
39+
pinMode(static_cast<uint8_t>(args[0].integer), static_cast<uint8_t>(args[1].integer));
40+
return return_bool(result, true);
41+
}
42+
if (host_name_equals(name, name_len, "gpio::digital_write") && arg_count == 2 &&
43+
args[0].tag == RUSTSCRIPT_VALUE_INT && args[1].tag == RUSTSCRIPT_VALUE_BOOL) {
44+
digitalWrite(static_cast<uint8_t>(args[0].integer), args[1].boolean ? HIGH : LOW);
45+
return return_bool(result, true);
46+
}
47+
if (host_name_equals(name, name_len, "gpio::digital_read") && arg_count == 1 &&
48+
args[0].tag == RUSTSCRIPT_VALUE_INT) {
49+
return return_bool(result, digitalRead(static_cast<uint8_t>(args[0].integer)) == HIGH);
50+
}
51+
if (host_name_equals(name, name_len, "mcu::delay_ms") && arg_count == 1 &&
52+
args[0].tag == RUSTSCRIPT_VALUE_INT && args[0].integer >= 0) {
53+
delay(static_cast<unsigned long>(args[0].integer));
54+
return 0;
55+
}
56+
if (host_name_equals(name, name_len, "serial::write_line") && arg_count == 1 &&
57+
args[0].tag == RUSTSCRIPT_VALUE_STRING) {
58+
Serial.write(args[0].data, args[0].len);
59+
Serial.println();
60+
return 0;
61+
}
62+
return -1;
63+
}
64+
65+
} // namespace
66+
67+
extern "C" void *rustscript_platform_alloc(size_t size, size_t align) {
68+
if (size == 0 || align == 0 || (align & (align - 1)) != 0) {
69+
return nullptr;
70+
}
71+
if (align <= alignof(std::max_align_t)) {
72+
return std::malloc(size);
73+
}
74+
if (size > std::numeric_limits<size_t>::max() - align - sizeof(void *)) {
75+
return nullptr;
76+
}
77+
void *raw = std::malloc(size + align - 1 + sizeof(void *));
78+
if (raw == nullptr) {
79+
return nullptr;
80+
}
81+
const uintptr_t start = reinterpret_cast<uintptr_t>(raw) + sizeof(void *);
82+
const uintptr_t aligned = (start + align - 1) & ~(static_cast<uintptr_t>(align) - 1);
83+
reinterpret_cast<void **>(aligned)[-1] = raw;
84+
return reinterpret_cast<void *>(aligned);
85+
}
86+
87+
extern "C" void rustscript_platform_dealloc(void *pointer, size_t, size_t align) {
88+
if (pointer == nullptr) {
89+
return;
90+
}
91+
if (align <= alignof(std::max_align_t)) {
92+
std::free(pointer);
93+
} else {
94+
std::free(reinterpret_cast<void **>(pointer)[-1]);
95+
}
96+
}
97+
98+
void setup() {
99+
Serial.begin(115200);
100+
const int32_t status = rustscript_run_vmbc(
101+
RUSTSCRIPT_PROGRAM_VMBC,
102+
RUSTSCRIPT_PROGRAM_VMBC_LEN,
103+
dispatch_host,
104+
nullptr,
105+
1000000
106+
);
107+
Serial.print("rss:status=");
108+
Serial.println(status);
109+
}
110+
111+
void loop() {
112+
delay(1000);
113+
}

firmware/simulator/arduino_sim.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <Arduino.h>
2+
3+
#include <array>
4+
#include <cstdio>
5+
6+
namespace {
7+
std::array<uint8_t, 64> pin_levels{};
8+
}
9+
10+
SimSerial Serial;
11+
12+
extern "C" void rust_eh_personality() {}
13+
14+
void SimSerial::begin(unsigned long baud) {
15+
std::printf("sim:serial=%lu\n", baud);
16+
}
17+
18+
void SimSerial::print(const char *value) {
19+
std::fputs(value, stdout);
20+
}
21+
22+
void SimSerial::print(int32_t value) {
23+
std::printf("%d", value);
24+
}
25+
26+
void SimSerial::println() {
27+
std::putchar('\n');
28+
}
29+
30+
void SimSerial::println(int32_t value) {
31+
std::printf("%d\n", value);
32+
}
33+
34+
size_t SimSerial::write(const uint8_t *data, size_t len) {
35+
return std::fwrite(data, 1, len, stdout);
36+
}
37+
38+
void pinMode(uint8_t pin, uint8_t mode) {
39+
std::printf("sim:pin-mode=%u,%u\n", pin, mode);
40+
}
41+
42+
void digitalWrite(uint8_t pin, uint8_t value) {
43+
if (pin < pin_levels.size()) {
44+
pin_levels[pin] = value;
45+
}
46+
std::printf("sim:digital-write=%u,%u\n", pin, value);
47+
}
48+
49+
int digitalRead(uint8_t pin) {
50+
return pin < pin_levels.size() ? pin_levels[pin] : LOW;
51+
}
52+
53+
void delay(unsigned long milliseconds) {
54+
std::printf("sim:delay-ms=%lu\n", milliseconds);
55+
}
56+
57+
int main() {
58+
setup();
59+
return 0;
60+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
6+
#define HIGH 1
7+
#define LOW 0
8+
#define INPUT 0
9+
#define OUTPUT 1
10+
11+
class SimSerial {
12+
public:
13+
void begin(unsigned long baud);
14+
void print(const char *value);
15+
void print(int32_t value);
16+
void println();
17+
void println(int32_t value);
18+
size_t write(const uint8_t *data, size_t len);
19+
};
20+
21+
extern SimSerial Serial;
22+
23+
void pinMode(uint8_t pin, uint8_t mode);
24+
void digitalWrite(uint8_t pin, uint8_t value);
25+
int digitalRead(uint8_t pin);
26+
void delay(unsigned long milliseconds);
27+
28+
void setup();
29+
void loop();

partitions.csv

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# Name, Type, SubType, Offset, Size, Flags
22
nvs, data, nvs, 0x9000, 0x5000,
3-
otadata, data, ota, 0xe000, 0x2000,
4-
app0, app, ota_0, 0x10000, 0x200000,
5-
rustscript, 0x40, 0x00, 0x210000, 0x100000,
6-
spiffs, data, spiffs, 0x310000, 0x0f0000,
3+
app, app, factory, 0x10000, 0x0a0000,
4+
rustscript, 0x40, 0x00, 0x0b0000, 0x010000,

platformio.ini

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ include_dir = include
77
platform = espressif32@6.12.0
88
board = esp32-c3-devkitm-1
99
framework = arduino
10+
build_src_filter = +<*.cpp> -<arduino/> -<simulator/>
1011
board_build.partitions = partitions.csv
1112
extra_scripts =
1213
pre:scripts/build_rust.py
@@ -20,3 +21,29 @@ build_flags =
2021
-DARDUINO_USB_CDC_ON_BOOT=1
2122
-DARDUINO_USB_MODE=1
2223
-DRUSTSCRIPT_SD_CS=7
24+
25+
[env:arduino-pico]
26+
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#aa70b802be8851668053d4f09734e4089fe41932
27+
board = pico
28+
framework = arduino
29+
board_build.core = earlephilhower
30+
board_build.filesystem_size = 0m
31+
build_src_filter = +<arduino/main.cpp>
32+
extra_scripts = pre:scripts/build_arduino.py
33+
custom_rust_target = thumbv6m-none-eabi
34+
monitor_speed = 115200
35+
build_flags =
36+
-Os
37+
-ffunction-sections
38+
-fdata-sections
39+
40+
[env:native-sim]
41+
platform = native@1.2.1
42+
build_src_filter =
43+
+<arduino/main.cpp>
44+
+<simulator/arduino_sim.cpp>
45+
extra_scripts = pre:scripts/build_arduino.py
46+
custom_rust_target = x86_64-unknown-linux-gnu
47+
build_flags =
48+
-std=gnu++17
49+
-Ifirmware/simulator/include

rust-toolchain.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
channel = "1.94.1"
33
profile = "minimal"
44
components = ["clippy", "llvm-tools-preview", "rustfmt"]
5-
targets = ["riscv32imc-unknown-none-elf"]
5+
targets = ["riscv32imc-unknown-none-elf", "thumbv6m-none-eabi"]

0 commit comments

Comments
 (0)