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

Correct way to change frequency with APP_PWM.

I'm trying to create a siren tone using the pwm library, that ramps up and down between 2kHz and 3kHz.

Initially I started with the example provided in SDK v15.3.0
nRF5_SDK\examples\peripheral\pwm_library
using this with the nrf52840 DK.

Example was working with 2kHz  @ 30% duty.

I attempted to modify the example to allow for frequency shifts, but the output waveform is not as expected.

Here is the example output  as expected with fixed frequency:

This is the attempted frequency shift with edges in the spaces that I cannot make sense of at the moment:

Ignoring the dodgy uS increment Slight smile
Here is the code snippet of the attempt.

APP_PWM_INSTANCE(PWM1,1);                   // Create the instance "PWM1" using TIMER1.

static volatile bool ready_flag;            // A flag indicating PWM status.

void pwm_ready_callback(uint32_t pwm_id)    // PWM callback function
{
    ready_flag = true;
    app_pwm_disable(&PWM1);
    app_pwm_uninit(&PWM1);
}

static const uint32_t base_frequency = 500; //in us or 2kHz
int main(void)
{
    ret_code_t err_code;

    /* 2-channel PWM, 200Hz, output on DK LED pins. */
    //static app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_1CH(500, 30);

    static app_pwm_config_t pwm1_cfg = {
        .pins            = {30, APP_PWM_NOPIN},
        .pin_polarity    = {APP_PWM_POLARITY_ACTIVE_HIGH, APP_PWM_POLARITY_ACTIVE_LOW},
        .num_of_channels = 1,
        .period_us       = base_frequency
    };

    /* Initialize and enable PWM. */
    err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback);
    APP_ERROR_CHECK(err_code);
    app_pwm_enable(&PWM1);
    app_pwm_channel_duty_set(&PWM1, 0, 30);

    while (true)
    {
        ready_flag = false;

        /* ... or wait for callback. */
        while (!ready_flag);
        pwm1_cfg.period_us += 10;        
        err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback);
        APP_ERROR_CHECK(err_code);
        app_pwm_enable(&PWM1);
        app_pwm_channel_duty_set(&PWM1, 0, 30);

    }
}


/** @} */

I'm not entirely sure what is going wrong here, and I'm not sure what the intended method for updating frequency with the APP_PWM library.

Secondly, what does the PWM state change busy and ready mean? Does ready occur on full period, or duty set?

Ideally, I would like to get the timer->PPI->GPIOTE method working as one of targets does not have PWM peripherals.

I'm hoping I'm missing something very simple. Thanks in advance.

Parents
  • Hi 

    I would not recommend using the app_pwm driver for this, for a couple of reasons. 

    Essentially app_pwm was developed for the older nRF51 devices which did not have a dedicated PWM controller, so it's running PWM using a TIMER, but it is not designed to change the PWM frequency dynamically, only the duty cycle, making it less suitable for your application. 

    If I am correct you are simply looking at outputting a square wave with varying frequency, you're not looking at generating more advanced sound waves?

    If so I would definitely recommend the TIMER->PPI->GPIOTE method. Essentially you have to set up a GPIOTE channel in OUT mode with toggle enabled, and connect a compare task of the timer to the out task of the GPIOTE through PPI. Then you just need to change the compare value of the timer to change the sound frequency. 

    Best regards
    Torbjørn

  • Thankfully its not a complex sound wave. I managed to work one of the examples as per your suggestion.

    This seemed to work fine - and couldn't reproduce the janky pulse widths in my first attempt.
    If this helps anyone else, an example would look something like this.

    #include <stdbool.h>
    #include <stdint.h>
    #include "nrf.h"
    #include "app_error.h"
    #include "nrf_drv_timer.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_gpiote.h"
    
    static const nrf_drv_timer_t m_ext_timer0 = NRF_DRV_TIMER_INSTANCE(0);
    
    void timer_0_isr_handler(nrf_timer_event_t event_type, void * p_context)
    {
        if(event_type == NRF_TIMER_EVENT_COMPARE1) 
        { 
          /* update new sequence here */
        }
    }
    
    static void _init_gpiote(uint32_t pin)
    {
        ret_code_t err_code;
    
        err_code = nrf_drv_gpiote_init();
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_gpiote_out_config_t out_cfg = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
        err_code = nrf_drv_gpiote_out_init((nrf_drv_gpiote_pin_t)pin,&out_cfg);
        APP_ERROR_CHECK(err_code);
    }
    
    static void _init_ppi(uint32_t pin)
    {
        ret_code_t err_code;
    
        uint32_t mark_evt_addr;
        uint32_t gpiote_task_addr;
        nrf_ppi_channel_t mark_ppi_channel;
    
        uint32_t period_evt_addr;
        uint32_t gpiote_task_addr1;
        nrf_ppi_channel_t period_ppi_channel;
        
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        // Set the pulse width channel.
        err_code = nrf_drv_ppi_channel_alloc(&mark_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        mark_evt_addr = nrf_drv_timer_event_address_get(&m_ext_timer0, NRF_TIMER_EVENT_COMPARE0);
        gpiote_task_addr = nrf_drv_gpiote_clr_task_addr_get(pin);
    
        err_code = nrf_drv_ppi_channel_assign(mark_ppi_channel, mark_evt_addr, gpiote_task_addr);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_ppi_channel_enable(mark_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        //Set the channel for the total period
        err_code = nrf_drv_ppi_channel_alloc(&period_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        period_evt_addr = nrf_drv_timer_event_address_get(&m_ext_timer0, NRF_TIMER_EVENT_COMPARE1);
        gpiote_task_addr1 = nrf_drv_gpiote_set_task_addr_get(pin);
    
        err_code = nrf_drv_ppi_channel_assign(period_ppi_channel, period_evt_addr, gpiote_task_addr1);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_ppi_channel_enable(period_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
    }
    
    static void _init_timer(void)
    {
        ret_code_t err_code;
    
        nrf_drv_timer_config_t timer_cfg =    {
            .frequency          = NRF_TIMER_FREQ_16MHz,
            .mode               = NRF_TIMER_MODE_TIMER,
            .bit_width          = NRF_TIMER_BIT_WIDTH_32,
            .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
            .p_context          = NULL
        };
    
        err_code = nrf_drv_timer_init(&m_ext_timer0, &timer_cfg, timer_0_isr_handler);
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_timer_compare(
            &m_ext_timer0,
            NRF_TIMER_CC_CHANNEL0,
            2400, //duty @30%
            false
        );
    
        //2khz waveform @ 16MHz - clear cc1 short, and interrupt for sequence update.
        nrf_drv_timer_extended_compare(
            &m_ext_timer0, 
            NRF_TIMER_CC_CHANNEL1, 
            8000, 
            NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, 
            true
        );
    
    }
    
    int main(void)
    {
        _init_gpiote(30);
        _init_ppi(30);
        _init_timer();
    
        nrf_drv_gpiote_out_task_enable(30);
        nrf_drv_timer_enable(&m_ext_timer0);
    
        while (true)
        {
            // Do Nothing - GPIO can be toggled without software intervention.
        }
    }

    At any rate, thanks for your help.

  • Great to hear that you figured it out, and thanks for sharing your code Slight smile

Reply Children
No Data
Related