Skip to content
Snippets Groups Projects
README.md 4.89 KiB
Newer Older
  • Learn to ignore specific revisions
  • Quentin Bolsee's avatar
    Quentin Bolsee committed
    # Microquine: self-replicating microcontroller code
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    <img src="img/board_transfer.jpg" width=70%></img>
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    In this project, we explore self-replication of microcontroller code. The code can jump hosts by simply streaming its own bytes on a UART port.
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    We picked an RP2040 microcontroller equipped with Micropython for the following reasons:
    - As an interpreted language, it offers self-reflection at no extra cost
    - The Python interpreter (REPL) can be made available directly on the UART port of the RP2040
    - Sending a `CTRL-C` (=`\x03`) character resets the target microcontroller and gets it ready for code injection, no matter its current state
    
    ## Board
    
    The board we built for these experiments is  a xiao RP2040 with a single cell LiPo battery, a piezo buzzer and UART connectors:
    
    <img src="img/board_v1_1.jpg" width=70%></img>
    
    The LiPo battery is mounted in the back, in a 3D printed enclosure. Thanks to a specific charging manager IC, it can be charged directly from the USB connector's 5V.
    
    <img src="img/parts.jpg" width=70%></img>
    
    ## Micropython firmware
    
    For the purpose of this project, we use a version of Micropython in which the REPL can talk to the UART port, in addition to the usual USB CDC port.
    
    You can find a `.uf2` build of this firmware [here](./firmware).
    
    You can install Micropython on the board by resetting the xiao RP2040 and dragging the `.uf2` file onto the flash drive that shows up.
    
    To verify that the REPL is available on the RP2040's UART port, you can connect a USB-to-serial adapter directly to it and power the board through its battery alone:
    
    <img src="img/uart.jpg" width=70%></img>
    
    The USB-to-serial adapter should be set to a baudrate of `115200`. If successful, you'll be greeted by the REPL as if you were directly connected to the RP2040 through its native USB port.
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    ## Code
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    example codes are found [here](./code).
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    ### Minimal self-replicating code
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    ```py
    # main.py
    import machine
    import time
    
    # inject
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    with open("main.py", "rb") as f:
        print("\x03", end="")
        print("f = open('main.py', 'wb')")
        print("f.write(")
        print(f.read())
        print(")")
        print("f.close()")
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    # blink
    p = machine.Pin(25, machine.Pin.OUT)
    p.value(0)
    time.sleep_ms(200)
    p.value(1)
    ```
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    ### Song and dance: using the buzzer and neopixel
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    ```py
    # main.py
    import machine
    import time
    import neopixel
    
    # code injection
    with open("main.py", "rb") as f:
        print("\x03", end="")
        print("f = open('main.py', 'wb')")
        print("f.write(")
        print(f.read())
        print(")")
        print("f.close()")
        print("import machine")
        print("machine.reset()")
    
    # start of code
    duty = int(0.6*65535)
    pwm0 = machine.PWM(machine.Pin(3),freq=50_000,duty_u16=0)
    
    # from Ride of the Valkyries, Richard Wagner
    note_time_us = 110_000
    notes = [
        39, 0, 0, 34, 39, 42, 42, 42, 42, 42, 39, 39, 39, 39, 39,
        42, 0, 0, 39, 42, 46, 46, 46, 46, 46, 42, 42, 42, 42, 42,
        46, 0, 0, 42, 46, 49, 49, 49, 49, 49, 37, 37, 37, 37, 37,
        42, 0, 0, 37, 42, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46
    ]
    
    # neopixel color
    machine.Pin(11, machine.Pin.OUT).value(1)
    n = neopixel.NeoPixel(machine.Pin(12), 1)
    n[0] = 0, 0, 24
    n.write()
    
    #    C#    Eb       F#    Ab    Bb       C#    Eb       F#    Ab    Bb
    # C4    D4    E4 F4    G4    A4    B4 C5    D5    E5 F5    G5    A5    B5 C6
    # 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
    pitches = [1e5, 27.50000,29.13524,30.86771,32.70320,34.64783,36.70810,38.89087,
        41.20344,43.65353,46.24930,48.99943,51.91309,55.00000,58.27047,61.73541,
        65.40639,69.29566,73.41619,77.78175,82.40689,87.30706,92.49861,97.99886,
        103.8262,110.0000,116.5409,123.4708,130.8128,138.5913,146.8324,155.5635,
        164.8138,174.6141,184.9972,195.9977,207.6523,220.0000,233.0819,246.9417,
        261.6256,277.1826,293.6648,311.1270,329.6276,349.2282,369.9944,391.9954,
        415.3047,440.0000,466.1638,493.8833,523.2511,554.3653,587.3295,622.2540,
        659.2551,698.4565,739.9888,783.9909,830.6094,880.0000,932.3275,987.7666,
        1046.502,1108.731,1174.659,1244.508,1318.510,1396.913,1479.978,1567.982,
        1661.219,1760.000,1864.655,1975.533,2093.005,2217.461,2349.318,2489.016,
        2637.020,2793.826,2959.955,3135.963,3322.438,3520.000,3729.310,3951.066,
        4186.009]
    
    delays_us = [int(1e6/(2*pitch)) for pitch in pitches]
    
    def play():
        t = time.ticks_us()
        for k in range(len(notes)):
            tend = t+note_time_us
            if notes[k] == 0:
                while (t < tend):
                    t = time.ticks_us()
                continue
            delay_us = delays_us[notes[k]]
            while (t < tend):
                t = time.ticks_us()
                pwm0.duty_u16(duty)
                time.sleep_us(delay_us)
                pwm0.duty_u16(0)
                time.sleep_us(delay_us)
    
    play()
    
    machine.reset()
    ```
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    
    nikhil b lal's avatar
    nikhil b lal committed
    ## Files
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    
    
    Quentin Bolsee's avatar
    Quentin Bolsee committed
    [OnShape Assembly](https://cad.onshape.com/documents/dbb1f2f6468431d768c0d460/w/ff7db2adb0811f91412017d1/e/b7272632b534832a71510b02)
    
    ## License
    
    This project is provided under the MIT license.
    
    Quentin Bolsée and Nikhil Lal, 2024.