BluePill fast 10 us Timer interrupt example code needed

Post here first, or if you can't find a relevant section!
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by dannyf »

10 us is the fastest I could get
at 72Mhz, that means you have 720 instructions to spare between each interrupt. your overhead is about 20 ticks, and your task may take 20 ticks. So your cpu utilization from this task along is about 10%.

it is fairly easy:

1) pick a timer. For this, a timer with overflow interrupt or compare interrupt would work. I prefer the use of compare channels.
2) install a user handler.

this is what I use for tim4 (it should be easily copied over to other channels or other timers).

Code: Select all

//tmr4
//global variables
static void (* _tim4_ovfisrptr)(void)=empty_handler;				//TIM4_ptr pointing to empty_handler by default
static void (* _tim4_cc1isrptr)(void)=empty_handler;				//TIM4_ptr pointing to empty_handler by default
static void (* _tim4_cc2isrptr)(void)=empty_handler;				//TIM4_ptr pointing to empty_handler by default
static void (* _tim4_cc3isrptr)(void)=empty_handler;				//TIM4_ptr pointing to empty_handler by default
static void (* _tim4_cc4isrptr)(void)=empty_handler;				//TIM4_ptr pointing to empty_handler by default

static uint16_t _tim4_cc1=0;				//output compare registers
static uint16_t _tim4_cc2=0;
static uint16_t _tim4_cc3=0;
static uint16_t _tim4_cc4=0;

//isr for timer1 capture / compare
void TIM4_IRQHandler(void) {
	//oc1..4 portion
	//if ((TIM4->DIER & TIM_DIER_CC1IE) && (TIM4->SR & TIM_SR_CC1IF)) {		//output compare 1 flag is set
	if ((TIM4->SR & TIM_SR_CC1IF)) {TIM4->SR &=~TIM_SR_CC1IF; TIM4->CCR1 += _tim4_cc1; _tim4_cc1isrptr();}
	if ((TIM4->SR & TIM_SR_CC2IF)) {TIM4->SR &=~TIM_SR_CC2IF; TIM4->CCR2 += _tim4_cc2; _tim4_cc2isrptr();}
	if ((TIM4->SR & TIM_SR_CC3IF)) {TIM4->SR &=~TIM_SR_CC3IF; TIM4->CCR3 += _tim4_cc3; _tim4_cc3isrptr();}
	if ((TIM4->SR & TIM_SR_CC4IF)) {TIM4->SR &=~TIM_SR_CC4IF; TIM4->CCR4 += _tim4_cc4; _tim4_cc4isrptr();}

	//ovf isr
	if ((TIM4->SR & TIM_SR_UIF)) {TIM4->SR &=~TIM_SR_UIF; _tim4_ovfisrptr();}
}

//initialize the timer4 (16bit)
void tmr4Init(uint16_t ps) {
	//route the clock to timer
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;

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

	//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
	//TIM4->CR1 = 0;						//much easier

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

	//set the prescaler
	TIM4->PSC = ps - 1;					//set the prescaler
	TIM4->RCR = 0;						//repetition counter = 0 (=no repetition)
	TIM4->ARR = -1;						//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
}

//set up the period
void tmr4SetPR(uint32_t pr) {
	TIM4->ARR = pr - 1;
}

//activate the isr handler
void tmr4OVFAttachISR(void (*isrptr)(void)) {
	NVIC_DisableIRQ(TIM4_IRQn);			//disable irq

	_tim4_ovfisrptr = isrptr;			//install user handler

	//clear the flag
	TIM4->SR &=~TIM_SR_UIF;			//clear the interrupt flag
	TIM4->DIER |= TIM_DIER_UIE;		//enable the isr

	NVIC_EnableIRQ(TIM4_IRQn);			//enable irq
	//priorities not set -> default values used.
}

//set TIM4_oc1 period
//pr is 16-bit. 32-bit used for compatability;
void tmr4OC1SetPR(uint16_t pr) {
	//save the period value
	_tim4_cc1 = pr - 0;
	TIM4->CCR1 = _tim4_cc1;

	//clear the flag
	//TIM4->SR &=~TIM_SR_CC1IF;			//clear the interrupt flag
	//TIM4->DIER &=~TIM_DIER_CC1IE;		//disable the isr
}

//activate the isr handler
void tmr4OC1AttachISR(void (*isrptr)(void)) {
	NVIC_DisableIRQ(TIM4_IRQn);			//disable irq

	_tim4_cc1isrptr = isrptr;			//install user handler

	//clear the flag
	TIM4->SR &=~TIM_SR_CC1IF;			//clear the interrupt flag
	TIM4->DIER |= TIM_DIER_CC1IE;		//enable the isr

	NVIC_EnableIRQ(TIM4_IRQn);			//enable irq
	//priorities not set -> default values used.
}
you will need to derive a .h file from it.

the usage is fairly simple:

Code: Select all

  tmr4Init(TIM_PS1x); //initialize tmr4 to 1:1 prescaler, free running.
  tmr4OC1SetPR(10*clockCyclesPerMicrosecond); //set the period for compare ch1 to be invoked. 10us in this case
  tmr4OC1AttachISR(myISR);  //install user isr - where you flip the output pin(s)
After that, the user task, myISR(), is called every 10us.

this particular timer on this particular device has 4 compare channels + 1 overflow. so you in theory can have it trigger 3 independent tasks using compare ch2/3/4, and one more for the overflow channel (not independent however).
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by dannyf »

again, it should be said that interrupts are a terribly way for frequently invokation of tasks, even if quick tasks. in this case, think about other hardware based solutions (pwm or dma->port).
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by RobertoBerner »

Hello ag123, thank you ! Please guys, let me reply in order to the different an valuable material I am receiving. Every piece of code and advice is very important to me.

dannyf: Thank you, I have tested the LL ( low level code), and YES ! It is naturally much more efficient, by about 8 times !!
However, with your permission, I modified your code a little because I only have 2 brand new BluePill boards and that's it. So, by now, I am unable to test on F4's, but I will do it very soon. In the meantime, I will learn the STM32 basics with this basic F1 board. of course, as soon as I get the main ideas about mastering internal registers and knowing the general structure and functions, it will be nice to migrate to an F4 Nucleo board or so.

Here is your first suggested code, with some interesting results. Later, I will read all of the suggestions and ideas and test them one by one, returning with results. It is a very interesting, rich discussion.

Code: Select all

#define LED_PORT    GPIOC
#define LED_PIN     (1<<13)   //LED on C13
#define LEDPIN      PC13

#define IO_SET(port, pins)  port |= (pins)
#define IO_CLR(port, pins)  port &=~(pins)
#define IO_FLP(port, pins)  port ^= (pins)

#define GIO_SET(gpio, pins) IO_SET(gpio->ODR, pins)
#define GIO_CLR(gpio, pins) IO_CLR(gpio->ODR, pins)
#define GIO_FLP(gpio, pins) IO_FLP(gpio->ODR, pins)

#define FIO_SET(gpio, pins) IO_SET(gpio->BSRR, pins)      //normally BSRR - changed for F1
#define FIO_CLR(gpio, pins) IO_CLR(gpio->BRR, pins)       //normally BRR - changed for F1
#define FIO_FLP(gpio, pins) GIO_FLP(gpio, pins)           //fast io not available


void setup() 
{
  RCC->APB2ENR |=  (1<<4);        // Enable GPIOC clock ADDED
  //pinMode(LED_PIN , OUTPUT);
  while (1) 
  {
    GIO_SET(LED_PORT, LED_PIN); GIO_CLR(LED_PORT, LED_PIN);   // best ! 112 ns 
    //FIO_SET(LED_PORT, LED_PIN); FIO_CLR(LED_PORT, LED_PIN); // does not work :(
    //FIO_FLP(LED_PORT, LED_PIN);                             // good ! 164 ns
    //digitalWrite(LEDPIN,HIGH);digitalWrite(LEDPIN,LOW);       // bad ! 820 ns
  }
}

void loop()
{
  
}
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by dannyf »

bad me.

for the two FIO macros, try this:

Code: Select all

#define FIO_SET(gpio, pins) gpio->BSRR=(pins)      //normally BSRR - changed for F1
#define FIO_CLR(gpio, pins) gpio->BRR=(pins)       //normally BRR - changed for F1
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by RobertoBerner »

Dear ag123, such an interesting reply, thank you.
Both boards are very interesting and for sure my next step soon. I can easily get Nucleo boards locally in Buenos Aires, but not the WeAct tiny board. I will have to order it.

You and dannyf are right. A timer overflow driving a pin, or Output Compare will do the job with almost no code overhead. I need to count the generated signal pulses, but that can be done with the same timer interrupt. I will be working in the 5 ~ 10 KHz range, so I think an F1 will work fine.

I will leave DMA for the near future. It needs some previous skills, but with all this, I think I have the tools.

About the extra interrupts of the Arduino environment, you are right. The sum of the intervening interrupts are dropping the bandwidth a lot.

I like your GirinoScope project ! Great. Later, I will take a look at the code, but I understand what you mean. I have used this technique with Freescale's S08 ADC, starting the ADC conversion in the callback and picking up the result later when it comes available.

Code: Select all

void CAdcMgr::adctimerhandle(void) {
	//start conversion when called
	ADC1->regs->CR2 |= ADC_CR2_SWSTART;
}
I payed attention to the 'art accelerator' issue. Of course this makes a huge difference.

I will go step by step, and now I will gather the information that you guys are providing to make the Hardware Timer do the job. I will report all results in case somebody else will need all these goodies.

Thank you !
Roberto
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by RobertoBerner »

Dear danyf:

In response to your last reply:
Yes ! This is what I need to do:
it is fairly easy:

1) pick a timer. For this, a timer with overflow interrupt or compare interrupt would work. I prefer the use of compare channels.
2) install a user handler.
I will study your code and try to make it work on my BluePill. Sometimes I have problems to know if I have to include something or not. It seems that all the LL used does not need any special .h HALL file in the Arduino environment. And the access to the registers seems to be simple. However, I need to deep study the reference manuals to have a complete idea on how each register works.

Will be back to report.

Thank you !
Roberto
GonzoG
Posts: 403
Joined: Wed Jan 15, 2020 11:30 am
Answers: 27
Location: Prudnik, Poland

Re: BluePill fast 10 us Timer interrupt example code needed

Post by GonzoG »

dannyf wrote: Sun May 14, 2023 1:11 pm
digitalWriteFast/digitalReadFast need 1 MCU cycle.
that would be very difficult to do.

digitalWriteFast() calls digital_io_write(), which goes through a if..then statement before calling LL_GPIO_SetOutputPin(), which goes to a set of register writes.

Hard to believe all that could be done in one cycle.

regardless, it would be interesting to see how fast it actually does.
In tests I got:
F103: dRF - 19MOps, dWF - 33MOps
F401: dRF - 72MOps, dWF - 69MOps
F411: dRF - 83MOps, dWF - 79MOps

I think it has to do with -O3 optimization but it's fast.
RobertoBerner wrote: Sun May 14, 2023 1:32 pm ...
BTW, where do I change the -O3 setting?
In Arduino IDE menu -> tools -> Optimize
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by dannyf »

And the access to the registers seems to be simple.
it is often debatable how much abstraction actually helps - each has its own unique style of coding that you need to learn and get used to. On top of that, your code developed (directly) on top of such abstration is heavily tied to that abstraction, making porting that code to other platforms difficult. That is precisely why the vendors introduce such "help" to their customers.

I take two approaches to remediate that:
1. I often code directly to the datasheet / header file. Not that difficult once you get used to it.
2. I use a middle layer of my own to hide the abstraction away from my application code. The middle layer has the same syntax across different chips and is implemented for a particular abstraction. I started with with the Luminary LM3S chips, then TI's TM4C, and then ST's SPL. This gets me up to speed quickly on a new chip, yet doesn't force my own code to be tied to a vendor / chip.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by dannyf »

I think it has to do with -O3 optimization but it's fast.
Tough to optimize if your end result has 1 or two instructions.

when i get more time (one of those days), i will see how faster I can make this thing to be.
RobertoBerner
Posts: 36
Joined: Tue May 09, 2023 10:45 pm

Re: BluePill fast 10 us Timer interrupt example code needed

Post by RobertoBerner »

Hello friends, I started a new post here about what we have been talking.
Is my first attempt of bare metal coding, direct access to registers.
I don't feel good with HAL.
Here is the new post just to keep separate subjects.
Just in case you want to follow up.
Thank you.

Roberto
Post Reply

Return to “General discussion”