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