===== 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 ''bf782a78c5abf63e4fef54c54ed8d13134f9c9c041bff3a56114eb34c3530724''. Two dumps, identical. | | Readout protection | Off. Option bytes at 0x1FFFF800 read ''ffff5aa5''; the RDP byte is 0xA5. | | 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 [[#winbond_flash|Winbond flash]] below. | 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**: a 4 KB bootloader. Reset vector at 0x08000D9C. //In the code.// * **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 ''PRGAMOK''. If it finds it, the bootloader erases the application area, reads a new image from Winbond address 0x032000, XORs each 32-bit word with 0x4E4E4E4E, programs the result into flash, and then clears the flag. This is almost certainly how a firmware update is installed. //In the code (flow read); not exercised.// On my unit the staging area at 0x032000 is all 0xFF, so no update image is staged. Startup of the application runs through 0x080304D4, then SystemInit (0x08001200, //inferred//), then the C runtime at 0x08030C34. That enables the FPU and calls ''main'' at 0x0801CD7C. ==== Main loop ==== ''main'' is not a set of one-time init calls followed by sleep. It is a cooperative loop. Each pass writes a step number to RAM 0x20007E41, runs one task, and clears the soft-watchdog counter (more on that under Power and resets). The tasks that matter so far: ^ 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/Wi-Fi module open and close, driven by the enable flags. | | 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/Wi-Fi module (channel 4). Receive ring 1500 bytes. | In the code. Module traffic observed over SPP. | | 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 ''+++'', cleared by one that starts with ''---''. While it is set, any frame that is not a known command and is at least 6 bytes long goes out to the UM980 unchanged. //Observed.// A text command sent this way got a reply from the UM980, and RTCM replayed through this path decoded on the UM980's COM2 status log. The flag is RAM only, so a reboot clears it. * **Flag B** (0x20007DE7) is ''GNSS.PASS''. When it is ON, every frame goes to the UM980, including frames that are known JMS commands. //In the code. The chain from the JMS SET handler to this flag is confirmed. Not yet tested live.// * **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 (''+SPPDATA=len,...'') to the UM980. That path never fires on this unit, because the module runs in transparent mode and delivers raw bytes. //Observed: raw bytes arrived with no header.// So the vendor's own "phone internet" corrections path appears not to work on this hardware. The route that works is ''+++'' followed by raw data. //Observed for RTCM at modest rates; see the limits above.// ==== Bluetooth module ==== At boot the MCU runs a short AT sequence on UART4: ''AT+VER'', ''AT+ADDR'', ''AT+STAT'', ''AT+NAME'', ''AT+ROLE=1'', ''AT+TPMODE=1''. The reply to ''AT+VER'' decides the receive mode. ''BT986'' gives mode 1, ''BW236'' gives mode 2, ''BW246'' gives mode 3. This unit reports ''FSC-BW246,V5.1.3'', so it runs in mode 3. //In the code and observed in the boot log.// 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 ''AT+WFSEND=0,''. //In the code.// ==== Corrections: what the limits did to the test ==== These are numbers from my own tests, not from the code. //Observed.// * 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, while the same corrections over RS232 produced RTK float. That points at the UM980 side rather than the MCU link. See lead A under Open questions. ===== 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 ''A5 5A''. The probe read on the laptop at Winbond 0x000000 returned ''00 00 AA 55 05 43 4D 49 4F 54'', which is the APN record ''CMIOT''. I do not yet know how these two fit together. The full read will settle it. ===== 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: ''(adc - 325) * 10 / 9'', clamped to 0 to 100, where adc is the battery sense reading on PC3. //In the code.// | | 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" in the line is only the section name. The field does not say whether the unit is a base. ==== Base and rover ==== The mode is RAM flag A (0x20007E23): 0 = rover, 1 = base. Setting ''GNSS.MODE'' changes it and writes index 0x6CD. The MCU then sends its GNSS setup to the UM980 again. The UM980 keeps its own base/rover configuration, so the MCU's RAM flag is only what the firmware itself remembers about the mode. //In the code, inferred for the UM980 side.// ===== The #JMS command parser ===== The parser at 0x0801C6A0 handles the ''#JMS'' frames. The frame layout is: #JMS,GET,GNSS.MODE*xx #JMS,SET,GNSS.MODE,MOVE*xx Each frame is under 201 bytes. The checksum is the XOR of the body, written as two uppercase hex digits. Replies start with ''@JMS,''. A ''HEART'' command sets a link-alive byte (0x20007D9A), which is what ''$JMS,LINK'' prints. Group dispatch covers BASE, GPRS, GNSS, BLE, WIFI, and RADIO. Anything else returns CMD ERR. //In the code.// The same parser reads ''X11-CONFIG.txt'' from the TF card (see below). The line syntax is identical. 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 ''X11-CONFIG.txt'' in the root directory. It reads the file in 100-byte pieces and sends each piece through the #JMS parser. It runs once per insertion. //In the code.// * The same firmware writes GGA logs to the card as ''GGA-*.txt'' files. The writer is at 0x0800E7D0 and is driven from the main loop. What data goes into the log I have not traced. //In the code.// Because the card path uses the same parser and the same setters, a correctly checksummed ''#JMS,SET'' line in ''X11-CONFIG.txt'' would persist exactly like one sent over Bluetooth. //Inferred. Not run.// ===== 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 ^ | | TFGPS | Set JT808 platform parameters, fields as A C I K N P S T X p t x y | Fields not decoded | | | - | Report the SPTF values | - | | | GDGPS | Set. Fields E N P X Y Z | Purpose not decoded | | | YCGPS | Set NTRIP client settings. ''*r:0'' selects NTRIP, ''*r:1'' selects QXSDK | Partly decoded | | | - | Report the SPYC values | - | | , | - | Information report | Not decoded | | , | - | Reboot ("Reset OK!") | Reachable by code placement; not run | | | - | Factory reset | **Destructive.** See below | | '' calls 0x0800D67E. The routine: 1. Writes ''AA 55'' to bytes 0 and 1 of the config block. 2. Zeroes config indexes 65 to 2039. 3. Writes the defaults. The APN becomes ''CMNET''. The JT808 identity becomes placeholders (HT-600, 13800180001, plate "Yue A88888"). GNSS.MODE becomes MOVE. The enable flags, including GNSS.ENABLE and BLE.ENABLE, become 0. 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. //Inferred.// Second, the APN ''CMIOT'' on this unit is written by a different reset path, one that the binary factory-test commands reach. So the unit was probably provisioned on a bench through that path. //Inferred.// Do not send '''' without reading the config block first and keeping a copy. ===== 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 ''LOW POWER :%d'' and resets through AIRCR SYSRESETREQ at 0x0802BA9E. //In the code.// With the battery pack disconnected, PC3 reads about 0.55 V, and this fires about 9 seconds after power-up. //Observed on the bench, 2026-11-06.// * **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 ''Soft Watch Dog Time Out!'' and resets through 0x08015A00. Idle peak was 22 ticks. //In the code; limit and idle peak observed.// This is the best candidate for the unexplained restarts, but I have not proved it. * **Remote reset commands.** '''' and the JT808 server command both reset the unit. See Text commands and Factory reset. 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's read routine at 0x0801E442 uses only opcodes 0x03 (read) and 0x05 (status). The write and erase routines exist in the image (opcodes 0x06, 0x02, and 0x20), and factory reset uses the 0x20 sector erase. //In the code.// 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, ''PRGAMOK'' when an update is pending | | 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 ''AA 55'' | | 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 ''interfacemode com1 compass compass on'' (six sites) or ''interfacemode com1 auto auto on'' (three sites). If "compass" makes COM1 ignore RTCM, that would explain why Bluetooth corrections decode on the UM980 but never change the solution. Not tested. Test by sending the auto form from the COM2 terminal, then repeating the Bluetooth correction test. * **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 ''A5 5A''. The laptop probe shows ''00 00 AA 55'' at offset 0. Needs the full read. * **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: * ''fwtool.py'' - disassembly walker, cross-references, callers, string search, and literal pool resolution. Subcommands: ''dis'', ''xref'', ''callers'', ''findstr'', ''str'', ''hex'', ''word'', ''func''. * ''fw1.dis'' - the full listing, generated with objdump. * ''swd_capture.py'' - the one-halt capture chain for the Winbond config blocks and probes. Written for the low-power shutdown window. * ''winbond_dump.py'' - the original read-only Winbond reader, which calls the firmware's routine over SWD. * ''ocd_peek.py'' and ''wd_watch.py'' - read-only RAM peeks for the Bluetooth flags, UART ring, and watchdog counter. The JMS and RTCM test tools (''spp2tcp.py'', ''rtcm_relay.py'', ''rtcm_replay.py'') are described on the Software page.