I am trying to get and understanding of PWM sound for the Raspberry Pi Pico. I have a passive buzzer hooked up to pin 10 (GPIO 7) of my pico and ran an example Python program that plays some tones and it seems to work fine.
Eventually I'd like to try to play a wav file
You're probably going to have to ditch Python, and use C/C++. Python is likely to be way too slow.
Eventually I'd like to try to play a wav file and I understand I'll probably need to swap out my buzzer for an actual speaker. But for now, I am trying to play a simple WAV file that is just a tone of middle C.
WAV files contain headers. You can try to decode them if you like, but it will be easier if you convert the files to RAW format of the desired bitrate and datatype. RAW just means raw binaries of the samples.
You may be interested in a little project that I did that demonstrates playing hard-coded audio:
https://github.com/blippy/rpi/tree/mast ... -hard-pico
It seems to work quite well.
I've made some notes about converting wav to raw here:
You might also consider Audacity if you on Windows.
My goals is to conceptually understand how PWM works. I have some understanding, but I feel there are some gaps here.
I've dumped this file into a byte array in Python. These are the details of this sound:
1 second long
8 bit (1 byte) unsigned samples
This means, the sound is exactly 8000 samples (i.e. 8000 bytes) large. As far as I understand, every 1/8000 of a second, I should set the PWM duty cycle to that sample since the PWM duty cycle will "fake" the analog equivalent of that sample. And since the PWM duty cycle can go from 0 to 65535, I multiply each 1 byte sample by (65535/255). Is my understanding of this correct?
Note sure 65535 is correct. This may not work. But you're on the right lines, very close.
I have presented some calculations on this page:
There are two things you want as inputs:
* "freq" - your sample rate (e.g. 8000Hz)
* "top" - the maximum value of each sample. If you're using unsigned 8-bit values, for example, the top will be 255.
Given freq and top, you set the pwm clk divider. The divider is calculated like so:
Code: Select all
/** NB clock divider must be in range 1.f <= value < 256.f
float pwm_divider(int freq, int top)
uint32_t f_sys = clock_get_hz(clk_sys); // typically 125'000'000 Hz
float scale = (top+1) * freq;
return f_sys / scale;
and you set it like so:
Code: Select all
uint slice_num = pwm_gpio_to_slice_num(SPK);
int top = 255;
int sampling_freq = 44'100;
sampling_freq = 8000;
float divider = pwm_divider(sampling_freq, top);
pwm_set_clkdiv(slice_num, divider); // pwm clock should now be running at 1MHz
Now, you must ensure that divider is at least 1, and less that 256, otherwise you're going to get strange results.
Now, it so happens that you can do a little check to ensure that this is so: run my utility ucalcs, available here:
The issue I am having right now is that the passive buzzer makes this glitchy high pitched sound, which is definitely not middle C. I am not sure if it is because of my understanding of PWM, if I have a buzzer rather than a speaker, or if my code is incorrect.
The following issues spring to mind:
* yeah, a passive buzzer is likely to be a bad choice
* your sample frequency is 8000Hz. So, 8000 times a second, the output pin will go high. This is well within human hearing, creating a very disagreeable high-pitch tone. You need to up the frequency to double or more.
* middle c has a frequency of 278Hz, which doesn't divide into 8000Hz exactly. So if you wrap around your samples, you'll find that the volume of the end of the samples is not the same as at the beginning, creating a jump in volume, which you'll notice.
One of my confusions is I am not sure what to set the frequency of the PWM to since I am using a timer to feed the sample. Right now I have it set to 8000 which is the same as the timer frequency, but I have a feeling this is wrong.
You haven't quite made it clear if you want to generate pure square-wave tones at a given frequency, or play audio samples. The former is actually quite straightforward, and you don't need wav files. I'm assuming the latter.
In my example code (link above) I actually recorded the samples at 8000Hz. I tripled it up to 32kHz to get rid of the high-pitch tone. This means that I only change the level every third call to the interrupt I set up.
I also used a top of 1023 instead of 255, making me scale up the volume. I created unnecessary work on that, though. I left it as is for now, as it's not doing any harm.
Note that I use an interrupt to reset the level every (third) wrap of the pwm clock. The use of interrupts on the wrap ensures that my setting of the level value correctly coincides with a wrap. Otherwise I could just be setting the new level willy-nilly, and the whole thing would just be wrong.
Hope that helps.