In one of my past Blogs I have described how to implement a simple tone generator using a Repeating Timer for the Raspberry Pico with the C++ SDK.
This time I want to describe an approach which is a little bit more versatile: The Pico provides no built-in DAC and thus we have no real analog output, but we can generate PWM signals on all pins – so if we use a basic PWM frequency which is well above the hearing range (which is 20 – 20’000 hz) we can get a very similar result. The Pico SDK has a powerful API which allows us to achieve this easily.
Then we just need to make sure that we output the sound samples an a exact predefined sampling rate. Here as well we can use the Repeating Timer functionality of the SDK – we just need to make sure that we can provide the data quick enough so that the timer callback can finish before the next call!
In the example below I take a SineWaveGenerator as input – put we could use any other source which provides raw audio signals (e.g. wav file, raw file…).
#include "SineWaveGenerator.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "hardware/pwm.h"
#include "pico/time.h"
const int gpio = 2; // output pin
const int amplitude = 256; // amplitude of square wave (pwm values -amplitude to amplitude)
const int sampleRate = 10000; // sample rate in Hz
const int pwm_freq = 60000; // audable range is from 20 to 20,000Hz
repeating_timer_t timer;
SineWaveGenerator<int16_t> sineWave(amplitude);
const int tones[] = {523,587,659};
int idx = 0;
uint slice;
uint channel;
// writes a an individual value
void writeTone(int16_t value){
// shift up so min value must be 0
uint16_t output = value + amplitude
// output signal
pwm_set_chan_level(slice, channel, output);
}
// timed output executed at the sampleRate
bool defaultAudioOutputCallback(repeating_timer* ptr) {
uint16_t sample = sineWave.readSample() ;
writeTone(sample);
return true;
}
// setup pwm pin and timer
void begin(int gpio){
// setup pwm pin
gpio_set_function(gpio, GPIO_FUNC_PWM);
slice = pwm_gpio_to_slice_num(gpio);
channel = pwm_gpio_to_channel(gpio);
// setup pwm frequency
pwm_config cfg = pwm_get_default_config();
float pwmClockDivider = static_cast<float>(clock_get_hz(clk_sys)) / (pwm_freq * amplitude * 2);
Serial.printf("clock speed is %f\n", static_cast<float>(clock_get_hz(clk_sys)));
Serial.printf("divider is %f\n", pwmClockDivider);
pwm_config_set_clkdiv(&cfg, pwmClockDivider);
pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_FREE_RUNNING);
pwm_config_set_phase_correct(&cfg, true);
pwm_config_set_wrap (&cfg, amplitude);
pwm_init(slice, &cfg, true);
// set initial output value
pwm_set_chan_level(slice, channel, 0);
// setup timer
uint64_t time = 1000000UL / sampleRate;
Serial.printf("Timer value %ld\n", time);
if (!add_repeating_timer_us(-time, defaultAudioOutputCallback, nullptr, &timer)){
Serial.println("Error: alarm_pool_add_repeating_timer_us failed; no alarm slots available");
}
}
// Ends the output
void end(const int16_t gpio){
cancel_repeating_timer(&timer);
if (gpio >- 1) {
uint slice = pwm_gpio_to_slice_num(gpio);
pwm_set_enabled(slice, false);
}
}
// we start the sound processing
void setup() {
Serial.begin(119200);
begin(gpio);
sineWave.begin(sampleRate, 659);
}
// we just change the frequency every second
void loop() {
delay(1000);
Serial.printf("Playing frequency %d\n", tones[idx]);
sineWave.setFrequency(tones[idx]);
if (++idx>=3){
idx = 0;
}
}
2 Comments
Aram Perez · 19. May 2021 at 21:21
Where’s “SineWaveGenerator.h”?
pschatzmann · 19. May 2021 at 21:27
I didnt’ think that this is of much insterrest, but here it is:
https://gist.github.com/pschatzmann/9e0bce76a901294f65d90dbb1ecd912c