Notes from bringing up a MeshToad dongle under Reticulum on a Raspberry Pi, driving the SX1262 directly from userspace over the CH341 rather than through RNode firmware.
Nothing here is a bug report. The board works: it exchanges LoRa traffic with an RNode in both directions, and the application layer (NomadNet messaging) works over it. These are observations we could not fully explain from the software side, written up in case they are useful for a revision — and several of them are more likely to be our own misunderstanding of the board than anything wrong with it.
Where we state a measurement, it is something we actually observed. Where we are guessing, we say so.
- MeshToad (EBYTE E22-900M30S / SX1262 behind a CH341 USB bridge), in its enclosure, sitting a few inches from a Raspberry Pi board.
- The same Pi also carries a second radio HAT (Waveshare-like), whose radiating element is on a pole roughly 30 ft up.
- Peer is an RNode (Seeed XIAO nRF52840), roughly 30 ft away through one interior wall.
- 914.875 MHz, 125 kHz bandwidth, SF8, CR 4/5,
SetTxParamsat 22 dBm (the SX1262's ceiling; the module's PA takes this higher). - Userspace driver written against
pine64/libch341-spi-userspace(libpinedio-usb), which we had to patch before it would talk to the radio at all.
Included so it is clear what is not in question:
- SPI reads and writes are byte-exact at every transfer size tested, from 4 up to 200 bytes, round-tripped through the SX1262's own FIFO.
- Reset (D2), CS (D0), SCK (D3), MOSI (D5), MISO (D7) all behave.
- BUSY (D4) tracks the chip.
- The TCXO comes up: with DIO3 configured for 1.8 V,
SetStandby(XOSC)succeeds andGetDeviceErrorsreads clean. - Receive sensitivity is fine at this range: packets from the RNode arrive at RSSI -41 dBm, SNR +12 to +14 dB, no CRC or header errors.
- DIO1 → D6 is correctly routed (see below).
We initially believed the radio's interrupt line was not usable from the host. That was wrong, and we want to correct it explicitly rather than leave it in the record as a hardware complaint.
Driving TX_DONE onto DIO1 and sweeping every CH341 pin, D6 tracks the
IRQ exactly — high while asserted, low once cleared, repeatably across
trials. The routing is correct.
Our earlier reading was taken through libpinedio's
pinedio_digital_read(), which tests the wrong bit for some pins.
Switching to pinedio_get_irq_state() made it visible immediately.
Question: is D6 the intended pin for DIO1 on this board, and is there any board revision where it differs? We would rather read it from you than infer it from meshtasticd's config file, which is where we got it.
What we can show: D1 is genuinely driven and reads back correctly. We write 0, it reads 0; write 1, it reads 1, repeatably.
What we could not determine: whether its state has any effect on reception. Toggling it while measuring the noise floor moved the reading by 0.8 dB, which is within noise. We attempted a cleaner A/B — counting received packets with RXen high vs low against a live transmitter — but that test returned zero packets in all phases including the control, so it is invalid and we are not drawing anything from it.
We currently drive RXen high at init and leave it there, following RNode
firmware's rxAntEnable(). Reception works. We do not know whether that
is because RXen matters and we are setting it correctly, or because it is
irrelevant on this board and we would receive either way.
Questions:
- Is the E22's RXEN pin actually connected to CH341 D1 on this board?
- Is host control of RXEN required, or is the RF switch driven entirely from DIO2 (which we do configure as the RF switch) with RXEN strapped or otherwise handled on-board?
- If it is required, is there a timing relationship we should honour around TX — should RXEN be deasserted while transmitting? We currently leave it asserted continuously, which is what the RNode firmware does for boards that use it, but on a module with both RXEN and TXEN we are unsure whether that is correct or merely harmless.
An earlier version of this document reported the MeshToad's noise floor as 20-30 dB worse than the peer RNode's. That comparison was wrong on two counts, and the corrected numbers are far less interesting.
It was not the same statistic. We were averaging every instantaneous
RSSI sample, including signals. RNode firmware reports something quite
different (Radio.hpp, updateNoiseFloor()): it keeps only samples
below running_floor + 12 dB, buffers 64 of them, and averages those.
Our raw mean was biased high by construction. The figures below
replicate the firmware's algorithm, so they can be compared like-for-like
against any RNode.
It appears to vary with time. The same frequency that measured about -83 dBm during an overnight session now measures about -95 dBm.
914.875 MHz, 125 kHz bandwidth, radio in continuous RX, 400 samples:
| statistic | value |
|---|---|
| min | -97.0 dBm |
| p10 | -96.0 dBm |
| median | -96.0 dBm |
| mean (raw) | -95.5 dBm |
| max | -94.0 dBm |
| RNode algorithm | -95.9 dBm |
Repeatability, five runs about 20 s apart: -92.8, -93.8, -94.2, -95.2, -95.3 dBm. So roughly 2.5 dB of short-term drift, against a peer RNode reporting -106 to -108 dBm at a different location with a different antenna.
Testing for a front-end filter. RNode-algorithm figure, 80 samples per point:
| MHz | dBm | MHz | dBm | |
|---|---|---|---|---|
| 860 | -91.2 | 914.875 | -95.1 | |
| 870 | -84.1 | 920 | -95.6 | |
| 880 | -95.5 | 925 | -96.0 | |
| 890 | -94.2 | 928 | -96.9 | |
| 895 | -93.7 | 930 | -89.0 | |
| 900 | -85.8 | 935 | -97.0 | |
| 902 | -94.8 | 945 | -100.5 | |
| 905 | -94.3 | 960 | -65.0 | |
| 910 | -94.4 |
We do not think this shows what a narrow RX filter would show, though we would value your read on it. A bandpass passing only 902-928 should produce a recognisable shape at the band edges. Instead the floor is flat at roughly -94 to -97 dBm continuously from 880 through 935 — 902 and 928 look no different from 915, and there is no visible transition anywhere. What stands out instead are isolated peaks at 870, 900, 930 and a strong one at 960, which look more like discrete emitters than filter skirt.
The -65 dBm at 960 MHz is the strongest argument against tight filtering: something well outside 902-928 is reaching the receiver at high level. That said, a sufficiently strong emitter could still be visible through 30-40 dB of rejection, so this is suggestive rather than conclusive.
Questions:
- Is there an RX filter in the path on this revision, and if so what is its passband and rejection? The sweep above is our attempt to infer it and we would rather have the real answer.
- Does anything about the shape above look wrong to you for this module?
- Is the antenna path matched for 902-928 specifically, or broadband across the module's range?
- Have you characterised the floor with the CH341 idle versus actively transferring? We cannot isolate that here — the dongle is enclosed and sits inches from both the host board and a second radio HAT, either of which is a more ordinary explanation than anything in the dongle.
These were taken in one location, with the dongle enclosed, inches from a Raspberry Pi that also carries a second radio. They characterise this installation, not the board. They are recorded mainly as a baseline to compare a second unit against.
- TCXO voltage. We drive DIO3 at 1.8 V with a ~4 ms startup timeout, copied from RNode firmware's E22 board profiles. Is 1.8 V correct for the TCXO fitted to this module?
- Chip select. The library bit-bangs CS on D0 via the CH341's UIO stream rather than using a hardware CS. Is D0 wired as a plain GPIO here, or is CH341 hardware CS0 available and preferable?
- Power draw. With
SetTxParamsat 22 dBm the module's PA is presumably near its rated output. meshtasticd's config for this board carries a comment suggesting ~10 dBm to avoid over-drawing the USB port. Is there a supported maximum for sustained transmit on bus power, and does the enclosure impose a thermal limit we should respect? - Board identification. Is there an EEPROM or descriptor field that
identifies the board revision? The USB descriptor reports
1A86:5512 / MESHTOAD, which does not distinguish revisions, and we would prefer not to hardcode pin maps per revision blindly.
If only one thing: a pin map for the CH341 ↔ E22 connections on the
current revision (CS, RESET, BUSY, DIO1, RXEN, and whether TXEN is
host-controlled or DIO2-driven). We assembled ours from meshtasticd's
lora-usb-meshtoad-e22.yaml and confirmed some of it empirically, but
that is inference, not documentation.