Skip to content
Snippets Groups Projects
Select Git revision
  • main default protected
1 result

hexcell

  • Clone with SSH
  • Clone with HTTPS
  • Quentin Bolsee's avatar
    Quentin Bolsee authored
    610c0d53
    History
    Name Last commit Last update
    code
    firmware
    img
    pcb
    .DS_Store
    .gitignore
    README.md

    Microquine: self-replicating microcontroller code

    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.

    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:

    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.

    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.

    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:

    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.

    Code

    example codes are found here.

    Minimal self-replicating code

    # main.py
    import machine
    import time
    
    # inject
    print(f"\3f=open('main.py', 'wb')\nf.write({open('main.py', 'rb').read()})\nf.close()\nimport machine\nmachine.reset()")
    
    # blink
    p = machine.Pin(25, machine.Pin.OUT)
    p.value(0)
    time.sleep_ms(200)
    p.value(1)

    Song and dance: using the buzzer and neopixel

    # 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()

    Files

    OnShape Assembly

    License

    This project is provided under the MIT license.

    Quentin Bolsée and Nikhil Lal, 2024.