Hello, good evening everyone.
I'm creating this thread to discuss about programming Fremont Micro Device's series of PIC clones, available at
LCSC for prices roughly the same of the awesome
Padauk microcontroller lineup. For example, the
FT60F211 offers 1 Kword flash, 64 byte SRAM, 128 byte EEPROM for about 5 cents each by buying 150 units.
These microcontrollers, just like Padauk's, have absolutely zero public documentation on how programming these work, and the manufacturer expects you to program them with either their standalone which isn't available on LCSC or any other reputable provider I've looked for, and
goes for about 40€ on AliExpress. I think, just as we did for the Padauk, we can do better.
Now that we have fully reverse engineered Padauk's inner workings, have them supported in SDCC, and being cheaper than FMD's, one might wonder - what's the point?. Well, there's one advantage that FMD seems to offer vs Padauk's -
how simple the programmer can get.
After spending a couple hours looking online, I found the following valuable resources:
- Thanks to FMD's official programming manual, I found that these devices require only four wires: VCC, GND, clock and data. Unlike Padauk, there's no provisioning for Vpp that is complex to generate.
- Further looking online, I found that there are also what FMD calls "Link Bridge": a smaller, cheaper in-circuit programmer, which is available on AliExpress for 30€.
This gets more interesting - not only we can clearly see that are no coils for elevating the voltage, but we can also see in one of the pictures the very IC they are using: an off-the-shelf STM32F103RB6T. - Finally, looking deeper in FMD's programmer software, I found three files which looked suspiciously like ARM bytecode. These were:
- Ver1/AppUpdate.bin
- Ver3/AppUpdate.bin (load address 0x08008000)
- Ver3in1/AppUpdate.bin (load address 0x08004000)
Thanks to the previous picture, I tried loading them under IDA using the asumption that they were STM32 firmwares and... oh boy they were:
There are no debugging symbols. However, they're only about 50KB and are not obfuscated, meaning I've managed to manually and painstakingly decompile a good chunk of them.
Thanks to all these sources, I'm glad to announce that I've found out how to:
- Enter the programming mode
- Read part of the flash contents
And the best thing of all: I've done it using an Arduino, with absolutely no extra hardware.
SignalsFMD devices use four wires during programming:
- VCC, inside their normal operating range
- ICSPDAT, the data pin
- ICSPCLK, the clock pin
- GND
If you have worked previously on Padauk, the signaling is pretty similar: the programmer always generates the clocking signal, and the data pin is shared and changes direction implicitly depending on the operation and the step they're into.
For instance, sending 0xA3 would look like:
Sample: V V V V V V V V
___ ___ ___ ___ ___ ___ ___ ___
Clock: | | | | | | | | | | | | | | | |
___| |___| |___| |___| |___| |___| |___| |___| |____
_______________ _______ _______
Data: | | | | | |
_| |_______________________| |_______| |__
Data is sent and received
least significant first, and is
sampled at the falling edge of the clock signal.
Entering programming modeFor entering programming mode, all microcontrollers follow the same procedure:
def enter_prog():
vcc_on()
serial_write(data=0x1CA3, bits=16)
delay_ms(70)
There is no acknowledgement - the microcontroller will not reply with anything, and the only way to know it has entered successfully programming mode is to execute a command and check its return value.
Programming opcodesThe programming protocol uses three different kind of opcodes:
Implicit opcodeThey consist of a bare 5-bit opcode.
def op_execute(opcode):
# Send opcode
serial_write(data=opcode, bits=5)
Known such commands so far are:
- 0x01: unknown meaning
- 0x02: unknown meaning
- 0x04: reset address counter
- 0x05: increment address counter
- 0x06: unknown meaning
- 0x07: unknown meaning
Write opcodeThey consist of a 5-bit opcode, a 8-bit parameter, and an extra dummy 0 bit.
def op_write(opcode, parameter):
# Send opcode
serial_write(data=opcode, bits=5)
# Send the parameter
serial_write(data=parameter, bits=8)
# Dummy cycle
serial_write(data=0, bits=1)
Known such commands so far are:
- 0x08: store parameter into LSB latch?
- 0x09: store parameter into MSB latch?
- 0x0A: change mode?
Read opcodeThey consist of a 5-bit opcode, and the device replies with an 8-bit response, plus a dummy bit during which the device frees the data pin.
def op_read(opcode):
# Send opcode
serial_write(data=opcode, bits=5)
# Read response
response = serial_read(bits=8)
# Dummy cycle
serial_read(bits=1)
return response
Known such commands so far are:
- 0x0C: read LSB from current position?
- 0x0D: read MSB from current position?
Programming sequencesUsing the opcodes explained above, the programmer defines the following sequences for programming the devices:
Program memory readdef prog_read(word_count):
# Set mode to 0x20, program flash read
op_write(0xA, 0x20)
# Reset address counter
op_execute(0x4)
words = []
for i in range(word_count):
# Read MSB and LSB. These two can be used in either order
msb = op_read(0xD) & 0x7F
lsb = op_read(0xC) & 0x7F
words.append(msb << 7 | lsb)
# Increment address counter
op_execute(0x5)
return words
Program memory writedef prog_write(words):
# ???
op_write(0xA, 0x56);
op_write(0xA, 0x20);
op_write(0xA, 0x24);
op_write(0x9, 0x1A);
op_write(0x8, 0x00);
op_execute(0x6);
# Reset address pointer
op_execute(0x4);
# Iterate through words and write each one
for word in words:
# Set into programming mode?
op_write(0xA, 0x30)
# Latch MSB and LSB
op_write(0x9, (word >> 7) & 0x7F)
op_write(0x8, word& 0x7F)
# ???
op_execute(0x1)
op_execute(0x6)
# Wait until the word is written
delay_us(1500)
# ???
op_execute(0x7)
# Increment address pointer
op_execute(0x5);
Program memory erasedef prog_erase(words):
# ???
op_write(0xA, 0x20)
op_write(0xA, 0x25)
op_write(0x9, 0x1A)
op_write(0x8, 0x08)
op_execute(0x6)
op_execute(0x4)
op_write(0xA, 0x31)
op_execute(0x2)
# Wait until memory is erased
delay_ms(50)
# ???
op_execute(0x7)
TO-DO- Figure out configuration flash commands
- Try to make sense of the opcodes, maybe?
Any help is welcome!