MeshTerm on the PicoCalc is a manual from the shopping list to a working radio
docs/picocalc.md starts from a stock PicoCalc with its Pico and ends with a Linux handheld running MeshTerm with a LoRa radio soldered inside: a priced shopping list, the Lyra swap, writing Calculinux, the serial console, Wi-Fi, the setup script phase by phase, then PlatformIO, the firmware build with the patch explained hunk by hunk and a by-hand route for when it stops applying, flashing, the four wires, the Lyra setup, day-to-day use and troubleshooting. The README gains a Documentation table and points the handheld paragraph at the two manuals; the script indexes and the changelog follow. hardware.md stays as the reference behind both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
eb96d6b449
commit
8533d9a9e8
5 files changed
+790
-10
No files matched your search
@@ -17,6 +17,16 @@ allowed to change behaviour, not just add to it.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- **Two installation manuals, one per handheld.** [`docs/picocalc.md`](docs/picocalc.md)
|
||||
takes a stock ClockworkPi PicoCalc from a shopping list to a Linux handheld running
|
||||
MeshTerm with a LoRa radio soldered inside — the Lyra swap, writing Calculinux, the setup
|
||||
script, building and flashing the XIAO firmware (the patch it still needs, explained
|
||||
hunk by hunk), the wiring, and the Lyra setup. [`docs/uconsole.md`](docs/uconsole.md)
|
||||
installs the SPI bridge on a uConsole with the AIO LoRa board and connects MeshTerm to
|
||||
it. Both are step-by-step in plain language; [`docs/hardware.md`](docs/hardware.md) stays
|
||||
as the reference behind them, and the README's new **Documentation** table points at all
|
||||
of it.
|
||||
|
||||
## [0.3.5] — 2026-09-14
|
||||
|
||||
### Added
|
||||
|
||||
@@ -167,6 +167,17 @@ question is still being sorted out ([why](https://github.com/pypi/support/issues
|
||||
|
||||
Setting up for development instead? That's in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## Documentation
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| **[The command line](docs/cli.md)** | Every subcommand and option, what each prints on the plain and JSON faces, the exit statuses, recipes. |
|
||||
| **[MeshTerm on the PicoCalc](docs/picocalc.md)** | Building the handheld from a stock PicoCalc: the shopping list, swapping in the Lyra, installing Calculinux and MeshTerm, the soldered LoRa radio. |
|
||||
| **[MeshTerm on the uConsole](docs/uconsole.md)** | A uConsole with the AIO LoRa board: installing the bridge that fronts its SPI radio as a companion, and connecting to it. |
|
||||
| **[Hardware reference](docs/hardware.md)** | Behind the two manuals: what each device-side script does and why, and what has only ever been proven on one bench. |
|
||||
| [CONTRIBUTING.md](CONTRIBUTING.md) | The development setup and the house rules. |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | What changed, newest first. |
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
@@ -189,9 +200,12 @@ meshterm --mock --platform picocalc
|
||||
meshterm specimen
|
||||
```
|
||||
|
||||
Putting MeshTerm on that handheld — the console font, the boot services, a soldered radio for
|
||||
it, and the bridge that fronts a uConsole's SPI LoRa chip as a companion — is
|
||||
[`scripts/`](scripts/), and **[`docs/hardware.md`](docs/hardware.md) is its manual**.
|
||||
Putting MeshTerm on a handheld has its own manuals. **[MeshTerm on the
|
||||
PicoCalc](docs/picocalc.md)** takes a stock PicoCalc from the shopping list to a Linux
|
||||
handheld with a LoRa radio soldered inside; **[MeshTerm on the uConsole](docs/uconsole.md)**
|
||||
connects a uConsole's SPI LoRa board through a small bridge. The device-side scripts both
|
||||
manuals run are in [`scripts/`](scripts/), and [`docs/hardware.md`](docs/hardware.md) is the
|
||||
reference behind them.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -0,0 +1,753 @@
|
||||
# MeshTerm on the PicoCalc
|
||||
|
||||
You start with a stock ClockworkPi PicoCalc — the kit that ships with a Raspberry Pi Pico —
|
||||
and a short shopping list. You end with a Linux handheld: MeshTerm running full-screen on
|
||||
the PicoCalc's own display and keyboard, a LoRa radio soldered inside, and no laptop needed
|
||||
to use it day to day. There are three phases: build the machine (swap the Pico for a Linux
|
||||
board), install the OS and MeshTerm, and add the radio. Budget an afternoon for the first
|
||||
two phases, most of it spent waiting for a toolchain to download; the radio phase needs a
|
||||
soldering iron and can be done later — MeshTerm's `--mock` mode runs perfectly well without
|
||||
a radio in the meantime.
|
||||
|
||||
- [Shopping list](#shopping-list)
|
||||
- [Before you start](#before-you-start)
|
||||
- [Phase 1 — Build the machine](#phase-1--build-the-machine)
|
||||
- [Step 1 — Assemble the PicoCalc](#step-1--assemble-the-picocalc)
|
||||
- [Step 2 — Swap the Pico for the Lyra](#step-2--swap-the-pico-for-the-lyra)
|
||||
- [Step 3 — Attach the Wi-Fi dongle](#step-3--attach-the-wi-fi-dongle)
|
||||
- [Phase 2 — Install Calculinux and MeshTerm](#phase-2--install-calculinux-and-meshterm)
|
||||
- [Step 4 — Write the image to the microSD](#step-4--write-the-image-to-the-microsd)
|
||||
- [Step 5 — First boot](#step-5--first-boot)
|
||||
- [Step 6 — Open the serial console](#step-6--open-the-serial-console)
|
||||
- [Step 7 — Join Wi-Fi](#step-7--join-wi-fi)
|
||||
- [Step 8 — Copy the setup scripts to the device](#step-8--copy-the-setup-scripts-to-the-device)
|
||||
- [Step 9 — Run calculinux-setup.sh](#step-9--run-calculinux-setupsh)
|
||||
- [Step 10 — Log in as meshterm](#step-10--log-in-as-meshterm)
|
||||
- [Phase 3 — Add the radio](#phase-3--add-the-radio)
|
||||
- [Step 11 — Install PlatformIO](#step-11--install-platformio)
|
||||
- [Step 12 — Build the firmware](#step-12--build-the-firmware)
|
||||
- [Step 13 — Flash the XIAO](#step-13--flash-the-xiao)
|
||||
- [Step 14 — Wire the radio](#step-14--wire-the-radio)
|
||||
- [Step 15 — Set up the Lyra](#step-15--set-up-the-lyra)
|
||||
- [Step 16 — Run it](#step-16--run-it)
|
||||
- [Day-to-day](#day-to-day)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [What has been tested](#what-has-been-tested)
|
||||
|
||||
---
|
||||
|
||||
## Shopping list
|
||||
|
||||
### The machine
|
||||
|
||||
| Item | Product / what to search for | Approx. price | Link | Note |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| PicoCalc kit | ClockworkPi PicoCalc | $89 | [clockworkpi.com/product-page/picocalc](https://www.clockworkpi.com/product-page/picocalc) | Comes with a Raspberry Pi Pico 1H, screen, keyboard, shell, hex key and a 32 GB SD card. **18650 batteries are not included.** |
|
||||
| Luckfox Lyra | "Luckfox Lyra 128MB, pre-soldered header, no NAND" | $20–25 | [luckfox.com/Luckfox-Lyra](https://www.luckfox.com/Luckfox-Lyra) | Buy the **plain 128 MB RAM, Pico-form-factor** variant, **with the header pre-soldered**. See the note below — this is the one part of the list where the wrong SKU won't work. |
|
||||
| microSD card | 16 GB+, Class 10 or better | $6–10 | — | The PicoCalc's stock 32 GB card works fine — it's more than the 8 GB minimum. You can reuse it instead of buying a new one. |
|
||||
| Two 18650 batteries | e.g. Samsung 30Q, Molicel P26A, Sony VTC6 (examples, not endorsements) | $10–20 | — | **Unprotected**, flat-top or button-top, Ø18 × 65–69 mm. See [the battery note](#a-note-on-batteries) below. |
|
||||
| USB Wi-Fi dongle | search the **chipset name**: RTL8192CU, R8712U, or RTL8188EU | $5–10 | — | Must run at **3.3 V** — see [Step 3](#step-3--attach-the-wi-fi-dongle). Only needed for setup and updates; MeshTerm itself runs offline. |
|
||||
| USB-C OTG adapter | "USB-C male to USB-A female OTG adapter" | $3–6 | — | Plugs the Wi-Fi dongle into the Lyra's USB-C port. |
|
||||
|
||||
### The radio
|
||||
|
||||
| Item | Product / what to search for | Approx. price | Link | Note |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| XIAO nRF52840 + Wio-SX1262 kit | "XIAO nRF52840 & Wio-SX1262 Kit for Meshtastic", SKU 102010710 | $13.49 | [seeedstudio.com](https://www.seeedstudio.com/XIAO-nRF52840-Wio-SX1262-Kit-for-Meshtastic-p-6400.html) | Includes the XIAO nRF52840, the Wio-SX1262 LoRa board (stacked and wired together) and an antenna. Covers both 868 and 915 MHz. Plain or Sense XIAO both work. **Do not** buy the "-N" variant — it has no antenna connector. |
|
||||
|
||||
### Tools and consumables
|
||||
|
||||
| Item | Note |
|
||||
| --- | --- |
|
||||
| Soldering iron, fine tip, and solder | For the four radio wires. |
|
||||
| Hookup wire, 26–30 AWG, four short lengths | TX and RX get crossed — see [Step 14](#step-14--wire-the-radio). |
|
||||
| 2.5 mm hex key | Comes with the PicoCalc kit. |
|
||||
| USB-C data cable, ×2 | One is convenient for the serial console, a second for flashing the XIAO — you can get by with one and swap it. |
|
||||
| microSD card reader | For writing the Calculinux image from your PC. |
|
||||
| Small side cutters | For trimming wire ends. |
|
||||
| A PC (Windows/macOS/Linux) with Python 3 and git | Runs the image-writing, firmware-build and flashing steps. |
|
||||
|
||||
### Optional
|
||||
|
||||
| Item | Why |
|
||||
| --- | --- |
|
||||
| A second 18650 battery | The holder takes two; one is enough to run, two roughly double your runtime. |
|
||||
| Powered USB hub | If you want the Wi-Fi dongle and something else on the Lyra's single USB-C port at once. |
|
||||
| Foam tape | To secure the XIAO board inside the shell once it's wired in. |
|
||||
|
||||
Approximate total: **$130–150** for the machine and radio together, before batteries — most
|
||||
of that is the two boards ($89 + ~$22) and the radio kit ($13.49).
|
||||
|
||||
**What you do NOT need:**
|
||||
|
||||
- A level shifter. Every signal in this build — the Lyra's header, the XIAO, the SX1262 — is
|
||||
3.3 V.
|
||||
- A separate USB-to-serial adapter. The PicoCalc mainboard already has one built in.
|
||||
- Bluetooth anything. Neither the Lyra nor this radio path uses it.
|
||||
- A new SD card, if your stock 32 GB card is handy — it's well over the 8 GB minimum
|
||||
Calculinux asks for.
|
||||
|
||||
### A note on batteries
|
||||
|
||||
ClockworkPi has not published an official battery spec for the PicoCalc — a
|
||||
[still-open GitHub issue](https://github.com/clockworkpi/PicoCalc/issues/6) asking for one
|
||||
has gone unanswered. What's known from the kit itself: the holder takes two 18650 cells, one
|
||||
cell is enough to power the board, and the physical envelope is about Ø18 mm × 65–69 mm,
|
||||
which rules out most *protected* cells — the protection circuit adds length that doesn't
|
||||
fit. Unprotected flat-top or button-top cells both work; the PicoCalc mainboard has its own
|
||||
charge circuit built in. Buy from a reputable brand rather than a cheap unbranded listing —
|
||||
Samsung 30Q, Molicel P26A and Sony VTC6 are commonly recommended examples, not an
|
||||
endorsement of any one of them.
|
||||
|
||||
---
|
||||
|
||||
## Before you start
|
||||
|
||||
Work happens on two machines, and it helps to keep straight which is which:
|
||||
|
||||
- **Your PC** writes the Calculinux image to the microSD card, builds the radio firmware,
|
||||
and flashes the XIAO. Nothing in these three jobs touches the PicoCalc itself.
|
||||
- **The PicoCalc** (really, the Luckfox Lyra inside it) does everything else — setup,
|
||||
Wi-Fi, running MeshTerm — either over the serial console from your PC or on its own
|
||||
screen and keyboard. Both work identically; the serial console is just easier for pasting
|
||||
long commands.
|
||||
|
||||
The device has two logins you'll use for different things: **`root`**, which you use once
|
||||
to run the setup script and fix Wi-Fi, and **`meshterm`**, the unprivileged account
|
||||
MeshTerm actually runs under day to day.
|
||||
|
||||
Calculinux is a console-only Linux — no desktop, no window manager. That's deliberate: the
|
||||
PicoCalc's screen and keyboard are a terminal, and MeshTerm is a full-screen terminal
|
||||
program built for exactly that.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Build the machine
|
||||
|
||||
### Step 1 — Assemble the PicoCalc
|
||||
|
||||
Follow ClockworkPi's own
|
||||
[assembly guidelines](https://github.com/clockworkpi/PicoCalc/blob/master/Clockwork_PicoCalc_Assembly_Guidelines.pdf)
|
||||
(a PDF in the [PicoCalc repository](https://github.com/clockworkpi/PicoCalc), which also
|
||||
has a wiki) to put the kit together: mainboard, screen, keyboard, speakers and shell. Insert your two
|
||||
18650 batteries, watching the `+`/`-` marks in the battery compartment. Be careful when you
|
||||
tighten the shell's screws near the display — a misaligned screen can crack under the
|
||||
screws, and you'll be opening the shell again in the next step regardless.
|
||||
|
||||
Power it on once with the **stock Pico still in place**. You should see the stock BASIC
|
||||
firmware boot on the screen, and the keyboard should respond. This confirms the screen,
|
||||
keyboard and battery are all good before you start swapping boards.
|
||||
|
||||
Power it back off.
|
||||
|
||||
### Step 2 — Swap the Pico for the Lyra
|
||||
|
||||
Remove the back of the shell with the hex key. Lift the Raspberry Pi Pico straight out of
|
||||
its socket — **keep it**, you're not damaging or discarding it, just setting it aside.
|
||||
|
||||
If your Luckfox Lyra has SPI NAND on board (a "Lyra B"), erase it first — the boot ROM
|
||||
otherwise ignores the SD card entirely and boots the on-board flash instead. See
|
||||
[Calculinux's hardware requirements page](https://calculinux.org/getting-started/hardware-requirements/)
|
||||
for how.
|
||||
|
||||
Seat the Lyra into the same socket the Pico came out of, in the same orientation — its USB-C
|
||||
port should line up with the cutout in the back of the shell where the Pico's USB port used
|
||||
to be. Press it down fully; a Lyra that isn't fully seated won't boot.
|
||||
|
||||
You should see the Lyra's USB-C port sitting flush in the back-panel cutout, with no rocking
|
||||
or gap at either end of the board.
|
||||
|
||||
Leave the back off for now. The microSD card goes into the **Lyra's own slot** in
|
||||
[Step 4](#step-4--write-the-image-to-the-microsd), and that slot is easier to reach with the
|
||||
board exposed.
|
||||
|
||||
### Step 3 — Attach the Wi-Fi dongle
|
||||
|
||||
Plug the USB-C-to-USB-A OTG adapter into the Lyra's USB-C port (through the same back
|
||||
cutout), then plug your Wi-Fi dongle into the adapter.
|
||||
|
||||
> **The Lyra's USB port is 3.3 V, not 5 V.** A standard 5 V USB Wi-Fi dongle will not work
|
||||
> and may damage the board. Stick to the tested chipsets — RTL8192CU, R8712U, or RTL8188EU
|
||||
> — which is why the shopping list tells you to search by chipset name rather than by
|
||||
> brand.
|
||||
|
||||
Don't power the device on yet — Wi-Fi is set up later, once Calculinux is installed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Install Calculinux and MeshTerm
|
||||
|
||||
### Step 4 — Write the image to the microSD
|
||||
|
||||
On your PC, download the latest `calculinux-image-luckfox-lyra.rootfs.wic.gz` from the
|
||||
[Calculinux releases page](https://github.com/Calculinux/meta-calculinux/releases).
|
||||
|
||||
Decompress it:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
gunzip calculinux-image-luckfox-lyra.rootfs.wic.gz
|
||||
```
|
||||
|
||||
(The Calculinux installation page mentions `unxz`, but the file is a `.gz`, so use
|
||||
`gunzip`. 7-Zip works on any platform if you'd rather not use the command line.)
|
||||
|
||||
Write the resulting `.wic` file to your microSD card. Three options:
|
||||
|
||||
- **Balena Etcher** (recommended, all platforms) — open it, select the `.wic` file, select
|
||||
your SD card, click Flash.
|
||||
- **`dd`**, on Linux/macOS. Check the device name first — writing to the wrong device
|
||||
destroys its contents:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
lsblk # find your SD card's device, e.g. /dev/sdb
|
||||
sudo dd if=calculinux-image-luckfox-lyra.rootfs.wic of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
```
|
||||
|
||||
- **Rufus**, on Windows — select the device and the image, choose the **MBR** partition
|
||||
scheme, and start.
|
||||
|
||||
This erases everything already on the card. Once it finishes, eject it and put it in the
|
||||
**Luckfox Lyra's own microSD slot** — not the PicoCalc's front-facing slot. The Lyra boots
|
||||
from its own slot; the front slot is only reachable once MeshTerm (or anything else) is
|
||||
already running on Linux.
|
||||
|
||||
### Step 5 — First boot
|
||||
|
||||
With the card in the Lyra, power the PicoCalc on. Give it 30–60 seconds — the first boot is
|
||||
slower than later ones. You should see boot messages scroll on the PicoCalc's own screen,
|
||||
ending in a `Calculinux GNU/Linux` login prompt.
|
||||
|
||||
Log in:
|
||||
|
||||
```
|
||||
login: root
|
||||
Password: root
|
||||
```
|
||||
|
||||
Change the password immediately:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
passwd
|
||||
```
|
||||
|
||||
### Step 6 — Open the serial console
|
||||
|
||||
The PicoCalc's own screen and keyboard work fine, so you can skip this step. The serial
|
||||
console makes pasting the longer commands in this guide much easier, though, so it is
|
||||
recommended.
|
||||
|
||||
The PicoCalc mainboard's USB-C port carries a USB-to-UART bridge (a CH340 chip). Plug a
|
||||
USB-C cable from that port into your PC. It shows up as:
|
||||
|
||||
- `/dev/ttyUSB0` on Linux (you may need to be in the `dialout` group),
|
||||
- a `COM` port on Windows,
|
||||
- `/dev/cu.usbserial-*` on macOS.
|
||||
|
||||
Settings: **1500000 8N1**, no flow control. Using `pyserial`'s `miniterm`:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
pip install pyserial
|
||||
python3 -m serial.tools.miniterm /dev/ttyUSB0 1500000
|
||||
```
|
||||
|
||||
minicom and PuTTY work too, at the same settings. To leave `miniterm`, press **Ctrl+]**.
|
||||
|
||||
You should see the same login prompt you saw on the PicoCalc's screen, now in your PC's
|
||||
terminal.
|
||||
|
||||
### Step 7 — Join Wi-Fi
|
||||
|
||||
Calculinux manages Wi-Fi with `iwd`. The easiest way is its text-mode picker: run `uwific`,
|
||||
highlight your network, press Enter and type the passphrase (`Q` quits). Or do the same
|
||||
from the command line:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
iwctl station wlan0 scan
|
||||
iwctl station wlan0 get-networks
|
||||
iwctl station wlan0 connect "<your SSID>"
|
||||
```
|
||||
|
||||
It'll prompt for the passphrase. Check you're online:
|
||||
|
||||
```bash
|
||||
ip addr show wlan0
|
||||
ping -c 3 1.1.1.1
|
||||
```
|
||||
|
||||
You should see an `inet` address on `wlan0`, and replies from the ping.
|
||||
|
||||
If you'd rather not type this by hand every time, `calculinux-setup.sh` (the next step) can
|
||||
provision the network for you: export `WIFI_SSID` and `WIFI_PSK` before running it and it
|
||||
writes the credential itself, so this manual step becomes optional.
|
||||
|
||||
### Step 8 — Copy the setup scripts to the device
|
||||
|
||||
You need this repository's `scripts/` directory on the Lyra. Three ways to get it there,
|
||||
in order of ease:
|
||||
|
||||
- **`scp` from your PC**, once Wi-Fi is up. Find the Lyra's IP address with `ip addr show
|
||||
wlan0` on the device, then from your PC:
|
||||
|
||||
```bash
|
||||
# on the PC, from your MeshTerm checkout
|
||||
scp -r scripts root@<lyra-ip>:/root/
|
||||
```
|
||||
|
||||
If root is refused over SSH, copy as the `pico` user (password `calc`) instead, then
|
||||
move the directory as root on the device:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
scp -r scripts pico@<lyra-ip>:/home/pico/
|
||||
# on the PicoCalc, as root
|
||||
mv /home/pico/scripts /root/
|
||||
```
|
||||
|
||||
- **The microSD card.** Pull the card, copy `scripts/` onto it from your PC's card reader,
|
||||
put it back.
|
||||
- **`git clone` on the device.** Only once `git` is installed, and the setup script is what
|
||||
installs it — so this is a route for later updates, not the first copy.
|
||||
|
||||
`scp` is the recommended route.
|
||||
|
||||
### Step 9 — Run calculinux-setup.sh
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root, with scripts/ copied over
|
||||
cd scripts
|
||||
sh calculinux-setup.sh
|
||||
```
|
||||
|
||||
It's idempotent — safe to re-run — and it checks before it acts. Two things it can't figure
|
||||
out on its own, both covered by environment variables read at the top of the script:
|
||||
|
||||
| Knob | Default | Effect |
|
||||
| --- | --- | --- |
|
||||
| `DEPLOY_USER` | `meshterm` | the login MeshTerm runs under |
|
||||
| `TIMEZONE` | `America/Toronto` | any IANA zone |
|
||||
| `REPO_URL` | the project's HTTPS URL | cloned when there's no deploy key |
|
||||
| `REPO_SSH` | the project's SSH URL | cloned when there is one |
|
||||
| `KEY_PATH` | `/home/$DEPLOY_USER/.ssh/id_ed25519` | a read-only GitHub deploy key, if you want SSH instead of HTTPS |
|
||||
| `WIFI_SSID`, `WIFI_PSK` | empty | set **both** to have `iwd`'s credentials written for you |
|
||||
|
||||
You don't need to set any of these for a normal install — the defaults clone MeshTerm's
|
||||
public repository over plain HTTPS, which needs no credential at all.
|
||||
|
||||
The nine phases, one line each:
|
||||
|
||||
1. **opkg packages** — installs `python3-modules`, `python3-pip`, `git`, `kbd`, because
|
||||
Calculinux ships a stripped Python missing `pip`, `venv` and several stdlib modules.
|
||||
2. **Wi-Fi kick** — installs a boot-time service that works around a cold-boot race between
|
||||
the Wi-Fi dongle's firmware and `iwd`'s first scan.
|
||||
3. **Time sync** — installs a boot-time service that sets the clock over the network, since
|
||||
this board has no battery-backed real-time clock.
|
||||
4. **Timezone** — sets `$TIMEZONE` (Eastern by default).
|
||||
5. **Deploy user** — creates the `meshterm` login and puts it in the groups it needs.
|
||||
6. **Clone** — checks out MeshTerm to `/home/meshterm/MeshTerm`.
|
||||
7. **venv and install** — builds a Python virtual environment and `pip install -e .`s
|
||||
MeshTerm into it.
|
||||
8. **Login PATH** — puts that venv's `bin` on the `meshterm` user's PATH.
|
||||
9. **Console font** — builds and installs the console font MeshTerm's UI needs (braille
|
||||
charts, node glyphs, rounded corners).
|
||||
|
||||
This takes a few minutes on a good connection — most of it is `pip install` compiling
|
||||
things. It ends with `done` and tells you to log in as `meshterm`. That user has no
|
||||
password yet, so set one first:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
passwd meshterm
|
||||
```
|
||||
|
||||
> **No Wi-Fi dongle attached takes about six minutes to boot past the kick service.**
|
||||
> That's expected — it's giving a slow-enumerating radio every chance before giving up.
|
||||
> Nothing is broken; see [Day-to-day](#day-to-day) for how to skip this if the device is
|
||||
> going to run wired or offline permanently.
|
||||
|
||||
### Step 10 — Log in as meshterm
|
||||
|
||||
Log out of `root`, and log in as `meshterm` with the password you just set. Try the app:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as meshterm
|
||||
meshterm --mock
|
||||
```
|
||||
|
||||
You should see MeshTerm's full-screen interface come up at **53 columns**, with a simulated
|
||||
radio behind it, drawing braille charts and node glyphs cleanly — no empty tofu boxes. Along
|
||||
the bottom you should see the **F-key lane**: five labelled chips instead of the footer hint
|
||||
line a desktop terminal shows.
|
||||
|
||||
Have a look around. **Esc** backs out of any screen, and **Quit** on the main menu leaves
|
||||
the app.
|
||||
|
||||
Then look at the whole visual language on one card:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as meshterm
|
||||
meshterm specimen
|
||||
```
|
||||
|
||||
There are two console fonts available — a 6×12 one (53×26 rows) and a 6×8 one (53×40 rows,
|
||||
more rows but no Cyrillic glyphs). Try both from *Preferences → Display → Console font* —
|
||||
picking one previews it immediately, and the screen repaints at the new row count so you can
|
||||
see the choice before you keep it.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Add the radio
|
||||
|
||||
This phase can wait. Everything above already gives you a working MeshTerm in `--mock`
|
||||
mode. Come back to this when you have the soldering iron out.
|
||||
|
||||
### Step 11 — Install PlatformIO
|
||||
|
||||
On your PC:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
pip install platformio
|
||||
```
|
||||
|
||||
(or use the
|
||||
[official installer script](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html)
|
||||
if you'd rather not touch your system Python). Confirm `pio` is on your PATH:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
pio --version
|
||||
```
|
||||
|
||||
The first firmware build downloads the nRF52 toolchain — several hundred megabytes — so do
|
||||
this somewhere with a decent connection.
|
||||
|
||||
### Step 12 — Build the firmware
|
||||
|
||||
```bash
|
||||
# on the PC (Git Bash or WSL on Windows)
|
||||
cd scripts/xiao-radio
|
||||
sh build-firmware.sh
|
||||
```
|
||||
|
||||
You should see it clone MeshCore, apply a patch, build, and finish with:
|
||||
|
||||
```
|
||||
DONE. Firmware: /path/to/scripts/xiao-radio/meshcore-xiao-radio.uf2
|
||||
Next: put the XIAO in bootloader (double-tap reset) and run: python flash.py
|
||||
```
|
||||
|
||||
Here's what it did, so you could do it by hand if you needed to:
|
||||
|
||||
1. Cloned [MeshCore](https://github.com/meshcore-dev/MeshCore) into
|
||||
`scripts/xiao-radio/_meshcore-build`.
|
||||
2. Checked out the pinned commit `e9edfc8e` on the `dev` branch — the commit this patch is
|
||||
known to apply cleanly to and build against.
|
||||
3. Applied `meshcore-uart1.patch`.
|
||||
4. Built the `Xiao_nrf52_companion_radio_serial` PlatformIO environment:
|
||||
`pio run -e Xiao_nrf52_companion_radio_serial`.
|
||||
5. Converted the resulting `.hex` to a `.uf2` with MeshCore's own converter, using the
|
||||
nRF52840 UF2 family id:
|
||||
`python bin/uf2conv/uf2conv.py firmware.hex -c -f 0xADA52840 -o meshcore-xiao-radio.uf2`.
|
||||
|
||||
#### What the patch changes, and why it is still needed
|
||||
|
||||
`meshcore-uart1.patch` touches two files, and both hunks are required:
|
||||
|
||||
- **`examples/companion_radio/main.cpp`.** MeshCore's companion-over-serial code declares
|
||||
`HardwareSerial companion_serial(1)` — a numbered constructor. On the Adafruit nRF52 core,
|
||||
`HardwareSerial` is an abstract base class with no numbered constructor at all, so this
|
||||
line simply doesn't compile for the XIAO nRF52840. The patch adds an `#if
|
||||
defined(NRF52_PLATFORM)` branch that binds `companion_serial` to `Serial1` instead — the
|
||||
concrete UART object the nRF52 core always provides. `Uart::setPins(rx, tx)`, called later
|
||||
in the same file, takes the same arguments either way, so nothing else needs to change.
|
||||
- **`variants/xiao_nrf52/platformio.ini`.** This adds a new
|
||||
`Xiao_nrf52_companion_radio_serial` build environment that puts the companion serial link
|
||||
on pins **D6 (TX)** and **D7 (RX)**. It also **moves I²C off those same two pads**, onto
|
||||
two internal pins, 16 and 17, that nothing in this build uses. This second change is the one that matters: the
|
||||
stock `Xiao_nrf52` environment maps I²C onto D6/D7 by default, and both the board's
|
||||
`begin()` and the sensor code start the I²C bus on them automatically. Without the move,
|
||||
I²C seizes the UART pins at boot — the wiring is correct, the firmware builds and runs,
|
||||
and the serial link is still completely silent, because something else already owns the
|
||||
pins.
|
||||
|
||||
Both changes have been submitted upstream to MeshCore, but as of this writing they are
|
||||
**not merged**, and there's no sign that will happen soon — upstream `dev` still declares
|
||||
the bare `HardwareSerial(1)` with no nRF52 branch, and the XIAO's `platformio.ini` still has
|
||||
no `_serial` companion environment. Building with the patch is the normal route here, not a
|
||||
stopgap; when the patch does land, the `git apply` step (and the patch file itself) can be
|
||||
deleted.
|
||||
|
||||
#### Building by hand
|
||||
|
||||
If you want to track newer MeshCore instead of the pinned commit:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
git clone https://github.com/meshcore-dev/MeshCore.git
|
||||
cd MeshCore
|
||||
git checkout dev # or MESHCORE_COMMIT=dev sh build-firmware.sh
|
||||
git apply /path/to/scripts/xiao-radio/meshcore-uart1.patch
|
||||
pio run -e Xiao_nrf52_companion_radio_serial
|
||||
python bin/uf2conv/uf2conv.py .pio/build/Xiao_nrf52_companion_radio_serial/firmware.hex \
|
||||
-c -f 0xADA52840 -o meshcore-xiao-radio.uf2
|
||||
```
|
||||
|
||||
If `git apply` fails — upstream has moved and the patch context no longer lines up — the two
|
||||
hunks are small enough to make by hand:
|
||||
|
||||
1. In `examples/companion_radio/main.cpp`, find the line
|
||||
`HardwareSerial companion_serial(1);` and replace it with:
|
||||
|
||||
```cpp
|
||||
#if defined(NRF52_PLATFORM)
|
||||
Uart& companion_serial = Serial1;
|
||||
#else
|
||||
HardwareSerial companion_serial(1);
|
||||
#endif
|
||||
```
|
||||
|
||||
2. At the end of `variants/xiao_nrf52/platformio.ini`, add a new environment:
|
||||
|
||||
```ini
|
||||
[env:Xiao_nrf52_companion_radio_serial]
|
||||
extends = Xiao_nrf52
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${Xiao_nrf52.build_flags}
|
||||
-I examples/companion_radio/ui-orig
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D QSPIFLASH=1
|
||||
-D SERIAL_TX=D6
|
||||
-D SERIAL_RX=D7
|
||||
-D PIN_WIRE_SCL=16
|
||||
-D PIN_WIRE_SDA=17
|
||||
build_unflags =
|
||||
-D PIN_WIRE_SCL=D6
|
||||
-D PIN_WIRE_SDA=D7
|
||||
build_src_filter = ${Xiao_nrf52.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||
lib_deps =
|
||||
${Xiao_nrf52.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
```
|
||||
|
||||
Only the pinned commit (`e9edfc8e`) is known to build cleanly with this patch — a newer
|
||||
`dev` may need small further adjustments.
|
||||
|
||||
### Step 13 — Flash the XIAO
|
||||
|
||||
Plug the **XIAO's own USB-C port** into your PC — it doesn't matter yet whether it's also
|
||||
wired to the Lyra. Then:
|
||||
|
||||
```bash
|
||||
# on the PC
|
||||
python flash.py
|
||||
```
|
||||
|
||||
It first tries a 1200-baud "touch" over USB serial to reset the XIAO into its bootloader —
|
||||
this only ever targets Seeed's own USB vendor id, so it won't disturb another nRF52840 board
|
||||
on the same bench. If that doesn't take, **double-tap the XIAO's reset button** and run it
|
||||
again.
|
||||
|
||||
Once it finds the bootloader, it checks two things before writing anything:
|
||||
|
||||
- **`INFO_UF2.TXT`'s `Model` field** must say a XIAO — a drive letter isn't an identity, and
|
||||
another board could have inherited the letter your XIAO just gave up.
|
||||
- **`SoftDevice` must not be `6.1.1`.** This firmware links for SoftDevice S140 v7 (app at
|
||||
`0x27000`); a S140 6.1.1 bootloader wants the app at `0x26000` and would flash successfully
|
||||
and then simply never boot.
|
||||
|
||||
`--force` overrides both checks, if you're certain. A clean flash ends with:
|
||||
|
||||
```
|
||||
DONE. XIAO flashed and rebooting into the radio firmware.
|
||||
```
|
||||
|
||||
**If no UF2 drive ever appears**, that's not necessarily wrong — some XIAO bootloaders
|
||||
expose only a serial port, no mass-storage drive at all. `flash.py` recognises this case and
|
||||
prints the fallback command instead of failing silently:
|
||||
|
||||
```bash
|
||||
# on the PC, from your MeshCore checkout
|
||||
pio run -e Xiao_nrf52_companion_radio_serial -t upload --upload-port <bootloader port>
|
||||
```
|
||||
|
||||
That command ends in `Device programmed.` and the XIAO reboots itself into the radio
|
||||
firmware.
|
||||
|
||||
Afterwards, the XIAO's USB serial port goes **silent** to companion frames — the companion
|
||||
protocol now lives on D6/D7, not on USB. That silence is correct; it doesn't mean the flash
|
||||
failed.
|
||||
|
||||
### Step 14 — Wire the radio
|
||||
|
||||
Four wires, TX and RX crossed:
|
||||
|
||||
| XIAO pad | → | Lyra header | Physical pin | Carries |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **D7** (Serial1 RX) | → | **GP4** | pin **6** | Lyra UART1 **TX** → XIAO RX |
|
||||
| **D6** (Serial1 TX) | → | **GP5** | pin **7** | XIAO **TX** → Lyra UART1 RX |
|
||||
| **GND** | → | **GND** | pin **8** | common ground |
|
||||
| **3V3** | → | **3V3 OUT** | pin **36** | power, so it runs without USB |
|
||||
|
||||
> **The Lyra does not use Raspberry Pi Pico GPIO numbering.** The 40-pin header is
|
||||
> physically Pico-shaped, but each pad's function is Luckfox's own RK3506 mapping: GP4 is
|
||||
> `gpio0-0` and GP5 is `gpio0-1`. Don't reach for a Pico pinout for anything else on this
|
||||
> header. Every signal here is 3.3 V, and there is **no 5 V rail** on this board — power the
|
||||
> XIAO from 3V3 only.
|
||||
|
||||
With the PicoCalc powered off, solder the four wires between the XIAO's pads and the Lyra's
|
||||
header pins as above. Attach the LoRa antenna to the Wio-SX1262's u.FL connector before you
|
||||
power anything back on — running the radio without an antenna attached can damage it.
|
||||
Tuck the XIAO + Wio-SX1262 stack into the shell wherever it fits without straining the
|
||||
wires; a strip of foam tape holds it in place against the inside of the case.
|
||||
|
||||
### Step 15 — Set up the Lyra
|
||||
|
||||
With the wiring done and the shell closed back up, copy `scripts/` to the device if you
|
||||
haven't already (Step 8), then:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
sh scripts/xiao-radio/lyra-setup.sh # MT_USER=meshterm by default
|
||||
```
|
||||
|
||||
It does three idempotent things:
|
||||
|
||||
1. Installs `uart1-mux.py` as `/usr/local/bin/uart1-radio-mux.py`, run by a oneshot
|
||||
`uart1-radio-mux.service` on every boot. This is the part that actually makes
|
||||
`/dev/ttyS1` reach the header pins — Calculinux enables the UART1 controller but never
|
||||
wires it to a physical pad on its own, and the RK3506's flexible pin matrix needs a
|
||||
direct register poke to route it onto GP4/GP5 instead.
|
||||
2. Adds `$MT_USER` to the `dialout` group, so it can open the serial port. **This needs a
|
||||
fresh login before it takes effect.**
|
||||
3. Writes a MeshTerm profile, *only if none exists yet*, at
|
||||
`/home/$MT_USER/.meshterm/config.toml`:
|
||||
|
||||
```toml
|
||||
default_profile = "picocalc"
|
||||
|
||||
[profiles.picocalc]
|
||||
port = "/dev/ttyS1"
|
||||
baudrate = 115200
|
||||
transport = "serial"
|
||||
default_tx_power = 22
|
||||
description = "XIAO nRF52840 + SX1262 via Lyra UART1 on GP4/GP5"
|
||||
```
|
||||
|
||||
If a `config.toml` already exists, it prints those lines instead of touching your file —
|
||||
paste them in yourself.
|
||||
|
||||
### Step 16 — Run it
|
||||
|
||||
Log out and back in as `meshterm` (so the `dialout` group membership takes effect), then:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as meshterm
|
||||
meshterm
|
||||
```
|
||||
|
||||
Because `default_profile` is set, a bare `meshterm` connects straight to the radio. You
|
||||
should see your node come up under whatever name the firmware advertises — both the device
|
||||
page and the dashboard header show it.
|
||||
|
||||
If you want to prove the wiring itself before suspecting anything else, there's a deeper
|
||||
check described in [`docs/hardware.md`](hardware.md#run-it-and-check-the-link): open
|
||||
`/dev/ttyS1` at 115200 as root and send a raw `APP_START` frame — a live radio answers with
|
||||
a framed `SELF_INFO` reply. That's a lower-level check than running MeshTerm itself and
|
||||
mostly useful for isolating a wiring problem.
|
||||
|
||||
---
|
||||
|
||||
## Day-to-day
|
||||
|
||||
**Powering off.** Shut down cleanly rather than pulling the batteries:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc
|
||||
sudo poweroff
|
||||
```
|
||||
|
||||
**Updating MeshTerm.** Either re-run the setup script from root (it pulls the repository,
|
||||
reinstalls, and rebuilds the console font):
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
sh calculinux-setup.sh
|
||||
```
|
||||
|
||||
or, as the `meshterm` user, do just the update:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as meshterm
|
||||
cd ~/MeshTerm && git pull && .venv/bin/pip install -e .
|
||||
```
|
||||
|
||||
If an update adds new glyphs to the console font, rebuild it on its own afterwards:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
sh calculinux-console-font.sh
|
||||
```
|
||||
|
||||
**Changing Wi-Fi.** Run the interactive join script any time — it lists known and nearby
|
||||
networks, then asks for an SSID and password:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root (or it re-execs itself under sudo)
|
||||
sh calculinux-wifi-set.sh
|
||||
```
|
||||
|
||||
**The six-minute boot without a dongle.** If the device is going to run permanently wired
|
||||
or fully offline, the Wi-Fi kick service is just wasted time on every boot. Disable it:
|
||||
|
||||
```bash
|
||||
# on the PicoCalc, as root
|
||||
systemctl disable --now wifi-kick.service
|
||||
```
|
||||
|
||||
**The console font restores itself.** MeshTerm saves whatever font the console was using
|
||||
before it starts, and puts it back when it exits — on a normal quit and on a crash alike.
|
||||
You don't need to do anything to keep the shell's own font intact.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Check |
|
||||
| --- | --- |
|
||||
| Nothing on the PicoCalc's screen at all | Is the SD card in the **Lyra's own slot**, not the PicoCalc's front slot? Is a "Lyra B" (SPI NAND) erased? Is it the **128 MB RAM, Pico-form-factor** variant — other RAM sizes have incompatible pinouts? |
|
||||
| Wi-Fi never comes up | Is the dongle one of the tested chipsets (RTL8192CU / R8712U / RTL8188EU)? Remember the Lyra's USB port is **3.3 V** — a 5 V dongle won't work. Give it the full six minutes on a cold boot before assuming it's stuck. |
|
||||
| `pip install` fails with `ENOSPC` | `/tmp` is a small RAM disk. `calculinux-setup.sh` already sets `TMPDIR=$HOME/tmp` for its own install; if you're running `pip` by hand, do the same. |
|
||||
| `meshterm: command not found` after setup | Log out and back in — the PATH line is added to `~/.profile`, which only takes effect on a fresh login shell. |
|
||||
| Tofu boxes instead of braille charts or node glyphs | The console font script hasn't run, or didn't persist. Re-run `sh calculinux-console-font.sh`, and check `/etc/vconsole.conf` has a `FONT=` line. |
|
||||
| `meshterm` can't open `/dev/ttyS1` | Is the `meshterm` user in `dialout` (`groups`)? Group changes need a fresh login to take effect. |
|
||||
| Port opens but the radio never answers | Re-flash with the **`_serial`** environment, not `_ble` or `_usb` — only it defines `SERIAL_RX`/`SERIAL_TX`. The XIAO's USB serial should be silent once it's running the radio firmware; that's expected, not a fault. |
|
||||
| Still silent with wiring confirmed | Confirm the firmware actually carries the I²C remap (`PIN_WIRE_SCL=16`, `PIN_WIRE_SDA=17`) — a build without the patch's second hunk looks identical until you check this. |
|
||||
| Flashed fine, XIAO never comes back | Wrong board, or the wrong SoftDevice — `INFO_UF2.TXT` must report a XIAO and **S140 v7**, not `6.1.1`. `flash.py` refuses to flash either mismatch without `--force`. |
|
||||
| `flash.py` finds no UF2 drive | Expected on a CDC-only bootloader — use the `pio … -t upload --upload-port` command it prints instead. |
|
||||
| Nothing on `/dev/ttyS1` after a reboot | `systemctl status uart1-radio-mux`, then `journalctl -b -u uart1-radio-mux` — the pin-mux poke has to run every boot, and a failed oneshot says why. |
|
||||
|
||||
---
|
||||
|
||||
## What has been tested
|
||||
|
||||
This whole path — from a stock PicoCalc kit through a running radio — has been exercised on
|
||||
one maintainer's bench: one Luckfox Lyra, one PicoCalc, one XIAO + Wio-SX1262 pair. It
|
||||
works. What hasn't been independently confirmed — the exact RK3506 register map behind the
|
||||
UART1 pin-mux poke, whether the Calculinux kernel's `/dev/mem` permissions are guaranteed
|
||||
rather than observed, the PicoCalc header's physical pin numbers, and a handful of other
|
||||
specifics — is listed honestly in
|
||||
["What is verified and what is not"](hardware.md#what-is-verified-and-what-is-not) in
|
||||
`docs/hardware.md`. If you build this and something disagrees with what's written here,
|
||||
that's worth reporting — a second board is the only way to learn which parts of this were
|
||||
about the hardware and which were about the one unit it was built on.
|
||||
+5
-3
@@ -6,9 +6,11 @@ MeshCore radio over UART, and fronting an SPI-attached LoRa chip as if it were a
|
||||
device. They run **on the device**, most of them as root, and they are meant to be read
|
||||
before they are run.
|
||||
|
||||
**[`docs/hardware.md`](../docs/hardware.md) is the manual** — what each target is, what the
|
||||
scripts assume about the machine, why each step is the way it is, and what has only ever
|
||||
been proven on one bench. Start there and pick your target; the table below is the index.
|
||||
**Start with the manual for your device**: [`docs/picocalc.md`](../docs/picocalc.md) walks a
|
||||
stock PicoCalc from the shopping list to a working radio, and [`docs/uconsole.md`](../docs/uconsole.md)
|
||||
installs the SPI bridge on a uConsole. [`docs/hardware.md`](../docs/hardware.md) is the
|
||||
reference behind both — what the scripts assume about the machine, why each step is the way
|
||||
it is, and what has only ever been proven on one bench. The table below is the index.
|
||||
|
||||
| File | Runs on | What it does |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -4,10 +4,11 @@ A Seeed XIAO nRF52840 + Wio-SX1262 wired to the Luckfox Lyra's UART1, running Me
|
||||
companion firmware on `Serial1`, so MeshTerm on the PicoCalc opens `/dev/ttyS1` like any
|
||||
other serial companion. No BLE, no USB host, no SD-slot sacrifice.
|
||||
|
||||
**The manual is [`docs/hardware.md`](../../docs/hardware.md#a-real-radio-for-the-picocalc-xiao-nrf52840-over-uart)**
|
||||
— parts, the firmware patch and why it is still needed, flashing a bootloader that offers no
|
||||
drive, what the register poke does, troubleshooting, and what has only been proven on one
|
||||
bench. This page is the bench card.
|
||||
**The step-by-step guide is [`docs/picocalc.md`](../../docs/picocalc.md)** — parts, the
|
||||
firmware build and the patch explained, flashing, wiring, and the Lyra setup.
|
||||
[`docs/hardware.md`](../../docs/hardware.md#a-real-radio-for-the-picocalc-xiao-nrf52840-over-uart)
|
||||
is the reference behind it: what the register poke does, and what has only been proven on
|
||||
one bench. This page is the bench card.
|
||||
|
||||
**Licensing.** The firmware is MeshCore, under the MIT License. The `meshcore-uart1.patch`
|
||||
modifies MeshCore source and `build-firmware.sh` produces a MeshCore binary; the licence is
|
||||
|
||||
Reference in new issue
Block a user