Shop OBEX P1 Docs P2 Docs Learn Events
RP2040 Pico Micropython propeller loader — Parallax Forums

RP2040 Pico Micropython propeller loader

Don MDon M Posts: 1,652
edited 2024-09-15 11:25 in PASM/Spin (P1)

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

Sign In or Register to comment.