How to Use Asyncio in MicroPython (Raspberry Pi Pico) | Digi-Key Electronics

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
as you dig into more advanced topics in micro python at some point you'll probably ask yourself hm can i run two things at the same time this question unfortunately opens up a whole new can of worms known as concurrent programming and in this video we're going to barely scratch the surface of concurrent programming or perhaps you're familiar with multitasking in regular python and you're curious to know if it will run on microcontrollers using micro python [Music] here is the basic idea we want our code to start running and we set up two or more tasks each with their own while loop these tasks run independently of each other you can get even more complicated by having some of these tasks return and having tasks spawn additional tasks but let's stick with this simple example this is known as concurrency we want to have at least two tasks making progress seemingly at the same time there are a few ways to accomplish this the first is to run each task in its own core with this approach the tasks are being executed simultaneously and it's known as parallelism or parallel execution another option is to use time slicing here there is a scheduler that runs as its own task in the background at a set interval maybe something like one millisecond the scheduler stops whatever task is running to let another task run this allows you to work on two tasks in an apparently simultaneous manner on the same core note that you can mix time slicing and parallelism if you want to have two cores working on more than two tasks if the scheduler or operating system can stop a task to run another task it's known as preemptive multitasking regular python supports preemptive multitasking with its threading library you can use it to create multiple threads that run like the tasks we just saw multi-threaded programming can be quite complex as you need to worry about priorities and memory access to avoid things like race conditions this is an example of a basic race condition where two tasks share the same global variable tasks a and b at first successfully read increment and write the value back to memory however below the dotted line we see that task a gets the value from memory but task b interrupts or preempts task a before task a can finish writing back to memory task b reads the same value increments it and writes it back to memory however since task a did not finish it attempts to increment the old value and write it to memory now the global variable did not update properly and this is known as a race condition i recommend checking out my introduction to real-time operating systems series on this channel if you'd like to learn more about preemptive scheduling in it we go through some advanced topics using arduino and free rtos but let's get back to python yet another approach is to use what's called cooperative multitasking here the tasks must explicitly yield to the scheduler when that happens the scheduler picks another task to run until that task yields writing cooperative tasks is generally easier as you know exactly when other tasks will run rather than guessing when the scheduler will interrupt your task while it won't eliminate race conditions it does make them a little easier to spot as it's no longer the fault of the scheduler running at an inopportune time however with cooperative multitasking you have to put a lot of thought into writing your tasks to make sure that they yield often enough to allow the other tasks to accomplish their jobs python 3 has this wonderful library called async io which stands for asynchronous input output i highly recommend reading through the api documentation on this page to learn more about its functions and objects at this time micro python barely supports full multi threading to give us preemptive multitasking it's listed here as highly experimental so i'd be cautious about using it however it does support a stripped-down version of async io which gives us some limited cooperative multitasking abilities running multiple tasks at the same time can be extremely useful if you want to do things like pull a sensor at a regular interval or wait for a return code from a website while performing other tasks let's take a look at an example on the raspberry pi pico for this demo i'm going to use the pico but it should work with most boards that run a recent version of micro python i'll connect a simple push button to pin gp15 make sure to connect the other side to ground we'll use an internal pull-up so we won't need to add an external resistor here to start open a new document in thawney and we're going to import the machine library i believe that this is imported by default so you don't necessarily need it here but i like to be explicit next we're going to import the uasync io which is the micro python of the async io library and it contains a subset of the available functions so not everything is implemented but it's enough to get some co routines going so that we can do some cooperative multitasking for now i'm just going to blink an led as a demonstration so we'll set up our led object to actually write a function that will be executed concurrently with some other functions we do so with this async keyword before we define our function this turns it into what's known as a co-routine a co routine is part of the async io library and it allows us to create functions and tasks that can run concurrently with other co-routines the idea is that the scheduler will not automatically switch between co-routines so you need to have some sort of weight or yield function going on in there such as our uasync io.sleep and notice that we call that with the keyword await a wait is a special keyword part of the async io library that tells us to wait for this future object or function to finish doing what it needs to do in this case all we're going to do is call the special sleep function that's part of async io or in this case uasync io every async i o program needs to be part of an event loop and this is kind of the overall structure that works as our scheduler and some things are going on in the back end that we can't necessarily see or we don't have insights to unless you dig into the source code for async io however we need to call dot run from the async io library and give it an entry point for our cooperative multitasking program in this case i'm going to define a function or in this case a co-routine called main and then we're going to call dot run on that main to let the program know that's the entry point for our cooperative multitasking program which which allows us to run several tasks at once if i want to call this blink routine i need to do so with the await function because i defined it as a co-routine using async without a weight if i just call blink and then pass it some sort of delay that's just going to return what's known as a future object and i can't do much with that although there are some functions associated with it if i want to create some underlying code or library that builds upon some of the low-level functions of async io but for now just know that blank isn't going to do much if i just call it directly in fact we can try that i'm going to show a case here that when blink finishes executing it will print done however if you notice that this is a wild true loop that should never happen to actually start running our cooperative multitasking program we need to point you async io.run to our entry point and generally speaking you only want one run function per program unless you finish executing everything in this part of the program and you want to go to another part the idea is you should only really have one co-routine running that's been called from run at a time from your entry point you can spawn multiple co-routines to run as tasks which we'll look at in a minute let's try running this on our raspberry pi pico i'm going to save it as main.pi in my pico and what you'll see here is it reaches done however the led never blinked and that's because when we called blink here blink didn't run it just gave us a future object and we can do stuff with the future object but that's a little outside the scope of this video right now if we actually want blink to run we need to put a weight in front of it which tells the event loop that it's time to run this as a function and it will go through this function and then wait for this function to be done because of this we should never actually get to this print done statement let's try running this here you'll see the led is blinking just like we said at 200 milliseconds and it never finishes because this line is waiting for this function to finish due to this await keyword now let's see how to run blink as its own task so that it runs concurrently with something else we're going to do in main that allows us to have two things going on at the same time instead of calling await we're going to call uasync io.createtask this function is similar to a weight in that it causes the function that's passed as a parameter to execute however it returns immediately that being said what probably actually happens is that led toggles when this function is called and then the sleep function is called which causes the processor to yield to another task in this case the task would be the rest of main which we're going to write another while true loop here we can get rid of the await blink function and write our main loop down here when we run this code what you'll notice is that the led might blink once it'll toggle but then as soon as the while true loop starts executing here this task never yields the processor as a result this blink function never gets to run again and so all that's happening is it's just printing done forever and the led doesn't blink in order to get cooperative multitasking to play nicely we need to make sure that every task yields the processor at some point so that other tasks get an opportunity to run to do that i'm just going to put in another await uasync.io sleep this time but you'll notice that the micro python version of async i o has a sleep milliseconds function which is not found in the big python async io library it works very much like sleep but instead of passing it a floating point decimal number that indicates the amount of seconds we want to sleep you give it the number of milliseconds you want to sleep in this case 1000 milliseconds is one second so i will stop my raspberry pi pico and i will run this code here you should see the led blinking at about 200 milliseconds just like before and done is being printed to the console once every second now we actually have two tasks running at the same time i'll stop the processor and remove this comment since we are now appropriately yielding like we should be while blinking an led might seem silly this is a very good way to do something like sampling a sensor every so many milliseconds or so many seconds and you can do this without using a hardware timer or without creating the normal timestamp method in your main or waiting for it to happen and not being able to execute things at the same time now we're going to include our button and i'm going to show you how to create something that can execute while waiting for another value to be returned as you saw earlier we connected our button to pin 15 and i'm going to declare it as an input using an internal pull-up so we don't have to add an external resistor to our breadboard we'll leave blink on a timer so that will continue executing concurrently with our other code here the idea is we want to create a function that waits does nothing until it receives a button press this can be something like waiting for a sensor to toggle a pin or waiting for a website to come back with a return value if you're saying posting something to the internet because you're making an iot device you often don't know how long something like that will take and if you wait for that return value your main program could just stall for quite some time and you're unable to do other things like read sensors or calculate values because you're just waiting for that website or waiting for some value and waiting for a button press is something we're going to use as a way to emulate that because well our program doesn't know when a user is going to press a button here we're going to sample the button it's one if it's high or zero if it's low and we're going to assign that value to our button previous variable we're also going to sample the button every time through this while loop if it is one which means the button is high or if the button value is equal to the previous value we're going to continue doing this while loop and the idea here is we're looking for a button press where it goes from one or zero and when this while loop sees that edge that one to zero transition it's going to exit out of the while loop and there's an implied return even though there's no return value here that will exit out of this function each time through the loop it's going to sample the button value again assign that to the previous variable and then we will play nice with other tasks and sleep for about 40 milliseconds note that you do not want to use the regular sleep here as that hogs the processor we need to use the special uasync sleep or sleep milliseconds function without it it's not yielding the processor and you won't be able to run multiple tasks all we're going to do is just have our main while true loop here await the wait button function so the wait button function gets called it sits here in this loop until that transition that high to low transition is seen from pressing the button when it exits out of the loop the rest of this code in the while true loop executes so it should only print bloop whenever we press the button and then the wait button function gets called again and it just does that forever in the meantime it is blinking the led let's go ahead and run this as you can see it's blinking the led and it is waiting for the button so i will press it and you see bloop appear here the led never stops blinking no matter how many times i press the button and get bloop to show up let's stop our processor i want to build up to showing you how to use cues so that you can pass messages or data between your different tasks to do that i'm going to make a simple demo where we just measure the time between button presses what i'm going to do is put that time in a queue message and then send that to the blink function where the blink function will use that information to update how long it delays its sleep for to start we're going to need the u time or mu time depending if you're pronouncing it as mu or u then we're going to grab an initial time stamp because we need it in order to figure out how long it took to press the button the first time we're going to measure the time between button presses by capturing the time stamp again storing it in a variable called new time and this only happens after the wait button function returns so we will await that function when it does return we get the timestamp and then we calculate the time between the button presses in delay time by subtracting the previous timestamp which at first will come from this time stamp and we will calculate the time between button presses by subtracting the time stamp which we got from this timestamp variable before everything started from the new timestamp and then of course we will update timestamp with the new time so that we have something for the next time through the loop for now we just want to print out that delay time to the console note that this is going to be in milliseconds let's run this and then whenever i press the button you should see it took me about three seconds to press it there another about almost four seconds and i'm going to press it a little bit faster and you can see whenever i press the button it measures how long it took me to press it and then displays that to the console async io in regular python does contain a cue class that we can use to pass messages between tasks however that does not seem to be the case with you async io however this does not seem to be the case with uasync io at least right now hopefully in the future we will see it added but for the time being we have to use a third-party q library that works with micro python's uasync io peter hinch helps contribute to a lot of micro python libraries and has written a bunch of things that help create or test uasync io his version three if you go there and go into primitives contains a q dot pi and that's what we want to add as a library in our micro python code click raw control a or command a to select all of the code and then copy it create a new document in thony paste it i noticed sometimes when i'm copying from github i get this line down at the bottom which is definitely not python code so i'm going to delete that and then i'm going to save this to my raspberry pi pico which apparently i need to stop first when i save it there i'm going to create a new directory called lib this is where micro python looks for libraries in lib i'm going to save it as q dot py now that we have it as a library in our pico we can use it in our program we want to import queue which is the q.py that we just created it looks for it in the lib folder instead of passing in a delay time for our blink co routine we're going to pass in a queue object we will initially start our delay as zero milliseconds but we will look for objects on the queue each time through this loop and update that delay if there are messages in the queue the queue object should have this empty function if we look in here sure enough returns true if the queue is empty false otherwise if it is not empty it will get a message from the front of that queue and a queue is first in first out or a fifo system or fifo depending on however you pronounce it note that this q.py module uses the uasync io library the functions that we call from it most of them at least are co-routines and in order to run them we need to call a weight and in order to await a co-routine and then get a return value we put the await behind the equal sign so await we'll call this function whatever gets returned will get passed to my variable here because i'm going to be measuring the time between button presses in milliseconds that's going to be what's passed as our message and i'm going to change my sleep function in blink to sleep ms and i'm going to sleep the amount of milliseconds provided by the message in the queue with this the led should continue to toggle at the rate specified by the button presses until we press the button again after we calculate this delay time which remember once again is in milliseconds i'm going to cap it at 2 000 milliseconds or 2 000 you don't have to add this but this is in case i don't press the button for a full minute it would take this co routine an entire minute because of this sleep milliseconds before it checks the messages again this can create some really funky effects where you might be mashing the button and nothing is updating because it's still sleeping until it can get the next message and then it tries to cycle through all those messages to update the amount of sleep so we're just going to cap it at two seconds once we have that we're going to call q dot put which puts the delay time in the queue remember this is first in first out so you might be able to press the button multiple times and then until this reads it it might be sleeping and then it goes through and then at least once it toggles the led and sleeps for each message it finds in the queue so it might take a little bit to get through all of the messages if you have long delays followed by short delays in your button presses once again this is a co routine so we need to call await let's try running this and i totally forgot to create my queue object so i'm trying to pass messages around in nothing before we can actually use the queue we need to create the queue in this case q is the module that was defined in the file we added and then the cue object which we just store in this queue variable once we create the object we then need to pass it to the blink function rather than a float object which is what this error message is actually about and then now that the cue object has been passed it can get used in the blink co routine let's try running it for real this time the led should be blinking at an extremely fast rate that we can't see it because we have a delay of zero milliseconds and in fact this only briefly yields the processor for not very long but it should be enough that when i press the button it sends a two second delay over to the co routine that then updates the led delay time now we can press it faster and you can see that we've got 163 milliseconds as soon as that message is received by the co routine the delay between the blinks is updated so that it's blinking a lot faster and each time i press it remember it's capped at two seconds the longest it will delay is two seconds between toggles and you can press it a little faster and if you wait to press it you might have to wait another two seconds before it reads your faster press in this case about half a second notice that what i'm doing here is using the async io co routines as often as i can in order to allow them to yield the processor when appropriate some libraries like this q.p.y library supports it and some don't you'll want to be very careful about what libraries and functions you're using when using async io for example some things like input say waiting for somebody to type something in the terminal or maybe some http or web iot type libraries may not be safe for async i o in that they may have delays in there while waiting for a return value that don't yield the processor in which case they're going to hog the processor that means you'll either have to find a library that uses async io or created on top of async io in order to do this or you might have to write your own from scratch where it is safe to use inside of your co routines and they don't hog the processor that's very important when it comes to mixing and matching async i o routines with non-async i o enabled library so be very careful when you go to write your program things can get complicated rather quickly multitasking can be a little confusing at first and there are a lot of commands and topics that i didn't cover here but i hope this has helped you get started creating your own concurrent programs good luck and as always happy hacking [Music] you
Info
Channel: DigiKey
Views: 31,375
Rating: undefined out of 5
Keywords: Raspberry Pi Pico, RP2040, MicroPython, Python, asyncio, multitasking, multithreading
Id: 5VLvmA__2v0
Channel Id: undefined
Length: 24min 49sec (1489 seconds)
Published: Mon Aug 09 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.