Advanced Programming in MicroPython: A Comprehensive Guide with Examples

MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimized to run on microcontrollers and constrained systems. Advanced programming in MicroPython allows developers to harness the full potential of these embedded devices, enabling complex tasks such as sensor data processing, motor control, and wireless communication. In this blog post, we will explore the fundamental concepts, usage methods, common practices, and best practices of advanced programming in MicroPython through practical examples.

Table of Contents#

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts#

Asynchronous Programming#

Asynchronous programming allows multiple tasks to run concurrently without blocking the execution of the main program. In MicroPython, the uasyncio library provides support for asynchronous programming.

import uasyncio as asyncio
 
async def task1():
    while True:
        print("Task 1 is running")
        await asyncio.sleep(1)
 
async def task2():
    while True:
        print("Task 2 is running")
        await asyncio.sleep(2)
 
async def main():
    task_a = asyncio.create_task(task1())
    task_b = asyncio.create_task(task2())
    await asyncio.gather(task_a, task_b)
 
asyncio.run(main())

Interrupt Handling#

Interrupts are used to handle external events such as button presses or sensor readings without constantly polling the hardware. In MicroPython, you can use the Pin.irq() method to set up interrupt handlers.

from machine import Pin
 
def button_callback(pin):
    print("Button pressed:", pin)
 
button = Pin(0, Pin.IN, Pin.PULL_UP)
button.irq(trigger=Pin.IRQ_FALLING, handler=button_callback)

Memory Management#

Memory management is crucial in MicroPython due to the limited resources available on microcontrollers. You can use the gc module to manually trigger the garbage collector and free up memory.

import gc
 
# Manually trigger the garbage collector
gc.collect()
 
# Check the amount of free memory
free_memory = gc.mem_free()
print("Free memory:", free_memory)

Usage Methods#

Working with Sensors#

MicroPython makes it easy to interface with various sensors such as temperature sensors or accelerometers. Here is an example of reading data from a DHT11 temperature and humidity sensor.

import dht
from machine import Pin
 
sensor = dht.DHT11(Pin(4))
 
try:
    sensor.measure()
    temperature = sensor.temperature()
    humidity = sensor.humidity()
    print("Temperature: {}°C, Humidity: {}%".format(temperature, humidity))
except OSError as e:
    print('Failed to read sensor.')

Controlling Actuators#

You can use MicroPython to control actuators such as motors or LEDs. Here is an example of controlling an LED using Pulse Width Modulation (PWM).

from machine import Pin, PWM
 
led = PWM(Pin(2))
led.freq(1000)
 
# Set the duty cycle to control the brightness
led.duty(512)

Wireless Communication#

MicroPython supports various wireless communication protocols such as Wi-Fi or Bluetooth. Here is an example of connecting to a Wi-Fi network.

import network
 
# Connect to Wi-Fi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('your_SSID', 'your_PASSWORD')
 
while not wlan.isconnected():
    pass
 
print('Network config:', wlan.ifconfig())

Common Practices#

Modular Programming#

Modular programming involves breaking your code into smaller, reusable modules. This makes your code more organized and easier to maintain. Here is an example of creating a simple module for reading sensor data.

# sensor_module.py
import dht
from machine import Pin
 
def read_dht11(pin):
    sensor = dht.DHT11(Pin(pin))
    try:
        sensor.measure()
        temperature = sensor.temperature()
        humidity = sensor.humidity()
        return temperature, humidity
    except OSError as e:
        return None, None
 
 
# main.py
from sensor_module import read_dht11
 
temperature, humidity = read_dht11(4)
if temperature is not None and humidity is not None:
    print("Temperature: {}°C, Humidity: {}%".format(temperature, humidity))
else:
    print('Failed to read sensor.')

Error Handling#

Proper error handling is essential to ensure the stability of your MicroPython programs. You can use try-except blocks to catch and handle exceptions.

try:
    # Code that may raise an exception
    result = 1 / 0
except ZeroDivisionError:
    print("Error: Division by zero.")

Logging#

Logging is a useful technique for debugging and monitoring the behavior of your MicroPython programs. You can use the print() function for simple logging or implement a more advanced logging system.

import time
 
def log(message):
    timestamp = time.localtime()
    log_message = "[{}] {}".format(time.strftime("%Y-%m-%d %H:%M:%S", timestamp), message)
    print(log_message)
 
log("Starting the program...")

Best Practices#

Code Optimization#

To optimize your MicroPython code, you can use techniques such as reducing the number of function calls, minimizing the use of global variables, and using more efficient data structures.

# Inefficient code
def add_numbers():
    numbers = [1, 2, 3, 4, 5]
    total = 0
    for num in numbers:
        total = total + num
    return total
 
# Optimized code
numbers = [1, 2, 3, 4, 5]
total = sum(numbers)

Testing and Debugging#

Testing and debugging are essential steps in the development process. You can use the print() function to debug your code or use a debugger if your development environment supports it.

# Debugging example
x = 5
y = 10
print("x:", x)
print("y:", y)
result = x + y
print("Result:", result)

Security Considerations#

When working with MicroPython, it is important to consider security aspects such as protecting sensitive data and preventing unauthorized access. You should use secure communication protocols and avoid hardcoding sensitive information in your code.

# Avoid hardcoding sensitive information
# Bad practice
wifi_ssid = "your_SSID"
wifi_password = "your_PASSWORD"
 
# Good practice
import ubinascii
import machine
 
# Store sensitive information in a secure location
key = machine.unique_id()
encrypted_ssid = ubinascii.hexlify(key)
encrypted_password = ubinascii.hexlify(key[::-1])

Conclusion#

Advanced programming in MicroPython offers a wide range of possibilities for developing embedded systems. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can write efficient, reliable, and secure code for your microcontroller projects. Whether you are working with sensors, actuators, or wireless communication, MicroPython provides a powerful and flexible platform for your development needs.

References#