RP2040 Pico Micropython propeller loader
I have some upcoming projects that will involve using the Propeller with a Pico. I thought that letting the Pico program the Propeller would save time in production and less connections. I wrote it in Micropython with the help of Phind AI and my Saleae logic analyzer to confirm. I had posted previously about the intricacies of the 3BP and found this document most useful from someones reply to my post.
Propeller Download-Protocol
I connect 3 pins to the Propeller
GPIO 0 (TX out) on RP2040 to Propeller P31 (RX in)
GPIO 1 (RX in) on RP2040 to Propeller P30 (TX out)
GPIO 3 to Propeller Reset
My spin program is saved as a binary file from Propeller Tool and then saved on the flash of the RP2040.
I thought I'd share my code and maybe someone else may find it useful.
Sorry I tried to find how to post code but didn't find out how. If someone can suggest i'll edit this post. I also attached a zip file of the code.
from machine import UART, Pin
import uos, utime, sys
tx_pin = Pin(0, Pin.OUT, value=1)
rx_pin = Pin(1, Pin.IN)
uart = UART(0, baudrate=115200, tx=tx_pin, rx=rx_pin)
reset_pin = Pin(2, Pin.OUT, value=1)
# Constants
LFSR_REQUEST_LEN = 250
LFSR_REPLY_LEN = 250
LFSR_SEED = ord("P")
class Commands:
SHUTDOWN = 0
LOAD_RAM_RUN = 1
LOAD_EEPROM = 2
LOAD_EEPROM_RUN = 3
def lfsr(seed):
while True:
yield seed & 0x01
seed = ((seed << 1) & 0xfe) | (((seed >> 7) ^ (seed >> 5) ^ (seed >> 4) ^ (seed >> 1)) & 1)
def generate_lfsr_sequence(seed, length):
lfsr_gen = lfsr(seed)
return [next(lfsr_gen) for _ in range(length)]
def _cleanup():
uart.deinit()
reset_pin.value(1)
def reset():
uart.flush()
reset_pin.value(0)
utime.sleep_us(25)
reset_pin.value(1)
utime.sleep_ms(90)
uart.flush()
def get_version():
version = _connect()
_write_long(Comands.SHUTDOWN)
utime.sleep(0.010)
reset()
print(f'Propeller version: {version}')
#return version
def _connect():
seq = generate_lfsr_sequence(LFSR_SEED, LFSR_REQUEST_LEN + LFSR_REPLY_LEN)
reset()
uart.write(b'\xf9' + bytes([each | 0xfe for each in seq[:LFSR_REQUEST_LEN]]))
uart.write(b'\xf9' * (LFSR_REPLY_LEN + 8))
for i in range(LFSR_REQUEST_LEN, LFSR_REQUEST_LEN + LFSR_REPLY_LEN):
if _read_bit(False, 100) != seq[i]:
print('No hardware found')
sys.exit()
version = 0
for i in range(8):
version = ((version >> 1) & 0x7f) | ((_read_bit(False, 50) << 7))
return version
def _prepare_code(code, eeprom=False):
if not code:
print('Empty file')
sys.exit()
if len(code) % 4 != 0:
print('Invalid code size')
sys.exit()
code_array = bytearray(code)
def encode_chunk(start, end):
chunk = b''
for i in range(start, end, 4):
chunk += _encode_long(
code_array[i] | (code_array[i+1] << 8) | (code_array[i+2] << 16) | (code_array[i+3] << 24)
)
return chunk
chunk_size = 4096
encoded_chunks = []
for i in range(0, len(code_array), chunk_size):
encoded_chunks.append(encode_chunk(i, min(i + chunk_size, len(code_array))))
return encoded_chunks
def _send_code(encoded_code, code_len, eeprom=False, run=True):
command = Commands.LOAD_EEPROM_RUN if eeprom and run else \
Commands.LOAD_RAM_RUN if run else \
Commands.LOAD_EEPROM if eeprom else \
Commands.SHUTDOWN
prepared_data = bytearray()
prepared_data.extend(_encode_long(command))
prepared_data.extend(_encode_long(code_len // 4))
prepared_data.extend(b''.join(encoded_code))
uart.write(prepared_data)
if _read_bit(True, 8000) == 1:
print('RAM Checksum error')
sys.exit()
def _write_byte(value):
uart.write(value.to_bytes(1, 'big'))
def _write_long(value):
uart.write(_encode_long(value))
def _encode_long(value):
result = []
for i in range(10):
result.append((0x92 | (value & 0x01) | ((value & 2) << 2) | ((value & 4) << 4)).to_bytes(1, 'big'))
value >>= 3
result.append((0xf2 | (value & 0x01) | ((value & 2) << 2)).to_bytes(1, 'big'))
return b''.join(result)
def _read_bit(echo, timeout_ms):
start = utime.ticks_ms()
end_time = start + timeout_ms
while utime.ticks_ms() < end_time:
if echo:
_write_byte(0xf9)
utime.sleep_ms(100)
c = uart.read(1)
if c:
return ord(c) & 0x01
print('Read timeout error')
return None # Return None if timeout occurs
def upload_code(code, eeprom=False, run=True):
try:
print('Preparing code for upload')
encoded_code = _prepare_code(code, eeprom) # try here first to get code ready
version = _connect()
print(f'Connected to Propeller (version: {version})')
print('Uploading code')
_send_code(encoded_code, len(code), eeprom, run)
print('Upload complete')
except Exception as e:
print('Error during upload')
sys.exit()
def upload(path, eeprom=False, run=True):
try:
with open(path, 'rb') as f:
code = f.read()
upload_code(code=code, eeprom=eeprom, run=run)
except Exception as e:
print('Error reading file')
sys.exit()
finally:
_cleanup()
def main():
path = 'your_path_file.binary'
upload(path, eeprom=True, run=True)
if __name__ == '__main__':
main()
Comments
Here is the attached zip file.
For the code formatting, add 3 backticks to the line before, and line after, your code.
I’ve edited your post, so if you edit your post you will see what was done.
All the best