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

How to use multiple timers

I have a device that I need to control, that needs 2 different square wave signals of different frequencies (although they may be multiples of each other), one at around 4 MHz and one at 10 KHz. Additionally (while the other two are running in the background) I need to communicate to the device with SPI upon an interrupt on a separate pin. To make matters worse, I will also need the SoftDevice for communications via BLE.

I have read that the nRF51822 only comes with 3 timers (one of which is reserved for th SoftDevice). Is there still a way to use one timer for multiple tasks?

Parents
  • Thanks Martin for your answer. I will go the PPI/GPIOTE way then. Am I correct in assuming, that given the frequencies are multiples (of 2) of each other I will be able to use a single timer for both square waves?

    I am still not very comfortable with dealing with this syntax. I found this sample code:

    static void ppi_init(void) {
    /* Configure PPI channel 0 to toggle PWM_OUTPUT_PIN1 on every Timer 1 COMPARE[0] match. */
    NRF_PPI->CH[0].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[0];
    NRF_PPI->CH[0].TEP = (uint32_t) &NRF_GPIOTE->TASKS_OUT[0];
    
    /* Configure PPI channel 1 to toggle PWM_OUTPUT_PIN1 on every Timer 1 COMPARE[1] match. */
    NRF_PPI->CH[1].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[1];
    NRF_PPI->CH[1].TEP = (uint32_t) &NRF_GPIOTE->TASKS_OUT[0];
    }
    

    How can I set the compare event for TIMER1? Do I need to set the GPIOTE "out task" to "toggle" explicitly? Where do I determine the pin to be toggled? Are there any higher level libraries for what I am trying to achieve?

Reply
  • Thanks Martin for your answer. I will go the PPI/GPIOTE way then. Am I correct in assuming, that given the frequencies are multiples (of 2) of each other I will be able to use a single timer for both square waves?

    I am still not very comfortable with dealing with this syntax. I found this sample code:

    static void ppi_init(void) {
    /* Configure PPI channel 0 to toggle PWM_OUTPUT_PIN1 on every Timer 1 COMPARE[0] match. */
    NRF_PPI->CH[0].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[0];
    NRF_PPI->CH[0].TEP = (uint32_t) &NRF_GPIOTE->TASKS_OUT[0];
    
    /* Configure PPI channel 1 to toggle PWM_OUTPUT_PIN1 on every Timer 1 COMPARE[1] match. */
    NRF_PPI->CH[1].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[1];
    NRF_PPI->CH[1].TEP = (uint32_t) &NRF_GPIOTE->TASKS_OUT[0];
    }
    

    How can I set the compare event for TIMER1? Do I need to set the GPIOTE "out task" to "toggle" explicitly? Where do I determine the pin to be toggled? Are there any higher level libraries for what I am trying to achieve?

Children
  • I'll apologize in advance for the sloppiness of this code. Writing your actual code would take awhile. This is just copied out of bits I have done. You still have to write your own code.

    You have most of it already for ppi/gpiote. Basically you point events to eep's, teps, etc. All the available configs and events are in the spec. The only trick with the SD is you have to use the API to do the channel assign so the SD knows what you are doing.

    Timer config stuff:

    	uint32_t          m_DelayuS     		 = 2160;                          	 /**< some number you choose*/
    			
    		NRF_TIMER1->TASKS_STOP        = 1;                      // Stop timer, if it was running
    NRF_TIMER1->TASKS_CLEAR       = 1;
    NRF_TIMER1->MODE              = TIMER_MODE_MODE_Timer;  // Timer mode (not counter)
    NRF_TIMER1->EVENTS_COMPARE[0] = 0;                      // clean up possible old events
    NRF_TIMER1->EVENTS_COMPARE[1] = 0;
    NRF_TIMER1->EVENTS_COMPARE[2] = 0;
    NRF_TIMER1->EVENTS_COMPARE[3] = 0;
    	
    	NRF_TIMER1->SHORTS      = (1 << TIMER_SHORTS_COMPARE0_CLEAR_Pos);  //Shorts can allow for reset of count after compare.  ie, like an oscillator
    	NRF_TIMER1->PRESCALER   = 4;                                     // Input clock is 16MHz, timer clock = 2 ^ prescale -> interval 1us
    	NRF_TIMER1->CC[0]       = m_DelayuS;                        	 // Delay for tx start for pa control
    

    //***Then do same for other cc registers as needed.

    So config and pin assign look like (look up the options you need in the guides, this is only an example):

    	NRF_GPIOTE->CONFIG[2] =((GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)
               |(22                         << GPIOTE_CONFIG_PSEL_Pos)
               |(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
               |(GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos));
    

    Then the eep and tep are set with the API. Again this is just an example. Look up with functions you need.

    	sd_ppi_channel_assign(2, &(NRF_TIMER1->EVENTS_COMPARE[0]), &(NRF_GPIOTE->TASKS_SET[2]));
    	sd_ppi_channel_assign(3, &(NRF_TIMER1->EVENTS_COMPARE[1]), &(NRF_GPIOTE->TASKS_CLR[2]));
    
    sd_ppi_channel_enable_set(PPI_CHEN_CH2_Msk);
    sd_ppi_channel_enable_set(PPI_CHEN_CH3_Msk);
    

    So, what did we learn here? Well there are both gpiote channels and ppi channels. They are different and both get assigned to something for a complete gpio solution. You have to watch out for the reserved channels in the SD guides. And you cannot assign your ppi/gpiote channels until after you start the SD.

    Then for your solution just use one of the extra cc registers for the other timer since they are related. Hopefully the registers are big enough to accommodate the differential.

  • Also, don't forget to start the timer. That is just: NRF_TIMER1->TASKS_START = 1;

    You also need to make sure the SD keeps the 32MHz clock running. Coming out of power manage it'll switch to HFINT and not HFEXT. Just look in the devzone on how to do this.

  • You should make the prescaler as big as possible. There is a minimum number of cycles though between clear and cc. I think it is 2 or 3 cycles. You may have to run with prescaler equal to zero.

  • Thanks AmbystomaLabs, your comments have been incredibly helpful! I got it working with the following minimal code (not worrying about SoftDevice and the like at the moment).

    int main(){
        sd_clock_hfclk_request();
        NRF_TIMER1->SHORTS      = (1 << TIMER_SHORTS_COMPARE0_CLEAR_Pos); 
        NRF_TIMER1->PRESCALER   = 0; 
        NRF_TIMER1->CC[0]       = 1;   
        NRF_TIMER1->TASKS_START = 1;
        NRF_PPI->CH[0].EEP  = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];
        NRF_PPI->CH[0].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
        NRF_PPI->CHEN       = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos);
      
        NRF_GPIOTE->CONFIG[0] =((GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)
                                |(6                         << GPIOTE_CONFIG_PSEL_Pos)
                                |(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
                                |(GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos));
    }
    
  • That is good news.

    The only difference between SD version and your version above is the PPI assignments and channel enable. With the SD running it will write over the PPI registers so you have to use the API command.

    Great work! Make sure to share the knowledge next time the question comes up in the devzone.

Related