[{"element_type":"text","content":"\n        \u003ch2\u003eThe \u003cem\u003eprint()\u003c/em\u003e function is your first debugging tool\u003c/h2\u003e\n\u003cp\u003eWhen creating a CircuitPython program a great way to follow program flow and to examine parameters and values is to use a \u003cem\u003eprint()\u003c/em\u003e statement sending the information to the REPL. This is simple, tried and true, and you can comment them out when no longer needed to turn them off. Many times this is the way to go.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eSometimes, however, you might have a lot of print statements and multiple function calls and all of the information flowing into the REPL becomes a bit hard to follow.\u0026nbsp;\u003c/p\u003e\n\u003ch3\u003eMaking \u003cem\u003eprint()\u003c/em\u003e output easier to read\u003c/h3\u003e\n\u003cp\u003eAt this point one strategy to make the print outputs easier to follow is to add divider lines, line feeds, special characters, etc. For example:\u003c/p\u003e\n      ","metadata":{}},{"element_type":"code","content":"print(\"--------------------------------------------\") # prints the dashes to the REPL\n\nprint(\"-\"*40) # prints 40 dashes to the REPL more efficiently\n\nprint(\"\\n\") # executes a line feed in the REPL\n\nprint(\"  Here is an indented statement\") # Add spaces to add indentions - use 2 spaces, 4 spaces, etc. for different indention levels\n\nprint(\"\u003e Adds a greater than symbol\") # Adding a special character makes certain lines stand out","metadata":{"language":"python","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eGet creative and use different characters for divider lines (i.e. =, +, *). You can also combine characters such as:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"print(\"-=\"*10) # Prints '-=-=-=-=-=-=-=-=-=-='\n\nprint(\"+  \"*5) # Prints '+  +  +  +  +  '","metadata":{"language":"auto","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eWhen \u003cem\u003eprint()\u003c/em\u003e isn't enough - Turning it up to 11\u0026nbsp;\u003c/h2\u003e\n\u003cp\u003eIn 99% of your projects these approaches will be just what you need. But then... what if your program has grown to almost 5000 lines of code, more than 40 functions and a really complex program flow. The print statements whiz by in the REPL at a dizzying speed. Is there a better way? If you have an SD card (or other writable partition) in your project, then there is!\u003c/p\u003e\n\u003cp\u003eI recently ran into this on a Fruit Jam project. Tons of memory means it supports really large programs and can process lots of data. I wanted to monitor the memory usage of different parts and check the timing as the program progresses. I found the best way to do this is to log everything to the SD card, so I created a simple function I named\u0026nbsp;\u003cem\u003elog()\u003c/em\u003e:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"def log(msg):\n    gc.collect()\n    t = time.monotonic()\n    m = gc.mem_free()\n    pt = rtc.RTC().datetime\n    txt = f\"{pt[0]}{pt[1]:02d}{pt[2]:02d}{pt[3]:02d}{pt[4]:02d}{pt[5]:02d},{t:.2f},{m},{msg}\\n\"\n    try:\n        time.sleep(.1)\n        with open(\"/sd/runlog.csv\", \"a\") as f:\n            f.write(txt)\n    except Exception as e:\n            print(\"Error writing to runlog file:\", e)\n    print(txt)","metadata":{"language":"python","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n        \u003ch6\u003eRemember to import gc, time and rtc to be able to use these in your code\u003c/h6\u003e\n      \n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch5\u003eFunction explanation step-by-step\u003c/h5\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cul\u003e\n\u003cli\u003eCalls\u0026nbsp;\u003ccode\u003egc.collect()\u003c/code\u003eto clean up memory\u003c/li\u003e\n\u003cli\u003eCaptures system time with\u003ccode\u003etime.monotonic()\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eCaptures available memory with\u003ccode\u003egc.mem_free()\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eRetrieves real-time clock values via\u003ccode\u003ertc.RTC().datetime\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eFormats all data into a CSV-friendly string\u003c/li\u003e\n\u003cli\u003eAppends the log entry to\u003ccode\u003e/sd/runlog.csv\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003ePrints the same entry to the REPL for live feedback\u0026nbsp;\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n        \u003cp\u003eUse is straightforward: instead of calling \u003cem\u003eprint()\u003c/em\u003e function substitute \u003cem\u003elog()\u003c/em\u003e. You put the message you want to send to the REPL in quotes just like a print statement. You can also use f-strings and/or concatenate multiple items to send just like you wanted to print it out.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eThe log() function will start by doing a garbage collection, gets system time and free memory (two good things to track in complex programs). In the project I set and use the RTC, so I also pull the real time and format is as a simple number - in essence this is an ever-increasing serial number. This is useful as system time resets each time the processor is reset. Then this is all appended to 'runlog.csv' on the SD card. As a bonus it also prints it to the REPL so you still have the REPL output for most troubleshooting.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eHere is some sample output from my project:\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n      \n\n","metadata":{}},{"element_type":"code","content":"20251102130611,5140.08,6485552,Page 10 wind speed graphics updated\n20251102130613,5141.49,6485504,\u003e Page 8 update time: 88.563 seconds\n20251102130614,5142.89,6488368,\u003e Through page 8 update took 161.38 seconds\n20251102130617,5145.71,6487856,History saved: 11/02/2025 1:03 PM\n20251102130618,5147.11,6488432,\u003e Through history data save took 165.60 seconds\n20251102130624,5152.96,6476032,History purged: kept 94 of 94 records\n20251102130625,5154.36,6488272,\u003e Through old history purge took 172.85 seconds","metadata":{"language":"terminal","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe big advantage to this approach is that you can then open the output in a spreadsheet and analyze program flow. By calculating the time difference in seconds in the system time column you can see how long each step between calls takes. Likewise, you can track the memory usage step by step, all in your spreadsheet rather than calculating that in the program flow itself.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eOf course, this adds some overhead to your program as well. Once you have completed debugging and everything does what you want, simply comment out the code in the \u003cem\u003elog()\u003c/em\u003e function and add a\u0026nbsp;\u003cem\u003epass\u003c/em\u003e statement - then you can always turn it back on if you need it again.\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eWhether you're just starting out or managing a complex CircuitPython project, these logging strategies can help you stay in control of your program flow. Start simple with \u003cem\u003eprint()\u003c/em\u003e, enhance readability with formatting tricks, and scale up with SD card logging when your project demands it.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n","metadata":{}}]