"Subroutines defined. Encapsulating logic into modular, reusable blocks to optimize memory and keep the system architecture clean."
Writing code line-by-line is fine for a quick test, but complex firmware requires modularity. Functions (or subroutines) allow you to write a block of logic once and call it infinitely. This adheres to the DRY (Don't Repeat Yourself) principle, saving precious flash memory on your microcontroller and making your code infinitely easier to debug.
In MicroPython, functions are defined using the def keyword, followed by the function name, parentheses, and a colon.
A function can take inputs, perform operations, and return an output.
- IoT Pro-Tip: MicroPython functions can easily return multiple values (as a tuple). This is incredibly useful for sensors that read multiple metrics at once.
# main.py - Basic Function
def read_dht_sensor():
# Simulated sensor logic
temp = 24.5
humidity = 60
return temp, humidity # Returns a tuple: (24.5, 60)
# Unpacking the returned values
current_temp, current_hum = read_dht_sensor()Functions become powerful when you pass data into them. MicroPython supports several ways to handle arguments.
The most common type. The order in which you pass the arguments matters.
def set_led_color(red, green, blue):
print(f"Setting color to R:{red} G:{green} B:{blue}")
set_led_color(255, 0, 128) # Order is strictly R, G, BYou can assign default values to parameters. If the caller doesn't provide them, the function uses the defaults. Perfect for configuration settings.
def connect_wifi(ssid, retries=5, timeout=10):
print(f"Connecting to {ssid}. Max retries: {retries}")
connect_wifi("CyberNet") # Uses default retries (5)
connect_wifi("CyberNet", retries=10) # Overrides default retriesSometimes you don't know how many items you need to process.
*args: Gathers remaining positional arguments into a Tuple. Great for processing a dynamic list of sensor pins.**kwargs: Gathers remaining keyword arguments into a Dictionary. Great for passing dynamic JSON configuration payloads.
def log_sensor_data(sensor_id, *readings, **metadata):
print(f"ID: {sensor_id}")
print(f"Data: {readings}") # Tuple of all unnamed arguments
print(f"Meta: {metadata}") # Dict of all named arguments
log_sensor_data("TEMP_01", 22.1, 22.4, 22.5, location="Server Room", status="Active")Variables created inside a function are local (they die when the function ends to save RAM). Variables created outside are global.
If a function needs to modify a global variable (like a system-wide state flag or an interrupt counter), you must explicitly use the global keyword.
system_armed = False
def toggle_alarm():
global system_armed # Pull the global variable into this function's scope
system_armed = not system_armedA lambda is a small, one-line function without a name. In embedded programming, these are frequently used as quick "callback" functions for hardware timers or button interrupts where defining a full def block is overkill.
# Syntax: lambda arguments: expression
# Normal function:
# def square(x): return x * x
# Lambda equivalent:
square = lambda x: x * x
# Common IoT Use Case: Quick callback mapping
button.irq(trigger=Pin.IRQ_FALLING, handler=lambda p: print("Intruder detected!"))Recursion occurs when a function calls itself to solve a smaller piece of a problem. It consists of two main parts:
- The Base Case: The condition that stops the recursion. (Without this, it loops forever).
- The Recursive Step: The function calling itself with modified data.
def calculate_factorial(n):
# 1. Base Case: Stop at 1
if n == 1:
return 1
# 2. Recursive Step: n * factorial of (n-1)
else:
return n * calculate_factorial(n - 1)
print(calculate_factorial(5)) # Outputs: 120In standard Python, recursion is a powerful tool for traversing directories or complex data trees. In MicroPython, use it with extreme caution.
Microcontrollers have very limited RAM. Every time a function calls itself, the system must save the current state to the "Call Stack" in memory. If the recursion goes too deep, you will cause a Stack Overflow, resulting in a fatal RuntimeError: maximum recursion depth exceeded and crashing your board. For embedded systems, an iterative while or for loop is almost always safer and more memory-efficient than recursion.