Non-blocking sounds using PWM (X2 PICAXEs)

#1
I recently developed a wireless link and needed sound alerts to indicate certain events or conditions. While PICAXE Basic has the Sound and Tune commands, both are blocking: all execution stops for the duration of the sound.

The code was being developed for a PICAXE 20X2, using both background Serial data reception (via the hSerial port) and the programmable background timer. I added some PWM management code to the background timer interrupt to allow different series of beeps to be played on a small speaker (I used Rev-Ed's SPE002 basic piezo transducer) while allowing the other functions of the data terminal to continue execution.

The wireless application required a fast execution speed (32MHz or higher). To keep PWM frequencies within the range of the piezo sounder, the maximum execution frequency is 32MHz is used.

The attached code is configured for a 20X2 but can easily be adapted to 28X2 or 40X2. The M2-series of chips do not have the programmable timer of the X2s, limiting the codes usefulness for them. The demonstration code requires 3 outputs, piezo speaker and 2 LEDs, to demonstrate the code.
To get the best effect, I used a 125mS timer interrupt - this is the standard 'unit length' of the beeps.

Due to forum posting limitations, the code is supplied in two separate posts.
Code:
[color=Green]'Code to demonstrate the use of PWM to allow non-blocking sounds to be generated[/color]
[color=Blue]Symbol [/color][color=Black]Version [/color][color=DarkCyan]=  [/color][color=Navy]0 [/color][color=Green]'10-Nov-2017  284 bytes Testing sound generation using PWM
'[/color]
[color=Blue]Symbol [/color][color=Black]Major [/color][color=DarkCyan]= [/color][color=Navy]1    [/color][color=Green]' Major revision ID
'[/color]
[color=Navy]#PICAXE [/color][color=Black]20X2[/color]
[color=Navy]#COM 4
#Terminal 38400[/color]
[color=Green]'
'#No_Table
'
' **** Hardware Pins Definitions - i prefix for inputs; o for outputs; b for bothway pins
'[/color]
[color=Blue]Symbol [/color][color=Black]oYellowLED    [/color][color=DarkCyan]= [/color][color=Blue]C.7[/color]
[color=Green]'[/color]
[color=Blue]Symbol [/color][color=Black]oSpeaker      [/color][color=DarkCyan]= [/color][color=Blue]C.5[/color]
[color=Green]'[/color]
[color=Blue]Symbol [/color][color=Black]oBlueLED      [/color][color=DarkCyan]= [/color][color=Blue]C.3[/color]
[color=Green]'
' **** Variables - t prefix: bit variable; b: byte; w: word; r: other RAM; s: scratchpad; e: EEPROM
'[/color]
[color=Blue]Symbol [/color][color=Black]bCounter      [/color][color=DarkCyan]= [/color][color=Purple]b4     [/color][color=Green]'w2[/color]
[color=Blue]Symbol [/color][color=Black]bData         [/color][color=DarkCyan]= [/color][color=Purple]b5     [/color][color=Green]'w2[/color]
[color=Blue]Symbol [/color][color=Black]bSoundNum     [/color][color=DarkCyan]= [/color][color=Purple]b6     [/color][color=Green]'w3[/color]
[color=Blue]Symbol [/color][color=Black]bSoundPtr     [/color][color=DarkCyan]= [/color][color=Purple]b7     [/color][color=Green]'w3[/color]
[color=Blue]Symbol [/color][color=Black]bPWMPeriod    [/color][color=DarkCyan]= [/color][color=Purple]b8     [/color][color=Green]'w4[/color]
[color=Blue]Symbol [/color][color=Black]bSoundSeq     [/color][color=DarkCyan]= [/color][color=Purple]b9     [/color][color=Green]'w4[/color]
[color=Blue]Symbol [/color][color=Black]bLoop         [/color][color=DarkCyan]= [/color][color=Purple]b10    [/color][color=Green]'w5[/color]
[color=Blue]Symbol [/color][color=Black]bEighthSecs   [/color][color=DarkCyan]= [/color][color=Purple]b11    [/color][color=Green]'w5    1/8 Seconds (125mS Tick)[/color]
[color=Blue]Symbol [/color][color=Black]bSeconds      [/color][color=DarkCyan]= [/color][color=Purple]b12    [/color][color=Green]'w6[/color]
[color=Blue]Symbol [/color][color=Black]wMinutes      [/color][color=DarkCyan]= [/color][color=Purple]w7     [/color][color=Green]'b14/15
'[/color]
[color=Blue]Symbol [/color][color=Black]wPWMDuty      [/color][color=DarkCyan]= [/color][color=Purple]w8     [/color][color=Green]'b16/17[/color]
[color=Blue]Symbol [/color][color=Black]wPWMDuty.Lo   [/color][color=DarkCyan]= [/color][color=Purple]b16    [/color][color=Green]'w8[/color]
[color=Blue]Symbol [/color][color=Black]wPWMDuty.Hi   [/color][color=DarkCyan]= [/color][color=Purple]b17    [/color][color=Green]'w8
'
' **** Constants - Prefix = c
'[/color]
[color=Blue]Symbol [/color][color=Black]False         [/color][color=DarkCyan]= [/color][color=Navy]0[/color]
[color=Blue]Symbol [/color][color=Black]True          [/color][color=DarkCyan]= [/color][color=Navy]1[/color]
[color=Blue]Symbol [/color][color=Black]cDQuote       [/color][color=DarkCyan]= [/color][color=Navy]34[/color]
[color=Blue]Symbol [/color][color=Black]cDisabled     [/color][color=DarkCyan]= [/color][color=Navy]255[/color]
[color=Green]'[/color]
[color=Blue]Symbol [/color][color=Black]mskBGTimer    [/color][color=DarkCyan]= [/color][color=Navy]%10000000 [/color][color=Green]'For the background timer (only) interrupt[/color]
[color=Blue]Symbol [/color][color=Black]flgBGTimer    [/color][color=DarkCyan]= [/color][color=Navy]%10000000 [/color][color=Green]'For the background timer (only) interrupt[/color]
[color=Blue]Symbol [/color][color=Black]tmrIntOn1stTick [/color][color=DarkCyan]= [/color][color=Navy]65535   [/color][color=Green]'Interrupt to be caused by roll over on first major tick[/color]
[color=Blue]Symbol [/color][color=Black]t125mS_32     [/color][color=DarkCyan]= [/color][color=Navy]49911     [/color][color=Green]'SetTimer value for 1/8 second ticks @ 32MHz
'[/color]
[color=Blue]Symbol [/color][color=Black]cOneMinute    [/color][color=DarkCyan]= [/color][color=Navy]60        [/color][color=Green]'60 Seconds
'[/color]
[color=Blue]Symbol [/color][color=Black]c80mS         [/color][color=DarkCyan]=  [/color][color=Navy]320      [/color][color=Green]'  80mS[/color]
[color=Blue]Symbol [/color][color=Black]c100mS        [/color][color=DarkCyan]=  [/color][color=Navy]400      [/color][color=Green]' 100mS[/color]
[color=Blue]Symbol [/color][color=Black]c1S           [/color][color=DarkCyan]= [/color][color=Navy]4000      [/color][color=Green]'1000mS
'
' **** EEPROM - Prefix = e (256 Bytes - 0 to 255d)
'
'Bytes 0-31 free
'The following data is used to drive PWM sounds to the piezo speaker[/color]
[color=Blue]Symbol [/color][color=Black]ePip [/color][color=DarkCyan]= [/color][color=Navy]32                 [/color][color=Green]'Lock' sound[/color]
[color=Blue]EEPROM [/color][color=Black]ePip, [/color][color=Blue]([/color][color=Navy]1[/color][color=Black], [/color][color=Navy]127[/color][color=Black], [/color][color=Navy]0[/color][color=Black], [/color][color=Navy]255[/color][color=Black], [/color][color=Navy]0[/color][color=Blue]) [/color][color=Green]'3900Hz[/color]
[color=Blue]Symbol [/color][color=Black]ePipPip [/color][color=DarkCyan]= [/color][color=Navy]37              [/color][color=Green]'Un-Lock' sound [/color]
[color=Blue]EEPROM [/color][color=Black]ePipPip, [/color][color=Blue]([/color][color=Navy]1[/color][color=Black], [/color][color=Navy]127[/color][color=Black], [/color][color=Navy]0[/color][color=Black], [/color][color=Navy]255[/color][color=Blue]) [/color][color=Green]'3900Hz[/color]
[color=Blue]EEPROM ([/color][color=Navy]2[/color][color=Black], [/color][color=Navy]0[/color][color=Black], [/color][color=Navy]0[/color][color=Black], [/color][color=Navy]0[/color][color=Blue])              [/color][color=Green]'No sound[/color]
[color=Blue]EEPROM ([/color][color=Navy]3[/color][color=Black], [/color][color=Navy]127[/color][color=Black], [/color][color=Navy]0[/color][color=Black], [/color][color=Navy]255[/color][color=Black], [/color][color=Navy]0[/color][color=Blue])       [/color][color=Green]'3900Hz[/color]
[color=Blue]Symbol [/color][color=Black]eBlip [/color][color=DarkCyan]= [/color][color=Navy]50                [/color][color=Green]'Good' sound[/color]
[color=Blue]EEPROM [/color][color=Black]eBlip, [/color][color=Blue]([/color][color=Navy]1[/color][color=Black], [/color][color=Navy]191[/color][color=Black], [/color][color=Navy]1[/color][color=Black], [/color][color=Navy]127[/color][color=Blue])   [/color][color=Green]'2600Hz[/color]
[color=Blue]EEPROM ([/color][color=Navy]2[/color][color=Black], [/color][color=Navy]166[/color][color=Black], [/color][color=Navy]1[/color][color=Black], [/color][color=Navy]77[/color][color=Black], [/color][color=Navy]0[/color][color=Blue])        [/color][color=Green]'3000Hz[/color]
[color=Blue]Symbol [/color][color=Black]eBloop [/color][color=DarkCyan]= [/color][color=Navy]59               [/color][color=Green]'Bad' sound[/color]
[color=Blue]EEPROM [/color][color=Black]eBloop, [/color][color=Blue]([/color][color=Navy]1[/color][color=Black], [/color][color=Navy]166[/color][color=Black], [/color][color=Navy]1[/color][color=Black], [/color][color=Navy]77[/color][color=Blue])   [/color][color=Green]'3000Hz[/color]
[color=Blue]EEPROM ([/color][color=Navy]2[/color][color=Black], [/color][color=Navy]191[/color][color=Black], [/color][color=Navy]1[/color][color=Black], [/color][color=Navy]127[/color][color=Blue])          [/color][color=Green]'2600Hz[/color]
[color=Blue]EEPROM ([/color][color=Navy]3[/color][color=Black], [/color][color=Navy]191[/color][color=Black], [/color][color=Navy]1[/color][color=Black], [/color][color=Navy]127[/color][color=Black], [/color][color=Navy]0[/color][color=Blue])       [/color][color=Green]'2600Hz[/color]
[color=Blue]Symbol [/color][color=Black]eNoSound [/color][color=DarkCyan]= [/color][color=Navy]72             [/color][color=Green]'Silence[/color]
[color=Blue]EEPROM [/color][color=Black]eNoSound, [/color][color=Blue]([/color][color=Black]cDisabled[/color][color=Blue]) [/color]
[color=Green]'[/color]
 
#2
Part 2 of the demo code
Code:
[color=Green]'**************************************************************************************************
'[/color]
[color=Black]Init: [/color][color=Blue]Output [/color][color=Black]oYellowLED, oBlueLED
      [/color][color=Blue]Low [/color][color=Black]oYellowLED, oBlueLED
      [/color][color=Blue]SetFreq m32
      Pause [/color][color=Black]c1S
      [/color][color=Blue]SerTxd (CR[/color][color=Black], [/color][color=Blue]LF[/color][color=Black], [/color][color=Blue]CR[/color][color=Black], [/color][color=Blue]LF[/color][color=Black], [/color][color=Red]"Booted "[/color][color=Blue])
      [/color][color=Green]'
      [/color][color=Blue]For [/color][color=Black]bLoop [/color][color=DarkCyan]=  [/color][color=Navy]1 [/color][color=Blue]to [/color][color=Navy]16
         [/color][color=Blue]Toggle [/color][color=Black]oYellowLED
         [/color][color=Blue]Pause [/color][color=Black]c100mS
      [/color][color=Blue]Next [/color][color=Black]bLoop
      [/color][color=Blue]SerTxd ([/color][color=Red]"PWM Sound Test v"[/color][color=Black], #Major, [/color][color=Red]"."[/color][color=Black], #Version, [/color][color=Blue]CR[/color][color=Black], [/color][color=Blue]LF)
      [/color][color=Green]'
      'Start the background timer (runs continuously)
      [/color][color=Purple]Timer [/color][color=DarkCyan]= [/color][color=Black]tmrIntOn1stTick
      [/color][color=Blue]SetTimer [/color][color=Black]t125mS_32                  [/color][color=Green]'Expires after 1/8 second @ 32 MHz
      [/color][color=Purple]Flags [/color][color=DarkCyan]= [/color][color=Navy]0                           [/color][color=Green]'Reset serial reception flag
      [/color][color=Blue]SetIntFlags [/color][color=Black]flgBGTimer, mskBGTimer  [/color][color=Green]'Set timer 0 to interrupt
      '
      [/color][color=Black]bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]eNoSound
      [/color][color=Green]'
      ' ------ MAIN LOOP ---------------
      '
      [/color][color=Blue]Do     [/color][color=Green]' Main Loop
         [/color][color=Blue]If [/color][color=Black]bCounter [/color][color=DarkCyan]= [/color][color=Navy]0 [/color][color=Blue]Then
            Select Case [/color][color=Black]bSoundNum
               [/color][color=Blue]Case [/color][color=Navy]0[/color][color=Black]: bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]ePip
               [/color][color=Blue]Case [/color][color=Navy]1[/color][color=Black]: bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]ePipPip
               [/color][color=Blue]Case [/color][color=Navy]2[/color][color=Black]: bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]eBlip
               [/color][color=Blue]Case [/color][color=Navy]3[/color][color=Black]: bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]eBloop
            [/color][color=Blue]EndSelect
            [/color][color=Black]bSoundNum [/color][color=DarkCyan]= [/color][color=Black]bSoundNum [/color][color=DarkCyan]+ [/color][color=Navy]1 [/color][color=DarkCyan]And [/color][color=Navy]%00000011
         [/color][color=Blue]EndIf
         Pause [/color][color=Black]c80mS                      [/color][color=Green]'Pauses can be trunkated by a timer 0 interrupt occurring
         [/color][color=Blue]Toggle [/color][color=Black]oYellowLED
         bCounter [/color][color=DarkCyan]= [/color][color=Black]bCounter [/color][color=DarkCyan]+ [/color][color=Navy]1 [/color][color=DarkCyan]And [/color][color=Navy]%00011111
      [/color][color=Blue]Loop[/color]
[color=Green]'
' ************************************************************
'  Interrupt handler
' ************************************************************
'  [/color]
[color=Blue]Interrupt:If [/color][color=Purple]TOFlag [/color][color=DarkCyan]= [/color][color=Black]True [/color][color=Blue]then
            [/color][color=Purple]TOFlag [/color][color=DarkCyan]= [/color][color=Black]False                            [/color][color=Green]'Reset (clear) the flag first
            [/color][color=Blue]Inc [/color][color=Black]bEighthSecs
            bEighthSecs [/color][color=DarkCyan]= [/color][color=Black]bEighthSecs [/color][color=DarkCyan]And [/color][color=Navy]%00000111   [/color][color=Green]'Limit to 0 - 7
            [/color][color=Blue]If [/color][color=Black]bEighthSecs [/color][color=DarkCyan]= [/color][color=Navy]0 [/color][color=Blue]Then
               [/color][color=Green]'Ie Once every second
               [/color][color=Blue]Toggle [/color][color=Black]oBlueLED                        [/color][color=Green]'Indicate 1-second time
               [/color][color=Blue]Inc [/color][color=Black]bSeconds
               [/color][color=Blue]If [/color][color=Black]bSeconds [/color][color=DarkCyan]= [/color][color=Black]cOneMinute [/color][color=Blue]Then
                  [/color][color=Black]bSeconds [/color][color=DarkCyan]= [/color][color=Navy]0
                  [/color][color=Blue]Inc [/color][color=Black]wMinutes
               [/color][color=Blue]EndIf
            EndIf                                     [/color][color=Green]'Every second
            [/color][color=Blue]Read [/color][color=Black]bSoundPtr, bData                     [/color][color=Green]'Every 1/8 second
            [/color][color=Blue]If [/color][color=Black]bData [/color][color=DarkCyan]< [/color][color=Black]cDisabled [/color][color=Blue]Then
               If [/color][color=Black]bData [/color][color=DarkCyan]= [/color][color=Navy]0 [/color][color=Blue]Then
                  PWMOut [/color][color=Black]oSpeaker, [/color][color=Blue]Off
                  [/color][color=Black]bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]eNoSound
               [/color][color=Blue]Else
                  [/color][color=Black]bData [/color][color=DarkCyan]= [/color][color=Black]bPWMPeriod                  [/color][color=Green]'Save a copy
                  [/color][color=Blue]Read [/color][color=Black]bSoundPtr, bSoundSeq, bPWMPeriod, wPWMDuty.Hi, wPWMDuty.Lo
                  bSoundPtr [/color][color=DarkCyan]= [/color][color=Black]bSoundPtr [/color][color=DarkCyan]+ [/color][color=Navy]4
                  [/color][color=Blue]If [/color][color=Black]bData [/color][color=DarkCyan]<> [/color][color=Black]bPWMPeriod [/color][color=DarkCyan]Or [/color][color=Black]bSoundSeq  [/color][color=DarkCyan]= [/color][color=Navy]1 [/color][color=Blue]Then
                     [/color][color=Green]'Avoid transitional 'click' between identical sounds but never on first sound
                     [/color][color=Blue]PWMOut PWMDiv16[/color][color=Black], oSpeaker, bPWMPeriod, wPWMDuty
                  [/color][color=Blue]EndIf  
               EndIf  
            EndIf    [/color][color=Green]'If not disabled 
            [/color][color=Purple]Timer [/color][color=DarkCyan]= [/color][color=Black]tmrIntOn1stTick                   [/color][color=Green]'Then reset the timer
          [/color][color=Blue]EndIf    [/color][color=Green]'Timer has ticked
          [/color][color=Blue]SetIntFlags [/color][color=Black]flgBGTimer, mskBGTimer          [/color][color=Green]'Set timer 0 to interrupt
          [/color][color=Blue]Return[/color]
[color=Green]'**************************************************************************************************[/color]
The tones can be altered using the PWM Wizard; I suggest that a 50% duty cycle be used but it is not critical. Note that the PWM Duty value is a word variable and its values must be stored as two bytes in EEPROM.

The speaker MUST be connected to a PWM output pin.

Peter
 

darb1972

Senior Member
#3
Hi Peter

Thanks for posting this effort. I love the code structure and variable labelling system. Very nice.

I can see a few applications for this work around. It's a shame that several commands in the PICaxe world are blocking and that it's necessary to manipulate code to overcome such situations.

Great effort!

Regards
Brad
 

hippy

Technical Support
Staff member
#4
Note, that while PWM generation of tones is itself non-blocking, lets the PICAXE program continue during sound output, that mechanism can be blocked by blocking commands such as SERIN, IRIN etc, during READTEMP etc.

It's a great idea and undoubtedly useful, but how useful will depend on what the program is doing.

The best way to have completely autonomous sounds is probably to go with a second PICAXE dedicated to generating sounds, controlled through serial from the master. That adds extra cost but should simplify the PICAXE coding.
 

hippy

Technical Support
Staff member
#6
No; TUNE blocks. I believe it can be terminated prematurely by an interrupt - I might be mistaken on that though.
 

darb1972

Senior Member
#8
The best way to have completely autonomous sounds is probably to go with a second PICAXE dedicated to generating sounds, controlled through serial from the master. That adds extra cost but should simplify the PICAXE coding.
I'm glad you reminded me of this alternative. Unless a design is destined for production in the millions, then adding another PICaxe (or two) to any project really isn't a big deal. PICaxe are cheap items, particularly when it comes to comparing cost verse capability/features. I often forget that sometimes a hardware solution is an even better workaround than complex code. Even dedicated special function chips that can be controlled by the PICaxe via I2C/SPI is another alternative to consider in some cases.
 
Top