The first step in creating a railroad blinky LED example is to import the asyncio library. In MicroPython, there is not an asyncio library exactly, but a uasyncio library. To improve portability, many developers will import uasyncio as if it were the asyncio library by importing it at the top of their application, as follows:
import uasyncio as asyncio
Next, we can define our LEDs, just like we did in all our other examples, using the following code:
LED_RED = 1
LED_GREEN = 2
LED_BLUE = 3
LED_YELLOW = 4
If you look back at our example of writing a thread-based application, you'll recall that our task1 code looked as follows:
def task1():
while True:
pyb.LED(LED_BLUE).toggle()
time.sleep_ms(150)
def task2():
while True:
pyb.LED(LED_GREEN).toggle()
time.sleep_ms(150)
This is important to review because creating a coroutine will follow a similar structure! In fact, to tell the Python interpreter that our tasks are asynchronous coroutines, we need to add the async keyword before each of our task definitions, as shown in the following code:
async def task1():
while True:
pyb.LED(LED_BLUE).toggle()
time.sleep_ms(150)
async def task2():
while True:
pyb.LED(LED_GREEN).toggle()
time.sleep_ms(150)
The functions are now coroutines, but they are missing something very important: a yield point! If you examine each of our tasks, you can tell that we really want our coroutine to yield once we have toggled our LED and are going to wait 150 milliseconds. The problem with these functions as they are currently written is that they are making a blocking call to time.sleep_ms. We want to update this with a call to asyncio.sleep_ms and we want to let the interpreter know that we want to relinquish the CPU at this point. In order to do that, we are going to use the await keyword.
The await keyword, when reached by the coroutine, tells the event loop that it has reached a point in its execution where it will be waiting for an event to occur and it is willing to give up the CPU to another task. At this point, control is handed back to the event loop and the event loop can decide what task should be executed next. Using this syntax, our task code for the railroad blinky LED applications would be updated to the following:
async def task1():
while True:
pyb.LED(LED_BLUE).toggle()
await asyncio.sleep_ms(150)
async def task2():
while True:
pyb.LED(LED_GREEN).toggle()
await asyncio.sleep_ms(150)
For the most part, the general structure of our coroutine/task functions remains the same. The difference is that we define the function as async and then use await where we expect the asynchronous function call to be made.
At this point, we just initialize the LEDs using the following code:
pyb.LED(LED_BLUE).on()
pyb.LED(LED_GREEN).off()
Then, we create our event loop.
Creating the event loop for this application requires just four lines of code. The first line will assign the asyncio event loop to a loop variable. The next two lines create tasks that assign our coroutines to the event loop. Finally, we tell the event loop to run forever and our coroutines to execute. These four lines of code look as follows:
loop = asyncio.get_event_loop()
loop.create_task(task1())
loop.create_task(task2())
loop.run_forever()
As you can see, we can create any number of tasks and pass the desired coroutine to the create_task method in order to get them into the event loop. At this point, you could run this example and see that you have an efficiently running railroad blinky LED program that uses cooperative multitasking.