Skip to content
Snippets Groups Projects
README.md 4.78 KiB
Newer Older
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.

## Minimal self-replicating code

```py
# 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

```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.