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

What is the maximum and minimum frequency of the PWM signal which can be measured?

What is the maximum and minimum frequency of the PWM signal/square wave that can be measured on the NRF 52DK board I'm using NRF 52832.

Also, what is the max and min "pulse width" of PWM/Square wave that can be measured on the NRF board? I'm a student so can you also help me on how to implement it. Till now I have used mbed compiler and wrote a simple code with an interrupt. But I wanted to use the SDK as it would provide me with more options, at least that's what I think.

  • Creating a PWM signal is a little more straight forward than reading it back, unfortunately. However, it is possible.

    Have you looked into the RTC on the nRF.  Combining this with PPI and GPIOs is what you want to do.

    Set up the RTC, then look for changes in on the GPIO with the pulse width signal, and check the counter (number of ticks) on the timer. 

    A more thorough explanation is given in this post.

    It is a bit hard to say something general on the ranges. A shorter range will give a lower accuracy, but you can use the prescaler on the timer to suit it to your need.

    What kind of ranges are you looking at?

  • Hey Edvin,

    Thanks for the help. By range, I meant calculating the pulse duration of the duty cycle (time escaped during the on time of the PWM). Correct me if I'm wrong so I can enable a pin as GPIOTE which will make it interrupt enabled and start a timer when an interrupt occurs and stop the timer when another interrupt occurs. I wanted high accuracy so I will use HFCLK (16Mhz and in 32 bit mode) which gives me a min of 62.5ns and max of ~268 secs for measuring the duration. 

    Few doubts I have are:

    • Are events and interrupts same? Is there any kind of latency associated with these events (interrupts)? Let's say I wanted to measure a pulse with duty cycle duration of 100ns. Will the events able to react that quick enough?

    • I have read the Application timer tutorial to get an understanding of timers, so far I only understood how to start a timer and do something in an event function when it times out. How can I read the values (measure the time passed) between the timer start and timer stop event?
  • Hello,

    I suggest that you look into the PPI module along with the timer. 

    Below is a short example on how to use the PPI, first to check GPIOs, the way that you would check for a PWM, but set up for a more visual display, by toggling LED1.

    // Peripheral channel assignments
    #define PWM0_GPIOTE_CH_HTL      0
    #define PWM0_GPIOTE_CH_LTH      1
    #define PWM1_GPIOTE_CH_out      2
    #define PWM0_PPI_CH_A       0
    #define PWM0_PPI_CH_B       1
    
    
        NRF_GPIOTE->CONFIG[PWM0_GPIOTE_CH_LTH] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos | 
                                             13 << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos;    // No effect in Event mode.
        
        NRF_GPIOTE->CONFIG[PWM0_GPIOTE_CH_HTL] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos | 
                                             13 << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos;    // No effect in Event mode.
            
        NRF_GPIOTE->CONFIG[PWM1_GPIOTE_CH_out] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos | 
                                             17 << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos;
        
        
    
        
        NRF_PPI->CH[PWM0_PPI_CH_A].EEP      = (uint32_t)&NRF_GPIOTE->EVENTS_IN[PWM0_GPIOTE_CH_HTL];
        NRF_PPI->CH[PWM0_PPI_CH_A].TEP      = (uint32_t)&NRF_GPIOTE->TASKS_CLR[PWM1_GPIOTE_CH_out];
        
        NRF_PPI->CH[PWM0_PPI_CH_B].EEP      = (uint32_t)&NRF_GPIOTE->EVENTS_IN[PWM0_GPIOTE_CH_LTH];
        NRF_PPI->CH[PWM0_PPI_CH_B].TEP      = (uint32_t)&NRF_GPIOTE->TASKS_SET[PWM1_GPIOTE_CH_out];
        
        NRF_PPI->CHENSET               = (1 << PWM0_PPI_CH_A) | (1 << PWM0_PPI_CH_B);

    This piece of code will check the input on pin 13, and reflect it's value to pin 17 (LED1 on the nRF52 DK). Note that the button will not work properly for this example, since it does not apply a pullup configuration on pin 13. In the code snippet, EEP will be the trigger of a channel, while TEP is the task for that channel.

    To set up a timer, you can e.g. use timer 3 like this:

    // TIMER3 reload value. The PWM frequency equals '16000000 / TIMER_RELOAD'
    #define TIMER_RELOAD        1024
    
    
    // This function initializes timer 3 with the following configuration:
    // 24-bit, base frequency 16 MHz.
    void timer_init()
    {
        NRF_TIMER3->BITMODE                 = TIMER_BITMODE_BITMODE_24Bit << TIMER_BITMODE_BITMODE_Pos;
        NRF_TIMER3->PRESCALER               = 0;
        NRF_TIMER3->MODE                    = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
    }

    Then you can fork some tasks to the triggers of channels, by adding the following to the end of the first snippet, before the NRF_PPI->CHENSET line:

    NRF_PPI->FORK[PWM0_PPI_CH_A].TEP     = (uint32_t)&NRF_TIMER3->TASKS_START;
    NRF_PPI->FORK[PWM0_PPI_CH_B].TEP     = (uint32_t)&NRF_TIMER3->TASKS_CAPTURE[0];
    
    NRF_PPI->CHENSET                     = (1 << PWM0_PPI_CH_A) | (1 << PWM0_PPI_CH_B);

    This way, you can read out the number of ticks in the duty cycle: value = NRF_TIMER3->CC[0];

    Regarding your questions:

    The reason I point you towards using the PPI is because of this. The PPI can be used without interrupting the CPU, and hence, it will run even if the CPU is busy with other things. This will also allow you to read the number of ticks. If you want to, you can let this run in the background, and read out the PWM signal whenever you need to, without being interrupted every time the PWM signal changes.

    If you want to read the both the PWM frequency AND PWM duty cycle, you can leave the timer running, and read the CC both when the PWM signal goes HiToLo and LoToHi. You can use several of the TASK_CAPTURE. One for reading the number of ticks between each High, and one for reading the number of ticks between High and Low.

    Unfortunately, we don't have any sample code doing all of this, but you can use this as a starting point. Play around with it to get familiar, and adapt it for your need.

    BR,

    Edvin

  • Thanks a lot this is really helpful, PPI definitely sounds a better alternative. I started reading PPI and it's amazing technology that Nordic has developed. Using interrupts I am able to measure the duty cycle of pwm signals of only around 30us(I think the interrupt latency, starting the timer, stopping the timer all adds up to it). I will now use PPI with your help and keep you updated. 

    One more thing which I wanted to ask was can I configure my input pin in PULL_DOWN mode? I found out how to make the initial value of the pin low but I dont think it's the same as PULL_DOWN mode.

     GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos;  
     

    why I wanted to this is because I only want to measure the "ON" time of my PWM wave.

  • You can set the PULL_DOWN in the gpio config (outside the PPI), using nrf_drv_gpiote_in_config_t, and the function nrf_drv_gpiote_in_init(...)

    BR,

    Edvin

Related