Where I describe various methods of debugging programs using MicroPython on the RP2040 (Pi Pico and Pi Pico W).
Programming is a science, debugging is an art. When I chat with students learning to program, I’ve found it isn’t “programming” which causes the most problems, its “what to do when the programming doesn’t work.”
On any new microcontroller, there is a new tool chain and a new process, which affects how you debug. For example, with the AVR family of microcontrollers, you have gdb, bloom as a hardware debugger (which is fantastic!), make and gcc. Or you may use the Arduino framework. Both approaches are well-defined, well-understood and work well.
Micropython is a bit more of a mystery. You may use typical Python tools such as Thonny to create the code and load the code, however, Thonny isn’t a hardware debugger, meaning it is unable to show variables in memory, set breakpoints and generally, dive into the internals of your program. MicroPython doesn’t lend itself well to line by line hardware debugging. Leaving you to determine the best method of solving “my program isn’t working.”
In other words, MicroPython’s greatest strength, ease of code generation, can be a problem when attempting to debug your program. The abstraction which the language provides encourages rapid code development, hinders rapid code debugging. That said, the REPL can be your friend, much more so than anything in the C language and we’ll lean on it heavily. Let’s begin.
One last point…there is no one way to do it. There are multiple methods of determining why your program is failing and each problem might require one or more methods. I’ll describe several methods below, and I encourage you to use them as appropriate.
Blink or Use the Builtin LED
Remember blink? It was the first program many of us attempted and it simply blinked our built-in LED. That’s it. Blink or indicating where our program last worked can be a powerful tool. For example, I use a slow blink to indicate my Pico W is attempting to connect with the wireless network. The LED stays off if the Pico has succeeded, and it blinks at a faster rate, if it fails. This provides me a ready indicator of what is happening in the early stages of my programming.
I strongly recommend your incorporate something similar into your coding early on. For example, here is my code which I use:
from machine import Pin, Timer # provide an indication as to why the connection failed # Use built-in LED to show status wireless = Pin("LED", Pin.OUT) # simple timer function to blink builtin LED showing wireless status # slow blink - attempting to connect attempting = 4 # fast blink - connection failed failure = 20 # off - connection success def tick(timer): global wireless wireless.toggle() # initiate a timer to indicate a success (or failure) blink = Timer() # slow blink to indicate attempting to connect blink.init(freq=attempting, mode=Timer.PERIODIC, callback=tick) # fast blink to indicate failure to connect blink.init(freq=failure, mode=Timer.PERIODIC, callback=tick)
This approach coupled with a reset button, provides me with my first indication something “has gone wrong”. Every I press reset I watch the LED. If it blinks slowly then goes off, I know I have connected to the network and may begin to debug the rest of my program. If it begins to blink fast, I have yet to connect and need to investigate startup issues.
Serial Monitor and REPL
NOTE: Serial Monitors
On macOS, I use Serial as it is outstanding. If you have a Mac and plan on doing a lot of Serial work, it is a must have. On Linux, moserial is decent and CoolTerm works on Windows, however, watch for malware masquerading as CoolTerm. Only download CoolTerm from the Roger Meier’s Freeware page, https://freeware.the-meiers.org. I provide the link visually so you get to the correct page!
One more recommendation is to use Python’s pyserial miniterm. Install via pypi then use it like this:
python3 -m serial.tools.miniterm /dev/cu.usbmodem14101 115200
The REPL is the classic method of debugging Python. If you are not sure what a command does, “try it out in the REPL”. This continues to be a great way to debug MicroPython. A simple sequence such as:
# connect to Pico via a serial program, CTRL-C to enter REPL from machine import Pin Pin("LED", Pin.OUT, value=1)
Is the fastest method of determining if you have a connection to your Pico, and it is working. (Just having a response from the REPL, also shows this, however, nothing like making the Pico do something to know for sure!)
In your serial program, hit the up arrow, change the “1” to a “0”, hit return and the LED is off. We’re good!
We still have that issue of the fast blink (or no blink) at startup, how do we solve that? In my programs, I’ll have everything be a function and the “main program” will be the following:
if __name__ == '__main__': web_server()
This allows me to do the following:
# connect to Pico via a serial program, CTRL-C to enter REPL MicroPython v1.20.0 on 2023-04-26; Raspberry Pi Pico W with RP2040 Type "help()" for more information. >>> from main import * >>> web_server() Connection failed: LINK_BADAUTH wireless connection failed
I clearly have an issue with my secrets.py file! The error message LINK_BADAUTH indicates I wasn’t able to connect due to “bad authorization credentials”. Yes, I can see, I dropped a character in my password. Change it, copy back over and we’re good to go!
Anther issue I have frequently, is I will add “.py” to a local import, which results in the following:
# connect to Pico via a serial program, CTRL-C to enter REPL MicroPython v1.20.0 on 2023-04-26; Raspberry Pi Pico W with RP2040 Type "help()" for more information. >>> from main import * Traceback (most recent call last): File "<stdin>", line 1, in <module> File "main.py", line 6, in <module> ImportError: no module named 'pins.py'
Arrrrgggh! Delete the .py, save and copy then restart. Everything works!
The point of this section is to remember, if something isn’t working on startup:
- Connect via serial monitor
- Ctrl-C to engage REPL
- Start the program manually and look for errors