timer source from other timer ?

Post here first, or if you can't find a relevant section!
picclock
Posts: 20
Joined: Sat Aug 14, 2021 8:21 am

timer source from other timer ?

Post by picclock »

I'm struggling a bit with the timers on the STM32f4011. Max time for Tim2 (32 bit) seems to be between 30-60 seconds.
I would like to use the output of one timer as the clock source for several others, like an additional prescaler. I'd prefer to do it this way as it will have minimum impact on system performance.
Not sure if its possible though ...
Any examples or pointers to code about this much appreciated, tried googling but all I got was std timer stuff.
Any help much appreciated.
Best Regards
picclock
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: timer source from other timer ?

Post by ag123 »

you can try playing with the prescaler
https://github.com/stm32duino/wiki/wiki ... er-library
note that there are some dependencies, e.g. if you use uS or Hz for setOverflow(), the prescaler would likely be re-configured.
for very long periods, the problem is you could either have the low periods and the high frequencies would be limited. or target the higher frequencies, and limit the long periods. As if you really need to target long periods, one of them is to reduce the peripheral clocks, e.g. instead of running at 80 mhz to run at 40 mhz or lower, this would likely slow downn all other pheriperials as well, e.g. spi

another way is to daisy chain the timers, e.g. timer 1 drives timer 2 etc, the update events can be chained.
that would allow extra long periods or more elaborate patterns etc.
for that check in the ref manuals, more than likely. you may need to tweak the registers directly.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: timer source from other timer ?

Post by dannyf »

sounds like you wanted longer intervals.

Three suggestions:
1. chain the timers - read the datasheet;
2. maintain a MSB that you increment in the ISR.
3. if you want precise but long interval, use the compare feature of the timer and augment it with #2 above.

here is an example of how #3 can be done.

Code: Select all

//set up the timer output compare
//global variables
volatile uint_32 tmr2_ovf=0;  //timer 2 overflow
volatile uint_32 tmr2_flg=0;  //timer 2 flag. set when time expires
uint_64 tmr2_interval;          //timer 2 desire interval - presummably much longer than 32-bit

//initialize variables
  //stop tmr2
  TIM2->CCR1 = TIM2->CNT;  //baseline CCR1
  TIM2->CCR1 += tmr2_interval; //use tmr2 compare channel1 - advance the compare register
  tmr2_ovf = tmr2_interval >> 32;  //tmr2 is a 32-bit timer
  set tmr2 compare ch1
  //start tmr2
  
  
//in tmr2 isr
  if (TIM2->SR & TIM_SR_CC1IF) {  //compare interrupt flag set
    if (tmr2_ovf--==0) {  //enough overflow has achieved
      TIM2->CCR1 += tmr2_interval; //use tmr2 compare channel1 - advance the compare register
      tmr2_ovf = tmr2_interval >> 32;  //tmr2 is a 32-bit timer
      tmr2_flg = 1;  //set the flag
      }
    }
I have used something like this many times so it should work.

PS: it requires CMSIS.
User avatar
MasterT
Posts: 33
Joined: Mon Apr 20, 2020 12:02 am

Re: timer source from other timer ?

Post by MasterT »

Here is an example for stm32G474re , TIM5 master to clock TIM 2&3

Code: Select all

static void TIM5_Config(void)
{
  TIM_HandleTypeDef htim5;

  TIM_ClockConfigTypeDef  sClockSourceConfig  = {0};
  TIM_MasterConfigTypeDef sMasterConfig       = {0};
  TIM_OC_InitTypeDef      sConfigOC           = {0};

  __HAL_RCC_TIM5_CLK_ENABLE();

  htim5.Instance                = TIM5;
  htim5.Init.Prescaler          = 0;
  htim5.Init.CounterMode        = TIM_COUNTERMODE_UP;
  htim5.Init.Period             = 1;
  htim5.Init.ClockDivision      = TIM_CLOCKDIVISION_DIV1;
  htim5.Init.AutoReloadPreload  = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
  {
    Error_Handler();
  }
  
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode     = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  sConfigOC.OCMode      = TIM_OCMODE_PWM2;
  sConfigOC.Pulse       = 1;
  sConfigOC.OCPolarity  = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode  = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_Base_Start(&htim5);

  if (HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}

static void TIM2_Config(void)
{
  TIM_HandleTypeDef htim2;

//  TIM_ClockConfigTypeDef  sClockSourceConfig  = {0};
  TIM_SlaveConfigTypeDef  sSlaveConfig        = {0};
  TIM_MasterConfigTypeDef sMasterConfig       = {0};
  TIM_OC_InitTypeDef      sConfigOC           = {0};

  __HAL_RCC_TIM2_CLK_ENABLE();
  htim2.Instance                = TIM2;
  
  htim2.Init.Prescaler          = 0;
  htim2.Init.CounterMode        = TIM_COUNTERMODE_UP;
  htim2.Init.Period             = 62;
  htim2.Init.ClockDivision      = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload  = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
/*
  sClockSourceConfig.ClockSource  = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
*/
  sSlaveConfig.SlaveMode    = TIM_SLAVEMODE_EXTERNAL1;//TIM_SLAVEMODE_RESET;
  sSlaveConfig.InputTrigger = TIM_TS_ITR4;
  if (HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
  sMasterConfig.MasterSlaveMode     = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  sConfigOC.OCMode      = TIM_OCMODE_PWM2;
  sConfigOC.Pulse       = 3;
  sConfigOC.OCPolarity  = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode  = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_Base_Start(&htim2);

  if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}

static void TIM3_Config(void)
{
  TIM_HandleTypeDef htim3;

//  TIM_ClockConfigTypeDef  sClockSourceConfig  = {0};
  TIM_SlaveConfigTypeDef  sSlaveConfig        = {0};
  TIM_MasterConfigTypeDef sMasterConfig       = {0};
  TIM_OC_InitTypeDef      sConfigOC           = {0};

  __HAL_RCC_TIM3_CLK_ENABLE();
  htim3.Instance                = TIM3;
  
  htim3.Init.Prescaler          = 0;
  htim3.Init.CounterMode        = TIM_COUNTERMODE_UP;
  htim3.Init.Period             = 62;
  htim3.Init.ClockDivision      = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload  = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
/*
  sClockSourceConfig.ClockSource  = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
*/
  sSlaveConfig.SlaveMode    = TIM_SLAVEMODE_EXTERNAL1;//TIM_SLAVEMODE_RESET;
  sSlaveConfig.InputTrigger = TIM_TS_ITR4;
  if (HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
  sMasterConfig.MasterSlaveMode     = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  sConfigOC.OCMode      = TIM_OCMODE_PWM2;
  sConfigOC.Pulse       = 3;
  sConfigOC.OCPolarity  = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode  = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_Base_Start(&htim3);

  if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}
My advise, get yourself CubeMX installed with examples. Code generated with Cube is fully compatible for arduino IDE, since same HAL drivers.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: timer source from other timer ?

Post by dannyf »

Not having a stm32 in front of me so I put this together on a PIC24 to demonstrate the basic concept - porting it over to any other chip isn't difficult.

Code: Select all

//global defines
#define isDLY32()	(dly_flg==1)		//true is dly32 has expired
#define clrDLY32()	dly_flg=0			//reset the flag

//global variables
uint32_t dly_duration = 0;				//dly_duration desired. max 32-bit
volatile uint16_t dly_ovf=0;			//overflow counter
uint16_t dly_inc=0;						//OC increments: 16-bit time base
volatile int dly_flg=0;					//dly_duration expiration flag: 1-> time is up; 0->not yet

//oc1 isr
//the default ISR has been modified so it does not advance OC1R
void myOC1ISR(void) {
	if (dly_ovf--==0) {					//enough cycles have been reached
		dly_ovf = dly_duration >> 16;	//reset overflow cycle counter -> 16-bit time base used
		//oc1 default ISR modified so not to increment OC1R
		//instead, OC1R is incremented here
		OC1R += dly_inc;				//advance OC1R to the next match point
		dly_flg = 1;					//set the flag
	}
}
//initialize the dly_duration timer - called once in setup()
uint32_t initDLY32(uint32_t duration) {
	dly_duration = duration;			//remember the duration desired
	dly_ovf = dly_duration >> 16;		//reset overflow counter: 16-bit time base
	dly_inc = dly_duration;				//reset dly_duration: 16-bit time base

	//set up the timer
	//tmr2 free running by default
	//nothing needs to be done here

	//set up the oc1
	//oc1 default isr modified so not to increment OC1R
	oc1Init(dly_inc);					//initialize oc1 to tmr2
	oc1AttachISR(myOC1ISR);				//install oc1ISR

	//now running

	return dly_ovf;
}
you can see that it follows what I laid out earlier easily. we are using a 16-bit timer as the time base (this particular chip allows timers to be chained). myOC1ISR() tracks how many "output compare" overflow has taken place and then set a flag, which is to be processed in the main loop - in this case flip a led and write some messages over uart.

Code: Select all

//user defined main loop
void loop(void) {
    static uint32_t tick0=0;
    uint32_t tmp0;

    //if enough time has elapsed
    if (isDLY32()) {							//if enough time has passed
        clrDLY32();								//reset the flag
        //tick0 += LED_DLY;						//advance to the next match point
        pinFlip(LED);							//digitalWrite(LED, !digitalRead(LED));	//flip led, 105 ticks

		//measure timing
        tmp0=ticks();
		//put some tasks here
        tmp0=ticks() - tmp0;

		//display something
        u2Print("F_CPU=                    ", F_CPU);
        u2Print("ticks=                    ", ticks());
        u2Print("tmp0 =                    ", tmp0);
		u2Println();
    }
}
Obviously depending on your particular application, you may implement this differently but the basic concept is the same.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: timer source from other timer ?

Post by ag123 »

for very long intervals/periods, say seconds to even hours, actually there is another way.
for that you can use the standard hardware timer
https://github.com/stm32duino/wiki/wiki ... er-library
the idea is this. you can attachInterrupt() and put your own function there.
so that if the period is longer than some intervals say 1 seconds (actually, i think hardware timer can easily address this).
Then you set the hardware timer period to fire/trigger at 1 seconds interval.

Next for instance, you can count this 1 seconds interval in your code, you can use an uint32_t that should give you some 4 billion seconds extended periods if you need to count that. This would easily solve the long periods problem.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: timer source from other timer ?

Post by dannyf »

here is a simplified version, similar to what ag123 proposed above.

Code: Select all

//PIC32duino code
// - using PIC32MX1xx/2xx
// - free running core timer for ticks() (or tmr2, selected by the user)
// - free running tmr2 for pwm / output compare / input capture
// - details available here: https://github.com/dannyf00/Minimalist-32-bit-Arduino-Clone
//
// - version history
// - v2.0, 5/13/2021: original port
// - v2.1, 5/26/2021: implemented SystemCoreClockUpdate() that updates SystemCoreClock during initialization
//                    implemented systicks() from TMR2 ISR - optional (in addition to core timer)
// - v2.2, 5/27/2021: implemented i2c/rtcc
// - v2.3, 6/19/2021: added support for output compare (dah!)
// - v2.4, 5/29/2021: output compare routines no longer advance compare registers
// - v2.5, 5/24/2022: added support for C32 compiler
//
//
//hardware configuration
//
//               PIC32MX1/2xx
//              |=====================|
//    Vcc       |                     |
//     |        |                AVdd |>---+--[1k]-->Vcc
//     |        |                     |  [.1u]
//     +-[10K]-<| MCLR           AVss |>---+-------->GND
//              |                     |
//              |                     |
//     +------->| OSCI           Vcap |>--[.1u]-+->GND
//  [Xtal]      |                     |         |
//     +-------<| OSCO           Vss  |>--------+
//              |                     |
//     +------->| SOSCI          RPB0 |>---------->Uart2TX
// [32,768Hz]   |                     |
//     +-------<| SOSCO           RB7 |>---------->LED
//              |                     |
//              |                     |
//              |                     |
//              |                     |
//              |                     |
//              |                     |
//              |                     |
//              |                     |
//              |                     |
//              |=====================|
//
//
//

#include "pic32duino.h"					//we use pic32duino

//hardware configuration
#define LED			PB7					//led pin
#define DLY_DUR		10					//desired duration, in DLY_TB
//end hardware configuration

//global defines
#define DLY_TB		(F_CPU/10)			//0.1 second in timebase

//global variables

//user defined set up code
void setup(void) {
    pinMode(LED, OUTPUT);				//led as output pin

    //initialize the uart
    //uart1Init(UART_BR9600);			//initialize uart1
    uart2Init(UART_BR9600);				//initialize uart2

	//enable interrupts
	ei();

}

//count the desired time base
//1-> time is up, 0->otherwise
int isDLY32(void) {
	static uint32_t tick0=0;				//tick counter
	static uint32_t dly_cnt=DLY_DUR;		//cycle counter
	int flg = 0;							//flag: 1-> time is up; 0-> otherwise

    if (ticks() - tick0 > DLY_TB) {			//if 1 second has passed
        tick0 += DLY_TB;					//advance to the next match point
        if (--dly_cnt==0) {					//enough seconds have passed
			dly_cnt=DLY_DUR;				//reset the counter
			flg=1;							//1->time is up; 0-> otherwise
        }
    }
    return flg;								//return 1 if enough cycle has passed; 0 otherwise
}
//user defined main loop
void loop(void) {

	if (isDLY32()) {						//if enough time has elapsed
		//user tasks started
		pinFlip(LED);						//digitalWrite(LED, !digitalRead(LED));	//flip led, 105 ticks
		//display something
        u2Print("F_CPU=                    ", F_CPU);
        u2Print("ticks=                    ", ticks());
        //u2Print("tmp0 =                    ", tmp0);
		u2Println();
		//user tasks finished
    }
}
isDLY32() detects if enough time has passed. Time is measured in DLY_TB (delay time base, in this case, 0.1 second), and DLY_DUR (delay duration) - in this example, we are looking for a duration of 1 second (10 * 0.1 sec).

The implementation is non-blocking. It will have small jitter - depending on how fast the main loop is executed. however, it is long-term accurate: a + jitter in one cycle will cause a - jitter the next cycle.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: timer source from other timer ?

Post by dannyf »

ticks() for this particular implementation is the core timer on a MIPS chip (PIC32). For a STM32F1, you can use the DWT, or SysTick - be careful with the math if you use SysTick as it is 24-bit only.
picclock
Posts: 20
Joined: Sat Aug 14, 2021 8:21 am

Re: timer source from other timer ?

Post by picclock »

Many Thanks for all the replies and ingenious suggestions.
I had tried changing the prescaler as suggested, but I must have mucked it up.
With The prescaler set to 0xffff, the maximum delay for a 32bit is 1864 hours :o . In my tests for seconds the setOverflow count must be set to n*640. So even with this prescaler setting granularity is still <2mS. For 16 bit timers this is ~102 secs max time period.

Best Regards
picclock

Code: Select all

//STM32F401
HardwareTimer *MinTim = new HardwareTimer(TIM2);//32 bit timer 

uint16_t mc=8;//time period in minutes
uint32_t mil;

void MinIsr(){ // Called by the MinTim Timer Interrupt 
     // called function here
     Serial.println("mc function called");  // function to run after timeout
     //MinTim ->pause(); //for once only .. .
         }


void setup() {
  Serial.setRx(PA10); // using pin name
  Serial.setTx(PA9); 
  Serial.begin(9600);
  delay(500);  
  Serial.println("n sec - Min ISR Test");

  MinTim->pause(); // UV purge timer
  MinTim->setPrescaleFactor(0xffff); // approx 640 counts per second
  uint16_t n=60;// time in seconds
  MinTim->setOverflow(n*640); // Period value in n seconds
  MinTim->refresh();
  MinTim->attachInterrupt(MinIsr);
  MinTim->resume();
  mil=millis();
}

void loop() {
  // put your main code here, to run repeatedly:

}
Last edited by picclock on Tue Jun 14, 2022 11:40 am, edited 2 times in total.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: timer source from other timer ?

Post by ag123 »

for very long time intervals, the usual trick is millis()

Code: Select all

uint32_t begin = 0;
void setup() {
	begin = millis();
}

void loop() {
	uint32_t duration = millis() - begin;
}
systick is always running which gives millis(). it is a 'classic' way to 'multi task' in loop().
in this way you can track n number of leds for instance and blink them at all different intervals.
the risk is duration can wrap around

i think firmwares like marlin (for 3d printing) still use a hardware timer instead to run the steppers as the demand for tight high frequency timing loops is still too tight.
Post Reply

Return to “General discussion”