Friday, November 8, 2024

Pico audio Part 5 - Talking clock

For an index to all my stories click this text

This is another entry in the series about producing audio with the Raspberry Pi Pico. Audio on the Pico sounds great. Not like the vintage Commodore speech synthesises. No this sounds like real audio. In this story I will show how to build a speaking clock. This story heavily leans on the previous stories so I urge you to read them first.

I like to emphasize again that this delivers a very good audio quality !!!

Audacity. This was the first story in this series. Here I showed how to record audio and save it in the required 8k wav file format. I chose audacity for this while it not only allows to record audio with a microphone but also can convert other audio files (like mp3) to the format we need. Read that story here:
http://lucstechblog.blogspot.com/2024/10/audacity-pico-audio-part-1.html

Pico audio 2. This story shows how to build the hardware needed for producing audio. Do not worry is is very cheap. The only thing you need is 6 resistors and 2 capacitors. Oh, and of course a Raspberry Pi Pico or Pico W. You can find this story here:
http://lucstechblog.blogspot.com/2024/10/audio-on-pico-part-2-hardware.html

Pico audio 3. This story deals with the software side. To produce audio on your Pico you will need no less then 5 libraries. This story shows where to get them and how to write your first program that speaks out loud "Hello World". All done in MicroPython. Read the story here:
http://lucstechblog.blogspot.com/2024/10/pico-audio-part-3.html

Pico audio 4. This story brings the first practical project: a speaking thermometer. For  building this project you will need a Dallas DS18B20 digital thermometer, next to the hardware described in the second story. The program is written in MicroPython. Read the story here:
http://lucstechblog.blogspot.com/2024/11/pico-audio-part-4-talking-thermometer.html

Getting the time.

Generally when you are working with your Pico in MicroPython you will have the Pico connected to Thonny. If you type the next commands in the shell:

import time
print(time.localtime())


The Pico will respond with something like:

(2023, 1, 25, 21, 8, 58, 2, 25)

The answer is build up like this:
year, month, day, hour, minute, seconds, weekday, yearday

The year is 2023, the month is 1 (January), the day is 25, the hour is 21, minutes is 8,seconds 58, weekday is 2 and yearday is 25. This data is stored in a tuple.

To get the hour you need to fetch the 3d element in the tuple like this:
hour = time.localtime()[3]

To get the minutes, fetch the 4th element like this:
minute = time.localtime()[4]

As you can see I was writing this story on the 25th of january 2023 at 21 hour 8 minutes and 58 seconds.
The weekday is 2. Weeks start at 0 on Sunday. So weekday 2 is tuesday. And it is the 25th day of this year.

So print(time.localtime()) gives us a load of information. Useful information with which we can make some great projects. Think Internet Of Things and actions that only need to be taken on MondaĆ½s for example or every 10th day.

But.........There always is a but...........

When you connect the Pico to Thonny your Pico gets the right time from your computer through Thonny. This means that if you disconnect and therefore power-down, Pico loses the right time.
So if you use the Pico stand-alone and power it from a usb power supply or battery or power-bank the Pico does not get the right time. So it starts with it's initial time and that is the unix Epoch time. The date is set to 1 January 1970 at midnight.

How to set the right time.

It is not much of a clock if it does not give you the right time.
There are 3 ways to set the right time in the Pico.

The first one is the old fashioned way to set the time manually. Add some switches to the Pico and with each press of one of the switches add 1 to the hour or add 1 to the minutes.

The second way is to use a special device called a real-time-clock. This is a clock chip with a small battery that operates for several year. I am not going to use that here.

Mind you. The Pico has a build in RTC (real time clock) but no battery back-up. So you still have to set the time each time the Pico reboots.

The third solution is to use a Pico W. The Pico W has wifi and can connect to a so called NTP-server. This is an abbreviation of Network-Time-Protocol server. You need to install a MicroPython library for this. If you want to know how to do that just look at this story:
http://lucstechblog.blogspot.com/2023/04/getting-right-time-with-micropython.html

In this version of the talking clock we are going to set the time manually by means of 2 buttons: one for the hour and one for the minutes.

With print(time.localtime()) we can get the time. However we can not set the right time this way. We need to address the internal RTC for that. Therefore the RTC library is needed and that is included in MicroPython so no need to download that.

The exact syntax for this is:

import machine
rtc = machine.RTC()


and then:

rtc.datetime((year, month, date, weekday, hour, minute, seconds, subseconds))

The value of subseconds is hardware dependent so different values on a Pico then from another microcontroller. Usually you will only set year, month, day, hour and minutes and set the remaining variables to zero. That is when setting the time manually. The NTP server or a RTC module will give the right seconds to.

Now we know how to set the right time we need to complete our speech files.

Revisit Audacity

In the first story I used Audacity to record audio files with a microphone. We recorded the words 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 40, 50, "it is now", "degrees". We needed those for our talking thermometer.

Please restart Audacity and record the words "minutes", "midnight" and "hour" and add these to your Pico's Sounds directory.

The hardware setup

For building the talking clock we need the Raspberry Pi Pico (or Pico W) and the hardware for producing the audio as discussed in the second story in this series. You can find that here:
http://lucstechblog.blogspot.com/2024/10/audio-on-pico-part-2-hardware.html



For setting the clock's hour and minutes I added two buttons. And I added another button to get the time. The buttons are attached to a 10K pull-up resistor. The hour button is attached to GP18. The minutes button is attached to GP19. The button for getting the time is attached to GP20.

The talking clock program

In the Picoaudio 2 story you can find where to get the necesary libraries. It also discuses a small test program to check wether your audio setup is working correctly. Please read that story first and try the test before proceeding.

And here is the full program:

import time
import machine
from wavePlayer import wavePlayer

but01=machine.Pin(18, machine.Pin.IN)
but02=machine.Pin(19, machine.Pin.IN)
but03=machine.Pin(20, machine.Pin.IN)

player = wavePlayer()

rtc = machine.RTC()

# set your own date and time here
year = 2023  
month = 1
date = 25
hour = 17
minute = 54

rtc.datetime((year, month, date, 0, hour, minute, 00, 0))

print(time.localtime())
time.sleep(2)

while True:
        
  if (but01.value()==0):
      print("button 1 pressed")
      hour = time.localtime()[3]
      minute = time.localtime()[4]
      hour = hour + 1
      if (hour == 24):
          hour = 0
      hour2=hour

      print (hour2)
      rtc.datetime((year, month, date, 0, hour, minute, 00, 0))
      if (hour2 == 00):
        player.play("/Sounds/midnight.wav") 
      if (hour2>0 and hour2<=20):
        player.play("/Sounds/"+str(hour2)+".wav")
      if (hour2 == 24):
        player.play("/Sounds/midnight.wav")   
      if (hour2>20 and hour2<=23):
        player.play("/Sounds/"+str(20)+".wav")
        hour2 =  hour2-20
        player.play("/Sounds/"+str(hour2)+".wav")
      print(time.localtime())
      time.sleep(.5)
    
  if (but02.value()==0):
      print("button 2 pressed")
      hour = time.localtime()[3]
      minute = time.localtime()[4]
      minute = minute + 1
      if (minute == 60):
          minute = 0
      minute2=minute    
      rtc.datetime((year, month, date, 0, hour, minute, 00, 0))
      if (minute2 == 0):
        print("0 minutes")   
      if (minute2>0 and minute2<=20):
        player.play("/Sounds/"+str(minute2)+".wav")
        
      if (minute==30):
        player.play("/Sounds/"+str(30)+".wav")
      if (minute2>20 and minute2<30):
        player.play("/Sounds/"+str(20)+".wav")
        minute2 =  minute2-20
        player.play("/Sounds/"+str(minute2)+".wav")
        
      if (minute==40):
        player.play("/Sounds/"+str(40)+".wav")
      if (minute2>30 and minute2<40):
        player.play("/Sounds/"+str(30)+".wav")
        minute2 =  minute2-30
        player.play("/Sounds/"+str(minute2)+".wav")

      if (minute==50):
        player.play("/Sounds/"+str(50)+".wav")
      if (minute2>40 and minute2<50):
        player.play("/Sounds/"+str(40)+".wav")
        minute2 =  minute2-40
        player.play("/Sounds/"+str(minute2)+".wav")
 
      if (minute2>50 and minute2<60):
        player.play("/Sounds/"+str(50)+".wav")
        minute2 =  minute2-50
        player.play("/Sounds/"+str(minute2)+".wav")
      print(time.localtime())
      time.sleep(.5)
      
  if (but03.value()==0):
    print("=========================================")  
    print(time.localtime())
    print("=========================================")
    player.play("/Sounds/itsnow.wav")
    
    # get the stored hour
    hour = time.localtime()[3]
    
    if (hour == 00):
      player.play("/Sounds/midnight.wav") 
    if (hour>0 and hour<=20):
      player.play("/Sounds/"+str(hour)+".wav")
    if (hour == 24):
      player.play("/Sounds/midnight.wav")   
    if (hour>20 and hour<=23):
      player.play("/Sounds/"+str(20)+".wav")
      hour =  hour-20
      player.play("/Sounds/"+str(hour)+".wav")
    player.play("/Sounds/hour.wav")  
    
    # get the stored minutes
    minute = time.localtime()[4]

    if (minute>0 and minute<=20):
        player.play("/Sounds/"+str(minute)+".wav")
        
    if (minute==30):
        player.play("/Sounds/"+str(30)+".wav")
    if (minute>20 and minute<30):
        player.play("/Sounds/"+str(20)+".wav")
        minute =  minute-20
        player.play("/Sounds/"+str(minute)+".wav")

    if (minute==40):
        player.play("/Sounds/"+str(40)+".wav") 
    if (minute>30 and minute<40):
        player.play("/Sounds/"+str(30)+".wav")
        minute =  minute-30
        player.play("/Sounds/"+str(minute)+".wav")

    if (minute==50):
        player.play("/Sounds/"+str(50)+".wav") 
    if (minute>40 and minute<50):
        player.play("/Sounds/"+str(40)+".wav")
        minute =  minute-40
        player.play("/Sounds/"+str(minute)+".wav")
 
    if (minute>50 and minute<60):
        player.play("/Sounds/"+str(50)+".wav")
        minute =  minute-50
        player.play("/Sounds/"+str(minute)+".wav")
        
    if (minute != 0): 
        player.play("/Sounds/minutes.wav")

    time.sleep(1)

You can copy this and paste it into Thonny.

As usual on this weblog I am going to discuss some of the details in this program.

import time
import machine
from wavePlayer import wavePlayer

These are the necesaqry libraries. Time is obviously needed when building a clock.... The machine library is needed for thr RTC and the buttons. And the waveplayer library is the one that produces the audio.

but01=machine.Pin(18, machine.Pin.IN)
but02=machine.Pin(19, machine.Pin.IN)
but03=machine.Pin(20, machine.Pin.IN)

The buttons are defined and assigned to their pins. but01 on GP18 for setting the hour. but02 on GP19 for setting the minutes. And but03 on GP20 for getting the time.

player = wavePlayer()

rtc = machine.RTC()

The waveplayer library is initiated and we call it player for convenience. The RTC library is also initiated and that is called rtc.

# set your own date and time here
year = 2023  
month = 1
date = 25
hour = 17
minute = 54

rtc.datetime((year, month, date, 0, hour, minute, 00, 0))

print(time.localtime())
time.sleep(2)

If you start the program from Thonny, which you should for testing purposes, you can fill in here the current date and time. Then you do not need to set the time with the buttons. You can fill in any date you like. The RTC does not mind....... For testing purposes set the hour and minutes a bit earlier so you can test the buttons.

  if (but01.value()==0):
      print("button 1 pressed")
      hour = time.localtime()[3]
      minute = time.localtime()[4]

When the first button is pressed the program gets the hour and minutes from the rtc using time.localtime. The first time the program is run the hour and minutes are those that we put in at the beginning of the program. The second time the button is pressed the hour is already different and therefore we need to check it.

      hour = hour + 1
      if (hour == 24):
          hour = 0
      hour2=hour

Then we add 1 to the hour. Then we check if the hour is 24. If it is the clock must not say 24 hour xxx minutes. The clock starts again at 00 at 24.00 hour. So we set it to 0.
Next the value of the hour variable is copied into the hour2 variable. We will use that one to do some calculations.

      print (hour2)
      rtc.datetime((year, month, date, 0, hour, minute, 00, 0)

As a check the hour2 variable is printed in Thonny's shell. The Real Time Clock is set to the new values in this case only the hour has changed.

      if (hour2 == 00):
        player.play("/Sounds/midnight.wav") 

The program tests if the hour is 0 and then the player speaks the word "Midnight".

      if (hour2>0 and hour2<=20):
        player.play("/Sounds/"+str(hour2)+".wav")

If it is between 0 and 21 hour the player speaks out one of the 20 wav files in the Sounds library. The hour2 value is an integer value so we translate it first to a string and then add ".wav"
This way the hour 10 becomes "10.wav"

      if (hour2>20 and hour2<=23):
        player.play("/Sounds/"+str(20)+".wav")
        hour2 =  hour2-20
        player.play("/Sounds/"+str(hour2)+".wav")

If the hour is between 20 and 24 the player speaks out the file "20.wav" next 20 is subtracted from the hours value and the remaining figure is spoken.

This is code is basically the same for but02. Only here we test for the minutes and the test is a bit more extensive as the minutes go up to 59.

And for but03 the code is basically also the same. The difference is that this button is only used for having the clock speaking the time and no values are altered.

Using the clock

First you need to decide whether you want this clock program to start up immediately after the Pico is powered up. If so you need to save this program using the name main.py MicroPython automatically runs a program with the name main.py at powering up.

I advise however to first test this program running from Thonny. Then you can intervene if something is not working. So first save it as for example Talkclock.py

When powering the Pico from a power bank (and the program is started as main.py) the clock will not have the actual time. So press the hour button and each time you do the hours are raised by one and you will hear the clock saying the hour in your own voice. Same goes for setting the minutes with the second button.

When the time is set no further action is needed. Just press the third button when you want to know the time.


Here is my breadboard setup. The Pico on the right side with a label lying on top of it showing the pin numbers. You can find that here: http://lucstechblog.blogspot.com/2021/03/raspberry-pico-pin-layout-help.html The audio hardware is still on a separate breadboard as I use it on several Pico's.  The audio output is connected with crocodile clips to an active speaker. And yes: on the left side on the bottom is an SD card. 

Expansions

The clock speaks the time in hours and minutes. So at 13.45 it wil say "It is now 13 hour 45 minutes" At 10.30 the clock says "It is now 10 hour, 30 minutes"
This is not exactly like how we look at a clock in real life. Normally we say it is quarter to one or half past 10 etc. The problem is that we run out of memory on the Pico. There is not a lot room left to add more voice recordings. Luckily there is a solution. We can attach an SD card to the Pico and then we get virtually unlimited memory. That story is coming up.

If you are using the Pico W instead of the ordinary Pico you can connect the Pico to the internet to get the exact time from a Network Time Protocol server. You can put that time into the RTC and you always will have the right time.

Another expansion would be to attach the DS18B20 to the Pico like we did in the third story. Add another button and presto we have the time and temperature at hand.

Another thing that comes to my mind is incorporating an alarm. You can record the sound of an alarm bell or have your spouse/partner speak a wake-up call. For this we would also need more memory for the voice/audio recordings.

But for now this works great !!
And it really is a fun project.

Till next time.
Have fun


Luc Volders