General Pico Tips and Tricks
Universal LED Blink
As discussed in the MicroPython section of the book, the on-board LED on the original Pico corresponds with GPIO pin 25, but this was changed to be connected to one of the GPIO pins from the wireless chip (CYW43439) on the Pico W. The examples used in this book use code suitable for the Pico W and to adapt any of that code for the Pico we need to change ‘LED’ for 25 in the following code example;
from machine import Pin
led = Pin('LED', Pin.OUT)
led.value(1)
However, as responsible engineers with an eye to the foibles of uncertain hardware changes, it might be useful if we had some code that would allow us to illuminate the LED independent of which board we were using. Well good news, we can utilise the board class of functions that will allow us to determine just which type of Pico we are using and this will let us set the LED pin appropriately. The following code demonstrates this;
from machine import Pin
from board import Board
import time
BOARD_TYPE = Board().type
print("Board type: " + BOARD_TYPE)
if BOARD_TYPE == Board.BoardType.PICO_W:
led = Pin("LED", Pin.OUT)
elif BOARD_TYPE == Board.BoardType.PICO:
led = Pin(25, Pin.OUT)
while (True):
led.on()
time.sleep(.2)
led.off()
time.sleep(.2)
The Watchdog Timer
A WatchDog Timer (WDT) is a hardware timer that is used to detect and recover from errors in our programs or faults in their execution. Once initiated, a watchdog timer is constantly counting down and when (or if) it reaches zero, it reboots our device. The only thing that stops the timer reaching zero is periodic resetting of the timer back to its starting position. We place lines in our code at strategic points that perform this reset, so that under normal operation the timer should never reach zero. These resets are referred to as ‘patting the dog’, ‘feeding the dog’ or cruelly, ‘kicking the dog’ (not happy about that one).
While we aren’t going to purposely design our software to freeze, strange things can happen (cosmic rays - really!) and it is often practical to prepare for the unexpected. Conversely, you might notice that your device hangs for no apparent reason after long periods. Weird stuff does happen. When it’s more important that the system keeps functioning than it is to troubleshoot the problem, a watchdog timer could be your friend.
In the Raspberry Pi Pico or more precisely the RP2040, the watchdog has a 24-bit counter that decrements from a user defined value. The maximum time between resetting the watchdog counter is approximately 8.3 seconds before it reaches zero and reboots our device.
In a very simple code example from the MicroPython documentation we can see the watchdog timer library loaded, the timer is enabled with a time of 2000 milliseconds (2 seconds) and then the watchdog is fed.
from machine import WDT
wdt = WDT(timeout=2000) # enable it with a timeout of 2s
wdt.feed()
In our code we would set our timer appropriate for the occasion and place the feeding statements in strategic places so that under normal circumstances, there shouldn’t be a situation where it would run down for more than the specified amount of time before being fed again.
Logging to Help with Troubleshooting
Alrighty… Let’s get the obvious part of the discussion on this topic out of the way…
There are a wide range of possible mechanisms for troubleshooting depending on the skill level of the practitioner, the complexity of the code and the capabilities of the platform. In short, you will ultimately fall to using the techniques that work for you the best depending on your circumstances.
I am capturing the description of this method, not because I think it is the best or even if I think that it’s advisable. It suited me for a task and so I believed that it might suit me again at some time in the future. Therefore I thought I should write this down so that I don’t forget what the code does, and what better place to write it down than in a book :-).
This particular piece of code is written to capture notes as our Micropython code executes and to write those notes into a log file so that we can examine them at a later date. The particular occasion I needed something like this was when I had a Pico that would run for many days and would then fail. I couldn’t determine why it was failing, and so I decided that a piece of code that would write lines to a file so that I could see when and where the failure occurred would be a good start.
Therefore, the way this piece of code would be used is if we place the initial set-up and function definition at the start of the program we are troubleshooting and then we place the small note capturing code at various places where we want to know that the program has gotten to or looking at values that we want to check.
It captures the time that the event is written, the size of the log file and the message that we want to pass to it. This message can be text and / or values.
Enough talk, this is what it looks like;
import os
from machine import RTC
rtc = RTC()
rtc.datetime()
#Check to see if file present and create if necessary
try:
os.stat('/log.dat')
print("File Exists")
except:
print("File Missing")
f = open("log.dat", "w")
f.close()
def log(loginfo:str):
# Format the timestamp
timestamp=rtc.datetime()
timestring="%04d-%02d-%02d %02d:%02d:%02d"%(timestamp[0:3] +
timestamp[4:7])
# Check the file size
filestats = os.stat('/log.dat')
filesize = filestats[6]
if(filesize<200000):
try:
log = timestring +" "+ str(filesize) +" "+ loginfo +"\n"
print(log)
with open("log.dat", "at") as f:
f.write(log)
except:
print("Problem saving file")
# sample usage
val = 456
text = "some information"
combo = text + " " + str(val)
log(combo)
To make life easier for future me (and hopefully you) here’s the description of some of the parts.
We import the os module and RTC from machine
import os
from machine import RTC
rtc = RTC()
rtc.datetime()
This is so that we can us the os module to determine the size of the file we are generating and to make sure that we don’t write so large a file that it overwhelms our available storage.
RTC is used to generate a timestamp so that we know when an event occurs. Of course, if we don’t set the time initially, we are going to be left with a time stamp that starts at 2021-01-01 00:00:00. We could connect to NTP time first if we had a network connection, but that’s not always going to be available. This way we will at least have a feel for how our comments are being captured relative to each other and the time that the program started. There are several different time modules that we could use to do this. time, utime, and possibly others. Each has some slight differences in terms of the start of the timestamp or similar, and I fell on RTC. It does the job.
If the file that we’re going to use for capturing our information doesn’t exist, we need to create it.
#Check to see if file present and create if necessary
try:
os.stat('/log.dat')
print("File Exists")
except:
print("File Missing")
f = open("log.dat", "w")
f.close()
The first use of the open command to append information to a file will create the file if it doesn’t exist, but we will want to check the size of the file before we write anything to it, so this small piece of code checks for its existence and if it isn’t there creates it.
Then we get into the function definition.
We find the time and format it as a string in a nice tidy format. For those of you who are writing your dates in a dd/mm/yyyy format, using the alternative of yyyy/mm/dd makes it easier to sort.
# Format the timestamp
timestamp=rtc.datetime()
timestring="%04d-%02d-%02d %02d:%02d:%02d"%(timestamp[0:3] +
timestamp[4:7])
We can then check out the file size;
# Check the file size
filestats = os.stat('/log.dat')
filesize = filestats[6]
The os.stat call responds with a range of different metrics (not all of which are applicable for every platform). The one we want is accessed as [6] in the array.
After checking to make sure that our file hasn’t grown too large we combine the time, the size of the file and the information that we want to note specifically (this comes from then individual calls to the function in the program).
log = timestring +" "+ str(filesize) +" "+ loginfo +"\n"
print(log)
with open("log.dat", "at") as f:
f.write(log)
We also add a newline "\n" in to break the lines up.
The final block of the code is the piece that we would put in a range of places in our program to capture information.
# sample usage
val = 456
text = "some information"
combo = text + " " + str(val)
log(combo)
This is obviously just a sample, but we have a value val that could be any number used in the program and a comment some information that again could be something that acts as a reference for that portion of the code that we’re wanting to know something about. For example, it could be when the program starts and then when the device connects to the network, and then if it strikes an error in reading a value from a sensor.