Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terrific Work! I created an asynchrous version #1

Open
bai-yi-bai opened this issue Jun 5, 2022 · 0 comments
Open

Terrific Work! I created an asynchrous version #1

bai-yi-bai opened this issue Jun 5, 2022 · 0 comments

Comments

@bai-yi-bai
Copy link

bai-yi-bai commented Jun 5, 2022

What a wonderful team you have! I am incredibly impressed.

This simple tutorial showed me what I needed to integrate sound into my project. This work is exactly what is needed to inspire learners around the world to go down the path of learning to code. This knowledge and videos like these will make a positive impact on the future. I'm incredibly envious. I wish I had access to micro-controllers when I was a teenager and I wish micropython had existed then! I'm a strong supporter of those who create projects, tutorials, and videos for learners of all ages.

Currently, I am working on a RPI Pico-based project which I hope to release on github within the next couple months. I have been waiting for parts to arrive so that I could expand its functionality. Yesterday, a shipment arrived with a speaker and I wanted to use it right away.

I am quite fascinated by the ability of micro-controllers to do multiple things at once... not just sleep all the time :) Micropython has an excellent uasyncio library, thanks to the work of Peter Hinch. https://github.com/peterhinch/micropython-async. With this library I've been able to rapidly write asynchronous code. Just for comparison, I found writing Arduino multitasking so tedious, that it pushed me away from the platform five years ago.
Asynchronous programming allows writing far more complex and capable code. It really unlocks the power of these cheap micro-controllers. I encourage everyone to check out this great video by Digikey's Shawn Hymel: https://www.youtube.com/watch?v=5VLvmA__2v0&t=1s&ab_channel=Digi-Key

There's so much more I want to write with it and I want to encourage more people to learn how to use it.

And now for my contribution: a uasyncio of play_theme_micro.py

Asynchronous Version of async_play_theme_micro.py

This program demonstrates playing music using the uasyncio library. Feel free to do whatever you like with it: commit it to the repo, make a video on asyncio, or anything. I just didn't want to keep this for myself.

When the program first runs, it plays the original time.sleep-based version written by GurgleApps, then it begins the uasyncio loop which plays the song again. I did this to demonstrate to the listener that there is no difference in output.

The async code allows three things to occur simultaneously:

  • Play the notes of the song
  • Change the brightness of the RPI Pico's built-in LED using a sinusoidal wave function. This is similar to how the old Apple iBook "sleep" LEDs worked. I tuned this example based on other examples on YouTube, I find this implementation the most pleasing.
  • Print messages to the console, such as the point in the code, the details of the note being played, and "Free time for other tasks!"

Installation Steps

  1. Save the queue library to your RPI-Pico, or other microcontroller, as queue.py:
    https://github.com/peterhinch/micropython-async/blob/master/v3/primitives/queue.py
    I use Thonny, so I created a new file, copied the contents from the GitHub page, and pasted it. Then is used control+alt+s and chose to save it to my "Raspberry Pi Pico". I repeated this for good measure in a new project directory.
  2. Next, copy my code to a new file.
  3. Modify the speaker pin if necessary.
  4. Save the file as async_play_theme_micro.py.
  5. Run the code using Thonny's play button.
from machine import Pin, PWM
import time
import uasyncio
import utime
import queue

picoled = Pin(25, Pin.OUT, value=1)
LEDpwm = PWM(picoled)
LEDpwm.duty_u16(255 * 255)
speaker = PWM(machine.Pin(8))  

note_freq = {
  "A4": 440,
  "C5": 523,
  "D5": 587,
  "E5": 659,
  "R": 100,
  "END": 100
}
tune = [["D5", 0.5], ["C5", 0.5], ["D5", 0.5], ["R", 0.5], ["D5", 0.5], ["C5", 0.5], ["D5", 0.5],
         ["R", 0.5], ["D5", 0.5], ["C5", 0.5], ["A4", 0.5], ["C5", 0.5], ["E5", 0.5], ["C5", 0.5], ["D5", 2], ["END", 0]]

def play_note(note_name, duration):
    frequancy = note_freq[note_name]
    if note_name == "END":
        pass
    if note_name == "R":
        speaker.duty_u16(0)
    else:
        speaker.duty_u16(int(65535/2))
    
    speaker.freq(frequancy)
    time.sleep(duration)
    
async def async_play_tune(tune):
    print("Begin async loop...")
    while True:
        print("In the While True block...")
        for note in tune:
            note_name = note[0]
            frequancy = note_freq[note_name]
            duration = int(note[1]*1000) # sleep_ms usese milliseconds and requires integers
            print("In the for loop, on note:", note_name, frequancy, note[1], duration)
            if note_name == "END": # Using "if note == tune[-1]:" causes the song to end prematurely if that note appears elsewhere
                print("Tune finished...")
                speaker.duty_u16(0)
                return False
            if note_name == "R":
                speaker.duty_u16(0)
            else:
                speaker.duty_u16(int(65535/2))
            speaker.freq(frequancy)
            await uasyncio.sleep_ms(duration)
            
# Coroutine: heartbeat 
async def heartbeat():
    while True:
        duty = 255 # Start at full power
        direction = -1 # Start by decreasing brightness
        for repeat in range(511):
            duty += direction
            if duty > 255:
                duty = 255
                direction = -1
            LEDpwm.duty_u16(duty * duty)
            await uasyncio.sleep_ms(4)

async def main():
    print("Playing tune with regular for loop and time.sleep...")
    for note in tune:
        play_note(note[0], note[1])
        speaker.duty_u16(0)
    time.sleep(1)
    print("Playing tune with asynchronous library while changing LED brightness...")
    
    task_heart_beat = uasyncio.create_task(heartbeat())
    task_play_note = uasyncio.create_task(async_play_tune(tune))
    while True:
        print("Free time for other tasks!")
        await uasyncio.sleep_ms(500)

uasyncio.run(main())

#speaker = machine.PWM(machine.Pin(8))
#speaker.duty_u16(0)

I introduced a concept of "END" into the song structure. Without this, a song will play over and over.

This script can be modified in multiple ways, such as:

  • Adding more asynchronous tasks
    task___ = uasyncio.create_task(different function here())
  • Launching tasks using buttons by using the asynchronous button library. I won't detail that here, but my project uses this extensively. Multiple different tunes can be created and provided as arguments to the async_play_tune function.

I really appreciate all the work you've done.

My next goal is to try to replicate the iconic sounds of old DOS games from the 1980s and early 1990s which used the PC Speaker using an RPI Pico. There is a treasure trove of sounds from Apogee and ID Software that I wish I could get playing. I have not been successful, so far, but I will keep trying.

Keep up the great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant