Non-blocking sounds using PWM (X2 PICAXEs)

inglewoodpete

Senior Member
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.
Rich (BB code):
'Code to demonstrate the use of PWM to allow non-blocking sounds to be generated
Symbol Version =  0 '10-Nov-2017  284 bytes Testing sound generation using PWM
'
Symbol Major = 1    ' Major revision ID
'
#PICAXE 20X2
#COM 4
#Terminal 38400
'
'#No_Table
'
' **** Hardware Pins Definitions - i prefix for inputs; o for outputs; b for bothway pins
'
Symbol oYellowLED    = C.7
'
Symbol oSpeaker      = C.5
'
Symbol oBlueLED      = C.3
'
' **** Variables - t prefix: bit variable; b: byte; w: word; r: other RAM; s: scratchpad; e: EEPROM
'
Symbol bCounter      = b4     'w2
Symbol bData         = b5     'w2
Symbol bSoundNum     = b6     'w3
Symbol bSoundPtr     = b7     'w3
Symbol bPWMPeriod    = b8     'w4
Symbol bSoundSeq     = b9     'w4
Symbol bLoop         = b10    'w5
Symbol bEighthSecs   = b11    'w5    1/8 Seconds (125mS Tick)
Symbol bSeconds      = b12    'w6
Symbol wMinutes      = w7     'b14/15
'
Symbol wPWMDuty      = w8     'b16/17
Symbol wPWMDuty.Lo   = b16    'w8
Symbol wPWMDuty.Hi   = b17    'w8
'
' **** Constants - Prefix = c
'
Symbol False         = 0
Symbol True          = 1
Symbol cDQuote       = 34
Symbol cDisabled     = 255
'
Symbol mskBGTimer    = %10000000 'For the background timer (only) interrupt
Symbol flgBGTimer    = %10000000 'For the background timer (only) interrupt
Symbol tmrIntOn1stTick = 65535   'Interrupt to be caused by roll over on first major tick
Symbol t125mS_32     = 49911     'SetTimer value for 1/8 second ticks @ 32MHz
'
Symbol cOneMinute    = 60        '60 Seconds
'
Symbol c80mS         =  320      '  80mS
Symbol c100mS        =  400      ' 100mS
Symbol c1S           = 4000      '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
Symbol ePip = 32                 'Lock' sound
EEPROM ePip, (1, 127, 0, 255, 0) '3900Hz
Symbol ePipPip = 37              'Un-Lock' sound 
EEPROM ePipPip, (1, 127, 0, 255) '3900Hz
EEPROM (2, 0, 0, 0)              'No sound
EEPROM (3, 127, 0, 255, 0)       '3900Hz
Symbol eBlip = 50                'Good' sound
EEPROM eBlip, (1, 191, 1, 127)   '2600Hz
EEPROM (2, 166, 1, 77, 0)        '3000Hz
Symbol eBloop = 59               'Bad' sound
EEPROM eBloop, (1, 166, 1, 77)   '3000Hz
EEPROM (2, 191, 1, 127)          '2600Hz
EEPROM (3, 191, 1, 127, 0)       '2600Hz
Symbol eNoSound = 72             'Silence
EEPROM eNoSound, (cDisabled) 
'
 
Last edited:

inglewoodpete

Senior Member
Part 2 of the demo code
Rich (BB code):
'**************************************************************************************************
'
Init: Output oYellowLED, oBlueLED
      Low oYellowLED, oBlueLED
      SetFreq m32
      Pause c1S
      SerTxd (CR, LF, CR, LF, "Booted ")
      '
      For bLoop =  1 to 16
         Toggle oYellowLED
         Pause c100mS
      Next bLoop
      SerTxd ("PWM Sound Test v", #Major, ".", #Version, CR, LF)
      '
      'Start the background timer (runs continuously)
      Timer = tmrIntOn1stTick
      SetTimer t125mS_32                  'Expires after 1/8 second @ 32 MHz
      Flags = 0                           'Reset serial reception flag
      SetIntFlags flgBGTimer, mskBGTimer  'Set timer 0 to interrupt
      '
      bSoundPtr = eNoSound
      '
      ' ------ MAIN LOOP ---------------
      '
      Do     ' Main Loop
         If bCounter = 0 Then
            Select Case bSoundNum
               Case 0: bSoundPtr = ePip
               Case 1: bSoundPtr = ePipPip
               Case 2: bSoundPtr = eBlip
               Case 3: bSoundPtr = eBloop
            EndSelect
            bSoundNum = bSoundNum + 1 And %00000011
         EndIf
         Pause c80mS                      'Pauses can be trunkated by a timer 0 interrupt occurring
         Toggle oYellowLED
         bCounter = bCounter + 1 And %00011111
      Loop
'
' ************************************************************
'  Interrupt handler
' ************************************************************
'  
Interrupt:If TOFlag = True then
            TOFlag = False                            'Reset (clear) the flag first
            Inc bEighthSecs
            bEighthSecs = bEighthSecs And %00000111   'Limit to 0 - 7
            If bEighthSecs = 0 Then
               'Ie Once every second
               Toggle oBlueLED                        'Indicate 1-second time
               Inc bSeconds
               If bSeconds = cOneMinute Then
                  bSeconds = 0
                  Inc wMinutes
               EndIf
            EndIf                                     'Every second
            Read bSoundPtr, bData                     'Every 1/8 second
            If bData < cDisabled Then
               If bData = 0 Then
                  PWMOut oSpeaker, Off
                  bSoundPtr = eNoSound
               Else
                  bData = bPWMPeriod                  'Save a copy
                  Read bSoundPtr, bSoundSeq, bPWMPeriod, wPWMDuty.Hi, wPWMDuty.Lo
                  bSoundPtr = bSoundPtr + 4
                  If bData <> bPWMPeriod Or bSoundSeq  = 1 Then
                     'Avoid transitional 'click' between identical sounds but never on first sound
                     PWMOut PWMDiv16, oSpeaker, bPWMPeriod, wPWMDuty
                  EndIf 
               EndIf 
            EndIf    'If not disabled
            Timer = tmrIntOn1stTick                   'Then reset the timer
          EndIf    'Timer has ticked
          SetIntFlags flgBGTimer, mskBGTimer          'Set timer 0 to interrupt
          Return
'**************************************************************************************************
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
 
Last edited:

darb1972

Senior Member
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
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
No; TUNE blocks. I believe it can be terminated prematurely by an interrupt - I might be mistaken on that though.
 

darb1972

Senior Member
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