Add micropython

This commit is contained in:
Tom 2024-09-09 10:17:01 +02:00
parent 90e4ba94c3
commit 2cd8a7a27e
9 changed files with 2123 additions and 74 deletions

View File

@ -7,7 +7,7 @@
width=128 height=128> width=128 height=128>
</a> </a>
<section> <section class = "text">
<section class="title-date-container"> <section class="title-date-container">
<h2 class="p-name blogroll-title"><a class="u-uid u-url" href="{{ post.url }}">{{ post.title }}</a></h2> <h2 class="p-name blogroll-title"><a class="u-uid u-url" href="{{ post.url }}">{{ post.title }}</a></h2>
<time class="dt-modified" datetime="{{ post.last_modified_at | date_to_xmlschema }}">{{ page.last_modified_at | date: '%b %Y' }}</time> <time class="dt-modified" datetime="{{ post.last_modified_at | date_to_xmlschema }}">{{ page.last_modified_at | date: '%b %Y' }}</time>

View File

@ -0,0 +1,33 @@
---
title: MicroPython Simulator
layout: post
excerpt: Embedded Programming is fun again!
image: /assets/blog/micropython/simulated_display.png
thumbnail: /assets/blog/micropython/simulated_display.png
assets: /assets/blog/micropython
alt: A simulator for my USB C Power supply project using Micropython running in websassembly.
head: |
<script src="/assets/blog/micropython/micropython.min.mjs" type="module"></script>
---
This simulator lets me quickly try out micropython code drawing to a 240x240 pixel color lcd display. *
This particular display uses 5, 6 and 5 bits for each channel, respectively. The raw pixel data gets passed from micropython to javascript where it gets converted to normal RGB before being blitted to the `<canvas>` tag. Under the hood it uses the fact that the micropython VM supports being compiled to WASM.
I'm using a ttf font called [gunship](https://www.iconian.com/g.html) converted to bitmap format and frozen into the firmware along with other library code.
Building the code looks like:
```sh
cd /ports/webassembly
make min FROZEN_MANIFEST=/path/to/custom/manifest.py
cp ...wasm and ....min.mjs to your webserver directory
```
<script src="{{page.assets}}/cm6.bundle.min.js"></script>
<script src="{{page.assets}}/simulator.js" type = "module"></script>
<usbc-power-supply-simulator code="{{page.assets}}/demo.py" ></usbc-power-supply-simulator>

View File

@ -24,7 +24,11 @@ head: |
} }
</script> </script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script> <script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
<script type="module" src="/assets/js/kicanvas.js"></script>
--- ---
{% include mastodon_post.html post_id = "111813225328398667" %} {% include mastodon_post.html post_id = "111813225328398667" %}
{% include mastodon_post.html post_id = "111816310882560850" %} {% include mastodon_post.html post_id = "111816310882560850" %}
<kicanvas-embed src="https://raw.githubusercontent.com/TomHodson/pcbs/main/usb-c%20psu/usb-c%20psu.kicad_sch" controls="basic"> </kicanvas-embed>

View File

@ -24,4 +24,8 @@ article.project {
padding: 0; padding: 0;
justify-content: center; justify-content: center;
} }
section.text {
width: 100%;
}
} }

BIN
assets/blog/kicad/kicad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

View File

@ -1,62 +1,15 @@
import asyncio import asyncio
import gc import gc
import struct
import sys import sys
from array import array
import console import console
import display import display
import drawing as draw
import fonts import fonts
import framebuf import framebuf
WHITE = 255 WHITE = 255
BLACK = 0
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( print(
"This is μPython saying hello from the on page console!\n" "This is μPython saying hello from the on page console!\n"
@ -74,40 +27,90 @@ w, h = 240, 240
buf = bytearray(w * h) buf = bytearray(w * h)
fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS8) fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS8)
mode = "night" # "night"
bg_color = WHITE if mode == "day" else BLACK
text_color = BLACK if mode == "day" else WHITE
def hbar(fbuf, x, y, w, h, level, c=BLACK):
fbuf.rect(x, y, w, h, c)
fbuf.rect(x, y, int(w*level), h, c, True)
def vbar(fbuf, x, y, w, h, level, c=BLACK):
fbuf.rect(x, y, w, h, c)
fbuf.rect(x, y, w, int(h*level), c, True)
def curved_bar(fbuf, x, y, r1, r2, theta1, theta2, level, c=0, n = 10):
dtheta = theta2 - theta1
w = draw.wedge(r1, r2, theta1, theta2, n)
fbuf.poly(x, y, w, c, False)
w2 = draw.wedge(r1, r2, theta1, theta1 + dtheta*level, n)
fbuf.poly(x, y, w2, c, True)
i = 0 i = 0
while True: while True:
i += 1 i += 1
fbuf.rect(0, 0, 240, 240, 255, True) fbuf.fill(255)
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( quads = 0b1010 if (i % 10) < 5 else 0b0101
r1, r2 = 120, 115
fbuf.ellipse(120, 120, r1, r1, 0, True, quads)
fbuf.ellipse(120, 120, r2, r2, 255, True)
power = i % 90 + 200
volts = 24.1 + (i % 10)/10
amps = power / volts
x, y = draw.display_with_units(
fbuf, fbuf,
"mW", 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, x,
145, 175,
255, text_color,
font=fonts.gunship30, bg=bg_color,
ha="left", mainfont=fonts.gunship30,
va="bottom", subfont=fonts.gunship20,
bg=0,
) )
display.draw(buf) w = 100
x = 120 - w//2
hbar(fbuf, x, 50, w, 10, power / 300)
vbar(fbuf, 50, 50, 8, 30, power/300)
vbar(fbuf, 40, 50, 8, 30, volts/50)
curved_bar(fbuf, x=120, y=120, r1=105, r2=110,
theta1=4, theta2=6, level=power/300,
c=0, n=15)
display.draw_GS8(buf)
print(gc.mem_free())
gc.collect()
# Note: Because of the way the webassembly port works, this code is actually running like an asyncio thread # Note: Because of the way the webassembly port works, this code is actually running like an asyncio thread
# This call to asyncio.sleep yeilds back to the JS event loop and gives the browser a chance to update the display. # This call to asyncio.sleep yeilds back to the JS event loop and gives the browser a chance to update the display.
# This is not needed on a real device. # 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 # 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 # Which apparently kills performance. See https://github.com/tomhodson/micropython/commit/2fa6373d226b65f977486ecda32b8786cd1dceed
await asyncio.sleep(0.2) await asyncio.sleep(0.1)

View File

@ -0,0 +1,101 @@
import asyncio
import gc
import sys
import console
import display
import drawing as draw
import fonts
import framebuf
print(
"This is μPython saying hello from the on page console!\n"
f"Version {sys.version}"
)
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 * 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.RED if mode == "day" else draw.WHITE
def draw_ui(fbuf, power, amps, volts):
fbuf.fill(bg_color)
quads = 0b1010 if (i % 10) < 5 else 0b0101
r1, r2 = 120, 115
fbuf.ellipse(120, 120, r1, r1, draw.BLUE, 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.MAGENTA)
draw.vbar(fbuf, 50, 50, 8, 30, power/300, draw.YELLOW)
draw.vbar(fbuf, 40, 50, 8, 30, volts/50, draw.CYAN)
draw.curved_bar(fbuf, x=120, y=120, r1=105, r2=110,
theta1=4, theta2=6, level=power/300,
c=draw.GREEN, 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)
print(gc.mem_free())
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

1904
assets/js/kicanvas.js Normal file

File diff suppressed because one or more lines are too long