Squashed commit of the following:

commit 36cc58f5c418dbd55aecac0fe2b92977951e4401
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 12:04:44 2024 +0100

    revert

commit 74d40e498ac65c1789ceb5b07c9bad92e5356773
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 11:51:25 2024 +0100

    Update jekyll.yml

commit b9a1c11dbfdeeb4d26fdf54a70b47a7d16e71e3c
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 11:42:48 2024 +0100

    Update jekyll.yml

commit 94619844e19ceb61fa68c7d75a2508ff19f0bd08
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 11:33:15 2024 +0100

    Update _config.yml

commit e2a12fcf8e8e95b28be0f0c75c61745ee8afa7ac
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 11:18:01 2024 +0100

    change concurrency group

commit 3575764efaa6b8edb02d71ec6c1c84b9eefbb21d
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 11:12:27 2024 +0100

    Update Gemfile

commit 5f044a048a8c1b5f96e99e75eb91435ad3921e2a
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 11:09:57 2024 +0100

    setup a branch build

commit 9721978cb20bedd8a39abb89f436386585eab25f
Author: Tom <thomas.hodson@ecmwf.int>
Date:   Mon Jul 29 10:49:20 2024 +0100

    add micropython draft
This commit is contained in:
Tom 2024-07-29 12:04:57 +01:00
parent 52eec1ec4c
commit b1c92c3152
11 changed files with 325 additions and 3 deletions

View File

@ -55,13 +55,13 @@ jobs:
JEKYLL_ENV: production
- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by default
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v4
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
url: "${{steps.pages.outputs.base_path}}"
runs-on: ubuntu-latest
needs: build
steps:

View File

@ -22,6 +22,9 @@ kramdown:
syntax_highlighter_opts:
line_numbers: inline
flexible_include:
die_on_flexible_include_error: false
include:
- node_modules

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