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:
Invalid ROM table
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 board that I had the experience with itself is surprisingly good:
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:
STLink will not work.FLM implements a custom flash FSM, flashing the chip without the FLM (or the reverse engineered routines) does not work..FLM.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.
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:
.FLM to figure out how to actually flash the deviceInitially 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:
Invalid ROM tableExample:
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 was - in hindsight - very simple:
mem_ap target via OpenOCDAfter those steps the chip partially was able to boot (until it double faulted (repeatedly entered HardFault) on corrupted memory regions).
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 adaptermem_ap target avoided Cortex autodetection crashesInstead 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 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:
0x7A83
0x8FCA
0x7654DCBA
and the relevant flash FSM registers:
0x400000A8
0x40000080
0x400000D0
0x00010000
0x00010010
0x00010014
0x00010018
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:
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.
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).
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.
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.
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:
mem-ap was still aliveSo before throwing away any board or microcontroller:
mem-ap accessThis article is tagged:
Dipl.-Ing. Thomas Spielauer, Wien (webcomplainsQu98equt9ewh@tspi.at)
This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/