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”?

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *