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.

Parents
  • 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

Reply
  • 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

Children
  • 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.

Related