• Hello ES! We could use some help to get us past the finish line on building the new knowledgebase for the forum.
    Can you donate? Please see our fundraising page. Thank you!

Reverse engineering UART protocol of XCM controllers

46eh01

New here
Joined
Oct 23, 2025
Messages
8
Location
Germany
Hi guys,

i'm trying to reverse engineer the UART protocol of an XCM-K based controller.
I have connected an Arduino to the Serial port between Display and Controller for sniffing the data.

Till now (just started about 20mins ago), this is the communication of the display to the controller from turning on and then turning off:

Code:
FF
01 03 F9 5B 03 1B 03 00 32 80 00 00 2E 38 1F
01 03 FA 58 03 1C 03 00 32 80 00 00 2E 38 18
01 03 FB 59 03 81 03 00 32 80 00 00 2E 38 85
01 03 FC 06 03 2A 03 00 32 80 00 00 2E 38 76
01 03 FD 07 03 7F 03 00 32 80 00 00 2E 38 23
01 03 FE 04 03 80 03 00 32 80 00 00 2E 38 DC
01 03 FF 05 03 05 03 00 32 80 00 00 2E 38 59
01 03 00 82 03 7E 03 00 32 80 00 00 2E 38 5A
01 03 01 03 03 03 03 00 32 80 00 00 2E 38 A7
01 03 02 80 03 04 03 00 32 80 00 00 2E 38 20
01 03 03 81 03 29 03 00 32 80 00 00 2E 38 0D
01 03 04 6E 03 32 03 00 32 80 00 00 2E 38 FE
01 03 05 6F 03 27 03 00 32 80 00 00 2E 38 EB
01 03 06 6C 03 28 03 00 32 80 00 00 2E 38 E4
01 03 07 6D 03 2D 03 00 32 80 00 00 2E 38 E1
01 03 08 0A 03 26 03 00 32 80 00 00 2E 38 82
01 03 09 0B 03 2B 03 00 32 80 00 00 2E 38 8F
01 03 0A 08 03 2C 03 00 32 80 00 00 2E 38 88
01 03 0B 09 03 51 03 00 32 80 00 00 2E 38 F5
01 03 0C 36 03 7A 03 00 32 80 00 00 2E 38 E6
01 03 0D 37 03 4F 03 00 32 80 00 00 2E 38 D3
01 03 0E 34 03 50 03 00 32 80 00 00 2E 38 CC
01 03 0F 35 03 55 03 00 32 88 00 00 2E 38 C1
01 03 10 32 03 4E 03 00 32 88 00 00 2E 38 C2
01 03 11 33 03 53 03 00 32 88 00 00 2E 38 DF
01 03 12 30 03 54 03 00 32 88 00 00 2E 38 D8
01 03 13 31 03 79 03 00 32 88 00 00 2E 38 F5
01 03 14 5E 03 82 03 00 32 88 00 00 2E 38 66
01 03 15 5F 03 77 03 00 32 80 00 00 2E 38 9B
01 03 16 5C 03 78 03 00 32 80 00 00 2E 38 94
01 03 17 5D 03 7D 03 00 32 80 00 00 2E 38 91
01 03 18 3A 03 76 03 00 32 80 00 00 2E 38 F2
01 03 19 3B 03 7B 03 00 32 80 00 00 2E 38 FF
01 03 1A 38 03 7C 03 00 32 80 00 00 2E 38 F8
01 03 1B 39 03 61 03 00 32 80 00 00 2E 38 E5
01 03 1C 66 03 0A 03 00 32 80 00 00 2E 38 D6
01 03 1D 67 03 5F 03 00 32 80 00 00 2E 38 83
01 03 1E 64 03 60 03 00 32 80 00 00 2E 38 BC
01 03 1F 65 03 65 03 00 32 80 00 00 2E 38 B9
01 03 20 62 03 5E 03 00 32 80 00 00 2E 38 BA
01 03 21 63 03 63 03 00 32 80 00 00 2E 38 87
01 03 22 60 03 64 03 00 32 80 00 00 2E 38 80
FC

I just turned the bike on, waited 1 second, turned the light on, waited 1 second, turned light off and after this, turned Bike off.

Not sure, why the first (and last) line is just 1 Byte, and why there is some missing from FF to F9.

B01 - Unknown (always 01)
B02 - Unknown (always 03)
B03 - Counter (FF-00, starts again fro FF, when 00 is reached)
B04 - Unknown (always changes)
B05 - Assist Level (see B07)
B06 - Unknown (always changes)
B07 - Assist Level (see B05)
B08 - Speed
B09 - Unknown (always 32)
B10 - Light (80=Off, 88 =On)
B11 - Unknown (always 00)
B12 - Unknown (always 00)
B13 - Unknown (always 2E)
B14 - Unknown (always 38)
B15 - Checksum (B1 XOR B2 XOR B3 ... XOR B14)

The UART connection is 1200bps, 8n1.

I will continuously update this post with new info's about this controller and its communication.

PS: English isn't my native language, so please forgive me spelling errors, etc.
 
Last edited:
I will continuously update this post
Do you have a link to this kind of displays? I wonder why you are putting effort in this stone age system. The most generic cheap controllers like Brainpower, Greentime etc. are using the well known No.2 protocol. The protocol has nothing to do with the processor like the title of this thread might imply.
 
No news anymore - the other Bytes never change under any condition.
I also recorded all frames from a 2 hour driving - no changes.

So nothing more to do here, sadly.
 
Hi guys,

i'm trying to reverse engineer the UART protocol of an XCM-K based controller.
I have connected an Arduino to the Serial port between Display and Controller for sniffing the data.

Till now (just started about 20mins ago), this is the communication of the display to the controller from turning on and then turning off:

Code:
FF
01 03 F9 5B 03 1B 03 00 32 80 00 00 2E 38 1F
01 03 FA 58 03 1C 03 00 32 80 00 00 2E 38 18
01 03 FB 59 03 81 03 00 32 80 00 00 2E 38 85
01 03 FC 06 03 2A 03 00 32 80 00 00 2E 38 76
01 03 FD 07 03 7F 03 00 32 80 00 00 2E 38 23
01 03 FE 04 03 80 03 00 32 80 00 00 2E 38 DC
01 03 FF 05 03 05 03 00 32 80 00 00 2E 38 59
01 03 00 82 03 7E 03 00 32 80 00 00 2E 38 5A
01 03 01 03 03 03 03 00 32 80 00 00 2E 38 A7
01 03 02 80 03 04 03 00 32 80 00 00 2E 38 20
01 03 03 81 03 29 03 00 32 80 00 00 2E 38 0D
01 03 04 6E 03 32 03 00 32 80 00 00 2E 38 FE
01 03 05 6F 03 27 03 00 32 80 00 00 2E 38 EB
01 03 06 6C 03 28 03 00 32 80 00 00 2E 38 E4
01 03 07 6D 03 2D 03 00 32 80 00 00 2E 38 E1
01 03 08 0A 03 26 03 00 32 80 00 00 2E 38 82
01 03 09 0B 03 2B 03 00 32 80 00 00 2E 38 8F
01 03 0A 08 03 2C 03 00 32 80 00 00 2E 38 88
01 03 0B 09 03 51 03 00 32 80 00 00 2E 38 F5
01 03 0C 36 03 7A 03 00 32 80 00 00 2E 38 E6
01 03 0D 37 03 4F 03 00 32 80 00 00 2E 38 D3
01 03 0E 34 03 50 03 00 32 80 00 00 2E 38 CC
01 03 0F 35 03 55 03 00 32 88 00 00 2E 38 C1
01 03 10 32 03 4E 03 00 32 88 00 00 2E 38 C2
01 03 11 33 03 53 03 00 32 88 00 00 2E 38 DF
01 03 12 30 03 54 03 00 32 88 00 00 2E 38 D8
01 03 13 31 03 79 03 00 32 88 00 00 2E 38 F5
01 03 14 5E 03 82 03 00 32 88 00 00 2E 38 66
01 03 15 5F 03 77 03 00 32 80 00 00 2E 38 9B
01 03 16 5C 03 78 03 00 32 80 00 00 2E 38 94
01 03 17 5D 03 7D 03 00 32 80 00 00 2E 38 91
01 03 18 3A 03 76 03 00 32 80 00 00 2E 38 F2
01 03 19 3B 03 7B 03 00 32 80 00 00 2E 38 FF
01 03 1A 38 03 7C 03 00 32 80 00 00 2E 38 F8
01 03 1B 39 03 61 03 00 32 80 00 00 2E 38 E5
01 03 1C 66 03 0A 03 00 32 80 00 00 2E 38 D6
01 03 1D 67 03 5F 03 00 32 80 00 00 2E 38 83
01 03 1E 64 03 60 03 00 32 80 00 00 2E 38 BC
01 03 1F 65 03 65 03 00 32 80 00 00 2E 38 B9
01 03 20 62 03 5E 03 00 32 80 00 00 2E 38 BA
01 03 21 63 03 63 03 00 32 80 00 00 2E 38 87
01 03 22 60 03 64 03 00 32 80 00 00 2E 38 80
FC

I just turned the bike on, waited 1 second, turned the light on, waited 1 second, turned light off and after this, turned Bike off.

Not sure, why the first (and last) line is just 1 Byte, and why there is some missing from FF to F9.

B01 - Unknown (always 01)
B02 - Unknown (always 03)
B03 - Counter (FF-00, starts again fro FF, when 00 is reached)
B04 - Unknown (always changes)
B05 - Assist Level (see B07)
B06 - Unknown (always changes)
B07 - Assist Level (see B05)
B08 - Speed
B09 - Unknown (always 32)
B10 - Light (80=Off, 88 =On)
B11 - Unknown (always 00)
B12 - Unknown (always 00)
B13 - Unknown (always 2E)
B14 - Unknown (always 38)
B15 - Checksum (B1 XOR B2 XOR B3 ... XOR B14)

The UART connection is 1200bps, 8n1.

I will continuously update this post with new info's about this controller and its communication.

PS: English isn't my native language, so please forgive me spelling errors, etc.
I like this topic.

I am also performing data reverse engineering on a few E-bike display panels that operate at 1200 BPS. It's slightly strange that my setup detects it at 1202 BPS.
  • The data type is 8 data bits with 1 stop bit.
  • B1 and B2 are the header, functioning as markers to indicate that the data read has begun.
  • The various unknowns that don't change are likely markers for data boundaries/limits.
  • The checksum at the end is for verifying the data's validity level.
  • B06 is likely some kind of Millis (milliseconds), transfer counter, or something similar.
I attach the logic analyzer screeshot.
 

Attachments

  • ebike panel.jpg
    ebike panel.jpg
    250.9 KB · Views: 14
B01 - Unknown (always 01)
B02 - Unknown (always 03)
B03 - Counter (FF-00, starts again fro FF, when 00 is reached)
B04 - Unknown (always changes)
B05 - Assist Level (see B07)
B06 - Unknown (always changes)
B07 - Assist Level (see B05)
B08 - Speed
B09 - Unknown (always 32)
B10 - Light (80=Off, 88 =On)
B11 - Unknown (always 00)
B12 - Unknown (always 00)
B13 - Unknown (always 2E)
B14 - Unknown (always 38)
B15 - Checksum (B1 XOR B2 XOR B3 ... XOR B14)
Hi, it would be correct to start the numbering from zero. Okay, in your system. For a similar protocol:

B06 - encoded assist level (the key and counter B03 are used).
B07 - bit 3 - soft start, bit 2 - cruize, bit 0 - mil/km
B12 - recovery
 
I have allso been doing alot with this mcu and I have confirmed with the manufacturer of the one I have is unlocked but of course the wont tell me what I need to get into it ik its a clone and 1200bauds is standard but my clk is off and mines running at 2275 ish I have a buload of stuff im working on this for multiple reasons hudge project will probably continue to work on it for months but I have been building a esp32 sniffer and Ai like tool that loggs alot of dat and I can set it up on a bench and let it run for hrs iv hit a wall though im getting data and all that but even running 2 esp32s and doing master slave task split to save ram its not enough to run my program fully so I just ordered a s3 its got 3x the ram my older ones do and has ps ram aswell ill drop in everything when it comes in and get it running right ill comepile the info I do have with an Ai to give u want lil iv got with what I can
JSBULL / G51 CONTROLLER SNIFFING RESULTS

PROJECT:
JSBull controller / G51 display single-wire protocol bridge/sniffer.

MAIN GOAL:
Use ESP32 as a controlled middle-man/router/sniffer between JSBull controller data line and G51 display so we can:
- decode controller/display traffic
- prevent or clear E30 display error
- keep display alive
- keep speed at zero if needed
- log all traffic for protocol reverse engineering
- eventually bridge Flipsky/VESC data to the display if possible

CONFIRMED / STRONG FINDINGS:
1. JSBull display/controller line is a single-wire data bus.
2. Tested as 3.3V logic on ESP32 side.
3. Use level protection / resistor when connecting ESP32.
4. Earlier captures showed best JSBull decode around 1275 baud, 8N1.
5. Measured timing matched about 7843 us per byte, which equals about 1275 baud.
6. Frame behavior found in G51_Bridge_v1:
- 97-byte fixed frame
- last byte position 97 is always 0x00
- frame interval about 811 ms
- about 50 ms silence/gap after frame
- data looks like state/bitfield encoding, not a normal checksum frame
7. Observed common JSBull byte values:
- 0xC0 = idle / logic-0 style state
- 0xFE = marker / logic-1 style state
- 0xFC = gear/state indicator
- 0x80 = active / transition / event
- 0x00 = terminator / end byte

IMPORTANT BYTE POSITIONS FROM OLD ANALYSIS:
- 10, 20, 25, 30, 60, 65, 70, 80, 90 = status/state bytes changing C0 <-> 80
- 53, 83, 93 = gear-related bytes changing FC <-> FE
- 38, 39, 40 = gear change zone
- 94, 95, 96 = gear change zone
- 15, 16, 26-29 = power event bytes
- 33-35 and 89-91 = brake event bytes
- 97 = always 0x00 terminator

CAPTURED SNIFFING NOTES:
- Passive dual-line UART sniffer was used.
- No transmit during sniffing.
- Serial monitor 115200.
- Test setup included:
- G51 line on GPIO16 in some tests
- JSBull line on GPIO22 in some tests
- later fixed sniffer used G51 TX GPIO32 at 9600 and JSBull DATA GPIO22 at 1275
- Logs showed repeated JSBull bytes like C0, FE, FC, 80, 00.
- G51 side showed bursts such as:
- 46 01 AA 00 AB 0D
- other longer G51 byte streams
- JSBull side repeatedly produced stable repeating state-like patterns.

IMPORTANT CAUTION:
The newer XCM Endless Sphere finding is separate from JSBull unless proven otherwise.
XCM decoded protocol:
- 1200 bps, 8N1
- 15-byte controller telemetry frame
- XOR checksum
That should NOT overwrite the JSBull/G51 1275-baud / 97-byte working assumption until we compare with real captures.

LOCKED / CURRENT JSBULL BRIDGE DIRECTION:
Use bridge/emulator-first, not blind brute force.

Best strategy:
1. Passive sniff first.
2. Record controller side and display side.
3. Detect baud, inversion, parity, timing, and frame length.
4. Build consensus frame from real captured frames.
5. Replay/bridge only after stable decode.
6. Add keepalive only when confidence is high.
7. Save best candidates to W25Q64.
8. Save full logs to SD.

CURRENT ADVANCED FIRMWARE DIRECTION:
G51_Bridge_Rev4 direction:
- autonomous self-decoding bridge
- baseline sniff mode
- decode candidate sweep
- bridge mode
- verified mode
- passthrough mode
- relay defaults to natural bypass
- ESP32 only becomes active after enough confidence

REV4 GPIO PLAN:
- GPIO16 = UART2 RX, G51 display side
- GPIO17 = UART2 TX, G51 display side
- GPIO25 = active half-duplex bus TX
- GPIO34 = passive sniff A, JSBull/controller side, input only
- GPIO35 = passive sniff B, G51/display side, input only
- GPIO27 = raw edge timing ISR
- GPIO26 = relay control
- GPIO33 = RS485 direction pin
- GPIO18 = SPI SCK
- GPIO19 = SPI MISO
- GPIO23 = SPI MOSI
- GPIO22 = SD CS
- GPIO13 = W25Q64 CS

DECODE MATRIX:
Baud rates to test:
- 1275
- 1200
- 1250
- 1300
- 1350
- 9600
- 10400
- 4800
- 2400
- 19200

Formats:
- normal polarity
- inverted polarity
- 8N1
- 8E1
- 8O1
- sample points 35%, 50%, 65%

Total candidate set:
10 baud rates x 2 polarities x 3 serial formats x 3 sample points = 180 candidates.

SCORING MODEL:
0-9 = reject
10-24 = very weak
25-49 = weak
50-64 = compare later
65-79 = promising
80-89 = strong, keepalive allowed
90-99 = very strong
100 = confirmed
101 = verified after restart/retest

SD LOG FILES TO CREATE EACH RUN:
Each boot should create:
/BRIDGE/RUNxxx/

Files:
- events.csv
- frames.csv
- timing.csv
- diffs.csv
- sniff_a.csv
- sniff_b.csv
- candidates.csv
- consensus.csv

W25Q64 ROLE:
W25Q64 stores:
- best candidate
- last-known-good timing
- last-known-good frame/profile
- crash recovery state
- compact summary/statistics

SD ROLE:
SD stores:
- full sniff logs
- full frame logs
- candidate results
- timing data
- diff analysis
- event markers

KNOWN/PLANNED SERIAL COMMANDS:
- C = mark E30 cleared
- E = mark E30 appeared
- G = mark ghost speed appeared
- R = mark display reset
- S = mark speed changed
- X = stop / reboot
- SPD:nn = set fake speed 0-99
- MODE:R = force route
- MODE:E = force emulate
- MODE:D = force discover
- DUMP = print current frame
- SAVE = save W25Q64 checkpoint
- STATUS = print status
- STABILITY = print byte stability
- CANDIDATES = print candidate results
- RELAY:0/1 = relay control

HARDWARE SAFETY:
- Do not directly force unknown display/controller bus without protection.
- Use passive sniff first.
- Relay should default to natural bypass.
- ESP32 must be silent until decode confidence is high.
- Use pullup/resistor/level protection on single-wire line.
- Avoid ESP32 boot strap pins for critical bus control when possible.
- Watchdog must stay enabled for long-duration tests.

BEST NEXT TEST:
Run the Rev4 autonomous bridge/sniffer with:
1. relay closed / natural bypass
2. 30-minute baseline passive sniff
3. log both sides
4. let it score baud/parity/inversion candidates
5. do not inject keepalive until score >= 80
6. only lock protocol profile after score 101 verified after restart

CURRENT STATUS:
JSBull/G51 is partially decoded but not finished.
Strongest working assumption:
- JSBull single-wire state bus
- around 1275 baud 8N1
- 97-byte frame
- 0x00 terminator
- repeating state bytes C0/FE/FC/80
- bridge should learn and replay real captured frames instead of using blind static guesses.






Sorry I know its incomplete like I said I didnt have enough ram to run my full programs and I used 2 different Ai to try to sume all the data so its lil wishy washy after I get my new microcontrollers and can run my full tester ill be able to give u more detailed results
 

Attachments

  • JSBull_Project_Handoff-1.txt
    11.7 KB · Views: 1
good luck, some of the big brains over at pedelec.de are upto their elbows in controller firmware projects, I received some helpful and friendly signposting there with my own initial naïve questions even when posted in English, my German language skills are sufficient to order a beer..)
 
Ya ill be doing alot of scans for along time on alot of different clones and off brands I have a hunch with a program and compatibility with display ik its about 20% it will work and will take awhile to do but hey wth I'll try it and get some code practice at same time
 
Wow im so mad I do believe I got it on accident but my sd set up was not set to chunk so it tryied to save all the dump at same time and I cannot get it to repeat again and I literally got it while spamming the power button on the controller waiting for it to connect and cannot get it to repeat i have been trying for 2 days
 

Attachments

  • Screenshot_20260524_124541_Claude.jpg
    Screenshot_20260524_124541_Claude.jpg
    706.6 KB · Views: 3
Back
Top