jms_x11:firmware
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| jms_x11:firmware [2026/11/06 13:04] – created millerjs | jms_x11:firmware [2026/11/07 06:48] (current) – [Jump Star GNSS X11 Firmware Analysis] millerjs | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ===== Jump Star GNSS X11 Firmware Analysis ===== | ===== Jump Star GNSS X11 Firmware Analysis ===== | ||
| + | |||
| + | [[..jms_x11|Main X11 page]] | ||
| + | |||
| + | This page is what I have worked out from the MCU firmware so far. Almost all of it comes from reading the GD32F303 flash dump in a disassembler. A smaller part comes from live tests over Bluetooth and from a few helper scripts. Where it matters I say which kind of evidence a claim rests on: | ||
| + | |||
| + | * **In the code** - I read it in the disassembly. The address is given. | ||
| + | * **Observed** - I saw it happen on the live unit. | ||
| + | * **Inferred** - my reading of the code. I have not tested it. | ||
| + | |||
| + | Treat the addresses as a map, not as gospel. Some were read by eye and some are still being checked. | ||
| + | |||
| + | Status as of 2026-11-06: the firmware is read and mostly mapped. The Winbond flash has not been read yet. The first read of the config block is scripted and waiting for the unit to be powered. | ||
| + | |||
| + | ===== The dump ===== | ||
| + | |||
| + | ^ Item ^ Value ^ | ||
| + | | Chip | GigaDevice GD32F303VCT6 | | ||
| + | | Flash dump | 262,144 bytes (256 KB). sha256 '' | ||
| + | | Readout protection | Off. Option bytes at 0x1FFFF800 read '' | ||
| + | | Image size | The last non-0xFF byte is at 0x31756. About 197 KB of the 256 KB is used. | | ||
| + | | External flash | Winbond W25Q128 (16 MB). The firmware reads it through its own routine. See [[# | ||
| + | |||
| + | I dumped the flash over SWD with an RP2040 debug probe. The read was done with the core halted, and nothing was written. | ||
| + | |||
| + | ===== Boot and application layout ===== | ||
| + | |||
| + | Internal flash holds two images: | ||
| + | |||
| + | * **0x08000000 - 0x08000FFF**: | ||
| + | * **0x08001000 onward**: the application. Initial stack pointer 0x2000AE70, reset handler 0x080304D4. //In the code.// | ||
| + | |||
| + | The bootloader looks at Winbond address 0x030000 for the ASCII string '' | ||
| + | |||
| + | Startup of the application runs through 0x080304D4, then SystemInit (0x08001200, | ||
| + | |||
| + | ==== Main loop ==== | ||
| + | |||
| + | '' | ||
| + | |||
| + | ^ Step ^ Function ^ What I think it does ^ | ||
| + | | 8 | 0x08018590 | Parser B. Reads text commands that arrive on the RS232 port (SP..., CK..., #JMS). | | ||
| + | | 9 | 0x08027C26 | Bluetooth/ | ||
| + | | 20 | 0x0800405C | Reads the UM980 receive buffer and drives the GGA logger to the TF card. | | ||
| + | | 12, 15, 21, 22 | 0x08015A36, 0x08014D8C, 0x0800C024, 0x0800C08A | Run only under certain flag values. Not identified yet. | | ||
| + | |||
| + | Full table of the other steps is in my working notes, not here. | ||
| + | |||
| + | ===== Serial ports ===== | ||
| + | |||
| + | The MCU has five USART/UART peripherals. The init order gives them channel numbers 0 to 4 (channel 0 = USART0 ... channel 4 = UART4). The UM980 and Bluetooth roles below are read from the code and the send-call sites, and the Bluetooth-to-UM980 path has been confirmed live. | ||
| + | |||
| + | ^ Peripheral | Role | Evidence ^ | ||
| + | | USART0 (IRQ 37) | MCU RS232 port, 9600 baud, on the 10 pin connector. Text commands, #JMS commands, and the binary factory-test frames all come in here. The boot log comes out here. | In the code (ISR 0x08025DAF). Boot log observed. | | ||
| + | | UART3 (IRQ 52) | UM980 link (channel 3). Receive ring 2500 bytes. | In the code: all UM980 commands go out on channel 3. Forwarding from Bluetooth to channel 3 observed live. | | ||
| + | | UART4 (IRQ 53) | FSC-BW246 Bluetooth/ | ||
| + | | USART1, USART2 | Not identified. Possibly the modem or radio. | Not yet. | | ||
| + | |||
| + | Receive handling on UART4 has limits that shape everything above it: | ||
| + | |||
| + | * The ISR only stores bytes. The frame handler runs when 10 ms have passed with no new byte. //In the code.// | ||
| + | * The frame handler takes at most 500 bytes per frame. It splits longer bursts at arbitrary byte positions, not at RTCM message boundaries. //In the code.// | ||
| + | * Frames under 6 bytes are dropped. //In the code.// | ||
| + | * A burst longer than 1500 bytes with no 10 ms gap overwrites its own start. //In the code, and the overrun was observed in a live buffer dump.// | ||
| + | |||
| + | ===== Bluetooth to UM980 forwarding ===== | ||
| + | |||
| + | The frame handler at 0x08026BD0 decides what happens to each Bluetooth frame. Three RAM flags control it: | ||
| + | |||
| + | * **Flag C** (0x20007DA0). Set by a frame that starts with '' | ||
| + | * **Flag B** (0x20007DE7) is '' | ||
| + | * **Flag A** (0x20007E23) is the base/rover flag. In rover mode the code has a path that sends the payload of a module-framed data packet ('' | ||
| + | |||
| + | So the vendor' | ||
| + | |||
| + | ==== Bluetooth module ==== | ||
| + | |||
| + | At boot the MCU runs a short AT sequence on UART4: '' | ||
| + | |||
| + | The firmware contains no AT command that configures Wi-Fi. There is no SSID, password, or AP/STA setting anywhere in the image. The only Wi-Fi string is '' | ||
| + | |||
| + | ==== Corrections: | ||
| + | |||
| + | These are numbers from my own tests, not from the code. // | ||
| + | |||
| + | * A one-message-per-write relay at 20 messages per second lost between 8% and 71% of messages, depending on type. | ||
| + | * At 0.15 s spacing about 95% of MSM messages arrived. | ||
| + | * A live MSM7 stream, paced at one message per write, decoded between 32% and 80% of messages per type. The relay fell behind and the backlog grew. | ||
| + | |||
| + | Those numbers are why the Bluetooth route is not yet a reliable correction path. The UM980 solution also stayed at quality 1 with Bluetooth corrections, | ||
| + | |||
| + | ===== Config store ===== | ||
| + | |||
| + | Everything that survives a power cycle lives in one 2048-byte block in the Winbond. There are two copies: | ||
| + | |||
| + | * **Primary** at Winbond 0x000000. | ||
| + | * **Backup** at Winbond 0x390000. | ||
| + | |||
| + | Every setter writes both copies. At boot the loader at 0x0800D14C to 0x0800D67C copies a fixed set of indexes into RAM variables. The accessors are: | ||
| + | |||
| + | ^ Function | Address | What it does ^ | ||
| + | | read byte | 0x0800CF78 | Read one byte at an index | | ||
| + | | write byte | 0x0800CDDC | Write one byte, both copies | | ||
| + | | write 16 bit | 0x0800CE50 | Big-endian 16-bit write | | ||
| + | | write n bytes | 0x0800CED4 | Write a run of bytes | | ||
| + | | read n bytes | 0x0800D08E | Read a run of bytes | | ||
| + | |||
| + | Some settings and where they live. Index numbers are byte offsets into the 2048-byte block: | ||
| + | |||
| + | ^ Key | Index | RAM variable | Note ^ | ||
| + | | GNSS.MODE | 0x6CD | 0x20007E23 | 0 = MOVE (rover), 1 = BASE | | ||
| + | | GNSS.ENABLE | 0x48D | 0x20007E29 | | | ||
| + | | GPRS.ENABLE | 0x6FA | 0x20007E25 | | | ||
| + | | BLE.ENABLE | 0x786 | 0x20007D9B | | | ||
| + | | WIFI.ENABLE | 0x79B | 0x20007D9C | | | ||
| + | | WIFI.NAME | 0x79C | (string) | | | ||
| + | | RADIO.ENABLE | 0x7BB | 0x20007E19 | | | ||
| + | | Backup server record | 46 | | | | ||
| + | | Main server record | 55 | | | | ||
| + | | NTRIP record | 1300 | | | | ||
| + | |||
| + | The header is the one open discrepancy in this section. The reader scans for '' | ||
| + | |||
| + | ===== The $JMS,BASE status line ===== | ||
| + | |||
| + | The unit sends a status line about once a second. In the code it is built at 0x080026EC. The fields I have decoded: | ||
| + | |||
| + | ^ # | Value | What it is ^ | ||
| + | | 1 | V1.03-250220 | Protocol version. Same as the BASE.PROTOCOL key. | | ||
| + | | 2 | 13900063393 | Serial number. | | ||
| + | | 3 | FY-X11-GGA-V0.30-251013 | Software version. | | ||
| + | | 4 | X11-V5-20241228 | Hardware version. | | ||
| + | | 5 | 24 | Board temperature in degrees C, one byte from RAM. | | ||
| + | | 6 | 42 | Battery percent: '' | ||
| + | | 7 | 0 | **1 when the external DC input is at or above 7.0 V.** The sense is on PC4 through an 11:1 divider. This settles the field the wiki page left open. //In the code.// | | ||
| + | | 8 | 0 | Corrections link flag. Set to 1 when request id 50 returns 1, and cleared by the keep-alive monitor. Which peer it refers to is not identified yet. //In the code, partly inferred.// | | ||
| + | | 9 to 13 | OFF ON ON OFF OFF | GPRS, GNSS, BLE, WIFI, and RADIO enable flags, in that order. //In the code, matches the key dump.// | | ||
| + | |||
| + | " | ||
| + | |||
| + | ==== Base and rover ==== | ||
| + | |||
| + | The mode is RAM flag A (0x20007E23): | ||
| + | |||
| + | ===== The #JMS command parser ===== | ||
| + | |||
| + | The parser at 0x0801C6A0 handles the ''# | ||
| + | |||
| + | < | ||
| + | # | ||
| + | # | ||
| + | </ | ||
| + | |||
| + | Each frame is under 201 bytes. The checksum is the XOR of the body, written as two uppercase hex digits. Replies start with '' | ||
| + | |||
| + | The same parser reads '' | ||
| + | |||
| + | Reply routing is not settled. For the TF card path my notes say replies go out on USART0. Over Bluetooth and RS232 I have seen replies come back on the link the command arrived on. See Open questions. | ||
| + | |||
| + | ===== TF card slot ===== | ||
| + | |||
| + | The datasheet says the slot is reserved. The firmware uses it, in SPI mode, with FatFs. | ||
| + | |||
| + | * Hardware: SPI3. SCK PB3, MISO PB4, MOSI PB5, chip select PA15, card detect PC7 (low = card present). Mode 3. The prescaler starts at /256 for card init and then goes to /64 or /2. //In the code.// | ||
| + | * On card insertion, after about two seconds, the firmware mounts the card and opens '' | ||
| + | * The same firmware writes GGA logs to the card as '' | ||
| + | |||
| + | Because the card path uses the same parser and the same setters, a correctly checksummed ''# | ||
| + | |||
| + | ===== Text commands ===== | ||
| + | |||
| + | The firmware has a family of text commands. They are reachable from the RS232 port, and the code placement suggests from Bluetooth too. //Reachable over Bluetooth is in the code; not tested live.// | ||
| + | |||
| + | ^ Prefix | Password (plain text in image) | Role | State of decoding ^ | ||
| + | | < | ||
| + | | < | ||
| + | | < | ||
| + | | < | ||
| + | | < | ||
| + | | < | ||
| + | | <INS: | - | Not decoded | - | | ||
| + | | < | ||
| + | | < | ||
| + | | < | ||
| + | | <EXT | - | Firmware update entry | Do not send | | ||
| + | | < | ||
| + | |||
| + | The binary frames on RS232 (a '' | ||
| + | |||
| + | ===== Factory reset ===== | ||
| + | |||
| + | ''< | ||
| + | |||
| + | 1. Writes '' | ||
| + | 2. Zeroes config indexes 65 to 2039. | ||
| + | 3. Writes the defaults. The APN becomes '' | ||
| + | 4. Writes the block to both copies. | ||
| + | 5. Erases Winbond 4 KB sectors at 0x002000, 0x00A000 to 0x011FFF, 0x016000 to 0x01FFFF, and 0x340000 to 0x344FFF (the 0x343000 sector is not in the list). | ||
| + | |||
| + | It does not touch the boot flag at 0x030000 or the staging area at 0x032000, so a reset cannot break the boot path. | ||
| + | |||
| + | Two consequences I have not tested. First, with GNSS.ENABLE and BLE.ENABLE both 0 after a reset, the unit may come up with no Bluetooth and no GNSS output, and would need an RS232 connection to recover. // | ||
| + | |||
| + | Do not send ''< | ||
| + | |||
| + | ===== Power and resets ===== | ||
| + | |||
| + | Three things can restart or power off the MCU. I can name the mechanism for each, but I have not caught all of them in the act. | ||
| + | |||
| + | * **Low battery.** The firmware reads the battery sense on PC3. If it is below 3.3 V, the code prints '' | ||
| + | * **Soft watchdog.** A 1 ms tick (TIM2) increments a counter at 0x20007D44. The main loop clears it. When the counter passes the limit at 0x20000922 (10000 on my unit), the firmware prints '' | ||
| + | * **Remote reset commands.** ''< | ||
| + | |||
| + | At boot the firmware prints its reset cause (POR/PDR, software, low-power, PIN, independent watchdog, window watchdog). It clears the RCC reset flags itself, so the cause cannot be read back over SWD after boot. Capture the RS232 port during a test to see the message. //In the code.// | ||
| + | |||
| + | ===== Winbond flash ===== | ||
| + | |||
| + | The external flash is a W25Q128 on SPI1 (PA5, PA6, PA7), with chip select PA4 driven by software. The firmware' | ||
| + | |||
| + | Known regions, from the code: | ||
| + | |||
| + | ^ Address | Contents | | ||
| + | | 0x000000 | Config block, primary copy (2 KB) | | ||
| + | | 0x002000 | Data store, erased by factory reset. Contents unknown. | | ||
| + | | 0x030000 | Boot flag, '' | ||
| + | | 0x032000 | Staged update image, XOR 0x4E4E4E4E | | ||
| + | | 0x00A000 to 0x011FFF | Data store, erased by factory reset. Contents unknown. | | ||
| + | | 0x016000 to 0x01FFFF | Data store, erased by factory reset. Contents unknown. | | ||
| + | | 0x390000 | Config block, backup copy (2 KB) | | ||
| + | | 0x395000 | Small record starting '' | ||
| + | | 0x340000 to 0x344FFF | Data store, erased by factory reset. Contents unknown. | | ||
| + | |||
| + | Observed on my unit: 0x032000 is all 0xFF, so there is no staged update. | ||
| + | |||
| + | The full image has not been read. A capture is scripted (swd_capture.py) to read the two config copies and a few probe ranges in one halted session, because the unit's low-power shutdown gives a window of about nine seconds. The full 16 MB read is not authorised yet. | ||
| + | |||
| + | ===== Open questions ===== | ||
| + | |||
| + | These are the things I could not settle from the code, and the things I think are most useful to check next: | ||
| + | |||
| + | * **Lead A: interfacemode.** Nine call sites set UM980 COM1 to either '' | ||
| + | * **Lead C: the cellular NTRIP client.** The MCU has its own NTRIP and JT808 clients over the modem. They write received corrections to the UM980 through the same kind of path as Bluetooth. Worth comparing that path with the Bluetooth one. | ||
| + | * **GNSS.PASS (flag B).** The chain from the SET handler to the flag is confirmed. The live behaviour is not. | ||
| + | * **Field 8 recipient.** The corrections-link flag is set by request id 50 through the port table at 0x20000890. The recipient UART is not identified. | ||
| + | * **Config header.** The disassembly says '' | ||
| + | * **Factory reset sector list.** One of my notes lists a sector set at 0xC000. The other lists 0x340000 to 0x344FFF. The second matches the code I read most recently. Check before relying on either. | ||
| + | * **Reply routing.** My notes disagree on whether #JMS replies go out on USART0 or on the link the command came in on. Test with one command over each link. | ||
| + | * **Undecoded.** The field tables for SPTF, SPGD, CKINFO, INS, and SIXENTS. The 01, 0A, 0D, and 37 arms of the binary test protocol. The driver pointer table at 0x20000208 (open +88, close +96, poll +100) is in a compressed blob in flash, so its values have to be read from RAM. | ||
| + | * **Startup flag 0x2000092C.** Its initial value is inside the compressed startup blob. Read it from RAM. | ||
| + | |||
| + | ===== Tools ===== | ||
| + | |||
| + | The analysis tools live with the firmware dump in the project directory: | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | The JMS and RTCM test tools ('' | ||
| + | |||
jms_x11/firmware.1793970277.txt.gz · Last modified: by millerjs
