mirror of
https://github.com/TomHodson/tomhodson.github.com.git
synced 2025-06-26 10:01:18 +02:00
Add micropython
This commit is contained in:
parent
90e4ba94c3
commit
2cd8a7a27e
@ -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>
|
||||||
|
33
_posts/2025-07-20-micropython-simulator.md
Normal file
33
_posts/2025-07-20-micropython-simulator.md
Normal 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>
|
@ -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>
|
||||||
|
@ -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
BIN
assets/blog/kicad/kicad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 285 KiB |
@ -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)
|
||||||
|
101
assets/blog/micropython/example_micropython_color.py
Normal file
101
assets/blog/micropython/example_micropython_color.py
Normal 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)
|
BIN
assets/blog/micropython/simulated_display.png
Normal file
BIN
assets/blog/micropython/simulated_display.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
1904
assets/js/kicanvas.js
Normal file
1904
assets/js/kicanvas.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user