Skip to content

Instantly share code, notes, and snippets.

@tedwardd
Created August 11, 2026 13:19
Show Gist options
  • Select an option

  • Save tedwardd/83ed57ba4f09bd59e53a03300a25eed4 to your computer and use it in GitHub Desktop.

Select an option

Save tedwardd/83ed57ba4f09bd59e53a03300a25eed4 to your computer and use it in GitHub Desktop.
MeshToad (E22-900M30S + CH341): observations and questions from a userspace Reticulum driver bring-up

MeshToad (E22-900M30S + CH341): observations and questions

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.

Setup these observations come from

  • 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, SetTxParams at 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.

What we confirmed working

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 and GetDeviceErrors reads 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).

1. DIO1 / D6 — resolved, no action needed

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.

2. RXen (D1) — unresolved

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.

3. Receive noise floor — remeasured, and much less alarming

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.

Baseline at the operating frequency

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.

Sweep, 860-960 MHz

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.

Caveats on all of the above

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.

4. Smaller questions

  • 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 SetTxParams at 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.

What would help most

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment