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