32-bit timers

Post your cool example code here.
Post Reply
dannyf
Posts: 167
Joined: Wed May 11, 2016 4:29 pm

32-bit timers

Post by dannyf » Mon Oct 09, 2017 12:03 pm

I wrote a quick piece to chain two 16-bit timers here: https://dannyelectronics.wordpress.com/ ... it-timers/

the relevent portion of the code is here:

Code: Select all

//initialize tim23 as a synchronized 32-bit timer
//tim2 as master / prescaler to tim3 / lsw of the 32-bit timer
//tim3 as slave / msw of the 32-bit timer
void tim23_init(uint16_t ps) {
	//initialize tim2 as master
	//enable clock to tim2
	RCC->APB1ENR |= 	RCC_APB1ENR_TIM2EN |
					0x00;
	//stop the timer to configure it
	TIM2->CR1 &=~TIM_CR1_CEN;			//clear cen. 0=disable the timer, 1=enable the timer
	TIM2->CR1 &=~TIM_CR1_CKD;			//clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
	TIM2->CR1 &=~TIM_CR1_DIR;			//clear DIR bit. 0=upcounter, 1=downcounter
	TIM2->CR1 &=~TIM_CR1_OPM;			//clear opm bit. 0=periodic timer, 1=one-shot timer
	//or to simply zero the register
	//TIMx->CR1 = 0;						//much easier
	TIM2->CR2 = 0x20;					//MMS = 0b010->tim2 as prescaler

	//source from internal clock -> disable slave mode
	TIM2->SMCR &=~TIM_SMCR_SMS;			//clear sms->master mode and use internal clock

	//clear the status register bits for capture / compare flags
	TIM2->SR &=~(TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF | TIM_SR_UIF);
	//disable the interrupt by clearing the enable bits
	TIM2->DIER &=~(TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE | TIM_DIER_UIE);

	//set the prescaler
	TIM2->PSC = ps - 1;					//set the prescaler to ps
	TIM2->RCR = 0;						//repetition counter = 0 (=no repetition)
	//user can specify a prescaler here. otherwise use 0xffff
	TIM2->ARR = 0xffff;					//auto reload register / period = 0; - need to change for downcounters
	TIM2->CNT = 0;						//reset the counter

	//enable the timer.
	TIM2->CR1 |= TIM_CR1_CEN;			//enable the timer

	//initialize tim3 as slave
	RCC->APB1ENR |= 	RCC_APB1ENR_TIM3EN |
					0x00;
	//stop the timer to configure it
	TIM3->CR1 &=~TIM_CR1_CEN;			//clear cen. 0=disable the timer, 1=enable the timer
	TIM3->CR1 &=~TIM_CR1_CKD;			//clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
	TIM3->CR1 &=~TIM_CR1_DIR;			//clear DIR bit. 0=upcounter, 1=downcounter
	TIM3->CR1 &=~TIM_CR1_OPM;			//clear opm bit. 0=periodic timer, 1=one-shot timer
	//or to simply zero the register
	//TIMx->CR1 = 0;						//much easier

	//source from internal clock -> disable slave mode
	TIM3->SMCR &=~TIM_SMCR_SMS;			//clear sms->master mode and use internal clock

	//clear the status register bits for capture / compare flags
	TIM3->SR &=~(TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF | TIM_SR_UIF);
	//disable the interrupt by clearing the enable bits
	TIM3->DIER &=~(TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE | TIM_DIER_UIE);

	//set the prescaler
	TIM3->PSC = 0;						//set the prescaler to 1:1 - master timer acts as prescaler
	TIM3->RCR = 0;						//repetition counter = 0 (=no repetition)
	TIM3->ARR = 0xffff;					//auto reload register / period = 0; - need to change for downcounters
	TIM3->CNT = 0;						//reset the counter

	//enable the timer.
	TIM3->CR1 |= TIM_CR1_CEN;			//enable the timer

	//source from trgo -> enable slave mode and trigger on trgo
	TIM3->SMCR = (TIM3->SMCR &~((0x07 << 4) | 0x07)) |
			(0x01 << 4) | 				//tab 71: 0b001->//slave tim3 driven by tim2
			(0x07 << 0) |				//0b111->external trigger on trgo
			0x00;
}

uint32_t tmr23_get(void) {
	uint16_t msw, lsw;					//timer's high/low words

	//double read to maintain atomicity
	do {
		msw = TIM3->CNT;				//read the msw
		lsw = TIM2->CNT;				//read the lsw
	} while (msw != TIM3->CNT);			//see if overflow has taken place
	return (msw << 16) | lsw;			//return 32-bit time
}

tmr23 is TIM3:TIM2 combined, where TIM2 is configured as the master and TIM3 the slave. it was created by copy-and-pasting two (master) timer routines and then configuring TIM3 as the slave.

Being a minimumlist implementation, it doesn't have fancy features like interrupts but you can certainly implement it on your own.

hope it helps.

code tested on a STM32F100 and should work for other chips as well - may need to watch out for RCC however.

stevestrong
Posts: 1811
Joined: Mon Oct 19, 2015 12:06 am
Location: Munich, Germany

Re: 32-bit timers

Post by stevestrong » Mon Oct 09, 2017 12:15 pm

dannyf wrote:
Mon Oct 09, 2017 12:03 pm

Code: Select all

uint32_t tmr23_get(void) {
  uint16_t msw, lsw; //timer's high/low words
  //double read to maintain atomicity
  do {
    msw = TIM3->CNT; //read the msw
    lsw = TIM2->CNT; //read the lsw
  } while (msw == TIM3->CNT); //see if overflow has taken place
  return (msw << 16) | lsw; //return 32-bit time
}
Shouldn't be the condition for the while loop this way correct?

Code: Select all

  } while (msw != TIM3->CNT); //see if overflow has taken place
So basically read as long as two consecutive reads are not equal.

dannyf
Posts: 167
Joined: Wed May 11, 2016 4:29 pm

Re: 32-bit timers

Post by dannyf » Mon Oct 09, 2017 12:59 pm

you are correct. my bad.

stevestrong
Posts: 1811
Joined: Mon Oct 19, 2015 12:06 am
Location: Munich, Germany

Re: 32-bit timers

Post by stevestrong » Mon Oct 09, 2017 1:23 pm

No problem. I have corrected that in your original post, if it is fine for you. ;)

dannyf
Posts: 167
Joined: Wed May 11, 2016 4:29 pm

Re: 32-bit timers

Post by dannyf » Mon Oct 09, 2017 1:33 pm

not a problem. thanks.

dannyf
Posts: 167
Joined: Wed May 11, 2016 4:29 pm

Re: 32-bit timers

Post by dannyf » Mon Oct 09, 2017 8:19 pm

here is a different approach: 32-bit input capture using a single 16-bit timer.

https://dannyelectronics.wordpress.com/ ... bit-timer/

tested on STM32F100 but should work on 103 as well.

User avatar
ddrown
Posts: 145
Joined: Sat Jan 09, 2016 4:49 am

Re: 32-bit timers

Post by ddrown » Mon Oct 09, 2017 8:36 pm

I also have a slightly different way of doing this (using the HAL):

Code: Select all

uint32_t get_counters() {
  uint16_t tim2_before, tim1, tim2_after;

  tim2_before = __HAL_TIM_GET_COUNTER(&htim2);
  tim1 = __HAL_TIM_GET_COUNTER(&htim1);
  tim2_after = __HAL_TIM_GET_COUNTER(&htim2);

  if(tim2_before != tim2_after) {
    if(tim1 > 60000) { // allow for ~5000 cycles between tim2_before/tim2_after - beware of long interrupt handlers
      tim2_after = tim2_before;
    }
  }

  return ((uint32_t)tim2_after) << 16 | tim1;
}
TIM2 in this case is the high 16 bits, and TIM1 is the low 16 bits.

TIM1 wrapping around to 0 can happen in four different places (__HAL_TIM_GET_COUNTER is atomic in terms of the register read):

wrap [before] [tim1] [after]
[before] wrap [tim1] [after]
[before] [tim1] wrap [after]
[before] [tim1] [after] wrap

The first and last cases are boring, and can be ignored.

The second case, before and after won't match, but tim1 will be near 0. The correct tim2 value is after

The third case, before and after won't match, but tim1 will be near 2^16-1 (65535). The correct tim2 value is before

I'm also using this in an input capture context. This 32 bit value is taken on the capture complete callback - https://github.com/ddrown/stm32-cdc-pps ... imer.c#L31

You can then take the 16 bit captured tim1 value with the 32 bit value to find both the interrupt latency and the full 32 bit input capture event timestamp

dannyf
Posts: 167
Joined: Wed May 11, 2016 4:29 pm

Re: 32-bit timers

Post by dannyf » Mon Oct 09, 2017 9:02 pm

more than one way of doing the same thing, :)

Thanks.

User avatar
ddrown
Posts: 145
Joined: Sat Jan 09, 2016 4:49 am

Re: 32-bit timers

Post by ddrown » Mon Oct 09, 2017 10:45 pm

dannyf wrote:
Mon Oct 09, 2017 9:02 pm
more than one way of doing the same thing, :)
That's very true!

dannyf
Posts: 167
Joined: Wed May 11, 2016 4:29 pm

Re: 32-bit timers

Post by dannyf » Mon Oct 09, 2017 11:58 pm

I implemented the same concept on the ATmega8/8L "ghetto chrono": https://github.com/dannyf00/Ghetto-Chro ... er/ATmega8

it worked out nicely.

Post Reply