Created a quick and dirty piezo buzzer driver - It works but the output frequency is a tad low?
Mahonroy
Posts: 175
in Propeller 1
Hey guys,
Created a quick and dirty piece of code to drive a piezo buzzer, code is below.
It sounds pretty good, but I can tell its "out of tune" - the notes I play from the piezo are a bit lower frequency than if I play them on a keyboard or computer. For a test I downloaded a guitar tuner app on my phone, and I had the piezo play a G7 (3134 Hz)... the tuner said I was playing a D7 (2348).
Is this just the nature of piezo buzzers or is something incorrect with my code? I am guessing it has to do with me not accounting for the actual delay of the instructions themselves, which is adding a tiny bit of extra time between the waitcnt functions... thus lowering my desired frequency. Can this be it? Is there a good way to account for this, or am I going to have to manually tune this thing on every single note? Thanks and any help is greatly appreciated!
Created a quick and dirty piece of code to drive a piezo buzzer, code is below.
It sounds pretty good, but I can tell its "out of tune" - the notes I play from the piezo are a bit lower frequency than if I play them on a keyboard or computer. For a test I downloaded a guitar tuner app on my phone, and I had the piezo play a G7 (3134 Hz)... the tuner said I was playing a D7 (2348).
Is this just the nature of piezo buzzers or is something incorrect with my code? I am guessing it has to do with me not accounting for the actual delay of the instructions themselves, which is adding a tiny bit of extra time between the waitcnt functions... thus lowering my desired frequency. Can this be it? Is there a good way to account for this, or am I going to have to manually tune this thing on every single note? Thanks and any help is greatly appreciated!
CON '//////////////////////////////////////////////////////////////////////////// '/// Notes '//////////////////////////////////////////////////////////////////////////// NOTE_B5 = 987 NOTE_E6 = 1318 NOTE_G6 = 1567 NOTE_E7 = 2636 NOTE_C7 = 2092 NOTE_D7 = 2348 NOTE_G7 = 3134 PUB PlayFrequency(pin, tone, duration)| counter repeat counter from 0 to (tone * duration / 1000) outa[pin] := 1 waitcnt(clkfreq / (tone + tone) + cnt) outa[pin] := 0 waitcnt(clkfreq / (tone + tone) + cnt) PUB PlaySound(pin,sound) case sound SOUND_COIN: PlayFrequency(pin,NOTE_B5,100) PlayFrequency(pin,NOTE_E6,850) SOUND_1UP: PlayFrequency(pin,NOTE_E6,125) PlayFrequency(pin,NOTE_G6,125) PlayFrequency(pin,NOTE_E7,125) PlayFrequency(pin,NOTE_C7,125) PlayFrequency(pin,NOTE_D7,125) PlayFrequency(pin,NOTE_G7,125) 'SOUND_SUCCESS: 'SOUND_FAIL:
Comments
Your notes are referencing the clock frequency (the CNT counter). With a typical 5.00MHz xtal and PLL16x you get a clock of 80MHz which is 12.5ns per clock.
To get a 1ms time you would use
waitcnt(cnt + clkfreq/1000)
where clkfreq/1000 = 80,000,000/1000 = 80,000
so this would wait 80,000 clocks.
If my maths are correct (need a coffee ) you need this...
waitcnt(cnt + (clkfreq/1_000_000)*NOTE_G7))
Note the parenthesis use denoting the divide before multiply to ensure no overflow!
Another way to use the output to the pin is
Thanks for the response!
I tried your piece of code "waitcnt(cnt + (clkfreq/1_000_000)*tone)" but it didn't work... as the frequencies increase, the tone decreases with this (because its creating a larger delay the larger the frequency).
My function's tone parameter is in Hz, since your note constants are in Hz. I guess Cluso thought you wanted tone to be a period in milliseconds and not a frequency in Hz.
This is theoretically slightly dangerous, in that if tone is too big, it will result in a delta that is too small, which will make the waitcnt wrap around. However, I'm pretty sure this won't happen for any audible frequencies.
I was just trying to give you an example.
Because you are outputting a 1 then a 0 for each waitcnt, you would need to divide by 2 because it is the time for half the note.
ie waitcnt(cnt + (clkfreq/1_000_000)*tone/2)
When you get to this point, take a look at the counter app notes.
In addition, the repeat call is possibly computing the count every time it goes through the loop, so I'd pre-compute that value, like this:
The argument to the repeat is only calculated once. There's not really any reason to cache it like that.
If you look at the resulting PNUT bytecode, you'll see that it computes the argument once at the beginning and then does a djnz loop on it, i.e. it loops while (internal_counter--) == 0
Yes, the "repeat i from x to y step z" version recomputes x, y, and z every iteration, according to BST's compiler listing. It even recomputes x every time, so that it can figure out if it's iterating forwards or backwards.
Thanks again for the help!
This piece of code does not play any sound either. I'm working on it to see where the bug is.
The "loops := (duration....)" line will take a decent amount of time to compute as it contains a divide and a multiply. A single Spin instruction has about 400 cycles of overhead, and your "waitcnt(time += delta)" line is likely that or a little better because it includes an assignment. Your highest frequency tone (3134) gives you a time delta of 12763 clocks, so you *should* have plenty of time left over, but if you go much higher in frequency you'll start cutting it close.
Thanks Jason, the problem was that it couldn't play a note that was under 1 second in duration. So I changed it to this and it now works great:
The notes are also in tune with how they are supposed to be.