Skip to content

Commit 8f068e8

Browse files
committed
examples/hwapi: Example showing best practices for HW API usage in apps.
Showing and providing detailed instructions and motivation.
1 parent 5c3d75c commit 8f068e8

4 files changed

Lines changed: 174 additions & 0 deletions

File tree

examples/hwapi/README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
This directory shows the best practices for using MicroPython hardware API
2+
(`machine` module). `machine` module strives to provide consistent API
3+
across various boards, with the aim to enable writing portable applications,
4+
which would work from a board to board, from a system to another systems.
5+
This is inherently a hard problem, because hardware is different from one
6+
board type to another, and even from examplar of board to another. For
7+
example, if your app requires an external LED, one user may connect it
8+
to one GPIO pin, while another user may find it much more convinient to
9+
use another pin. This of course applies to relays, buzzers, sensors, etc.
10+
11+
With complications above in mind, it's still possible to write portable
12+
applications by using "low[est] denominator" subset of hardware API and
13+
following simple rules outlined below. The applications won't be able
14+
to rely on advanced hardware capabilities of a particular board and
15+
will be limited to generic capabilities, but it's still possible to
16+
write many useful applications in such a way, with the obvious benefit of
17+
"write once - run everywhere" approach (only configuration for a particular
18+
board is required).
19+
20+
The key to this approach is splitting your application into (at least)
21+
2 parts:
22+
23+
* main application logic
24+
* hardware configuration
25+
26+
The key point is that hardware configuration should be a separate file
27+
(module in Python terms). A good name would be `hwconfig.py`, and that's
28+
how we'll call it from now on. Another key point is that main application
29+
should never instantiate (construct) hardware objects directly. Instead,
30+
they should be defined in `hwconfig.py`, and main application should
31+
import and reference hardware objects via this module. The simplest
32+
application of this idea would look like:
33+
34+
`hwconfig.py`:
35+
36+
from machine import Pin
37+
38+
LED = Pin("A3", Pin.OUT)
39+
40+
`app.py`:
41+
42+
from hwconfig import *
43+
import utime
44+
45+
while True:
46+
LED.value(1)
47+
utime.sleep_ms(500)
48+
LED.value(0)
49+
utime.sleep_ms(500)
50+
51+
52+
To deploy this application to a particular board, a user will need:
53+
54+
1. Edit `hwconfig.py` to adjust Pin and other hardware peripheral
55+
parameters and locations.
56+
2. Actually deploy `hwconfig.py` and `app.py` to a board (e.g. copy to
57+
board's filesystem, or build new firmware with these modules frozen
58+
into it).
59+
60+
Note that there's no need to edit the main application code! (Which may
61+
be complex, while `hwconfig.py` should usually remain short enough, and
62+
focused solely on hardware configuration).
63+
64+
An obvious improvement to this approach is the following. There're few
65+
well-known boards which run MicroPython, and most of them include an
66+
onboard LED. So, to help users of these boards to do configuration
67+
quickly (that's especially important for novice users, for who may
68+
be stumped by the need to reach out to a board reference to find LED
69+
pin assignments), `hwconfig.py` your application ships may include
70+
commented out sections with working configurations for different
71+
boards. The step 1 above then will be:
72+
73+
1. Look thru `hwconfig.py` to find a section which either exactly
74+
matches your board, or the closest to it. Uncomment, and if any
75+
adjustments required, apply them.
76+
77+
It's important to keep in mind that adjustments may be always required,
78+
and that there may be users whose configuration doesn't match any of
79+
the available. So, always include a section or instructions for them.
80+
Consider for example that even on a supported board, user may want to
81+
blink not an on-board LED, but the one they connected externally.
82+
MicroPython's Hardware API offers portability not just among "supported"
83+
boards, but to any board at all, so make sure users can enjoy it.
84+
85+
There's next step of improvement to make. While having one `hwconfig.py`
86+
with many sections would work for smaller projects with few hardware
87+
objects, it may become more cumbersome to maintain both on programmer's
88+
and user's sides for larger projects. Then instead of single
89+
`hwconfig.py` file, you can provide few "template" ones for well-known
90+
boards:
91+
92+
* `hwconfig_pyboard.py`
93+
* `hwconfig_wipy.py`
94+
* `hwconfig_esp8266.py`
95+
* etc.
96+
97+
Then step 1 above will be:
98+
99+
1. Look thru available `hwconfig_*.py` files and find one which matches
100+
your board the best, then rename to `hwconfig.py` and make adjustments,
101+
if any.
102+
103+
Again, please keep in mind that there may be users whose hardware will be
104+
completely unlike you heard of. Give them some helpful hints too, perhaps
105+
provide `hwconfig_custom.py` with some instructions.
106+
107+
That's where we stop with improvements to the "separate file for hardware
108+
configuration" idea, as it is already pretty flexible and viable. An
109+
application in this directory shows it in practice, using slightly less
110+
trivial example than just a blinking LED: `soft_pwm.py` implements a
111+
software PWM (pulse width modulation) to produce an LED fade-in/fade-out
112+
effect - without any dependence on hardware PWM availability.
113+
114+
Note that improvements to board configuration handling may continue further.
115+
For example, one may invent a "configuration manager" helper module which will
116+
try to detect current board (among well-known ones), and load appropriate
117+
`hwconfig_*.py` - this assumes that a user would lazily deploy them all
118+
(or that application will be automatically installed, e.g. using MicroPython's
119+
`upip` package manager). The key point in this case remains the same as
120+
elaborated above - always assume there can, and will be a custom configuration,
121+
and it should be well supported. So, any automatic detection should be
122+
overridable by a user, and instructions how to do so are among the most
123+
important you may provide for your application.
124+
125+
By following these best practices, you will use MicroPython at its full
126+
potential, and let users enjoy it too. Good luck!
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from machine import Pin
2+
3+
# ESP12 module as used by many boards
4+
# Blue LED on pin 2
5+
LED = Pin(2, Pin.OUT)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from machine import Pin
2+
3+
# Freescale/NXP FRDM-K64F board
4+
# Blue LED on port B, pin 21
5+
LED = Pin(("GPIO_1", 21), Pin.OUT)

examples/hwapi/soft_pwm.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import utime
2+
from hwconfig import LED
3+
4+
5+
# Using sleep_ms() gives pretty poor PWM resolution and
6+
# brightness control, but we use it in the attempt to
7+
# make this demo portable to even more boards (e.g. to
8+
# those which don't provide sleep_us(), or provide, but
9+
# it's not precise, like would be on non realtime OSes).
10+
# We otherwise use 20ms period, to make frequency not less
11+
# than 50Hz to avoid visible flickering (you may still see
12+
# if you're unlucky).
13+
def pwm_cycle(led, duty, cycles):
14+
duty_off = 20 - duty
15+
for i in range(cycles):
16+
if duty:
17+
led.value(1)
18+
utime.sleep_ms(duty)
19+
if duty_off:
20+
led.value(0)
21+
utime.sleep_ms(duty_off)
22+
23+
24+
# At the duty setting of 1, an LED is still pretty bright, then
25+
# at duty 0, it's off. This makes rather unsmooth transition, and
26+
# breaks fade effect. So, we avoid value of 0 and oscillate between
27+
# 1 and 20. Actually, highest values like 19 and 20 are also
28+
# barely distinguishible (like, both of them too bright and burn
29+
# your eye). So, improvement to the visible effect would be to use
30+
# more steps (at least 10x), and then higher frequency, and use
31+
# range which includes 1 but excludes values at the top.
32+
while True:
33+
# Fade in
34+
for i in range(1, 21):
35+
pwm_cycle(LED, i, 2)
36+
# Fade out
37+
for i in range(20, 0, -1):
38+
pwm_cycle(LED, i, 2)

0 commit comments

Comments
 (0)