micropython/examples/hwapi/README.md

5.7 KiB

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!