diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 28f4aef60..000000000 --- a/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -.vscode/* -site -secrets.py -__pycache__ -~$* -.venv -venv -src/kits/farm-animal-tracker/DesignSparkPCB_V11_0_1.exe diff --git a/docs/glossary.md b/.nojekyll similarity index 100% rename from docs/glossary.md rename to .nojekyll diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 682232c62..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "editor.fontSize": 10, - "cSpell.words": [ - "Adafruit", - "AGND", - "allowfullscreen", - "Autoshutdown", - "breadboarding", - "circuitpython", - "coderdojo", - "Cytron", - "Datasheet", - "ffprobe", - "framebuf", - "Framebuffers", - "localtime", - "maxtries", - "microcontroller", - "micropython", - "microsite", - "microswitch", - "microswitches", - "milliamp", - "milliamps", - "minicom", - "mkdocs", - "mpremote", - "NEOPIXEL", - "neopixels", - "OLED", - "ONESHOT", - "photosensor", - "Pinout", - "pydub", - "rshell", - "SMPS", - "solderless", - "SSID", - "Teachman", - "Thonny", - "timeset", - "timetries", - "tinyml", - "TTGO", - "urandom", - "urequests", - "usbmodem", - "utime", - "VBUS", - "VREF", - "VSYS", - "waveshare", - "WLAN" - ], - "markdown.experimental.validate.ignoreLinks": [ - "../img/pi-w.png" - ] -} \ No newline at end of file diff --git a/40-gpt-outliner.md b/40-gpt-outliner.md deleted file mode 100644 index 427241fb6..000000000 --- a/40-gpt-outliner.md +++ /dev/null @@ -1,25 +0,0 @@ -# GPT Outliner - -## GPT Promt -```txt -Create an outline for a course on microcontroller programming with Micropython for 9th-grade students. The students should have an interest in computers and be able to use a computer keyboard including copy/cut and paste commands. - -1. Introduction -2. Setting up your Python environment -3. Blink an LED -``` - -## GPT Response - -```txt -4. Reading analog inputs -4. Servos -5. LED strips -7. Buttons -8. Sound -9. Temperature sensors -10. Light sensing -11. Accelerometers -12. Motion sensors -13. Gesture sensors -``` \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 000000000..890744831 --- /dev/null +++ b/404.html @@ -0,0 +1,1261 @@ + + + +
+ + + + + + + + + + + + + + + + + + +This section includes some advanced topics that might be useful for helping you create and debug MicroPython projects.
+Topics include:
+An Interrupt Handler (also called an ISR for Interrupt Service Request) is a special Python function that is called when specific events occur such as a button being pressed. ISRs are the preferred way to detect external events, as opposed to polling methods that are inconsistent and inefficient. However, they are a bit tricky to setup and debug. So a good design should be as simple as possible and avoid using complex features unless you really know you need them.
+So why are ISRs so important? Let's illustrate this is a story.
+Imagine you have 10 friends each with a button at their home. In the polling method you would need to drive to each of their houses and ask them "Is the button get pressed"? You would have to do this frequently in case the button was pressed and released too quickly. This is a slow and painful process and takes a lot of CPU cycles.
+An interrupt handler on the other hand has each friend tell you directly if their button has been pressed. The messages are quick and efficient. They don't use a lot of extra CPU power and the results get handled quickly.
+However, there are specific rules about what we can and can't do within an ISR function. They need to be quick and efficient. We can't wonder off and do crazy things like printing debugging lines within a good ISR. Our job is typically update a global value and finish ASAP. A good ISR should be as efficient as possible.
+This is our first ISR example. It has several parts:
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 |
|
Now if you run this program, you will see that it prints to the Terminal each time the button is pressed and it also tells us how many times the button has been pressed.
+example output:
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 |
|
But if you are careful, you will note something slightly unexpected might happen. I the example above, I actually only pressed the button about 10 times. But the button value is 21! What could be going on here? Could there be a bug in the code?
+The answer is that buttons are not perfect on/off switches. They are essentially noisy on/off devices that may go through a transition of off/on/off/on each time we press the button.
+As a switch goes from open to closed, it moves from a stable state, through an unstable transition state and then it finally arrives at a new stable state. This is illustrated in the drawing below.
+We can reduce this "noise" with a small capacitor next to the button. The capacitor will quickly absorb the energy of the button transition and it will "smooth" out the spikes. This will give us a more consistent readout of the number of button presses and avoid accidental "double presses" that were not intended.
+However, we can also get a clean signal by using software. The key is when we first detect that a transition may be happening we "stop listening" for a short period of time until we are confident that the unstable transition state is over. This is typically around 20 milliseconds, but there may be a few stray signals left. Since we may not have to detect changes more frequently than 5 presses per second, we can go to sleep in our ISR for up to 200 milliseconds. This will give us a nice stable reading from the button.
+These are general rules but for our breadboard mounted momentary switches, the values are appropriate.
+Now let's show you the code that does the hard work of debouncing a signal from a button or switch.
+In this example, our ISR is called button_pressed_handler(pin)
. As soon as it is called, it checks the number of milliseconds since it was last called. If the time difference is under 200 milliseconds we are good to go and we update the button_presses global variable. If we are under the 200 millisecond window, we might be in that transition state and we don't do anything.
1 +2 +3 +4 +5 |
|
The net effect is that the presses variable will ONLY be incremented once, and not multiple times during the transition. Here is the full code:
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 |
|
Although there are benefits to the simplicity of the code above, some microcontrollers developers suggest that you simply deactivate the IRQ during the debounce sleep. This makes sense since there is two small calculation of the time differences (a subtraction and a compare operation) that do not need to be performed.
+The key lines we add are a deactivate of the IRQ, a sleep for 200 milliseconds and a re-enable of the IRQ after the sleep. Both approaches have worked for me and I will let you decide the tradeoffs.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 |
|
The Raspberry Pi Pico has an internal temperature sensor that can be access using machine.ADC(4)
. This might be useful to see if your RP2040 CPY is running "hot" and might benefit from a cooling fan.
1 +2 +3 +4 +5 +6 +7 +8 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 |
|
We often need to calculate how much time has elapsed since an event occurred. To to this we can use the ticks
functions in the MicroPython utime library.
There are one million microseconds in a single second. The utime
library allows us to count the number of microseconds that have elapsed since the processor was powered up.
The following example times the sleep function and measures the difference in the number of clock ticks in microseconds between the two events.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 |
|
results:
+1 +2 +3 +4 +5 +6 +7 |
|
You will note that the difference between the start and end time should be one million microseconds. However, the run-time libraries on the pico have some variability, so you will see the actual time vary by a few microseconds. Most of the time you can use milliseconds to compare time intervals.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 |
|
results:
+1 +2 +3 +4 +5 +6 +7 |
|
These results are almost always 1000 with an occasional 1001 value.
+ + + + + + + + + + + + + + + +Conda is a powerful tool for building consistent and stable Python environments. These environments include all the Python libraries that you need to be a productive MicroPython developer. Using Conda allows you to keep each of your Python projects cleanly separated. This may not be important on your 2nd or 3rd Python project, but as you do more Python projects you will benefit from isolated environments that each have their own versions of each Python libraries that will not conflict with each other.
+One other important fact to remember is that if you use a conda virtual environment you should never need to use sudo (root) to install Python libraries. This means your installation scripts are more secure and less likely to introduce security problems and accidentally remove libraries that other projects depend on.
+To get started, it is best to go directly to the Conda web site and follow the installation instructions for you specific version of your operating system. There are many variations of installation not just for Windows, Mac and Linux, but each version my had different steps required.
+Once you can open a terminal and type in conda --version
you have successfully installed Conda. For this document we have used conda:
1 |
|
which returns:
+1 |
|
Our fist job is to create a desktop environment that allows us to run Python programs that support the MicroPython development process.
+1 |
|
1 |
|
After this your prompt should now show that you are in the micropython environment.
+This process may take about five minutes, since all the most current libraries must be downloaded onto your desktop. Once this process finsihes you must remember to deactivate your current conda environment (if you have one and then activate your new micropython environment.
+In the Thonny tool, open the Terminal. At the REPL prompt type:
+help()
which returns
+1 |
|
returns
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 |
|
followed by
+help(modules)
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 |
|
1 +2 |
|
returns
+1 |
|
1 +2 +3 +4 +5 |
|
The Pico has two I2C hardware controllers. Each controller can talk to multiple IIC devices as long as all the devices communicating on each controller have distinct addresses.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 |
|
Results for both a OLED display on I2C 0 and a time-of-flight sensor on I2C 1
+1 +2 +3 +4 +5 +6 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
returns: +
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 |
|
Secure Digital (SD) is a non-volatile memory card format for use in portable devices such as cameras, MP3 players and portable devices.
+On Microcontrollers SD cards are usually access through an SPI interface although there are also devices that use I2C interfaces.
+GPIO Pin | +SD Mode | +SPI Mode | +
---|---|---|
GP10 | +CLK | +SCK | +
GP11 | +CMD | +SDI | +
GP12 | +DAT0 | +SD0 | +
GP13 | +DAT1 | +X | +
GP14 | +DAT2 | +X | +
GP15 | +CD/DAT3 | +CSn | +
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 |
|
Results:
+1 |
|
MicroPython was created in by Australian programmer Damian George in May of 2014. Although Adafruit originally supported MicroPython, in July 2017 Adafruit created a fork of MicroPython and called it CircuitPython. We can only speculate why this decisive action was taken, but the result is it divided the community into two incompatible branches and it doubled the amount of work needed to be done to introduce a new device to the Python community. Unfortunately, the consequence is that many programs written in CircuitPython are difficult to port to MicroPython.
+Today, according to Google Trends, MicroPython is still four more popular than CircuitPython when we look at worldwide web search comparisons. However, in the US, the popularity is more equal. Although the claim was that CircuitPython was done for "simplicity" there is little evidence that CircuitPython programs are smaller or easier to maintain than MicroPython.
+The one thing is clear, CircuitPython has lots of drivers for unusual hardware devices. If you find a driver you need in MicroPython you will need to convert it to MicroPython. This is usually done manually on a line-by-line basis.
+Because MicroPython and CircuitPython are incompatible, it is important that you don't intermix your Python libraries.
+Here is how we setup a virtual environment for CircuitPython using Conda.
+1 +2 |
|
1 |
|
1 |
|
Note
+ERROR: Could not find a version that satisfies the requirement adafruit-circuitpython-displayio-ssd1306 +ERROR: No matching distribution found for adafruit-circuitpython-displayio-ssd1306
+1 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 |
|
ChatGPT does a good job of automatically converting CircuitPython to MicroPython. In your prompt you just add the following instruction:
+1 |
|
An example of this working with the basic blink example is shown below:
+As of March 2021, MicroPython is about two to four times more popular than CircuitPython.
+ + + + + + + + + + + + + + + + +MicroPython now has a standard format for all remote access. The program +is called mpremote. There is +ample documentation on the site, and there is a higher chance it will include the latest features.
+There are three main reasons to use mpremote:
+A partial list of the most frequently used commands are:
+$EDITOR
program and then copy the file back to the Pico.Note that you can only be connected to one remote device at a time to use many commands.
+The first time:
+1 |
|
1 |
|
I use conda and you can see that it found the mpremote package version 1.20.0
+1 +2 +3 +4 +5 |
|
The version of mpremote is not yet working, but eventually, it will be used like this:
+1 |
|
1 |
|
1 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 |
|
Here is the syntax of the copy file command:
+1 |
|
This copies the local file "main.py" to your pico. The colon ":" is the root of the pico.
+If you get tired of typing "mpremote fs cp" you can create an command-line alias called "pcp" for Pico Copy:
+1 |
|
The copy command the becomes simply:
+1 |
|
If you place this line in your .bashrc or similar shell startup it saves you a lot of typing.
+File systems examples include:
+cat <file..>
to show the contents of a file or files on the devicels
to list the current directoryls <dirs...>
to list the given directoriescp [-r] <src...> <dest>
to copy filesrm <src...>
to remove files on the devicemkdir <dirs...>
to create directories on the devicermdir <dirs...>
to remove directories on the devicetouch <file..>
to create the files (if they don’t already exist)For large classrooms that teach MicroPython using kits, we recommend that you arrange
+all the mkdir
and copy (cp
) file shell commands in a single UNIX shell script for consistency.
Here are the steps:
+mkdir
If you follow these steps, then when the students connect to the Pico using Thonny they will see all the labs in the right order from simple to the most complex.
+ + + + + + + + + + + + + + + +MicroPython provides a small list of os functions that allow you to manipulate files on the local filesystem of a MicroController.
+These functions for filesystem access, and mounting, terminal redirection and duplication, and the uname and urandom functions.
+These commands include:
+1 +2 +3 |
|
Returns:
+1 +2 +3 +4 +5 +6 +7 |
|
Thonny does not easily allow you to delete files. To do this you will need to use the "os" functions.
+1 +2 +3 +4 |
|
To find out all the os functions use:
+1 +2 +3 +4 +5 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 |
|
1 |
|
returns:
+1 |
|
https://www.youtube.com/watch?v=jnSX8ZMmHZ4
+ + + + + + + + + + + + + + + +Note
+MicroPython now has standardized file transfers with the new "MicroPython Remote" or mpremote commands. +See the mpremote docs for details. +We will be updating all our documentation to use this standard. +This page is mostly for older systems.
+Using an IDE such as Thonny you can copy many files at a time to the Raspberry Pi Pico by using the GUI.
+However, this process becomes error prone if you want to copy a large number of files. To do this we will use the "remote shell" program from the command line. In our classes we often want to copy a dozen or more files to a new robot for student to try out.
+Rshell was written by David Hyland. The source code and installations are documented on Dave's GitHub repository here: https://github.com/dhylands/rshell. Dave added support for the Raspberry Pi Pico in release 0.0.30 in March of 2021.
+Rshell's primary use is to to get filesystem information on the pico (ls), and to copy files to and from MicroPython's filesystem. It can also be used as a terminal to run interactive REPL commands.
+If you are new to Python and you don't have any previous virtual environments set up you can skip this step. Experienced Python developers have many different environments that they want to keep separated due to library incompatibility issues. Here is how to create a new Python Conda environment that keeps your rshell libraries separated.
+1 +2 +3 |
|
Your prompt should now indicate you are in the pico environment.
+We will now use the standard pip installer tool to install the rshell command.
+1 |
|
You can check that rshell has been correctly installed in your command PATH by running the UNIX which
command.
1 |
|
Rshell communicates with the Pico through the USB port. When you plug in the Pico you should see a new file created in the UNIX /dev directory. It typically begins with the letters /dev/cu.modem
. One way to test this is to unlpug the pico and run the following command:
1 |
|
1 |
|
This is the port you will use to connect to the pico. We will use the -p
for port option to startup rshell.
1 |
|
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 |
|
The boards command will list the boards rshell is connected to:
+boards +pyboard @ /dev/cu.usbmodem14101 connected Epoch: 1970 Dirs: /pyboard/hello.py /pyboard/main.py
+We can see that the board is called "pyboard" and you can use that as a path to list the files on the board.
+1 +2 |
|
rshell will look for a program called board.py when it connects to the board. If this file contains a board name it will use that as the board name the next time it connects to the board. You can use the "echo" command to generate the file. In the example below, we will call our board "pico"
+1 |
|
After you use the CONTROL-C and reconnect you will see the following:
+1 |
|
Remember you must disconnect from rshell and reconnect before the boards.py function is used.
+For the remainder of this lesson we will assume you have renamed your board "pico".
+You can then type cd /pico
followed by a ls
to see the files on your pico.
You can enter the REPL loop using the repl
command and use the same commands that you used in the Thonny shell.
1 +2 |
|
returns
+1 |
|
You can type the help
command to see all the rshell commands:
1 +2 +3 +4 +5 +6 +7 +8 |
|
If you want to copy all the python files from the pico to a backup directory you can use the following command:
+1 +2 |
|
You will need to create the /Users/dan/backup directory before you do this. You can also use the tilde ~
character to stand in for your home directory like this:
1 |
|
Copying '/pico/hello.py' to '/Users/dan/backup/hello.py' ... +Copying '/pico/main.py' to '/Users/dan/backup/main.py' ...
+If you have a directory called ~/build
that contains many files you want to install on the pico file system you can use the following command:
1 |
|
If you have done a clone to the CoderDojoTC micropython repository and put it in your home directory under ~/micropython then following command will copy the python files from the Maker Pi RP2040 kit to your pico:
+1 +2 +3 +4 |
|
Note that the drivers will be placed in the /lob directory.
+You do not need to use an interactive session to run a command with rshell. You can just add the command you would like to run to the end of the rshell command like this:
+1 |
|
returns:
+1 |
|
The normal Python has advanced string formatting functions using "%" and .format methods documented at the PyFormat website.
+Although most of these functions work, there are some exceptions when using date formats.
+The following % formats do not work under MicroPython:
+The following example prints a floating point number with two decimals of precision in a field of six characters with leading zeros.
+1 |
|
returns:
+1 |
|
+When clocks are used to trigger future events, they are called timers. Timers are used to efficiently use CPU resources. In microcontrollers, the work of keeping track of timers is frequently delegated to hardware outside of the main CPU loop. This makes your microcontroller more efficient, more reliable, and makes your code easier to read.
Timers work independently of the main CPU's clock. As a result, they are called asynchronous objects. Using timers can be both efficient and reliable, but they can be complex to debug. Errors within Timers and interrupts can be difficult to get information on.
+Timers are used to schedule events in the future. There are two types:
+Both of these timer objects need a callback function to be specified when they are initialized. These callback functions are also called interrupt service routines (ISRs). This is the function that will be executed when the timer gets triggered. So we must define an ISR function before timers are properly initialized.
+Periodic timers are usually initialized with a period
parameter. This is the amount of time in milliseconds between each event. They are useful for doing like checking if new data is available on a network connection or checking if a sensor is still working.
One-shot timers are also initialized with a period
parameter often called a "timeout" period. This is the amount of time in milliseconds before the timer will fire. One-shot timers are used if you want to do something in the future but don't want to deal with it now. You can think of this as a reminder service.
Here is how both Periodic and one-shot timers are setup:
+1 +2 +3 +4 +5 +6 +7 |
|
A Periodic timer can be configured to use either a period or a frequency as a parameter. The frequency is the number of times the timer will fire per second. The period is the amount of time in milliseconds between each event. The frequency is used to calculate the period. The following are equivalent:
+1 +2 +3 +4 |
|
You can always convert between the two by taking the inverse and multiplying by 1000.
+1 +2 |
|
Older microcontrollers have fixed hardware associated with each timer. These are call "hardware timers". When you initialize a hardware timer, you specify what set of hardware to use using a fixed ID (0,1,2,3 etc.). On the Raspberry Pi Pico all timers are "virtual" and are implemented in software. All virtual timers have an ID of -1 and the timer ID does not need to be specified.
+In the following program we will create timer that will toggle the built-in LED on the Raspberry Pi Pico every second. We will create a new function called toggle_led()
that will toggle the builtin LED on and off each time it is called. There are three key lines in this program.
Here is a sample code to toggle the builtin LED on the Raspberry Pi Pico and off.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 |
|
Many times you want to animate an LED strip but not complicate up your main event loop with this code. For example, you might want to light up the entire strip with a pattern of motion on the LED strip. You can do this by creating a new function that will conditionally turn one pixel to a color and increment a global variable that keeps track of which pixel to turn on. This function will be called every time the timer goes off.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 |
|
For example, if a robot has a set of "modes" like driving forward, turning left, turning right, backing up etc. You can use a timer to flash the LED strip in different colors with different patterns to indicate the robot is in different modes.
+The Timer.deinit()
can be used to remove a timer from the system. This is useful if you want to stop a timer that is currently running. If you have many timers that are each using memory, it is a good practice to remove them when you are done with them.
In MicroPython on the RP2040, there are no limits placed on the number of timers other than you must have enough memory available. Because there are no specific hardware limits, these are often referred to as "virtual" timers. The number of virtual timers is limited by the amount of memory available to the microcontroller.
+Other implementations of MicroPython on different hardware have stricter limits placed on the number of timers. The ESP32 MicroPython port currently only has four hardware timers and a numeric ID of 0 to 3 must be used when you setup these timers. For the pyboard has a limit of 14 timers. Be aware of these limits if you are interested in creating portable MicroPython code.
+Unfortunately, different hardware implementations of MicroPython have different ways to setup and use timers. Some hardware requires timers to each have an ID tied to a specific resource. Because of this, be cautious about using timers in your code if you require portability between hardware.
+As we mentioned, timers need callback functions are a type of function called interrupt service requests (ISRs). In multi-core systems like the RP2040 these interrupts can only be called from the core that executed the timer. This means that if you are using a timer to trigger an interrupt, you must make sure that the interrupt is only called from the core that is executing the timer.
+There are special limitations on what can and can't be done within interrupts in most systems. For example, you are not allowed to allocate dynamic memory within an interrupt. Your interrupt handler should be a short function that performs the minimum work to change external variables. This is because the interrupt handler is called in a separate thread and can't allocate memory.
+In general, doing complex logic within timers and interrupts is not a good idea. If you are interested in doing complex logic, you should use a different method that is easy to debug. This will make your code easier to understand and maintain.
+machine.reset()
if it gets stuck? Hint: See Watchdog TimersIn the recent past, memory management has been a challenge for embedded systems. Before 2020 microcontrollers like the Arduino usually only came with a few kilobytes of RAM, and careful attention needed to be paid to each byte using clever memory management trickery. The memory management system in recent years has become much easier. Even a low-cost $4 Raspberry Pi Pico now comes with 264K RAM! This is an almost 100-fold increase over the older Arduino Uno which only had 2K of RAM. That being said, there are always developers that push their hardware to the limit, and knowing the basics of memory management is important to understand how embedded systems work.
+MicroPython is very clever about how memory is allocated. It is very stingy about memory use and will only allocate memory for what is needed. This is very different from the standard Python that allocates memory for everything that is needed up-front assuming that most laptops, desktops and servers have gigabytes of RAM and virtual memory support. This is an important difference, and it is important to understand if you want to push the limits of your microcontroller.
+This lab will cover the basics of memory management, and how to use the memory management system in MicroPython. +We will cover the following topics:
+The memory management system is responsible for allocating and freeing memory. The memory management system must allocate memory for data variables, and for freeing this memory when a variable is no longer needed. The system is also responsible for allocating memory for the parameters used by functions, and for freeing memory when a function is no longer needed. You can read more about memory management on the MicroPython Memory Management Docs website.
+The heap is the area of memory that is used to store general data. In MicroPython the heap is located in the lower memory and it grows upwards in memory address space. The exception is memory used as parameters to functions, which is stored in the stack.
+The stack is the area of memory that is used to store all the parameters to function calls. In MicroPython the stack is located in the upper memory and it grows downwards in memory address space. If you are calling highly recursive functions, the stack will get a lot of use.
+The garbage collector is a process to reuse memory that is no longer in use. The garbage collector is automatically run when the heap is full, but you can also run it manually to reclaim unused memory to get finer-grain control over memory usage and to avoid memory fragmentation.
+In MicroPython most of these operations are done automatically for you. You don't really need to worry about how memory works unless you are reaching the limits of what your microcontroller can do.
+One other key concept is continuous memory. If you are allocating a large amount of memory in an array or a long string, we need to allocate all this memory in one large chunk. As programs run for a long time memory becomes fragmented (many small free sections) and can't be used for storing large arrays.
+You can read more about how MicroPython is clever about memory usage by reading the MicroPython Optimizations Docs website.
+We will be using the "gc" module to show the amount of free and used memory. "gc" orignally stood for "garbage collection", but the model has been generalized to perform other memory management tasks.
+Here are the key functions to show the amount of free and used memory and current memory usage on a Raspberry Pi Pico with an RP2040 microcontroller: gc.mem_free()
and gc.mem_alloc()
:
1 +2 +3 +4 +5 |
|
results for RP2040:
+1 +2 |
|
You can see that although the RP2040 chip has a specification of 264K of RAM, it only has 187,232 bytes of RAM available for program use. The other RAM us used to store the MicroPython interpreter software. You can also see that the heap is currently using 4,864 bytes of RAM. This is typical of the additional overhead that MicroPython requires to run a program.
+You can use the micropython.mem_info(1)
function to view the memory layout of the MicroPython interpreter. This function returns a list of tuples, each tuple containing the address, size and type of each memory block. The address on the left of each row is the memory address of the start of the block within the heap. In MicroPython, memory blocks are each typically 16 bytes.
1 +2 +3 |
|
results: +
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 |
|
This shows that the garbage collector is responsible for managing a total of 192,064 bytes of memory. The other numbers give you an indication of how fragmented you heap is.
+Each of the letters represents the type of memory at that position on the heap:
+| Letter | Description | +||| +|.|Free memory| +|h|head of a block of memory| +|=|tail of a block of memory| +|T|Tuple| +|L|List| +|D|Dictionary| +|S|String| +|A|Array or Byte Array| +|F|Float| +|B|Function BC| +|M|Module|
+If the heap is 192,064 bytes and each block is 16 bytes then there should be 12,004 blocks on the heap. If each row in the report displays 62 characters then there are 12,004/62=193 rows in the report. To keep the report short, the function will only show the rows that are not free. The report indicates that there are 181 lines all free blocks, so it will only show the non-free lines which in the example above is about six non-free rows in lower heap memory.
+You can manually run the garbage collector using the gc.collect()
functions. This function is used to force garbage collection exactly when you want to, not when the heap is full. This may occur at a time that is inconvenient for the program when it must be sending data at a specific speed.
Note
+Still under development.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 |
|
You can determine how much space we have available on the Pico flash after programming an ELF or UF2 file. For example, if we have an ELF file that's 1 MB and we were to program via openocd, then where should we offset my user data in flash? (i.e. XIP_BASE + OFFSET)
+With an elf or uf2 file, using picotool info -a
on it will show the start and end of the binary (the start is usually 0x10000000 unless you did something to change it).
Inside your code, you can use the symbols defined by the linker script __flash_binary_start (defined here) and __flash_binary_end (defined here) like this:
+1 +2 +3 +4 +5 |
|
You need to set GP25 to output and set it high and also set GP29 to input with no pull resistors before reading. +And don't forget that the input from VSYS to ADC is divided by 3, so you have to multiply your result to get real value. +When I do that I get around 4.7 V when powered from USB, so it definitely works.
+https://forums.raspberrypi.com/viewtopic.php?t=301152
+1 +2 +3 +4 +5 +6 +7 +8 +9 |
|
Unlike older Arduino systems, MicroPython has full support for a "virtual file system" (VFS) that we use to store and retrieve programs and data. The way we access the file systems in MicroPython +is similar to the way we access files in standard Python.
+First, we need to define two key terms: blocks and fragments.
+The block size refers to the size of the fundamental unit of storage on the file system. All files and directories occupy an integral number of blocks, with the size of each file and directory being a multiple of the block size. The block size is typically a power of 2, and can vary depending on the file system and the size of the storage medium.
+The fragment size, on the other hand, refers to the smallest unit of space that can be allocated for a file or directory. Files and directories may occupy a number of fragments that is not necessarily an integer multiple of the fragment size. Fragmentation occurs when the file system is unable to allocate a contiguous block of storage for a file or directory, resulting in the file or directory being spread out over multiple fragments. The fragment size is typically smaller than the block size, and may also vary depending on the file system and the size of the storage medium.
+In MicroPython, the os.statvfs('/') function provides information about the root file system. Among the information it provides is the block size and fragment size of the file system.
+In the os.statvfs('/')
function, the block size and fragment size are reported as the first and second elements of the tuple returned by the function, respectively. Specifically, stats[0] contains the block size, and stats[1] contains the fragment size. These values can be used to calculate various file system statistics, such as the total size of the file system, the total number of blocks and fragments, and the amount of free space available.
You can also mount file systems on other flash drives. You can get the stats of these file systems by using the new mount point with the os.statvfs()
function.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 |
|
TinyML is designed to help you do machine learning and embedded systems. Once you have trained a deep learning model, you can use TinyML perform data analysis (inference) on a microcontroller or a cell phone.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 |
|
https://chat.openai.com/share/6e6d8123-ed4d-4dc6-a915-030fe2245dfe
+ + + + + + + + + + + + + + + +OK, I now have a Raspberry Pi Pico and the OLED display hooked up. I also have a microphone hooked up to ADC0 on port GPIO26. How can I test that the signal is coming into the ADC? Can you write a MicroPython program that displays the audio signal on the OLED?
+
+I am using a ssd1306 and I use the following MicroPython:
+
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 |
|
To test that the signal is coming into the ADC (Analog-to-Digital Converter) on the Raspberry Pi Pico via GPIO26, and to display the audio signal on your SSD1306 OLED, you can use the following MicroPython script. This script will read the analog values from the microphone connected to GPIO26 and plot these values in real-time on the OLED display, creating a simple waveform visualization.
+Note: Before running this script, ensure you have properly initialized and connected your microphone to GPIO26 (ADC0), and the OLED display is correctly wired for SPI communication as per your provided initialization.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 |
|
This script continuously reads the analog signal from the microphone and plots a simplified version of the waveform across the OLED display. Here's a breakdown of the plotting part:
+display.fill(0)
.display.show()
to visualize the waveform.This example provides a basic way to visualize audio signals. The resolution and quality of the visualization can be further enhanced or adjusted according to your requirements.
+ + + + + + + + + + + + + + + +The signal is very low on the display. I have added a Potentiometer on ADC1 which is on GPIO PIN 27. Can you please regenerate the entire program with sound on ADC0 and gain on ADC1. sound = ADC(Pin(26)) gain = ADC(Pin(27))
+Certainly! To adjust the signal's amplitude on the display based on the gain from a potentiometer connected to ADC1 (GPIO PIN 27), you can modify the program as follows. This updated script will use the value from the potentiometer to scale the audio signal from the microphone, making the waveform larger or smaller on the OLED display according to the potentiometer's position.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 |
|
In this script, the gain control (potentiometer) reading is used to adjust the amplitude of the sound signal before plotting it. The gain_value
is read from ADC1 and is used to scale the audio signal from the microphone connected to ADC0. The line adjusted_val = min(((val * gain_value) >> 16), 65535)
scales the microphone input based on the potentiometer's position, and the bit shift >> 16
is used to keep the value within the 16-bit range of the ADC's output.
This method should help make the displayed signal more responsive to the potentiometer's adjustments, allowing you to visually enhance or reduce the waveform amplitude on the OLED display.
+ + + + + + + + + + + + + + + +The display is a bit slow. Can you rewrite the plot\_signal\_with\_gain function so that the scroll() function is used to move the display to the left by one pixel?
+
To make the display update faster and to create a scrolling effect that moves the waveform to the left by one pixel, you can modify the plot_signal_with_gain
function to use the scroll()
method of the SSD1306 library. This will shift the entire display content to the left by one pixel, and then you can plot the new data point at the right edge of the display. Here's how you can implement this:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 |
|
In this updated function, the display is scrolled left by one pixel at the beginning of each update cycle using display.scroll(-1, 0)
. This creates room to draw the new data point at the far right edge (WIDTH - 1
) of the display. The loop that clears the last column before drawing the new point is optional and can be adjusted or removed depending on the visual effect you're aiming for.
Remember, while scroll()
improves the visual fluidity by creating a continuous scrolling effect, the update rate can still be limited by the display's physical refresh rate and the speed at which your MicroPython script can run. Experiment with the utime.sleep(0.1)
delay at the end of the main loop to find a balance between smoothness and speed that works for your specific application.
I would like to create an audio spectrum analyzer using a Raspberry Pi Pico and a 128x64 OLED display using MicroPython. How would you suggest I go about this project. Break it down step-by-step.
+
Creating an audio spectrum analyzer with a Raspberry Pi Pico and a 128x64 OLED display using MicroPython is a fascinating project that combines hardware interfacing with digital signal processing. Below is a step-by-step guide to help you get started on this project.
+This project involves a mix of hardware setup, digital signal processing, and real-time data visualization. Take it step by step, and don't hesitate to seek out community forums and resources if you encounter challenges.
+ + + + + + + + + + + + + + + +This section includes some advanced topics that might be useful for helping you create and debug MicroPython projects.
+Topics include:
+