micropython: add micropython component

This commit is contained in:
KY-zhang-X
2022-09-29 12:10:37 +08:00
parent 1514f1cb9b
commit dd76146324
2679 changed files with 354110 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
This directory shows the best practices for using MicroPython hardware API
(`machine` module). `machine` module strives to provide consistent API
across various boards, with the aim to enable writing portable applications,
which would work from a board to board, from a system to another systems.
This is inherently a hard problem, because hardware is different from one
board type to another, and even from examplar of board to another. For
example, if your app requires an external LED, one user may connect it
to one GPIO pin, while another user may find it much more convinient to
use another pin. This of course applies to relays, buzzers, sensors, etc.
With complications above in mind, it's still possible to write portable
applications by using "low[est] denominator" subset of hardware API and
following simple rules outlined below. The applications won't be able
to rely on advanced hardware capabilities of a particular board and
will be limited to generic capabilities, but it's still possible to
write many useful applications in such a way, with the obvious benefit of
"write once - run everywhere" approach (only configuration for a particular
board is required).
The key to this approach is splitting your application into (at least)
2 parts:
* main application logic
* hardware configuration
The key point is that hardware configuration should be a separate file
(module in Python terms). A good name would be `hwconfig.py`, and that's
how we'll call it from now on. Another key point is that main application
should never instantiate (construct) hardware objects directly. Instead,
they should be defined in `hwconfig.py`, and main application should
import and reference hardware objects via this module. The simplest
application of this idea would look like:
`hwconfig.py`:
from machine import Pin
LED = Pin("A3", Pin.OUT)
`app.py`:
from hwconfig import *
import utime
while True:
LED.value(1)
utime.sleep_ms(500)
LED.value(0)
utime.sleep_ms(500)
To deploy this application to a particular board, a user will need:
1. Edit `hwconfig.py` to adjust Pin and other hardware peripheral
parameters and locations.
2. Actually deploy `hwconfig.py` and `app.py` to a board (e.g. copy to
board's filesystem, or build new firmware with these modules frozen
into it).
Note that there's no need to edit the main application code! (Which may
be complex, while `hwconfig.py` should usually remain short enough, and
focused solely on hardware configuration).
An obvious improvement to this approach is the following. There're few
well-known boards which run MicroPython, and most of them include an
onboard LED. So, to help users of these boards to do configuration
quickly (that's especially important for novice users, for who may
be stumped by the need to reach out to a board reference to find LED
pin assignments), `hwconfig.py` your application ships may include
commented out sections with working configurations for different
boards. The step 1 above then will be:
1. Look thru `hwconfig.py` to find a section which either exactly
matches your board, or the closest to it. Uncomment, and if any
adjustments required, apply them.
It's important to keep in mind that adjustments may be always required,
and that there may be users whose configuration doesn't match any of
the available. So, always include a section or instructions for them.
Consider for example that even on a supported board, user may want to
blink not an on-board LED, but the one they connected externally.
MicroPython's Hardware API offers portability not just among "supported"
boards, but to any board at all, so make sure users can enjoy it.
There's next step of improvement to make. While having one `hwconfig.py`
with many sections would work for smaller projects with few hardware
objects, it may become more cumbersome to maintain both on programmer's
and user's sides for larger projects. Then instead of single
`hwconfig.py` file, you can provide few "template" ones for well-known
boards:
* `hwconfig_pyboard.py`
* `hwconfig_wipy.py`
* `hwconfig_esp8266.py`
* etc.
Then step 1 above will be:
1. Look thru available `hwconfig_*.py` files and find one which matches
your board the best, then rename to `hwconfig.py` and make adjustments,
if any.
Again, please keep in mind that there may be users whose hardware will be
completely unlike you heard of. Give them some helpful hints too, perhaps
provide `hwconfig_custom.py` with some instructions.
That's where we stop with improvements to the "separate file for hardware
configuration" idea, as it is already pretty flexible and viable. An
application in this directory shows it in practice, using slightly less
trivial example than just a blinking LED: `soft_pwm.py` implements a
software PWM (pulse width modulation) to produce an LED fade-in/fade-out
effect - without any dependence on hardware PWM availability.
Note that improvements to board configuration handling may continue further.
For example, one may invent a "configuration manager" helper module which will
try to detect current board (among well-known ones), and load appropriate
`hwconfig_*.py` - this assumes that a user would lazily deploy them all
(or that application will be automatically installed, e.g. using MicroPython's
`upip` package manager). The key point in this case remains the same as
elaborated above - always assume there can, and will be a custom configuration,
and it should be well supported. So, any automatic detection should be
overridable by a user, and instructions how to do so are among the most
important you may provide for your application.
By following these best practices, you will use MicroPython at its full
potential, and let users enjoy it too. Good luck!

View File

@@ -0,0 +1,9 @@
import utime
from hwconfig import LED, BUTTON
# Light LED when (and while) a BUTTON is pressed
while 1:
LED.value(BUTTON.value())
# Don't burn CPU
utime.sleep_ms(10)

View File

@@ -0,0 +1,21 @@
import utime
import machine
from hwconfig import LED, BUTTON
# machine.time_pulse_us() function demo
print(
"""\
Let's play an interesting game:
You click button as fast as you can, and I tell you how slow you are.
Ready? Cliiiiick!
"""
)
while 1:
delay = machine.time_pulse_us(BUTTON, 1, 10 * 1000 * 1000)
if delay < 0:
print("Well, you're *really* slow")
else:
print("You are as slow as %d microseconds!" % delay)
utime.sleep_ms(10)

View File

@@ -0,0 +1,18 @@
# This is hwconfig for "emulation" for cases when there's no real hardware.
# It just prints information to console.
class LEDClass:
def __init__(self, id):
self.id = "LED(%d):" % id
def value(self, v):
print(self.id, v)
def on(self):
self.value(1)
def off(self):
self.value(0)
LED = LEDClass(1)
LED2 = LEDClass(12)

View File

@@ -0,0 +1,22 @@
from machine import Pin, Signal
# 96Boards/Qualcomm DragonBoard 410c
#
# By default, on-board LEDs are controlled by kernel LED driver.
# To make corresponding pins be available as normal GPIO,
# corresponding driver needs to be unbound first (as root):
# echo -n "soc:leds" >/sys/class/leds/apq8016-sbc:green:user1/device/driver/unbind
# Note that application also either should be run as root, or
# /sys/class/gpio ownership needs to be changed.
# Likewise, onboard buttons are controlled by gpio_keys driver.
# To release corresponding GPIOs:
# echo -n "gpio_keys" >/sys/class/input/input1/device/driver/unbind
# User LED 1 on gpio21
LED = Signal(Pin(21, Pin.OUT))
# User LED 2 on gpio120
LED2 = Signal(Pin(120, Pin.OUT))
# Button S3 on gpio107
BUTTON = Pin(107, Pin.IN)

View File

@@ -0,0 +1,5 @@
from machine import Pin, Signal
# ESP12 module as used by many boards
# Blue LED on pin 2, active low (inverted)
LED = Signal(2, Pin.OUT, invert=True)

View File

@@ -0,0 +1,13 @@
from machine import Pin, Signal
# Red LED on pin LED_RED also kown as A13
LED = Signal("LED_RED", Pin.OUT)
# Green LED on pin LED_GREEN also known as A14
LED2 = Signal("LED_GREEN", Pin.OUT)
# Yellow LED on pin LED_YELLOW also known as A15
LED3 = Signal("LED_YELLOW", Pin.OUT)
# Blue LED on pin LED_BLUE also known as B4
LED4 = Signal("LED_BLUE", Pin.OUT)

View File

@@ -0,0 +1,9 @@
from machine import Signal
# 96Boards Carbon board
# USR1 - User controlled led, connected to PD2
# USR2 - User controlled led, connected to PA15
# BT - Bluetooth indicator, connected to PB5.
# Note - 96b_carbon uses (at the time of writing) non-standard
# for Zephyr port device naming convention.
LED = Signal(("GPIOA", 15), Pin.OUT)

View File

@@ -0,0 +1,5 @@
from machine import Pin, Signal
# Freescale/NXP FRDM-K64F board
# Blue LED on port B, pin 21
LED = Signal(("GPIO_1", 21), Pin.OUT)

View File

@@ -0,0 +1,38 @@
import utime
from hwconfig import LED
# Using sleep_ms() gives pretty poor PWM resolution and
# brightness control, but we use it in the attempt to
# make this demo portable to even more boards (e.g. to
# those which don't provide sleep_us(), or provide, but
# it's not precise, like would be on non realtime OSes).
# We otherwise use 20ms period, to make frequency not less
# than 50Hz to avoid visible flickering (you may still see
# if you're unlucky).
def pwm_cycle(led, duty, cycles):
duty_off = 20 - duty
for i in range(cycles):
if duty:
led.on()
utime.sleep_ms(duty)
if duty_off:
led.off()
utime.sleep_ms(duty_off)
# At the duty setting of 1, an LED is still pretty bright, then
# at duty 0, it's off. This makes rather unsmooth transition, and
# breaks fade effect. So, we avoid value of 0 and oscillate between
# 1 and 20. Actually, highest values like 19 and 20 are also
# barely distinguishible (like, both of them too bright and burn
# your eye). So, improvement to the visible effect would be to use
# more steps (at least 10x), and then higher frequency, and use
# range which includes 1 but excludes values at the top.
while True:
# Fade in
for i in range(1, 21):
pwm_cycle(LED, i, 2)
# Fade out
for i in range(20, 0, -1):
pwm_cycle(LED, i, 2)

View File

@@ -0,0 +1,31 @@
# Like soft_pwm_uasyncio.py, but fading 2 LEDs with different phase.
# Also see original soft_pwm.py.
import uasyncio
from hwconfig import LED, LED2
async def pwm_cycle(led, duty, cycles):
duty_off = 20 - duty
for i in range(cycles):
if duty:
led.value(1)
await uasyncio.sleep_ms(duty)
if duty_off:
led.value(0)
await uasyncio.sleep_ms(duty_off)
async def fade_in_out(LED):
while True:
# Fade in
for i in range(1, 21):
await pwm_cycle(LED, i, 2)
# Fade out
for i in range(20, 0, -1):
await pwm_cycle(LED, i, 2)
loop = uasyncio.get_event_loop()
loop.create_task(fade_in_out(LED))
loop.call_later_ms(800, fade_in_out(LED2))
loop.run_forever()

View File

@@ -0,0 +1,28 @@
# See original soft_pwm.py for detailed comments.
import uasyncio
from hwconfig import LED
async def pwm_cycle(led, duty, cycles):
duty_off = 20 - duty
for i in range(cycles):
if duty:
led.value(1)
await uasyncio.sleep_ms(duty)
if duty_off:
led.value(0)
await uasyncio.sleep_ms(duty_off)
async def fade_in_out(LED):
while True:
# Fade in
for i in range(1, 21):
await pwm_cycle(LED, i, 2)
# Fade out
for i in range(20, 0, -1):
await pwm_cycle(LED, i, 2)
loop = uasyncio.get_event_loop()
loop.run_until_complete(fade_in_out(LED))