Recovering from Invalid ROM table on an SNR8503M
27 May 2026 - tsp
Last update 27 May 2026
12 mins
Ever got an Invalid ROM table for your Snaner SNR8503M after playing around with your own flashing scripts, trying to migrate from the annoying closed Keil toolchain to open gcc, reverse engineering the FLM file, and generally making questionable life choices due to lack of time and too much curiosity?
Then this article might save you a fewĀ cents on your microcontroller.
In my case the chip was neverĀ really dead as most people I talked to suggested - actually the typical suggestion was to throw the chip away and replace it, a very bad trend. Instead, the Cortex-M0 CoreSight / MEM-AP infrastructure simply got into a state where memory reads returned stale or aliased values. The annoying Keil environment then failed with a non-telling message:
OpenOCD behaved similarly and Cortex auto detection failed.Ā At first sight this looks like a permanently corrupted chip or damaged ROM. It is not. Spoiler: Actually the view of the ROM is not read only and you have just overmapped the region during the flashing attempts. This yields the toolchains to simply refuse to interact with your microcontroller.
The device can actually be recovered.

The Hardware
The board that I had the experience with itself is surprisingly good:
- It is a solid BLDC driver frontend that can be repurposed as controller for turbomolecular pumps (hopefully a followup article on this will appear soon) as well as generic 3 phase power controller.
- It offers a very robust MosFET stage ā¦
- ⦠and a surprisingly resilient controller
The documentation on the other hand was ⦠minimalistic. The chip used here was an SNR8503M, which internally appears to be related to the LKS32MC03x family. Those chips do not appear in Europe very often, but they are pretty common in low cost motor controllers from China.
Important upfront:
- This is not an STM32 or compatible, even though being an Cortex-M0.
- Using STM32 flash devices like the
STLink will not work.
- The
FLM implements a custom flash FSM, flashing the chip without the FLM (or the reverse engineered routines) does not work.
What Was Needed
- Any CMSIS-DAP or J-Link compatible debugger. No STLink! In best case an original J-Link Pro.
- A lot of patience and a lack of time
- OpenOCD
- Python
- The original
.FLM
- The original
.axf firmware image (to have at least something that works - or in my case something where I wrongly assumed to work since it was not the original firmware - more on that experience later.
In case you think about trying with STLink devices just forget it. They seem to filter for STM32 and STM8 device families even though there is no real reason not to support other devices. Itās just an active filter, not missing features. In this case I would prefer some cheap CMSIS-DAP clone, those actually work and offer more flexibility.
How the Device Initially Got Bricked
The original goal was replacing the Keil flashing workflow with a custom Python implementation and the firmware for the controller, that was luckily open source, with an implementation that works directly with the gcc compiler for ARM. I also extended the firmware with custom startup for a turbomolecular pump.Ā That task involved:
- Reverse engineering the
.FLMĀ to figure out how to actually flash the device
- Replaying the flash FSM manually from Python
- Writing directly to the controller registers
- Generally poking memory locations one should perhaps not poke on a Friday evening
Initially this worked surprisingly well.Ā Until it suddenly did not. I made the mistake of writing back an supposed complete dump, that was much larger than the flash memory area, writing into arbitrary memory regions in the memory mapped space. This worked on the first try - but only on a device that was freshly reset and never executed firmware. The second time the memory controllers where already configured and thus the side effects where different.
After the last flashing attempt:
- Keil only reported
Invalid ROM table
- OpenOCD could still see the SWD DPIDR
- The cortex_m module never identified the core but reported an arbitrary CPU ID.
- All memory accesses became nonsense
- This persisted over multiple reboots and power cycles
Example:
0x00000000: 3b02c347 3b02c347 3b02c347 ...
0xe000ed00: 3b02c347 3b02c347 3b02c347 ...
or later:
0x20000000 -> 44444444
0x20000004 -> 44444444
0x20000008 -> 44444444
Independent addresses returned identical values.Ā The ROM table itself was not actually destroyed.Ā The debug bus had simply entered a completely broken state that was preserved over power cycling.
Recovering
Recovering was - in hindsight - very simple:
- Only addressing the
mem_ap target via OpenOCD
- Issuing the erase chip sequence that was recovered from the FLM
- Rebooting the device
After those steps the chip partially was able to boot (until it double faulted (repeatedly entered HardFault) on corrupted memory regions).
OpenOCD Configuration
The following OpenOCD configuration turned out to be sufficient:
source [find interface/cmsis-dap.cfg]
cmsis-dap quirk enable
transport select swd
adapter speed 5
set _CHIPNAME snr8503
swd newdap $_CHIPNAME cpu -expected-id 0x0bb11477
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
target create $_CHIPNAME.ap mem_ap -dap $_CHIPNAME.dap -ap-num 0
init
Important details:
cmsis-dap quirk enable was required for the cheap CMSIS-DAP adapter
- Using only a
mem_ap target avoided Cortex autodetection crashes
- Very low SWD speed (1 kHz) improved reliability dramatically. But actually only for this step.
Talking to OpenOCD from Python
Instead of directly implementing CMSIS-DAP packets, Python simply connected to OpenOCD over telnet:
import socket
import re
class OpenOCD:
def __init__(self, host="127.0.0.1", port=4444, timeout=3.0):
self.s = socket.create_connection((host, port), timeout=timeout)
self.s.settimeout(timeout)
self._read_until_prompt()
def close(self):
self.s.close()
def _read_until_prompt(self):
data = b""
while True:
chunk = self.s.recv(4096)
if not chunk:
break
data += chunk
if data.rstrip().endswith(b">"):
break
return data.decode(errors="replace")
def cmd(self, line):
self.s.sendall((line + "\n").encode())
return self._read_until_prompt()
def mdw1(self, addr):
out = self.cmd(f"mdw 0x{addr:08x} 1")
m = re.search(r"0x[0-9a-fA-F]+:\s+([0-9a-fA-F]{1,8})", out)
if not m:
raise RuntimeError(f"mdw failed:\n{out}")
return int(m.group(1), 16)
def mww(self, addr, value):
out = self.cmd(f"mww 0x{addr:08x} 0x{value:08x}")
if "Failed" in out or "Error" in out:
raise RuntimeError(f"mww failed:\n{out}")
def dap(self, command):
return self.cmd("snr8503.dap " + command)
The Key Discovery
The critical insight was, thatĀ the .FLM itself already contained the proper very simple erase sequence. An FLM simply is code that is loaded by the uploader into the device memory upfront flashing. The FLM provides a set of standardized callable methods that actually implement the flashing routines. This way the IDE can transfer the uploaded sectors into flash memory without the toolchains knowing how memory frameworks are actually implemented on the device side. The code is usually lightweight ARM bytecode.Ā Disassembling the FLM revealed the magic constants:
and the relevant flash FSM registers:
0x400000A8
0x40000080
0x400000D0
0x00010000
0x00010010
0x00010014
0x00010018
The Actual Recovery
The following erase sequence magically restored the ROM table - by erasing the whole chip and thus also recovering the persistent configuration of the bus system:
def try_chip_erase_from_flm(ocd):
ocd.mww(0x400000A8, 0x00007A83)
v = ocd.mdw1(0x40000080)
ocd.mww(0x40000080, v | 0x000001FF)
ocd.mww(0x400000D0, 0x00008FCA)
v = ocd.mdw1(0x00010000)
ocd.mww(0x00010000, v | 0x80008000)
ocd.mww(0x00010010, 0x7654DCBA)
for _ in range(1000):
done = ocd.mdw1(0x00010018)
if done == 1:
fail = ocd.mdw1(0x00010014)
print(f"Erase done, fail={fail}")
return fail == 0
After executing this sequence:
- The ROM table immediately became readable again
- CoreSight autodetection started working
- OpenOCD and Keil could reconnect
- The Cortex-M0 debug blocks appeared correctly again
OpenOCD suddenly reported:
Part is 0x471, Cortex-M0 ROM (ROM Table)
Part is 0x00a, Cortex-M0 DWT
Part is 0x00b, Cortex-M0 BPU
instead of invalid garbage.
Why This Works
The ROM table was never physically corrupted.Ā Instead the the flash FSM or internal configuration entered an inconsistent state andĀ the mem-ap returned stale values.Ā Keil interpreted those values as a broken ROM table.Ā The chip erase reset the internal state machine and restored proper CoreSight operation. Tooling just refused to execute the sequence without connecting to the core (which is required to execute the FSM on the device).
A Minor Quirk After Restoring the ROM Table
Trying to flash the firmware again with Keil after the ROM table appeared again - just broke again. The uploader executed erase chip correctly, it even started to transfer data. Just to abort. Giving this a quick glance with OpenOCD again showed the reason was the microcontroller double faulting. It just started to execute invalid code and the uploader simply did not halt the CPU correctly. The quick fix was to just increase flashing speed. This pushed in enough data to get more headroom till the uploader issued the halt. After a complete flash - the chip vanished and looked dead.
Final Surprise
After successfully flashing the - in hindsight -Ā wrong firmware image, the debug interface vanished again.Ā This turned out not to be another brick as I immediately suspected.Ā The firmware simply repurposed the SWD/ICP pins for board temperature monitoring.
The debugger pins became thermometer pins.
This explained why the interface disappeared completely afterwards. Of course a fix was trivial after recognizing what happened by pulling down the ICPCS pin to GND during the reset, then attaching the debugger and executing the flash again - this time with firmware that did not touch the SWD.
Conclusion
The important takeaway:
āInvalid ROM tableā does not necessarily mean permanent hardware damage.
If the SWD DPIDR still answers the device is recoverable.
In myĀ case it was very simple in hindsight:
- The
mem-ap was still alive
- The flash FSM was available and simple to read in its disassembled form
- A chip erase through the FLM recovery sequence restored the device
So before throwing away any board or microcontroller:
- Try low-level
mem-ap access
- Reverse engineer the FLM
- Replay the erase sequence manually
- Assume the problem is recoverable until proven otherwise
- Do not trust error messages too much
- Do not throw away your stuff prematurely like many people suggest. It takes some time but usually a hard brick is very hard to achieve.
References
This article is tagged: