This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

PWM with a piezo buzzer

I'm trying to get some sound out of a piezo buzzer using the PWM library here:

github.com/.../nrf51-pwm-library

My buzzer has a resonant frequency of 2,730Hz. The buzzer doesn't have its own driver, I need to generate the wave for it. What changes do I need to make to main_sin.c to get that frequency?

Here's what I've tried so far.

int main(void)
{
    uint32_t counter = 0;
    
    nrf_pwm_config_t pwm_config = PWM_DEFAULT_CONFIG;
    
    pwm_config.mode             = PWM_MODE_BUZZER_64; // Was PWM_MODE_LED_100
    pwm_config.num_channels     = 1; // Was 3

    // One channel only, on pin 14
    pwm_config.gpio_num[0]      = 14; // Was 8
    // pwm_config.gpio_num[1]      = 8;
    // pwm_config.gpio_num[2]      = 10;
    
    // Initialize the PWM library
    nrf_pwm_init(&pwm_config);

    while (true)
    {
        // Update the 3 outputs with out of phase sine waves
        nrf_pwm_set_value(0, sin_table[counter]);
        // nrf_pwm_set_value(1, sin_table[(counter + 33) % 100]);
        // nrf_pwm_set_value(2, sin_table[(counter + 66) % 100]);
        counter = (counter + 1) % 100;
        
        // Add a delay to control the speed of the sine wave
        // 125 kHz is one wave every 0.000008s or 0.008ms or 8us, so why is this 8000us?
        // nrf_delay_us(8000);

        // If 8000us gets us 125kHz, then 366,300 should get us 2730Hz.
        nrf_delay_us(366300);
    }
}

As the comments above show, I'm confused about how this delay relates to the PWM frequency.

I'd also like to be executing other code while the buzzer is sounding and thought that PPI made this possible. So why the while() loop here at all?

  • Eliot,

    I'm not familiar with the code you posted above or the library that it uses, however I have implemented code to control a piezo buzzer in much the same way that you require, which I'll provide snippets of below. Some of this code may be in the library you use, again I haven't looked at it.

    First, you can configure the pin to the buzzer as an output like below:

    // Audio / PWM Pin Setup
      nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER);		// Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as output
      nrf_gpiote_task_config(0, PWM_OUTPUT_PIN_NUMBER, NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW);
      NRF_PPI->CH[1].EEP = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[1];  // Configure PPI channel 1 to toggle PWM_OUTPUT_PIN on every TIMER2 COMPARE[1] match
      NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
      NRF_PPI->CHEN = (PPI_CHEN_CH1_Enabled << PPI_CHEN_CH1_Pos);  // Enable PPI channels 0-2
    

    Then you can configure the Timer to control the PWM output as below - this code will cause the PWM pin to toggle whenever the number in CC[1] is hit:

    // Configure Timer for Buzzer
    	NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;
      NRF_TIMER2->PRESCALER = 4; //1us resolution
      NRF_TIMER2->TASKS_CLEAR = 1;
      NRF_TIMER2->CC[1] = MAX_SAMPLE_LEVELS;
      NRF_TIMER2->INTENSET = (TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos);
      NVIC_EnableIRQ(TIMER2_IRQn); // Enable interrupt on Timer 2
      __enable_irq();
    

    Next, you can implement a handler for the timer using code like below:

    void TIMER2_IRQHandler(void)						// used for buzzer
    {
      if ((NRF_TIMER2->EVENTS_COMPARE[1] != 0) && ((NRF_TIMER2->INTENSET & TIMER_INTENSET_COMPARE1_Msk) != 0))
      {
        // Sets the next CC1 value
        NRF_TIMER2->EVENTS_COMPARE[1] = 0;
        NRF_TIMER2->CC[1] = (NRF_TIMER2->CC[1] + MAX_SAMPLE_LEVELS);
      }
    }
    

    To start the buzzer, simply start the Timer, as below:

    NRF_TIMER2->TASKS_START = 1;

    Try changing the value of MAX_SAMPLE_LEVELS around to achieve different tones.

    I hope this helps!

    Cheers, Steve

  • I should add that you can run other code while the buzzer is running since it will be toggled on the Timer - you may have to be clever about it if you want to change the tone frequency to create a tune, but it's doable.

  • Thanks Steven. I just tried running this. No sound yet. What would a typical value be for MAX_SAMPLE_LEVELS? How would I aim for 2,730Hz?

  • Hi Eliot

    Did you figure this out?

    You can modify the nrf51-pwm-library to run at a base frequency of about 2730 Hz quite easily, you just have to change the max value of the PWM using the following formula:

    pwm_max_value = 16.000.000 / (2 * 2^PRESCALER * FREQUENCY)

    With a prescaler of 0 this gives you a max value of 2930, and I implemented a new PWM mode in nrf_pwm.c to test this out:

        case PWM_MODE_BUZZER_2730HZ:
            PWM_TIMER->PRESCALER = 0;
            pwm_max_value = 2930;
            break;
    

    Now you just need to set the channel according to the duty cycle you want on your buzzer signal, for 50% duty cycle use 1465.

    Best regards Torbjørn

  • Sorry, I'm not following entirely. I've modified nrf_pwm_init() to use your extra case, thanks for that. But don't understand what you mean by "for 50% duty cycle use 1465". What should the pwm_config look like?

    nrf_pwm_config_t pwm_config =
    	{.num_channels  = 3,
    	.gpio_num       = {8,9,10},
    	.ppi_channel    = {0,1,2,3,4,5},
    	.gpiote_channel = {2,3,0},
    	.mode           = PWM_MODE_BUZZER_2730HZ};
    

    My buzzer has one pin on the Nordic's pin 14 and the other on ground, effectively.

Related