Update micropython post

This commit is contained in:
Tom 2024-08-03 15:35:38 +01:00
parent 5b35cf51df
commit f526039785
5 changed files with 173 additions and 39 deletions

View File

@ -21,15 +21,15 @@ head: |
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.
After a while as a kid playing with Arduino, I started to understand from forum posts and other places the magical truth: Arduino is is just C with some libraries and a easy to setup dev environment! I started to read the datasheets of things like the [ATmega328][atmega328_datasheet], I guess this way my version of the minicomputers that the generations before mine cut their teeth on.
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.
The atmega328 is a relatively simple computer and you can just about read and understand the datasheet in a reasonable amount of time. It has all sorts of hardware that you can configure, 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!
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 with python!
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.
@ -42,7 +42,9 @@ So anyway, here is a compendium of things I've being doing with [micropython][mi
<img src="{{page.assets}}/four_picos.jpg" alt="A photograph of 4 Raspberry Pi Pico Boards arranged in a row."/>
</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.)
The Raspberry Pi Pico (The offical dev board for the RP2040 micro-controller) is really nice, if you went to EMFcamp you can use [the badge][badge], and ESP32 based 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 (by holding the BOOT button and powering it up) and then dragging and dropping a .uf2 file onto a virtual file system that appears.
[badge]: https://tildagon.badge.emfcamp.org/
@ -53,33 +55,30 @@ mpremote is a really handy little tool for interacting with a micropython board.
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:
You can also go straight to a REPL:
```sh
cd /ports/webassembly
make min FROZEN_MANIFEST=/path/to/font_sources/manifest.py
cp ...wasm and ....min.mjs to your webserver directory
mpremote REPL
```
Then in the js console you can do:
```
const mp = await loadMicroPython();
mp.runPython("import fonts; print(fonts.gunship30)")
```
## Next Steps
In the next few posts I'll talk a little about:
Note that I've got access to my compiled in code here.
* Drawing graphics
* Using nice fonts
* Compiling your own custom micropython firmware builds and when that makes sense.
* Compiling you firmware to webassmebly so you can make a web based simulator.
* Debugging the RP2040 with the RP Debbug probe.
* Using the DMA hardware on the RP240 to offload work from the main CPU
* Async programming with micropython
Here's a little webassembly simulator of the micropython project I've been working on. I'll expand on this in later posts but very quickly:
* It's targeted at a 240x240 pixel circular display that stores RGB colors with 5, 6 and 5 bits for each channel, respectively.
* This is running under webasembly with some custom code to convert the RGB 565 data and display it in a `<canvas>` tag
* I'm using a ttf font called [gunship](https://www.iconian.com/g.html) converted to bitmap format and frozen into the firmware.
<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>
<usbc-power-supply-simulator disable-console disable-editor code="{{page.assets}}/demo.py"></usbc-power-supply-simulator>

View File

@ -0,0 +1,99 @@
import asyncio
import gc
import sys
import console
import display
import drawing as draw
import fonts
import framebuf
console.log(
"This is μPython saying hello from the JS console!\n"
f"Version {sys.version}"
)
w, h = 240, 240
buf = bytearray(w * h * 2)
fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.RGB565)
mode = "day" # "night"
bg_color = draw.WHITE if mode == "day" else draw.BLACK
text_color = draw.BLACK if mode == "day" else draw.WHITE
def draw_ui(fbuf, power, amps, volts):
fbuf.fill(bg_color)
level = power/300
color = draw.BLACK if level < 0.8 \
else draw.ORANGE if level < 0.9 \
else draw.RED
if level > 0.8:
quads = 0b1010 if (i % 10) < 5 else 0b0101
r1, r2 = 120, 115
fbuf.ellipse(120, 120, r1, r1, color, True, quads)
fbuf.ellipse(120, 120, r2, r2, bg_color, True)
x, y = draw.display_with_units(
fbuf,
str(power),
"W",
185,
145,
color=text_color,
bg=bg_color,
mainfont=fonts.gunship45,
subfont=fonts.gunship25,
)
draw.display_with_units(
fbuf,
f"{volts:3.1f}",
"V",
x,
y - fonts.gunship45.height(),
text_color,
bg=bg_color,
mainfont=fonts.gunship30,
subfont=fonts.gunship20,
)
draw.display_with_units(
fbuf,
f"{amps:3.1f}",
"A",
x,
175,
text_color,
bg=bg_color,
mainfont=fonts.gunship30,
subfont=fonts.gunship20,
)
w = 100
x = 120 - w//2
draw.hbar(fbuf, x, 50, w, 10, power / 300, c = draw.BLACK)
draw.vbar(fbuf, 50, 50, 8, 30, power/300, draw.BLACK)
draw.vbar(fbuf, 40, 50, 8, 30, volts/50, draw.BLACK)
draw.curved_bar(fbuf, x=120, y=120, r1=105, r2=110,
theta1=4, theta2=6, level=level,
c=color, n=15)
display.draw_RGB565(buf)
i = 0
while True:
i += 1
power = i % 90 + 200
volts = 24.1 + (i % 10)/10
amps = power / volts
draw_ui(fbuf, power, amps, volts)
gc.collect()
# Note: Because of the way the webassembly port works, this code is actually running like an asyncio thread
# This call to asyncio.sleep yields back to the JS event loop and gives the browser a chance to update the display.
# This is not needed on a real device.
# There is way to make it so that a bare time.sleep() will work but it requires emcripten's ASYNCIFY feature
# Which apparently kills performance. See https://github.com/tomhodson/micropython/commit/2fa6373d226b65f977486ecda32b8786cd1dceed
await asyncio.sleep(0.1)

File diff suppressed because one or more lines are too long

View File

@ -5,24 +5,46 @@ class USBCPowerSupplySimulator extends HTMLElement {
stderr: stdoutWriter,
});
let col565_to_rgb = (h, l) => {
const r = (((h >>> 3) & 0b11111) * 255) / 31; // take top 5 bits and rescale so range 0-31 becomes 0-255
// Take bottom three bits of high byte, upper 3 bits of low, and scale from 0-63 to 0-255
// '>>>' rights shifts without sign extension
const g = ((((h & 0b111) << 3) | (l >>> 5)) * 255) / 63;
const b = ((l & 0b11111) * 255) / 31; // lower 5 bits moved to the top
return [r, g, b];
};
mp.registerJsModule("console", { log: (s) => console.log(s) });
mp.registerJsModule("display", {
draw: (buf) => {
draw_GS8: (buf) => {
let bytes = [...buf].flatMap((x) => [x, x, x, 255]);
let image = new ImageData(new Uint8ClampedArray(bytes), 240, 240);
ctx.putImageData(image, 0, 0);
},
draw_RGB565: (buf) => {
const bytes_565 = new Uint8ClampedArray([...buf]);
const bytes_rgb = new Uint8ClampedArray(240 * 240 * 4);
for (let i = 0; i < 240 * 240; i++) {
const [r, g, b] = col565_to_rgb(
bytes_565[i * 2],
bytes_565[i * 2 + 1]
);
bytes_rgb[i * 4] = r;
bytes_rgb[i * 4 + 1] = g;
bytes_rgb[i * 4 + 2] = b;
bytes_rgb[i * 4 + 3] = 255;
}
let image = new ImageData(bytes_rgb, 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();
async init_editor(initial_code) {
// Create an initial state for the view
const initialState = cm6.createEditorState(program);
const initialState = cm6.createEditorState(initial_code);
let view = cm6.createEditorView(
initialState,
this.shadow.getElementById("editor")
@ -31,18 +53,33 @@ class USBCPowerSupplySimulator extends HTMLElement {
}
async setup() {
const editor_disabled = this.hasAttribute("disable-editor");
const console_disabled = this.hasAttribute("disable-console");
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 stdoutWriter = console_disabled
? console.log
: (line) => {
mp_console.innerText += line + "\n";
mp_console.scrollTo(0, mp_console.scrollHeight);
};
if (console_disabled) mp_console.style.display = "none";
const view = await this.init_editor();
let initial_code = "";
if (this.hasAttribute("code")) {
const resp = await fetch(this.getAttribute("code"));
if (resp.ok) initial_code = await resp.text();
}
if (!initial_code) initial_code = 'print("Hello, World!")';
const view = editor_disabled
? { state: { doc: initial_code } }
: await this.init_editor(initial_code);
const runPython = async () => {
mp_console.innerText = "";
@ -56,6 +93,7 @@ class USBCPowerSupplySimulator extends HTMLElement {
const run_button = this.shadow.getElementById("run");
run_button.onclick = runPython;
if (editor_disabled) run_button.style.display = "none";
runPython();
}
@ -71,11 +109,9 @@ class USBCPowerSupplySimulator extends HTMLElement {
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;