add micropython draft

This commit is contained in:
Tom 2024-07-29 10:49:20 +01:00
parent a58a6ff1f0
commit 9721978cb2
9 changed files with 319 additions and 0 deletions

View File

@ -0,0 +1,92 @@
---
title: MicroPython
layout: post
excerpt: Embedded Programming is fun again!
image: /assets/blog/micropython/xkcd.png
thumbnail: /assets/blog/micropython/thumbnail.png
assets: /assets/blog/micropython
alt: A crudely edited version of XKCD 353, the one about python, but with a greek letter mu stuck in front of "python".
head: |
<script src="/assets/blog/micropython/micropython.min.mjs" type="module"></script>
---
<figure style="width:max(300px, 100%);">
<img src="{{page.assets}}/xkcd.png"/>
<figcaption>
<small><a href = "https://xkcd.com/353/">Original</a></small>
</figcaption>
</figure>
My first exposures to programming as a kid were through [processing](https://processing.org/) and arduino.
With Arduino, I understood that it's really just C with some libraries and a toolchain and started to read the datasheets of things like the [ATmega328][atmega328_datasheet]. This is super fun because the atmega328 is a relatively simple computer and you can just about read through the datasheeet.
The atmega328 has all sorts of hardware that you can configure, like the timers that you can set up to count up/down, to trigger interupts, toggle pins etc. I had loads of fun with this as a nerdy kid.
However the compile and upload time is kinda long, C that hits registers directly is both ugly and hard to debug and when you start to work with bigger systems like the ESP32 and RP2040 that have WiFi and multiple cores and stuff this all starts to get a bit less fun, at least for me.
But because the likes of the ESP32 and the RP2040 are so much more powerful **they can run a python interpreter and it's surprisingly fast and really fun!**
Everyone loves to hate on python for being slow, and obviously don't write your tight loops in it (or do, I can't tell you what to do). But even on a resource constrained microprocessor you can have a fun time!
So anyway, here is a compendium of things I've being doing with [micropython][micropython]. Some of this is so that I don't forget how to do it later so there's a little more detail than might be warranted.
[micropython]: https://micropython.org/
[atmega328_datasheet]: https://ww1.microchip.com/downloads/en/DeviceDoc/40001906A.pdf
## Get yourself a dev board
<figure style="width:max(300px, 100%);">
<img src="{{page.assets}}/four_picos.jpg"/>
</figure>
The Raspberry Pi Pico is really nice, if you went to EMFcamp you can use [the badge][badge], and ESP32 boards work really well too! The easiest way to start is to flash a prebuilt firmware (for RP2040 boards that means putting the board in boot mode and then dragging and dropping a .uf2 file onto a virtual file system that appears.)
[badge]: https://tildagon.badge.emfcamp.org/
## Run some code!
mpremote is a really handy little tool for interacting with a micropython board. My first scripts to play with this looked like this
```sh
mpremote cp main.py :main.py
mpremote run main.py
```
You can also just run code
```python
{% flexible_include assets/blog/micropython/example_micropython.py %}
```
## Webassembly port
People are staring to use webassembly to create simulators for physical hardware that run in the browser, this can make the dev loop super fast. [More details here](https://github.com/micropython/micropython/blob/master/ports/webassembly/README.md)
For me this looks like:
```sh
cd /ports/webassembly
make min FROZEN_MANIFEST=/path/to/font_sources/manifest.py
cp ...wasm and ....min.mjs to your webserver directory
```
Then in the js console you can do:
```
const mp = await loadMicroPython();
mp.runPython("import fonts; print(fonts.gunship30)")
```
Note that I've got access to my compiled in code here.
<section class = "micropython-simulator">
<div id="editor"></div>
<button id=run title="Run code (Ctrl-Enter)" aria-title="Run code (Ctrl-Enter)">Run</button>
<canvas height="240" width="240" class = screen></canvas>
<pre id="micropython-stdout"></pre>
</section>
<script src="{{page.assets}}/cm6.bundle.min.js"></script>
<script src="{{page.assets}}/simulator.js" type = "module"></script>
<usbc-power-supply-simulator></usbc-power-supply-simulator>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,108 @@
import gc
import struct
import time
from array import array
import console
import display
import fonts
import framebuf
WHITE = 255
def palette_from_colors(*args):
pbuf = array("H", range(len(args)))
struct.pack_into(">HH", pbuf, 0, *args)
p = framebuf.FrameBuffer(
pbuf, len(args), 1, framebuf.RGB565
)
return p
def str_width(string, font, xpad=0):
return sum(font.get_ch(c)[2] + xpad for c in string)
def print_buf(
framebuffer,
string,
x,
y,
color,
font,
ha="left",
va="top",
bg=WHITE,
xpad=0,
):
total_width = str_width(string, font, xpad)
if ha == "center":
x -= total_width // 2
elif ha == "right":
x -= total_width
if va == "center":
y -= font.height() // 2
elif va == "bottom":
y -= font.height()
p = palette_from_colors(bg, color)
for c in string:
b, height, width = font.get_ch(c)
c_fbuf = framebuf.FrameBuffer(
array("B", b), width, height, framebuf.MONO_HLSB
)
framebuffer.blit(c_fbuf, x, y, bg, p)
x += width + xpad
return x, y
print(
"This is μPython saying hello from the on page console!"
)
console.log(
"This is μPython saying hello from the JS console!"
)
print(
f"Stack: alloc'd: {gc.mem_alloc()} free {gc.mem_free()} total {gc.mem_alloc() + gc.mem_free()}"
)
w, h = 240, 240
buf = bytearray(w * h)
fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS8)
i = 0
while True:
i += 1
fbuf.rect(0, 0, 240, 240, 255, True)
fbuf.pixel(120, 120, 128)
x, y = print_buf(
fbuf,
str(i % 150),
140,
145,
255,
font=fonts.gunship50,
ha="right",
va="bottom",
bg=0,
)
x, y = print_buf(
fbuf,
"mW",
x,
145,
255,
font=fonts.gunship30,
ha="left",
va="bottom",
bg=0,
)
display.draw(buf)
# The 'await' is necessary here to yield back to the JS event loop
# I tried to figure out how to hide this inside the JS implementation of sleep but
# couldn't make it work.
await time.sleep(0.2)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,117 @@
class USBCPowerSupplySimulator extends HTMLElement {
async init_mp(ctx, stdoutWriter) {
let mp = await loadMicroPython({
stdout: stdoutWriter,
stderr: stdoutWriter,
});
mp.registerJsModule("console", { log: (s) => console.log(s) });
mp.registerJsModule("time", {
sleep: async (s) => await new Promise((r) => setTimeout(r, s * 1000)),
});
mp.registerJsModule("display", {
draw: (buf) => {
let bytes = [...buf].flatMap((x) => [x, x, x, 255]);
let image = new ImageData(new Uint8ClampedArray(bytes), 240, 240);
ctx.putImageData(image, 0, 0);
},
});
return mp;
}
async init_editor() {
const resp = await fetch("/assets/blog/micropython/example_micropython.py");
const program = await resp.text();
// Create an initial state for the view
const initialState = cm6.createEditorState(program);
let view = cm6.createEditorView(
initialState,
this.shadow.getElementById("editor")
);
return view;
}
async setup() {
this.render();
const canvas = this.shadow.querySelector("canvas");
const ctx = canvas.getContext("2d");
const mp_console = this.shadow.getElementById("micropython-stdout");
const stdoutWriter = (line) => {
mp_console.innerText += line + "\n";
mp_console.scrollTo(0, mp_console.scrollHeight);
};
const [mp, view] = await Promise.all([
this.init_mp(ctx, stdoutWriter),
this.init_editor(),
]);
const runPython = async () => {
mp_console.innerText = "";
let mp = await this.init_mp(ctx, stdoutWriter);
try {
await mp.runPythonAsync(view.state.doc.toString());
} catch (e) {
stdoutWriter(e);
}
};
const run_button = this.shadow.getElementById("run");
run_button.onclick = runPython;
runPython();
}
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.setup().catch(console.error);
}
render() {
this.shadow.innerHTML = `
<style>
//The shadow root can be styled like a container
:host {
display: flex;
flex-direction: column;
align-items: center;
}
pre#micropython-stdout {
overflow-y: auto;
width: 80%;
white-space: pre-wrap;
height: 5em;
background-color: #d1d1d136;
border: 1px black solid;
border-radius: 5px;
padding: 5px;
}
button {
width: 5em;
align-self: flex-start;
}
.screen {
width: 240px;
height: 240px;
border-radius: 50%;
border: solid 1px black;
align-self: center;
}
</style>
<div id="editor"></div>
<button id=run title="Run code (Ctrl-Enter)" aria-title="Run code (Ctrl-Enter)">Run</button>
<canvas height="240" width="240" class = screen></canvas>
<pre id="micropython-stdout"></pre>
`;
}
}
customElements.define("usbc-power-supply-simulator", USBCPowerSupplySimulator);

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB