1
votes

I'm using the blue pill, and trying to figure out interrupts. I have an interrupt handler:

void __attribute__ ((interrupt ("TIM4_IRQHandler"))) myhandler()
{
    puts("hi");
    TIM4->EGR |= TIM_EGR_UG; // send an update even to reset timer and apply settings
    TIM4->SR &= ~0x01; // clear UIF
    TIM4->DIER |= 0x01; // UIE
}

I set up the timer:

    RCC_APB1ENR |= RCC_APB1ENR_TIM4EN;
    TIM4->PSC=7999;
    TIM4->ARR=1000;
    TIM4->EGR |= TIM_EGR_UG; // send an update even to reset timer and apply settings
    TIM4->EGR |= (TIM_EGR_TG | TIM_EGR_UG);
    TIM4->DIER |= 0x01; // UIE enable interrupt
    TIM4->CR1 |= TIM_CR1_CEN;
   

My timer doesn't seem to activate. I don't think I've actually enabled it though. Have I??

I see in lots of example code commands like:

NVIC_EnableIRQ(USART1_IRQn);

What is actually going in NVIC_EnableIRQ()?

I've googled around, but I can't find actual bare-metal code that's doing something similar to mine.

I seem to be missing a crucial step.

Update 2020-09-23 Thanks to the respondents to this question. The trick is to set the bit for the interrupt number in an NVIC_ISER register. As I pointed out below, this doesn't seem to be mentioned in the STM32F101xx reference manual, so I probably would never have been able to figure this out on my own; not that I have any real skill in reading datasheets.

Anyway, oh joy, I managed to get interrupts working! You can see the code here: https://github.com/blippy/rpi/tree/master/stm32/bare/04-timer-interrupt

2
Details related to Cortex core are generally mentioned in the "Programming Manual", not in the "Reference Manual".Tagli

2 Answers

3
votes

Even if you go bare metal, you might still want to use the CMSIS header files that provide declarations and inline version of very basic ARM Cortex elements such NVIC_EnableIRQ.

You can find NVIC_EnableIRQ at https://github.com/ARM-software/CMSIS_5/blob/develop/CMSIS/Core/Include/core_cm3.h#L1508

It's defined as:


#define NVIC_EnableIRQ __NVIC_EnableIRQ

__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    __COMPILER_BARRIER();
    NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __COMPILER_BARRIER();
  }
}

If you want to, you can ignore __COMPILER_BARRIER(). Previous versions didn't use it.

The definition is applicable to Cortex M-3 chips. It's different for other Cortex versions.

2
votes

With the libraries is still considered bare metal. Without operating system, but anyway, good that you have a desire to learn at this level. Someone has to write the libraries for others.

I was going to do a full example here, (it really takes very little code to do this), but will take from my code for this board that uses timer1.

You obviously need the ARM documentation (technical reference manual for the cortex-m3 and the architectural reference manual for armv7-m) and the data sheet and reference manual for this st part (no need for programmers manual from either company).

You have provided next to no information related to making the part work. You should never dive right into a interrupt, they are advanced topics and you should poll your way as far as possible before finally enabling the interrupt into the core.

I prefer to get a uart working then use that to watch the timer registers when the roll over, count, etc. Then see/confirm the status register fired, learn/confirm how to clear it (sometimes it is just a clear on read).

Then enable it into the NVIC and by polling see the NVIC sees it, and that you can clear it.

You didn't show your vector table this is key to getting your interrupt handler working. Much less the core booting.

08000000 <_start>:
 8000000:   20005000
 8000004:   080000b9
 8000008:   080000bf
 800000c:   080000bf
...
 80000a0:   080000bf
 80000a4:   080000d1
 80000a8:   080000bf
...
080000b8 <reset>:
 80000b8:   f000 f818   bl  80000ec <notmain>
 80000bc:   e7ff        b.n 80000be <hang>
...
080000be <hang>:
 80000be:   e7fe        b.n 80000be <hang>
...
080000d0 <tim1_handler>:

The first word loads the stack pointer, the rest are vectors, the address to the handler orred with one (I'll let you look that up).

In this case the st reference manual shows that interrupt 25 is TIM1_UP at address 0x000000A4. Which mirrors to 0x080000A4, and that is where the handler is in my binary, if yours is not then two things, one you can use VTOR to find an aligned space, sometimes sram or some other flash space that you build for this and point there, but your vector table handler must have the proper pointer or your interrupt handler won't run.

volatile unsigned int counter;
void tim1_handler ( void )
{
    counter++;
    PUT32(TIM1_SR,0);
}

volatile isn't necessarily the right way to share a variable between interrupt handler and foreground task, it happens to work for me with this compiler/code, you can do the research and even better, examine the compiler output (disassemble the binary) to confirm this isn't a problem.

ra=GET32(RCC_APB2ENR);
ra|=1<<11;  //TIM1
PUT32(RCC_APB2ENR,ra);

...

counter=0;
PUT32(TIM1_CR1,0x00001);
PUT32(TIM1_DIER,0x00001);
PUT32(NVIC_ISER0,0x02000000);
for(rc=0;rc<10;)
{
    if(counter>=1221)
    {
        counter=0;
        toggle_led();
        rc++;
    }
}
PUT32(TIM1_CR1,0x00000);
PUT32(TIM1_DIER,0x00000);

A minimal init and runtime for tim1.

Notice that the NVIC_ISER0 is bit 25 that is set to enable interrupt 25 through.

Well before trying this code, I polled the timer status register to see how it works, compare with docs, clear the interrupt per the docs. Then with that knowledge confirmed with the NVIC_ICPR0,1,2 registers that it was interrupt 25. As well as there being no other gates between the peripheral and the NVIC as some chips from some vendors may have.

Then released it through to the core with NVIC_ISER0.

If you don't take these baby steps and perhaps you have already, it only makes the task much worse and take longer (yes, sometimes you get lucky).

TIM4 looks to be interrupt 30, offset/address 0x000000B8, in the vector table. NVIC_ISER0 (0xE000E100) covers the first 32 interrupts so 30 would be in that register. If you disassemble the code you are generating with the library then we can see what is going on, and or look it up in the library source code (as someone already did for you).

And then of course your timer 4 code needs to properly init the timer and cause the interrupt to fire, which I didn't check.

There are examples, you need to just keep looking.

The minimum is

  1. vector in the table
  2. set the bit in the interrupt set enable register
  3. enable the interrupt to leave the peripheral
  4. fire the interrupt

Not necessarily in that order.