32-bit timers

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:

//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
}


stevestrong
Mon Oct 09, 2017 12:15 pm
[dannyf – Mon Oct 09, 2017 12:03 pm] –
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
}

dannyf
Mon Oct 09, 2017 12:59 pm
you are correct. my bad.

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
Mon Oct 09, 2017 1:33 pm
not a problem. thanks.

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.


ddrown
Mon Oct 09, 2017 8:36 pm
I also have a slightly different way of doing this (using the HAL):

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;
}


dannyf
Mon Oct 09, 2017 9:02 pm
more than one way of doing the same thing, :)

Thanks.


ddrown
Mon Oct 09, 2017 10:45 pm
[dannyf – Mon Oct 09, 2017 9:02 pm] –
more than one way of doing the same thing, :)

That’s very true!


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.


vitor_boss
Mon Feb 19, 2018 9:15 pm
I made a 32bit timer for a PIC using the timer overflow flag, it’s used only to capture a pulse period and width. Works perfectly.

If you got the idea ins’t hard. But if wanna the code just quote this


arpruss
Fri Feb 23, 2018 6:37 pm
I usually just time things with the DWT cycle counter. It’s 32-bits, high resolution (one cycle per count; at 72MHz, it can count up to about 59 seconds before overflowing), and works with interrupts enabled OR disabled, which is nice for precise bit-banging. The cost is that it slows things down a little (10-15%?) when you enable it.

Example:

#include <dwt.h> // https://github.com/arpruss/gamecube-usb-adapter/blob/master/dwt.h

void setup() {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= 1;
}

void pulsePinForCycles(unsigned pin, unsigned value, unsigned cycles) {
nvic_globalirq_disable(); // for greater precision
unsigned reversedValue = !value;
digitalWrite(pin,value); // use direct register manipulation for greater precision
DWT->CYCCNT = 0;
while (DWT->CYCCNT < cycles) ;
digitalWrite(pin,reversedValue);
nvic_globalirq_enable();
}


fpiSTM
Sat Feb 24, 2018 8:14 am
[arpruss – Fri Feb 23, 2018 6:37 pm] –
The cost is that it slows things down a little (10-15%?) when you enable it.

This seems a bit high as slowdown for an hardware counter


dannyf
Sat Feb 24, 2018 12:17 pm
The cost is that it slows things down a little (10-15%?) when you enable it.

i have never experienced it slowing down anything. it is a hardware counter that’s always there, with no cpu involvement in its operations. so hard to imagine a reason that it would slow down the rest of the chip.


fpiSTM
Sat Feb 24, 2018 3:34 pm
I agree with dannyf.

arpruss
Sun Feb 25, 2018 3:48 am
I thought I had seen a small slowdown in a benchmark. But I’m not seeing it now. I was wrong.

I also notice that the CYCCNT counter is active by default in the core. One can turn it off, but it makes no speed difference.


dannyf
Wed Jul 04, 2018 1:00 pm
the same logic can be applied to chain other timers, like tim15/16/17.

//initialize TIM1615 as a synchronized 32-bit timer
//tim16 as master / lsw (including prescaler) to tim15 / msw of the 32-bit timer
//tim15 as slave / msw of the 32-bit timer
void tim1615_init(uint16_t ps) {
//initialize TIM16 as master
//enable clock to TIM16
RCC->APB2ENR |= RCC_APB2ENR_TIM16EN |
0x00;
//stop the timer to configure it
TIM16->CR1 &=~TIM_CR1_CEN; //clear cen. 0=disable the timer, 1=enable the timer
TIM16->CR1 &=~TIM_CR1_CKD; //clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
TIM16->CR1 &=~TIM_CR1_DIR; //clear DIR bit. 0=upcounter, 1=downcounter
TIM16->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
//CR2 on TIM16/17 doesn't exist -> the following statement has no effect
//thus it doesn't work
TIM16->CR2 = 0x20; //MMS = 0b010->TIM16 as prescaler
//alternative: set up OC1 as the TRGO signal
TIM16->CCER&=~TIM_CCER_CC1E; //0->disable cc1, 1->enable cc1
//disable cc1e to change ccmr1
//TIM16->CCMR1&=~(0x03<<0); //0->CC1 as output
TIM16->CCMR1= 0x06<<4; //1->oc1ref high on match, 3->oc1ref flip on match, 6->pwm mode 1(
TIM16->CCER|= TIM_CCER_CC1E; //0->disable cc1, 1->enable cc1
//TIM16->EGR = TIM_EGR_UG | TIM_EGR_CC1G; //force an update
TIM16->CCR1= 0xffff; //set the match point: anything but 0
TIM16->BDTR|= TIM_BDTR_MOE; //1->set MOE

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

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

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

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

//initialize TIM15 as slave
RCC->APB2ENR |= RCC_APB2ENR_TIM15EN |
0x00;
//stop the timer to configure it
TIM15->CR1 &=~TIM_CR1_CEN; //clear cen. 0=disable the timer, 1=enable the timer
TIM15->CR1 &=~TIM_CR1_CKD; //clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
TIM15->CR1 &=~TIM_CR1_DIR; //clear DIR bit. 0=upcounter, 1=downcounter
TIM15->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
TIM15->SMCR &=~TIM_SMCR_SMS; //clear sms->master mode and use internal clock

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

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

//source from trgo -> enable slave mode and trigger on trgo
TIM15->SMCR = (TIM15->SMCR &~((0x07 << 4) | (0x07 << 0))) |
(0x02 << 4) | //tab 74: 0b010->//slave TIM15 driven by TIM16
(0x07 << 0) | //0b111->external trigger on trgo
(1<<7) | //set sms
0x00;

//enable the timer.
TIM15->CR1 |= TIM_CR1_CEN; //enable the timer
//enable the timer.
TIM16->CR1 |= TIM_CR1_CEN; //enable the timer
}


dannyf
Wed Jul 04, 2018 1:04 pm
on many STM32 chips, certain timers can act as both master and slaves. As such, it is possible to chain more than 2 timers together to form 48bit composite timers (3 x 16-bit timers), or 64bit composite timers (4 x 16-bit timers). or even more.

Not sure of their applications but it is pretty neat.


dannyf
Wed Jul 04, 2018 8:17 pm
here is an example of implementing 48-bit timers by chaining TIM2->TIM3->TIM4:

//initialize tim2/3/4 as a synchronized 32-bit timer
//tim2 as master / lsw (including prescaler) to tim3 / msw of the 48-bit timer
//tim3 as slave/master / middle word of the 48-bit timer
//tim4 as slave / msw word of the 48-bit timer
void tim234_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

//enable clock to tim3
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
TIM3->CR2 = 0x20; //MMS = 0b010->tim2 as prescaler

//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 ps
TIM3->RCR = 0; //repetition counter = 0 (=no repetition)
//user can specify a prescaler here. otherwise use 0xffff
TIM3->ARR = 0xffff; //auto reload register / period = 0; - need to change for downcounters
TIM3->CNT = 0; //reset the counter

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

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

//initialize tim4 as slave
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN |
0x00;
//stop the timer to configure it
TIM4->CR1 &=~TIM_CR1_CEN; //clear cen. 0=disable the timer, 1=enable the timer
TIM4->CR1 &=~TIM_CR1_CKD; //clear CKD0..1. 0b00->1x clock; 0b01->2:1 clock, 0b10->4:1 clk; 0b11->reserved
TIM4->CR1 &=~TIM_CR1_DIR; //clear DIR bit. 0=upcounter, 1=downcounter
TIM4->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
TIM4->SMCR &=~TIM_SMCR_SMS; //clear sms->master mode and use internal clock

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

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

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

//source from trgo -> enable slave mode and trigger on trgo
TIM4->SMCR = (TIM4->SMCR &~((0x07 << 4) | (0x07 << 0))) |
(0x02 << 4) | //tab 71: 0b010->//slave tim4 driven by tim3
(0x07 << 0) | //0b111->external trigger on trgo
0x00;

//enable the timer: MSW first, and LSW last
TIM4->CR1 |= TIM_CR1_CEN; //enable the timer
TIM3->CR1 |= TIM_CR1_CEN; //enable the timer
TIM2->CR1 |= TIM_CR1_CEN; //enable the timer
}


Leave a Reply

Your email address will not be published. Required fields are marked *