Hardware workflow · Tutorial
Turn an expansion prototype into a custom board
SolarOS lets you move from loose wires to a reproducible firmware target without describing untested hardware by hand. Start with a supported board, create buses and attach drivers at runtime, test the real devices, export the working topology, and promote that snapshot into a custom board manifest.
This tutorial follows the configuration that became the waveshare_esp32_s3_sim7670g_4g_epaper target. It adds a CardKB keyboard and a 400×300 SSD1683 E-paper display to the supported Waveshare ESP32-S3-SIM7670G-4G V2.0 board. The modem, GNSS receiver, battery gauge, SD slot, and status LED continue to come from the original board profile.
What this workflow is for
Use this flow when the base board and expansion drivers already exist in SolarOS, but your final wiring does not. It gives you three useful stages:
- A supported base board that is safe to flash and inspect.
- A runtime prototype whose pins and drivers can change while you experiment.
- A custom target whose settled devices are owned by the board and start automatically.
The export is a hardware contract, not a complete device backup. It does not contain Wi-Fi or modem credentials, network policy, jobs, application state, or shell startup commands.
Before you begin
You need the Waveshare ESP32-S3-SIM7670G-4G V2.0, a compatible CardKB, a Waveshare V2-style 400×300 SSD1683 module, a FAT32 SD card, and a checkout of SolarOS. The firmware must include the cardkb and ssd1683 expansion drivers.
Power the board off before changing wires. For this example set its DIP switches to:
CAM OFF
HUB ON
4G OFF
USB OFF
CAM OFF makes the unused camera pins available to the expansion system. 4G OFF leaves modem-rail control to SolarOS, while USB OFF keeps the USB path away from the modem.
Build and flash the supported base target first:
cd solar_os
pio run -e waveshare_esp32_s3_sim7670g_4g -t upload --upload-port <PORT>
Use pio device list to find <PORT>. On Linux it is commonly a /dev/ttyACM* device exposed by the onboard CH343 bridge.
Wire the prototype
The CardKB reuses the base board's shared i2c0 bus. The display gets a new runtime SPI bus on SPI2.
| Device signal | Board signal | Header position |
|---|---|---|
| CardKB SDA | GPIO15 / i2c0 SDA | P2 pin 8 |
| CardKB SCL | GPIO16 / i2c0 SCL | P2 pin 9 |
| E-paper SCLK | GPIO7 | P2 pin 10 |
| E-paper MOSI / DIN | GPIO8 | P2 pin 11 |
| SPI MISO | GPIO3 | P3 pin 12 |
| E-paper CS | GPIO9 | P2 pin 12 |
| E-paper DC | GPIO10 | P2 pin 13 |
| E-paper RESET | GPIO11 | P2 pin 14 |
| E-paper BUSY | GPIO12 | P2 pin 15 |
Connect both modules to the appropriate 3.3 V and ground pins. Check the labels on your particular modules before applying power; do not infer supply voltage from signal-level compatibility alone.
Create and test the runtime configuration
Open io on the device. Its bus editor shows the usable SPI controllers and pins from the live board map. Create a shared SPI bus named spi0 with SPI2, SCLK GPIO7, MOSI GPIO8, MISO GPIO3, CS GPIO9, and a 4096-byte maximum transfer.
Then open expansion. Attach cardkb as keyboard0 on i2c0 at address 0x5f, and attach ssd1683 as display0 on spi0 with CS GPIO9, DC GPIO10, RESET GPIO11, and BUSY GPIO12. The selectors exclude resources that are already claimed and show the connector positions beside eligible pins.
The equivalent shell commands are useful as a reproducible transcript:
expansion bus create spi spi0 host=spi2 sclk=gpio7 mosi=gpio8 miso=gpio3 cs=gpio9 max=4096
expansion attach cardkb keyboard0 i2c=i2c0 addr=0x5f
expansion attach ssd1683 display0 spi=spi0 cs=gpio9 dc=gpio10 reset=gpio11 busy=gpio12
Inspect the result before going further:
expansion devices
display list
display test display0
Type on the CardKB and confirm that the shell receives its keys. Confirm that display0 appears in display list and completes a test refresh. A successful attach only proves that the driver acquired its resources; this physical test is what validates the wiring.
If you want the prototype to survive reboots while you continue testing, save the SPI bus from its io detail view first, then save each attachment from its expansion detail view. SolarOS adds the normalized commands to the selected startup script. Persistence is optional for export: the exporter reads the currently attached runtime topology, not the startup file.
Export the settled hardware
Insert and mount the SD card, then check expansion devices one last time. It must show both keyboard0 and display0 as runtime devices. Export exactly that live state:
expansion export /sdcard/4g-epaper.toml
For this example the command exports one runtime bus and two runtime devices. The CardKB is included even though it references the board-owned i2c0 bus. The base board's modem, battery, storage, and status LED are not duplicated; the future target inherits them from the base manifest.
The generated file begins like this:
schema = 1
kind = "solaros-expansion"
[base]
board = "waveshare_esp32_s3_sim7670g_4g"
firmware = "4.13.1"
[[buses]]
name = "spi0"
protocol = "spi"
sharing = "shared"
host = "SPI2_HOST"
sclk = 7
mosi = 8
miso = 3
cs = [9]
max_transfer_size = 4096
[[devices]]
driver = "cardkb"
name = "keyboard0"
bindings = { i2c = "i2c0", addr = 0x5f }
[[devices]]
driver = "ssd1683"
name = "display0"
bindings = { spi = "spi0", cs = 9, dc = 10, reset = 11, busy = 12 }
Move 4g-epaper.toml from the SD card to the computer containing your SolarOS checkout. Treat it as generated interchange data: it records what worked on the device, while the board manifest created in the next step becomes the maintained source.
Promote the snapshot
Run the desktop board configurator from the SolarOS source directory:
python3 scripts/board_config.py --expansion-manifest /path/to/4g-epaper.toml
The snapshot already identifies the base target, so the configurator asks only for the new board identity. The worked example used:
Board ID: waveshare_esp32_s3_sim7670g_4g_epaper
Board name: Waveshare ESP32-S3-SIM7670G-4G E-paper
Vendor: Custom
Module: ESP32-S3R8 with 16 MB external flash
That reference target is now included in SolarOS. To repeat the tutorial in a current checkout without overwriting it, choose a unique ID such as my_4g_epaper; the remaining commands below use that name.
The configurator validates the imported buses, driver bindings, controller ownership, and pin conflicts before writing boards/manifests/my_4g_epaper.toml. It also promotes the SSD1683 to the board's primary display because the headless base has no existing display. GPIO3 and GPIO7 through GPIO12 become fixed board resources, SPI2 leaves the runtime controller pool, and the two devices become automatic board-owned attachments.