The XIAO stopped answering on 10-07 ("No response from meshcore
node" for many starts in a row). MeshCore's serial parser reads '<'
and a two-byte length, then waits for that many bytes with no time
limit, so one lost or damaged byte can make it swallow up to 65,535
bytes, and MeshTerm sends only 16 per start. Reproduced on the
PicoCalc: a half frame blocked two APP_STARTs until 600 filler bytes
arrived. meshcore-frame-timeout.patch drops a frame after 200 ms of
silence on the line (bytes queued while the loop was busy still
count), and build-firmware.sh applies it after meshcore-uart1.patch.
Upstream dev and main still have the old parser.
Flashing the XIAO with the new firmware, following the guide as
written, found these problems, now corrected:
- Step 11: `pip install platformio` failed (no pip outside a venv on
this PC), and it fails on externally managed Pythons or lands in an
active venv. The guide now uses the official installer only, and
build-firmware.sh and flash.py find pio in ~/.platformio/penv, so
nobody edits the PATH. uf2conv runs on PlatformIO's Python, because
a bare `python` is missing on many Linux systems.
- Step 12: the script's last line said to double-tap reset first;
flash.py touches the XIAO itself, and a wired XIAO's reset button is
inside the case.
- Step 13: this XIAO's bootloader has only a serial port, and flash.py
printed a pio command for the user to run "from your MeshCore
checkout", with pio not on the PATH. flash.py now does the serial
DFU upload itself (54 s here). Without pyserial, it runs itself
again with PlatformIO's Python instead of skipping the touch.
- A wired XIAO's USB power goes backward into the Lyra through the
3V3 wire (JP confirmed it on the bench). Step 13 now applies only
before wiring, and a new Day-to-day procedure updates the firmware
of a wired radio: poweroff, switch off, open, batteries out, flash.
After the flash, the radio kept its identity, MeshTerm's info command
connected, and an APP_START after a half frame and 0.5 s of silence
got its reply at once.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
54 KiB
MeshTerm on the PicoCalc
You start with a stock ClockworkPi PicoCalc (the kit that comes with a Raspberry Pi Pico) and a short shopping list. You end with a Linux handheld. MeshTerm runs full-screen on the display and the keyboard of the PicoCalc. A LoRa radio is soldered inside. You do not need a laptop for daily use.
There are three phases:
- Build the machine. You replace the Pico with a Linux board.
- Install the OS and MeshTerm.
- Add the radio.
Plan an afternoon for the first two phases. Most of this time is the wait for a toolchain
to download. The radio phase needs a soldering iron, and you can do it later. In the
meantime, the --mock mode of MeshTerm runs well without a radio.
Do you want to see MeshTerm before you build the machine? On any computer that has
MeshTerm installed, run meshterm emulate picocalc-lyra --mock. It opens a window that
shows MeshTerm as the display of the PicoCalc draws it, with a simulated radio. The
emulator is not a strict emulator. It copies the size, the font, and the colours of the
display, but it does not copy the machine. It shows you what you will build.
The emulator has the details.
⚠ WARNING: Read this first. In this build, you take the PicoCalc apart, replace its core board, and solder to it. You must be able to solder cleanly, use a multimeter, and work with small electronics without force. If a step goes wrong, the hardware can be damaged: a cracked screen, a broken shell post, a dead board, or an overcharged lithium cell. Some of this damage cannot be repaired. You do this at your own risk.
Read the whole manual one time before you buy or open anything. Several steps are clear only when you know a later step. These are where the SD card goes, what you must remove from the case before you press in a board, and which USB port you must never power. You can also make the shopping list more easily when you know what each part is for.
This is a guide, not a prescription. The author tried to make it correct, but it can have errors. Parts of it will become out of date when boards, images, and firmware change. You must check each fact in it against your own hardware and the current sources before you act on it. Each decision is yours. The author accepts no responsibility for damage, loss, or injury that results from this guide. It is not a warranty that your parts, your tools, or your hands will work as the parts, tools, and hands of the author did. If you are not comfortable with a step, stop and ask for help from a person who is.
- Shopping list
- Before you start
- Three ways to break it
- Phase 1: Build the machine
- Phase 2: Install Calculinux and MeshTerm
- Phase 3: Add the radio
- Day-to-day
- Troubleshooting
- What has been tested
Shopping list
The machine
| Item | Product / what to search for | Price (approximate) | Link | Note |
|---|---|---|---|---|
| PicoCalc kit | ClockworkPi PicoCalc | $89 | clockworkpi.com/product-page/picocalc | The kit has a Raspberry Pi Pico 1H, a screen, a keyboard, a shell, a hex key, and a 32 GB SD card. It does not have 18650 batteries. |
| Luckfox Lyra | "Luckfox Lyra 128MB, pre-soldered header, no NAND" | $20–25 | luckfox.com/Luckfox-Lyra | Buy the plain 128 MB RAM, Pico-form-factor variant, with the header pre-soldered. Other RAM sizes have pinouts that do not fit. This is the one part of the list where the wrong SKU does not work. |
| microSD card | 16 GB+, Class 10 or better | $6–10 | none | The stock 32 GB card of the PicoCalc works. It is more than the minimum of 8 GB. You can use it again and not buy a new card. |
| An 18650 battery | For example Samsung 30Q, Molicel P26A, Sony VTC6 (examples, not endorsements) | $5–10 | none | Unprotected, flat-top or button-top, Ø18 × 65–69 mm. One battery is enough to run the machine. A second battery is optional (refer to the battery note below). |
| USB Wi-Fi dongle | TP-Link TL-WN725N (an RTL8188EU nano dongle). You can also search for the chipset name: RTL8192CU, R8712U, RTL8188EU | $5–10 | amazon.ca/dp/B008IFXQFU | The TL-WN725N is the dongle that this guide used. It must run at 3.3 V (refer to Step 3). You need it only for setup and updates. MeshTerm itself runs offline. |
| MX1.25 4-pin pigtail | "MX1.25 4P cable with leads" (a pack of pre-crimped pigtails, 10–15 cm) | $2–4 | none | One end of the DIY USB lead in Step 3. Buy it pre-crimped, because MX1.25 contacts need a crimper that you probably do not have. |
| USB-A female socket | "USB 2.0 type A female socket, solder / through-hole" | $2–3 | none | The other end of the DIY lead. A ready-made MX1.25 4P to USB-A cable (approximately $1, 30 cm) also works. But it is much cable to fold into the shell. |
The radio
| Item | Product / what to search for | Price (approximate) | Link | Note |
|---|---|---|---|---|
| XIAO nRF52840 + Wio-SX1262 kit | "XIAO nRF52840 & Wio-SX1262 Kit for Meshtastic", SKU 102010710 | $13.49 | seeedstudio.com | The kit has the XIAO nRF52840, the Wio-SX1262 LoRa board (stacked and wired together), and an antenna. It covers 868 MHz and 915 MHz. The plain XIAO and the 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 are crossed (refer to Step 14). |
| 2.5 mm hex key | It comes with the PicoCalc kit. |
| USB-C data cable | For the serial console, and later to flash the XIAO. A cable that only charges does not work. The cable must carry data. |
| microSD card reader | To write the Calculinux image from your PC. |
| Small side cutters | To cut wire ends. Also to cut a section of the case grille, if the Wi-Fi lead needs the space. |
| Small needle file | To make the USB-C cutout in the back panel a little bigger, if the connector of the Lyra does not clear it. |
| Kapton (polyimide) tape | It wraps the XIAO stack and the DIY USB lead. Then no bare part touches the board or the shell. It is thin, it resists heat, and it comes off cleanly. |
| A PC (Windows/macOS/Linux) with Python 3 and git | It runs the steps that write the image, build the firmware, and flash the XIAO. |
Optional
| Item | Why |
|---|---|
| A second 18650 battery | The holder takes two batteries. One is enough to run the machine. Two give approximately twice the runtime. |
| Powered USB hub | Use it if you want the Wi-Fi dongle and another device on the single USB host socket of the Lyra at the same time. Also use it for a 5 V dongle. |
| Foam tape | To hold the XIAO board in the shell after you wire it. |
The approximate total is $130–150 for the machine and the radio together, before batteries. Most of this is the PicoCalc kit ($89), the Lyra (approximately $22), and the radio kit ($13.49).
What you do NOT need:
- A level shifter. Each signal in this build is 3.3 V: the header of the Lyra, the XIAO, and the SX1262.
- A separate USB-to-serial adapter. The PicoCalc mainboard has one built in.
- Bluetooth. Neither the Lyra nor this radio path uses it.
- A new SD card, if you have the stock 32 GB card. It is much more than the minimum of 8 GB that Calculinux asks for.
A note on batteries
ClockworkPi did not publish an official battery specification for the PicoCalc. A GitHub issue that asks for one is still open and has no answer. These facts come from the kit itself:
- The holder takes two 18650 cells.
- One cell is enough to power the board.
- The physical size is approximately Ø18 mm × 65–69 mm. This excludes most protected cells, because the protection circuit adds length that does not fit.
- Unprotected flat-top cells and button-top cells both work.
- The PicoCalc mainboard has its own charge circuit.
Buy from a reputable brand, not from a cheap unbranded listing. Samsung 30Q, Molicel P26A, and Sony VTC6 are examples that people recommend often. They are not an endorsement of one of them.
Before you start
You work on two machines. It helps to know which machine does which job:
- Your PC writes the Calculinux image to the microSD card, builds the radio firmware, and flashes the XIAO. These three jobs do not touch the PicoCalc itself.
- The PicoCalc (the Luckfox Lyra inside it) does all other jobs: setup, Wi-Fi, and MeshTerm. You can do these jobs over the serial console from your PC, or on the display and the keyboard of the PicoCalc. Both ways work in the same manner. The serial console is easier when you must paste long commands.
The PicoCalc has two logins for different jobs:
rootis for the setup script and for Wi-Fi repairs. You use it one time.meshtermis the unprivileged account that MeshTerm runs under each day.
Calculinux is a Linux that has a console only. It has no desktop and no window manager. This is intentional. The display and the keyboard of the PicoCalc are a terminal. MeshTerm is a full-screen terminal program that is made for this.
Three ways to break it
The PicoCalc is a kit. Its forum has a long list of the ways in which people damaged their PicoCalc. Three of them apply to this build. The first two are real. The author of this guide cracked a screen and broke screw posts when building the machine that this guide describes. Read this section before you open the shell.
⚠ WARNING: The screen is very fragile. It is a bare glass panel with no frame. When the case is closed, the screen is directly against the mainboard. Each force that bends the mainboard also bends the glass. These are the actions that bend it:
- You push a board into the Pico socket.
- You pull a board out.
- You close the shell when the screen is not square in its opening.
- You press a key while you screw the two halves together.
Forum members cracked screens in each of these ways. The swap in Step 2 is the classic case. If you press a new board into the socket while the mainboard is in the case, this alone can crack the screen. This is how the screen of the PicoCalc of the author broke. (Before replacing the Pico, Avoid breaking your screen)
Do these things:
- Never push a board into the socket, or pull one out, while the mainboard is in the case. Unplug the ribbon cable of the display (the FPC) from the mainboard. Lift the mainboard out. Do the swap on a flat, padded surface. A folded microfibre cloth is good. Ease a board out a little at each end in turn. Never lever one end.
- Hold the screen with Kapton tape along its edges. Then it cannot move in the opening while you work or while the case is closed. Kapton is the correct tape. It is thin, it stays in position, and it comes off cleanly. Do not use a tape that sets hard.
- Before you fit the back, check that the screen is flush and square in the opening of the front half, with no pressure on a corner. If the mainboard is not perfectly flush on the front half, stop. Find the cause before you add the back.
- Touch the panel as little as possible. Keep your fingers off the keyboard while you close the shell.
⚠ WARNING: Do not tighten the screws too much. The screw posts of the shell are brass inserts that are moulded into plastic. If you force a screw, the post breaks out of the shell. This repair needs epoxy and is never as good as the original (Broken screw thread). The author broke several posts at the first assembly. You need less force than you think. Turn each screw until it just seats, then stop. If a screw does not go in easily, the two halves are not aligned, or a part inside is in the way. More force is never the answer.
⚠ WARNING: Never power the Lyra through its own USB-C while the batteries are in. The PicoCalc charges its cells only through the USB-C port of the mainboard. A power-management chip (an AXP2101) controls the charge. Power that comes in through the USB port of the core board (the Pico's originally, now the Lyra's) goes to the system-power pin of the header. It bypasses the charge control. Forum members who powered the port of the Pico directly measured 4.46 V and 4.74 V on their cells afterwards. This is much more than the 4.2 V that a lithium cell can have ("New PicoCalc — battery charging"). An overcharged cell is a fire risk, not only a worn cell.
Nothing in this guide needs the USB-C of the Lyra. The serial console, the charging, and the power all use the port of the mainboard on the side of the case. The Wi-Fi dongle uses the 4-pin socket on top of the Lyra. If you must connect the USB-C of the Lyra to a PC (for example, to flash it again with the tools of Luckfox), remove both batteries first.
Phase 1: Build the machine
Step 1: Assemble the PicoCalc
- Follow the assembly guidelines of ClockworkPi to put the kit together. The guidelines are a PDF in the PicoCalc repository, which also has a wiki. You assemble the mainboard, the screen, the keyboard, the speakers, and the shell.
- Put in your 18650 battery (or two). Look at the
+/-marks in the battery compartment.
WARNING: Two of the warnings in Three ways to break it apply here. Before you close the shell, tape the edges of the screen with Kapton and check that the screen is square. Turn the screws only until they seat. You open the shell again in the next step. Thus there is no reason to make the screws tight now.
- Turn the power on one time with the stock Pico still in place. You must see the stock BASIC firmware start on the screen. The keyboard must respond. This confirms that the screen, the keyboard, and the battery are good before you replace boards.
- Turn the power off.
Step 2: Swap the Pico for the Lyra
- Remove the back of the shell with the hex key.
WARNING: Do the swap with the mainboard out of the case. This step is the one that cracks screens (refer to Three ways to break it).
- Unplug the ribbon cable of the display from the mainboard.
- Lift the mainboard out and put it on a padded surface.
- Ease the Raspberry Pi Pico out of its socket, a little at each end in turn. Keep the Pico. You only put it to one side.
- Check if your Luckfox Lyra has SPI NAND on the board (a "Lyra B"). If it has, erase the NAND first. If you do not erase it, the boot ROM ignores the SD card and starts from the flash on the board. Refer to the hardware requirements page of Calculinux to see how.
- Put the Lyra in the same socket that the Pico left. Keep the same orientation. The USB-C port of the Lyra must be at the end where the USB port of the Pico was. Then it lines up with the cutout in the back of the shell.
- Press the Lyra fully down while the mainboard is still on the padded surface. A Lyra that is not fully seated does not start.
- Connect the ribbon cable again.
- Put the mainboard back in the front half. Check that the screen is square before you tighten any screw.
You must see the Lyra flat on the socket, with no gap at either end. After the mainboard is back in the case, the USB-C port of the Lyra must be at the centre of the cutout in the back panel.
The cutout was made for the micro-USB port of the Pico. The USB-C connector of the Lyra has a different shape. Depending on the connector that Luckfox fitted to your board, the back panel may not close over it. If the panel catches, do these steps:
- Open the hole a little with a small needle file.
- Test the fit as you file.
- Stop when the panel sits down without pressure on the board.
Leave the back off for now. In Step 4, the microSD card goes into the slot of the Lyra itself. You can reach that slot more easily when the board is open.
Step 3: Attach the Wi-Fi dongle
The Lyra has two USB connectors. The USB-C connector on its edge is for power and flashing. You do not use it here. The small 4-pin socket on top of the board is the USB host. It is an MX1.25 connector, next to the two buttons at the USB-C end. The Wi-Fi dongle connects to it through a short lead that you make yourself. A ready-made cable is 30 cm long and is difficult to fold into the shell. Also, you already have the soldering iron out for the radio.
Make the lead. Solder the four wires of an MX1.25 pigtail to a USB-A female socket. Keep the lead short. A length of 5 to 8 cm is enough to reach a place where the dongle sits flat. The pins of the pigtail are in the standard USB order. The wire colours of the ready-made cable follow this order:
| MX1.25 pin | USB-A socket pin | Signal | Pigtail wire colour (usually) |
|---|---|---|---|
| 1 | 1 | VBUS (3.3 V on this board) | red |
| 2 | 2 | D− | white |
| 3 | 3 | D+ | green |
| 4 | 4 | GND | black |
The pins of the USB-A socket are numbered 1 to 4 across the opening of the connector. VBUS is on one edge, GND is on the other edge, and the data pair is between them. The colours of a pigtail are a convention, not a guarantee. Thus check pin 1 before you connect anything.
WARNING: Make sure that the soldering iron is in its stand when you do not use it. The tip is hot enough to burn your skin.
CAUTION: Do not swap VBUS and GND. This is the one mistake that damages the dongle.
- Solder the four wires to the USB-A socket.
- Turn the power of the Lyra on.
- With a multimeter, measure pin 1 of the socket against a GND pin of the header. It must read 3.3 V.
- Measure the pin at the other end of the socket. It must read 0 V.
- Turn the power off.
- Wrap the solder side of the socket and the bare wire in Kapton tape.
- Push the MX1.25 end into the socket on the Lyra. The socket is keyed, and the plug goes in only one way.
- Push the Wi-Fi dongle into the USB-A end.
- Put the lead and the dongle inside the shell, where they do not press on the board.
The MX1.25 socket is on top of the Lyra, and the lead leaves it upward. The grille of the back panel is there. The wire can interfere with the grille when you fit the back. If it does, cut a small section out of the grille with the side cutters, where the wire passes. Shape the edge until nothing presses on the plug or the wire. It is better to remove a sliver of plastic than to close the case against a connector.
CAUTION: The USB port of the Lyra is 3.3 V, not 5 V. A standard 5 V USB Wi-Fi dongle does not work and can damage the board. Use only the tested chipsets: RTL8192CU, R8712U, or RTL8188EU. For this reason, the shopping list names a known dongle (the TP-Link TL-WN725N). For other dongles, it tells you to search for the chipset and not for the brand.
Do not turn the power on yet. You set up Wi-Fi later, after you install Calculinux.
Phase 2: Install Calculinux and MeshTerm
Step 4: Write the image to the microSD
- On your PC, download
calculinux-image-luckfox-lyra.rootfs-<timestamp>.wic.gzfrom the newest release on the Calculinux releases page. Each Calculinux release is marked Pre-release. Thus there is no "Latest" badge to follow. Take the newest pre-release. - Decompress the file:
# on the PC
gunzip calculinux-image-luckfox-lyra.rootfs-*.wic.gz
The Calculinux installation page mentions unxz, but the file is a .gz. Thus use
gunzip. If you do not want to use the command line, 7-Zip works on all platforms.
CAUTION: Write the .wic file to the correct device. This action erases everything that is
on the card.
-
Write the resulting
.wicfile to your microSD card. Use one of these three tools:- Balena Etcher (recommended, all platforms): open it, select the
.wicfile, select your SD card, and click Flash. dd, on Linux or macOS. Check the device name first. If you write to the wrong device, you destroy its contents:
- Balena Etcher (recommended, all platforms): open it, select the
# 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.
- When the write is complete, eject the card.
- Put the card in the Luckfox Lyra's own microSD slot. Do not use the front slot of the PicoCalc. The Lyra starts from its own slot. You can reach the front slot only after MeshTerm (or another program) already runs on Linux.
- Leave the front slot empty for the first boot. The instructions of Calculinux also say this. A second card there that has the same partition labels makes the system read-only.
Step 5: First boot
- With the card in the Lyra, turn the PicoCalc on.
- Wait 30–60 seconds. The first boot is slower than the next boots. Boot messages scroll
on the display of the PicoCalc. They end with a
Calculinux GNU/Linuxlogin prompt. - Log in:
login: root
Password: root
- Change the password at once:
# on the PicoCalc, as root
passwd
Step 6: Open the serial console
The display and the keyboard of the PicoCalc work well, thus you can skip this step. But the serial console makes it much easier to paste the longer commands in this guide. We recommend it.
The USB-C port of the PicoCalc mainboard has a USB-to-UART bridge (a CH340 chip).
-
Connect a USB-C cable from that port to your PC. The port shows as:
/dev/ttyUSB0on Linux (you may need to be in thedialoutgroup)- a
COMport on Windows /dev/cu.usbserial-*on macOS
-
Open a terminal program with these settings: 1500000 8N1, no flow control. This example uses
minitermofpyserial:
# on the PC
pip install pyserial
python3 -m serial.tools.miniterm /dev/ttyUSB0 1500000
minicom and PuTTY also work, with the same settings. To leave miniterm, press
Ctrl+].
You must see the same login prompt that you saw on the display of the PicoCalc, now in the terminal of your PC.
Step 7: Join Wi-Fi
Calculinux manages Wi-Fi with iwd. The easiest way is its text-mode picker.
- Run
uwific. - Highlight your network and press Enter.
- Type the passphrase. (
Qquits.)
You can do the same from the command line:
# on the PicoCalc, as root
iwctl station wlan0 scan
iwctl station wlan0 get-networks
iwctl station wlan0 connect "<your SSID>"
The last command asks for the passphrase. Then check that you are online:
ip addr show wlan0
ping -c 3 1.1.1.1
You must see an inet address on wlan0, and replies from the ping. iwd remembers the
network. Thus you do this step one time. To change networks later, run uwific again.
Step 8: Copy the setup scripts to the device
You need the scripts/ directory of this repository on the Lyra. The Lyra runs the
picocalc-lyra/ folder in it. When Wi-Fi is up, you can get the directory there in two
ways:
-
scpfrom your PC. On the Lyra, runip addr show wlan0to find its IP address. Then, on your PC, run:# on the PC, from your MeshTerm checkout scp -r scripts root@<lyra-ip>:/root/If SSH refuses root, copy as the
picouser (passwordcalc). Then move the directory as root on the Lyra:# on the PC scp -r scripts pico@<lyra-ip>:/home/pico/ # on the PicoCalc, as root mv /home/pico/scripts /root/ -
git cloneon the Lyra. The Calculinux base image includesgit. As root, rungit clone https://github.com/jpmartineau/MeshTerm. Then use thescripts/folder in the clone.
We recommend scp.
Step 9: Run calculinux-setup.sh
# on the PicoCalc, as root, with scripts/ copied over
cd scripts/picocalc-lyra
sh calculinux-setup.sh
You can run the script again without a problem, and it checks before it acts. It reads these environment variables at the top of the script:
| Knob | Default | Effect |
|---|---|---|
DEPLOY_USER |
meshterm |
The login that MeshTerm runs under |
TIMEZONE |
America/Toronto |
Any IANA zone. Set it if you are not on Eastern time. |
REPO_URL |
the HTTPS URL of the project | The script clones this URL when there is no deploy key. |
REPO_SSH |
the SSH URL of the project | The script clones this URL when there is a deploy key. |
KEY_PATH |
/home/$DEPLOY_USER/.ssh/id_ed25519 |
A read-only GitHub deploy key, if you want SSH and not HTTPS |
WIFI_SSID, WIFI_PSK |
unset | If you set both, the script writes the credentials of iwd for that network. This is an alternative to Step 7. |
The defaults clone the public repository of MeshTerm over plain HTTPS. This needs no
credential. You probably need only TIMEZONE, if you live outside Eastern time:
# on the PicoCalc, as root
TIMEZONE=America/Vancouver sh calculinux-setup.sh
The script installs only the time-zone data for the Americas. Thus a zone in another region may not work. If the script cannot set the zone, it prints a warning and the clock stays on UTC.
The script does these things, one for each phase:
- opkg packages. It installs
python3-modulesandpython3-pip. It also installsgitandkbdif the image does not have them. Calculinux has a reduced Python that does not havepip,venv, and several modules of the standard library. - Wi-Fi kick. It installs a service for boot time that scans again until the dongle
joins the network that
iwdalready knows. On a cold boot, the firmware of the dongle finishes loading after the first scan ofiwd. Without the kick, the link does not come up. - Time sync. It installs a service for boot time that sets the clock over the network, because this board has no battery-backed real-time clock.
- Timezone. It sets
$TIMEZONE(America/Torontoby default). - Deploy user. It creates the
meshtermlogin and puts it in the groups that it needs. - Clone. It checks out MeshTerm to
/home/meshterm/MeshTerm. - venv and install. It builds a Python virtual environment and runs
pip install -e .to install MeshTerm in it. - Login PATH. It puts the
binof that venv on the PATH of themeshtermuser. - Console font. It builds and installs the console font that the UI of MeshTerm needs (braille charts, node glyphs, and rounded corners).
This takes a few minutes on a good connection. Most of the time is pip install, which
compiles code. The script ends with done and tells you to log in as meshterm. That user
has no password yet. Thus set one first:
# on the PicoCalc, as root
passwd meshterm
If no Wi-Fi dongle is attached, the next boot can take up to approximately eleven minutes. The Wi-Fi kick tries for approximately six minutes before it stops. Then the time sync waits up to five more minutes for a network route. Nothing is broken. If the PicoCalc will stay offline, refer to Day-to-day to switch both off.
Step 10: Log in as meshterm
- Log out of
root. - Log in as
meshtermwith the password that you just set. - Try the app:
# on the PicoCalc, as meshterm
meshterm --mock
The full-screen interface of MeshTerm must start at 53 columns, with a simulated radio behind it. It must draw braille charts and node glyphs cleanly, with no empty boxes. Along the bottom, you must see the F-key lane: five labelled chips, not the footer hint line that a desktop terminal shows.
Look around in the app. Esc goes back from any screen. To leave the app, choose Quit on the main menu, or hold Esc for three seconds from any screen. After one second, a box shows a bar that fills. If you release Esc before the bar is full, you stay where you were.
Then look at the whole visual language on one card:
# on the PicoCalc, as meshterm
meshterm specimen
Two console fonts are available: a 6×12 font (53 × 26) and a 6×8 font (53 × 40, which has more rows but no Cyrillic glyphs). Try both from Preferences → Display → Console font. When you select a font, MeshTerm shows it at once. The screen is painted again with the new row count, so you can see the result before you keep the choice.
Phase 3: Add the radio
You can do this phase later. The steps above already give you a working MeshTerm in --mock
mode. Come back to this phase when you have the soldering iron out.
Step 11: Install PlatformIO
PlatformIO builds the firmware and loads it onto the XIAO. If you already have it (for example, the PlatformIO extension of VS Code), go to Step 12.
On your PC, install PlatformIO with its
official installer script.
The script puts PlatformIO in a Python environment of its own, ~/.platformio/penv, and
does not change your system Python:
# on the PC (Git Bash or WSL on Windows)
curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py
python3 get-platformio.py # on Windows: py get-platformio.py
The installer ends with the full path of platformio, and it tells you to add its directory
to your PATH. You do not have to. build-firmware.sh and flash.py look for PlatformIO in
~/.platformio/penv when it is not on the PATH.
Do not use pip install platformio. Many systems refuse a pip install into the system
Python (Debian, Ubuntu, and Homebrew mark it "externally managed"). Also, in a shell with an
active virtual environment, the package goes into that environment.
The first firmware build downloads the nRF52 toolchain, which is several hundred megabytes. Do this where you have a good connection.
Step 12: Build the firmware
# on the PC (Git Bash or WSL on Windows), from your MeshTerm checkout
cd scripts/picocalc-lyra/xiao-radio
sh build-firmware.sh
The script clones MeshCore, applies two patches, and builds. The first build takes several minutes. The script ends with this output:
DONE. Firmware: /path/to/scripts/picocalc-lyra/xiao-radio/meshcore-xiao-radio.uf2
Next: connect the USB-C port of the XIAO to this machine, and run: python3 flash.py
(on Windows: py flash.py)
These are the actions of the script. You can do them by hand if you must:
- Clone MeshCore into
scripts/picocalc-lyra/xiao-radio/_meshcore-build. - Check out the pinned commit
e9edfc8eon thedevbranch. This is the commit that the patches apply to and build against without a problem. - Apply
meshcore-uart1.patch, thenmeshcore-frame-timeout.patch. - Build the
Xiao_nrf52_companion_radio_serialPlatformIO environment:pio run -e Xiao_nrf52_companion_radio_serial. - Convert the resulting
.hexfile to a.uf2file with MeshCore's own converter. Use the UF2 family id of the nRF52840:python bin/uf2conv/uf2conv.py firmware.hex -c -f 0xADA52840 -o meshcore-xiao-radio.uf2.
What the patches change, and why they are still necessary
meshcore-uart1.patch changes two files. Both changes are necessary:
examples/companion_radio/main.cpp. The companion-over-serial code of MeshCore declaresHardwareSerial companion_serial(1). This is a numbered constructor. On the Adafruit nRF52 core,HardwareSerialis an abstract base class and has no numbered constructor. Thus this line does not compile for the XIAO nRF52840. The patch adds an#if defined(NRF52_PLATFORM)branch. This branch bindscompanion_serialtoSerial1instead.Serial1is the concrete UART object that the nRF52 core always provides.Uart::setPins(rx, tx)is called later in the same file. It takes the same arguments in both cases, thus nothing else needs to change.variants/xiao_nrf52/platformio.ini. This change adds a newXiao_nrf52_companion_radio_serialbuild environment. The environment puts the companion serial link on pins D6 (TX) and D7 (RX). It also moves I²C off those same two pads, to two internal pins, 16 and 17, that nothing in this build uses. This second change is the important one. The stockXiao_nrf52environment maps I²C to D6/D7 by default. Thebegin()of the board and the sensor code both start the I²C bus on these pins automatically. Without the move, I²C takes the UART pins at boot. The wiring is correct, and the firmware builds and runs. But the serial link is completely silent, because another function already owns the pins.
We submitted both changes to MeshCore upstream. At the time of writing, they are not
merged, and there is no sign that this will happen soon. The upstream dev branch still
declares the bare HardwareSerial(1) with no nRF52 branch. The platformio.ini of the XIAO
still has no _serial companion environment. To build with the patch is the normal route
here, not a temporary measure. When the patch is merged, you can delete the git apply
step and the patch file.
meshcore-frame-timeout.patch changes the serial parser of the companion,
src/helpers/ArduinoSerialInterface.cpp and its header. Each protocol frame starts with <
and a length of two bytes, and the parser then reads that number of bytes. Without the
patch, the parser has no time limit. If one byte is lost or damaged on the wire, the parser
can read a wrong length of up to 65,535 bytes. Then it ignores each command until that many
bytes come in. MeshTerm sends one short request when it connects. Thus the radio does not
answer for many starts in sequence, and only a power cycle of the XIAO clears the fault.
With the patch, the parser drops a frame when its bytes stop for 200 ms, and the next command starts a new frame. Bytes that wait in the receive buffer while the firmware is busy still belong to the frame. Only silence on the wire resets the parser. All the serial companions of MeshCore have this parser, thus the patch is also a candidate for upstream. We have not submitted it yet.
Building by hand
If you want to follow a newer MeshCore and not the pinned commit, run:
# 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/picocalc-lyra/xiao-radio/meshcore-uart1.patch
git apply /path/to/scripts/picocalc-lyra/xiao-radio/meshcore-frame-timeout.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 changed and the context of the patch does not match. If
meshcore-frame-timeout.patch fails, open it and make its changes by hand in the two files
that it names. The two hunks of meshcore-uart1.patch are small, so you can also make them
by hand:
-
In
examples/companion_radio/main.cpp, find the lineHardwareSerial companion_serial(1);and replace it with:#if defined(NRF52_PLATFORM) Uart& companion_serial = Serial1; #else HardwareSerial companion_serial(1); #endif -
At the end of
variants/xiao_nrf52/platformio.ini, add a new environment:[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 without a problem with these patches. A
newer dev can need small changes.
Step 13: Flash the XIAO
CAUTION: Make sure that the XIAO is the board that you flash and that it has the S140 v7
bootloader. A flash to the wrong board, or to the wrong SoftDevice, can leave a board that
does not start. flash.py checks both before it writes to a UF2 drive. Through serial DFU,
it accepts only the USB ids of the XIAO bootloader.
- Connect the USB-C port of the XIAO itself to your PC. Do this before you wire the XIAO in Step 14. To flash a XIAO that is already wired, follow "Updating the radio firmware" in Day-to-day instead. The USB power of a wired XIAO goes backward into the Lyra.
- Run the flash tool, in the same directory as Step 12:
# on the PC (Git Bash or WSL on Windows)
python3 flash.py # on Windows: py flash.py
The tool first tries a 1200-baud "touch" over USB serial. This resets the XIAO into its bootloader. The touch targets only the USB vendor id of Seeed. Thus it does not disturb another nRF52840 board on the same bench. If the touch does not work, double-tap the reset button of the XIAO and run the tool again.
The bootloader then shows a UF2 drive, or only a serial port. When it shows a drive, the tool checks two things before it writes anything:
- The
Modelfield ofINFO_UF2.TXTmust say a XIAO. A drive letter is not an identity. Another board can have the letter that your XIAO just gave up. SoftDevicemust not be6.1.1. This firmware links for SoftDevice S140 v7 (the app is at0x27000). An S140 6.1.1 bootloader wants the app at0x26000. The flash then succeeds, but the board never starts.
If you are certain, --force overrides both checks. A good flash ends with this output:
DONE. XIAO flashed and rebooting into the radio firmware.
When the bootloader shows only a serial port, the tool loads the firmware through serial
DFU itself. The XIAO that we tested has this type of bootloader. The tool runs the
PlatformIO upload in _meshcore-build, which sends the firmware that Step 12 built. This
takes approximately one minute. The USB ids of the port identify the XIAO bootloader. The
upload ends with these lines:
Device programmed.
...
DONE. XIAO flashed and rebooting into the radio firmware.
After the flash, the USB serial port of the XIAO is silent to companion frames. The companion protocol is now on D6/D7, not on USB. This silence is correct. It does not mean that the flash failed.
Step 14: Wire the radio
You solder four wires. TX and RX are 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 that it runs without USB |
The Lyra does not use the GPIO numbering of the Raspberry Pi Pico. The 40-pin header has the physical shape of the Pico header. But the function of each pad is the RK3506 mapping of Luckfox. GP4 is
gpio0-0and GP5 isgpio0-1. Do not use a Pico pinout for other pins on this header. Each signal here is 3.3 V, and this board has no 5 V rail. Power the XIAO from 3V3 only.
- Turn the PicoCalc off and remove the back of the shell.
- Remove the batteries.
WARNING: Put the soldering iron in its stand when you do not use it. The tip is hot enough to burn your skin. Solder in a place that has good airflow.
CAUTION: Keep the iron on each pin only as long as necessary, and do not press down on the board. Too much heat or force can damage the Lyra.
- Solder the four wires between the pads of the XIAO and the header pins of the Lyra, as the table shows. The Lyra can stay in its socket. The tops of its header pins are accessible from inside the case, so solder to those.
CAUTION: Attach the LoRa antenna to the u.FL connector of the Wio-SX1262 before you turn any power on. If you run the radio without an antenna, you can damage it.
- Attach the LoRa antenna to the u.FL connector of the Wio-SX1262.
- Wrap the XIAO + Wio-SX1262 stack in Kapton tape. Then no pad can touch the Lyra, the mainboard, or a battery.
- Put the stack in the shell, in a place where it fits without strain on the wires.
- Use a strip of foam tape to hold it against the inside of the case.
Step 15: Set up the Lyra
- Close the shell. Put the batteries back in.
- If you did not do Step 8, copy
scripts/to the Lyra now. - Run the setup script:
# on the PicoCalc, as root
sh scripts/picocalc-lyra/xiao-radio/lyra-setup.sh # MT_USER=meshterm by default
You can run this script again without a problem. It does three things:
- It installs
uart1-mux.pyas/usr/local/bin/uart1-radio-mux.py. A oneshot service,uart1-radio-mux.service, runs it at each boot. This part makes/dev/ttyS1reach the header pins. Calculinux enables the UART1 controller, but it never wires the controller to a physical pad. The flexible pin matrix of the RK3506 needs a direct register poke to route UART1 to GP4/GP5. - It adds
$MT_USERto thedialoutgroup, so that this user can open the serial port. This change takes effect only after a new login. - It writes a MeshTerm profile, only if none exists yet, at
/home/$MT_USER/.meshterm/config.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, the script does not change your file. It prints only the
default_profile line, the [profiles.picocalc] heading, and the port. Copy the whole
block above into your file yourself.
Step 16: Run it
- Log out and log in again as
meshterm. Then thedialoutgroup membership takes effect. - Start MeshTerm:
# on the PicoCalc, as meshterm
meshterm
A bare meshterm opens on the Select a companion device splash. The picocalc profile
is a row in the list. Select it. To skip the splash, run meshterm -p picocalc. Your node
must start, under the name that the firmware advertises. Both the device page and the
dashboard header show the name.
If you want to prove the wiring before you look for other causes, there is a deeper check.
Open /dev/ttyS1 at 115200 as root. Send a raw APP_START frame (3c 0e 00 01, seven zero
bytes, then a name). A live radio answers with a framed 05, SELF_INFO. The command byte
and the reply code are from the companion protocol. The < and > marks and the
little-endian length in the framing come from the firmware. Confirm them against the
firmware. This check is lower-level than a run of MeshTerm. It is mostly useful to find a
wiring problem.
Day-to-day
Charging. Charge only through the USB-C port of the mainboard on the side of the case. ⚠ WARNING: Never charge through the USB-C of the Lyra at the back. Refer to Three ways to break it.
Powering off. Shut down the system. Do not pull the batteries:
# on the PicoCalc
sudo poweroff
Updating MeshTerm. The copy of scripts/ that you made in Step 8 becomes old as soon as
the checkout changes. Thus run the scripts inside the checkout. You can pull and run the
setup script again from root. This reinstalls MeshTerm and builds the console font again:
# on the PicoCalc, as root
su - meshterm -c 'git -C ~/MeshTerm pull'
sh /home/meshterm/MeshTerm/scripts/picocalc-lyra/calculinux-setup.sh
Or, as the meshterm user, do only the update:
# on the PicoCalc, as meshterm
cd ~/MeshTerm && git pull && TMPDIR=$HOME/tmp .venv/bin/pip install -e .
If an update adds new glyphs to the console font, build the font again afterward:
# on the PicoCalc, as root
sh /home/meshterm/MeshTerm/scripts/picocalc-lyra/calculinux-console-font-6x12.sh
Updating the radio firmware. A new firmware for the XIAO goes in through its USB-C port, as in Step 13. But the 3V3 wire of Step 14 connects the XIAO to the Lyra. When the XIAO gets USB power, that wire powers the Lyra backward.
- On the PC, build the firmware first (Step 12). Then the cable stays connected only for the flash.
- On the PicoCalc, run
sudo poweroff. Then set the power switch to off. - Remove the back of the shell. Then remove the batteries.
CAUTION: Remove the batteries before you connect the USB-C port of the XIAO. Through the 3V3 wire, the USB power of the XIAO goes backward into the Lyra and the circuits next to it.
- Connect the USB-C port of the XIAO to your PC.
- Run
flash.py(Step 13). - When the tool shows
DONE, disconnect the cable. - Put the batteries and the back of the shell in again. Then turn the PicoCalc on.
Changing Wi-Fi. Run uwific as root, as in Step 7. The kick at
boot time finds the new network by itself. It needs only that iwd knows the network.
The slow boot without a dongle. If the PicoCalc will always run offline, the Wi-Fi kick and the time sync waste time at each boot. It is not enough to disable the kick, because the time sync starts it again. Thus mask the kick. Then disable the time sync. Without this, the time sync waits up to five minutes for a network:
# on the PicoCalc, as root
systemctl mask --now wifi-kick.service
systemctl disable --now time-sync.service
To undo this later, run systemctl unmask wifi-kick.service and
systemctl enable time-sync.service.
The console font restores itself. MeshTerm saves the font that the console used before it starts. It puts that font back when it exits, after a normal quit and also after a crash. You do not need to do anything to keep the shell's own font.
Troubleshooting
| Symptom | Check |
|---|---|
| Nothing on the display of the PicoCalc | Is the SD card in the Lyra's own slot, not the front slot of the PicoCalc? Is a "Lyra B" (SPI NAND) erased? Is the board the 128 MB RAM, Pico-form-factor variant? Other RAM sizes have pinouts that do not fit. |
| Wi-Fi never comes up | Is the dongle one of the tested chipsets (RTL8192CU / R8712U / RTL8188EU)? The USB port of the Lyra is 3.3 V, thus a 5 V dongle does not work. On a cold boot, wait the full six minutes before you decide that it is 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 run pip by hand, set it in the same way. |
meshterm: command not found after setup |
Log out and log in again. The PATH line is in ~/.profile, which a new login shell reads. |
| Empty boxes instead of braille charts or node glyphs | The console font script did not run, or its result did not stay. Run sh calculinux-console-font-6x12.sh again. Check that /etc/vconsole.conf has a FONT= line. |
meshterm cannot open /dev/ttyS1 |
Is the meshterm user in dialout (groups)? A group change takes effect only after a new login. |
| The port opens but the radio never answers | Flash again with the _serial environment, not _ble or _usb. Only _serial defines SERIAL_RX/SERIAL_TX. When the XIAO runs the radio firmware, its USB serial must be silent. This is expected and is not a fault. |
| Still silent, and the wiring is correct | Make sure that the firmware has the I²C remap (PIN_WIRE_SCL=16, PIN_WIRE_SDA=17). A build without the second hunk of the patch looks the same until you check this. |
| The flash was good, but the XIAO never comes back | The board or the SoftDevice is wrong. INFO_UF2.TXT must report a XIAO and S140 v7, not 6.1.1. flash.py refuses to flash with either mismatch unless you use --force. |
flash.py finds no UF2 drive |
This is expected on a bootloader that has only a serial port. The tool then loads the firmware through serial DFU itself. If it says that there is no build, run build-firmware.sh first. |
Nothing on /dev/ttyS1 after a reboot |
Run systemctl status uart1-radio-mux, then journalctl -b -u uart1-radio-mux. The pin-mux poke must run at each boot. If the oneshot fails, the log tells you why. |
What has been tested
The maintainer tested this whole path, from a stock PicoCalc kit to a running radio, on one
bench. The bench had one Luckfox Lyra, one PicoCalc, and one XIAO + Wio-SX1262 pair. The path
works. A test in code checks one part: tests/test_theme16.py parses the font script and
compares it to the glyph inventory of MeshTerm. When the author wrote this guide, the author
checked these items by hand:
- the pinned MeshCore commit
- the need for the two hunks of the patch on
dev - the nRF52840 UF2 family id
- the XIAO bootloader ids, against the board files of Adafruit
On 2026-10-08, the author flashed the radio again, with the frame timeout, and checked these items on the bench:
- the build and the flash with the commands of this guide, on a Windows PC that did not have the PlatformIO command-line tool
- that the bootloader of this XIAO shows only a serial port, and that
flash.pythen loads the firmware through serial DFU - that the USB power of a wired XIAO goes backward into the Lyra
- that the radio keeps its identity through the flash
- that, after the flash, a frame that breaks off no longer blocks the link: the next
APP_STARTafter 0.5 s of silence gets its reply
The other facts are the findings of one bench. A second device can give other results:
- The RK3506 register map in
uart1-mux.py. This covers the two register blocks, the UART1 signal ids, the matrix mode, and the fact thatgpio0-0/gpio0-1are header GP4 and GP5. The author probed it live on one board. It is not from a reference manual. - That the Calculinux kernel permits those
/dev/memwrites, and that the mux oneshot really runs at each boot. - The header pin numbers 6, 7, 8, and 36 for GP4, GP5, GND, and 3V3. The numbering on the Pico side is correct. The wiring of the carrier is an observation from the bench.
- The pin order of the MX1.25 socket. It comes from the wire colours of Luckfox's own cable. For this reason, Step 3 tells you to check pin 1 with a meter.
- The 5 V path through the USB-C of the Lyra. The author read the mechanism from the mainboard schematic and from two forum measurements on the Pico. Nobody measured it with a Lyra. Use the warning as the safe assumption that it is.
- The facts of Calculinux that the scripts assert. These are the reduced Python and the
package names that repair it, which overlays are writable, that
/tmpis a small RAM tmpfs, the race of the dongle withiwdat cold boot, the occasional 404 of the opkg feed, that the stock Terminus console font is installed, and thatkbdbringssetvtrgb. - That this board has no RTC. Only the script says this. The clock-sync phase follows from it.
- The XIAO bootloader that showed no mass storage, and the S140 v7 /
0x27000requirement. Public references agree, but none is a primary source. - Whether the firmware patch still applies at the HEAD of MeshCore
dev. Only the pinned commit is known to build. - The framing of MeshCore's serial protocol around the raw
APP_STARTcheck in Step 16. The command byte and the reply code come from the protocol documentation. The framing bytes do not. - The 512-glyph limit of the framebuffer console. It is well known. The author accepted it and did not measure it again.
If you build this machine and a result is different from this guide, please report it. A second board is the only way to learn which parts are about the hardware in general and which parts are about the one unit that the author used.