micropython: add micropython component
This commit is contained in:
126
components/language/micropython/examples/hwapi/README.md
Normal file
126
components/language/micropython/examples/hwapi/README.md
Normal 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!
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
@@ -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)
|
38
components/language/micropython/examples/hwapi/soft_pwm.py
Normal file
38
components/language/micropython/examples/hwapi/soft_pwm.py
Normal 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)
|
@@ -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()
|
@@ -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))
|
Reference in New Issue
Block a user