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

How to start pwm sequence after particular time delay of gpio event?

I am working on an application where I need to start pwm sequence after particular delay (accurate delay of multiple of 100us).

I am quite new to nordic environment.

Here is my code . Can please guide me what is wrong here??

#define    Period    20000 //20ms

const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0);

APP_PWM_INSTANCE(PWM1,1); 

static int gpio_flag =0;

void pwm_update(void)
{
      uint32_t value;
      value = duty_1;
      ready_flag = false;
      /* Set the duty cycle - keep trying until PWM is ready... */
      
      

      while ((app_pwm_channel_duty_set(&PWM1, 0, value))&(app_pwm_channel_duty_set(&PWM1, 1, value)) == NRF_ERROR_BUSY);
 
      /* ... or wait for callback. */
      while (!ready_flag);
      APP_ERROR_CHECK(app_pwm_channel_duty_set(&PWM1, 1, value));
      APP_ERROR_CHECK(app_pwm_channel_duty_set(&PWM1, 0, value));

}


void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
   gpio_flag =1;
   nrf_drv_timer_enable(&TIMER_LED);

   
}

void timer0_handler(nrf_timer_event_t event_type, void* p_context)
{
  switch (event_type)
    {
        case NRF_TIMER_EVENT_COMPARE0:
             timer_flg =1;
             nrf_drv_timer_clear(&TIMER_LED);
             gpio_flag=0;
             pwm_update();
            break;

        default:
            //Do nothing.
            break;
    }
}


void timer_init(void)
{
    uint32_t time_us = 500; 
    uint32_t time_ticks;
    uint32_t err_code = NRF_SUCCESS;

    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    err_code = nrf_drv_timer_init(&TIMER_LED, &timer_cfg, timer0_handler);
    APP_ERROR_CHECK(err_code);

    time_ticks = nrf_drv_timer_us_to_ticks(&TIMER_LED, time_us);
    nrf_drv_timer_compare(
         &TIMER_LED, NRF_TIMER_CC_CHANNEL0, time_ticks, true);

    
}

Parents Reply Children
  • Hello again, Ram

    I have taken a look at the provided file, and here are my general comments:

    Overall I think you are on the right path, but it seems you have not implemented everything just yet. Your current pwm_update_duty_cycle is called by the CPU - which leaves it vulnerable to added delay due to other tasks of higher priority that the CPU has to finish handling before triggering the task.
    Instead, you should provide the NRF_DRV_PWM_FLAG_START_VIA_TASK flag to your simple_playback function as well, and connect the TIMER CC event to the PWM START_SEQ task through PPI - this will start the readied sequence as soon as the timer expires. You can read more about this particular usage in the PWM Driver usage documentation. You should also disable the PPI channels and timer + GPIOTE when the PWM is successfully started, and re-enable them if you need to update your delay later.
    You will also have to implement a function for updating the delay, that changes the expiration timeout and starts the timer. I am not yet sure what logic you would like to have trigger this change, but you could start by tying it up to a button press if you have not already decided what should trigger it.
    I.e implement a button handler that updates the delay, re-enables the PPI channels and the GPIOTE + TIMER peripherals, so that the new PWM's delay is updated.

    Additionally, some other minor comments:

    - You should use the NRF_GPIO_PIN_MAP macro for defining pins, instead of just writing their number. This will let you effortlessly use pins from both ports without any added issue, and make sure that the output of the macro is as expected by the SDK functions.

    - In general I would advice you to have power management running in you main loop, like demonstrated in the BLE peripheral examples. This will drastically reduce your power consumption in comparison to your current empty main loop, while not degrading your performance at all.

    Keep on the current path, this looks very promising! :) 

    Best regards,
    Karl

  • Hello Karl,

    You will also have to implement a function for updating the delay, that changes the expiration timeout and starts the timer. I am not yet sure what logic you would like to have trigger this change, but you could start by tying it up to a button press if you have not already decided what should trigger it

    At this point I don't want to update the delay instead at constant 1ms delay my pwm output should follow the in put pwm signal.

    our current pwm_update_duty_cycle is called by the CPU - which leaves it vulnerable to added delay due to other tasks of higher priority that the CPU has to finish handling before triggering the task.
    Instead, you should provide the NRF_DRV_PWM_FLAG_START_VIA_TASK flag to your simple_playback function as well, and connect the TIMER CC event to the PWM START_SEQ task through PPI - this will start the readied sequence as soon as the timer expires.

     pwm_start_task_address =
            nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1,
                NRF_DRV_PWM_FLAG_LOOP |
                NRF_DRV_PWM_FLAG_START_VIA_TASK);
                
    
    compare_event_addr = nrf_drv_timer_task_address_get(&PWM_DELAY_TIMER,NRF_TIMER_CC_CHANNEL0);
    
    nrf_drv_ppi_channel_assign(ppi_channel, compare_event_addr,pwm_start_task_address);

    Is this right way to connecting?

    Best regards,
    Ram

  • Hello Ram,

    RAM_MS said:
    At this point I don't want to update the delay instead at constant 1ms delay my pwm output should follow the in put pwm signal.

    Thank you for clarifying - you can then disregard the section of my previous comment relating to the updated-delay implementation.

    RAM_MS said:
    Is this right way to connecting?

    No, not exactly. Your call to nrf_drv_pwm_simple_playback looks correct, but it should be called at the end of your pwm init function, since this will ready the PWM to output the specified sequence once the start task is triggered. Remember to check its returned error code as well.

    You will need to use the nrf_pwm_task_address_get function to get the task address of the NRF_PWM_TASK_SEQSTART0.

    Best regards,
    Karl

  • Hello Karl,

    No, not exactly. Your call to nrf_drv_pwm_simple_playback looks correct, but it should be called at the end of your pwm init function, since this will ready the PWM to output the specified sequence once the start task is triggered. Remember to check its returned error code as well.

    You will need to use the nrf_pwm_task_address_get function to get the task address of the NRF_PWM_TASK_SEQSTART0.

    static void gpio_init(void)
    {
        ret_code_t err_code;
        
    
        err_code = nrf_drv_gpiote_init();
        APP_ERROR_CHECK(err_code);
    
        // Initialize the PPI module, make sure its only enabled once in your code
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
    // Allocate the channel from the available PPI channels
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
        in_config.pull = NRF_GPIO_PIN_PULLDOWN;
    
        err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);
        APP_ERROR_CHECK(err_code);
    
    
    
        nrf_drv_gpiote_in_event_enable(PIN_IN, true);
    }
    
    void timers_init(void)
    {
        uint32_t err_code;
        uint32_t timer_ticks;
        
        nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
        timer_cfg.frequency = NRF_TIMER_FREQ_1MHz;
        err_code = nrfx_timer_init(&PWM_DELAY_TIMER, &timer_cfg,pwm_delay_timeout_handler);
        APP_ERROR_CHECK(err_code);
    
        timer_ticks = nrfx_timer_us_to_ticks(&PWM_DELAY_TIMER, PWM_DELAY_US);
    
        nrfx_timer_extended_compare(&PWM_DELAY_TIMER, 
                                      NRF_TIMER_CC_CHANNEL0, 
                                      timer_ticks,
                                      NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                      true);
    
       nrfx_timer_enable(&PWM_DELAY_TIMER);
    
        compare_event_addr = nrf_drv_timer_task_address_get(&PWM_DELAY_TIMER,NRF_TIMER_CC_CHANNEL0);
        pwm_task_addr =  nrf_drv_pwm_task_address_get(&m_pwm0,NRF_PWM_TASK_SEQSTART0);
    
        err_code = nrf_drv_ppi_channel_assign(ppi_channel, compare_event_addr,pwm_task_addr);
        APP_ERROR_CHECK(err_code);
    
    
        err_code = nrf_drv_ppi_channel_enable(ppi_channel);
        APP_ERROR_CHECK(err_code);
    
    }
    
    
    
    static void pwm_init(void)
    {
        nrf_drv_pwm_config_t const config0 =
        {
            .output_pins =
            {
                OUTPUT_PIN, // channel 0
                OUTPUT_PIN2,             // channel 1
                NRF_DRV_PWM_PIN_NOT_USED,             // channel 2
                NRF_DRV_PWM_PIN_NOT_USED,             // channel 3
            },
            .irq_priority = APP_IRQ_PRIORITY_LOWEST,
            .base_clock   = NRF_PWM_CLK_1MHz,
            .count_mode   = NRF_PWM_MODE_UP,
            .top_value    = 20000,
            .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
        // Init PWM without error handler
        APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
    
        seq_values->channel_0 = duty ;
        seq_values->channel_1 = duty | 0x8000;
        
        APP_ERROR_CHECK(nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1,NRF_DRV_PWM_FLAG_LOOP|NRF_DRV_PWM_FLAG_START_VIA_TASK));
                    
    }
    

    Did this initialization and connection right ???

     Best regards,
     Ram

  • This looks mostly correct, but I cant see that you have connected the GPIOTE IN EVENT to the TIMER's START task through PPI anywhere.

    Do this, and try to run the code. Let me know what you observe - does anything unexpected happen?

    Best regards,
    Karl

Related