mirror of
https://github.com/TomHodson/tomhodson.github.com.git
synced 2025-06-26 10:01:18 +02:00
add micropython draft
This commit is contained in:
parent
a58a6ff1f0
commit
9721978cb2
92
_posts/2024-07-20-micropython-1.md
Normal file
92
_posts/2024-07-20-micropython-1.md
Normal 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>
|
1
assets/blog/micropython/cm6.bundle.min.js
vendored
Normal file
1
assets/blog/micropython/cm6.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
108
assets/blog/micropython/example_micropython.py
Normal file
108
assets/blog/micropython/example_micropython.py
Normal 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)
|
BIN
assets/blog/micropython/four_picos.jpg
Normal file
BIN
assets/blog/micropython/four_picos.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 MiB |
1
assets/blog/micropython/micropython.min.mjs
Normal file
1
assets/blog/micropython/micropython.min.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/blog/micropython/micropython.wasm
Executable file
BIN
assets/blog/micropython/micropython.wasm
Executable file
Binary file not shown.
117
assets/blog/micropython/simulator.js
Normal file
117
assets/blog/micropython/simulator.js
Normal 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);
|
BIN
assets/blog/micropython/thumbnail.png
Normal file
BIN
assets/blog/micropython/thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
assets/blog/micropython/xkcd.png
Normal file
BIN
assets/blog/micropython/xkcd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
Loading…
x
Reference in New Issue
Block a user