Reputation: 51
I have thoroughly searched the Datasheet and User's Manual for the STM32F4 MCU I am using (including PM0214 for STM32F4xx MCUs) as well as even information online about general MCUs to develop an understanding of how one would go about programming interrupts without a library... but to no avail. Is the NVIC so closely tied to the hardware that programming an interrupt and specifying an ISR address and acronym for a function unpractical nowadays without some sort of library? In every post and documentation I see something like:
NVIC_EnableIRQ(IRQn_Type IRQn)
NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
but what if someone wants to program an ISR from scratch for learning purposes?
What are the steps involved? Where is the supporting documentation? Is it advised/worth my while to do so?
Upvotes: 3
Views: 2388
Reputation: 71536
A complete example, no header files no libraries. NUCLEO-F411RE board. A number of the STMF4s are pretty much the same, the cortex-m4 is going to be the same. On any system MCU or otherwise you should work your way toward interrupting the cpu as slowly as you can, one layer/step at a time. Much easier that way.
flash.s
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang @ NMI
.word hang @ HardFault
.word hang @ MemManage
.word hang @ BusFault
.word hang @ UsageFault
.word hang @ 7
.word hang @ 8
.word hang @ 9
.word hang @ 10
.word hang @ SVCall
.word hang @ DebugMonitor
.word hang @ Reserved
.word hang @ PendSV
.word hang @ SysTick
.word hang @ External interrupt 0
.word hang @ External interrupt 1
.word hang @ External interrupt 2
.word hang @ External interrupt 3
.word hang @ External interrupt 4
.word hang @ External interrupt 5
.word hang @ External interrupt 6
.word hang @ External interrupt 7
.word hang @ External interrupt 8
.word hang @ External interrupt 9
.word hang @ External interrupt 10
.word hang @ External interrupt 11
.word hang @ External interrupt 12
.word hang @ External interrupt 13
.word hang @ External interrupt 14
.word hang @ External interrupt 15
.word hang @ External interrupt 16
.word hang @ External interrupt 17
.word hang @ External interrupt 18
.word hang @ External interrupt 19
.word hang @ External interrupt 20
.word hang @ External interrupt 21
.word hang @ External interrupt 22
.word hang @ External interrupt 23
.word hang @ External interrupt 24
.word hang @ External interrupt 25
.word hang @ External interrupt 26
.word hang @ External interrupt 27
.word hang @ External interrupt 28
.word hang @ External interrupt 29
.word hang @ External interrupt 30
.word hang @ External interrupt 31
.word hang @ External interrupt 32
.word hang @ External interrupt 33
.word hang @ External interrupt 34
.word hang @ External interrupt 35
.word hang @ External interrupt 36
.word hang @ External interrupt 37
.word hang @ External interrupt 38
.word hang @ External interrupt 39
.word hang @ External interrupt 40
.word hang @ External interrupt 41
.word hang @ External interrupt 42
.word hang @ External interrupt 43
.word hang @ External interrupt 44
.word hang @ External interrupt 45
.word hang @ External interrupt 46
.word hang @ External interrupt 47
.word hang @ External interrupt 48
.word hang @ External interrupt 49
.word tim5_handler @ External interrupt 50
.word hang @ External interrupt 51
.word hang @ External interrupt 52
.word hang @ External interrupt 53
.word hang @ External interrupt 54
.word hang @ External interrupt 55
.word hang @ External interrupt 56
.word hang @ External interrupt 57
.word hang @ External interrupt 58
.word hang @ External interrupt 59
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl DOWFI
DOWFI:
wfi
bx lr
notmain.c
void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void DOWFI ( void );
#define RCCBASE 0x40023800
#define RCC_CR (RCCBASE+0x00)
#define RCC_CFGR (RCCBASE+0x08)
#define RCC_APB1RSTR (RCCBASE+0x20)
#define RCC_AHB1ENR (RCCBASE+0x30)
#define RCC_APB1ENR (RCCBASE+0x40)
#define RCC_BDCR (RCCBASE+0x70)
#define GPIOABASE 0x40020000
#define GPIOA_MODER (GPIOABASE+0x00)
#define GPIOA_OTYPER (GPIOABASE+0x04)
#define GPIOA_OSPEEDR (GPIOABASE+0x08)
#define GPIOA_PUPDR (GPIOABASE+0x0C)
#define GPIOA_BSRR (GPIOABASE+0x18)
#define GPIOA_AFRL (GPIOABASE+0x20)
#define USART2BASE 0x40004400
#define USART2_SR (USART2BASE+0x00)
#define USART2_DR (USART2BASE+0x04)
#define USART2_BRR (USART2BASE+0x08)
#define USART2_CR1 (USART2BASE+0x0C)
#define TIM5BASE 0x40000C00
#define TIM5_CR1 (TIM5BASE+0x00)
#define TIM5_DIER (TIM5BASE+0x0C)
#define TIM5_SR (TIM5BASE+0x10)
#define TIM5_CNT (TIM5BASE+0x24)
#define TIM5_PSC (TIM5BASE+0x24)
#define TIM5_ARR (TIM5BASE+0x2C)
#define NVIC_ISER1 0xE000E104
#define NVIC_ICPR1 0xE000E284
//PA2 is USART2_TX alternate function 1
//PA3 is USART2_RX alternate function 1
static int clock_init ( void )
{
unsigned int ra;
//switch to external clock.
ra=GET32(RCC_CR);
ra|=1<<16;
PUT32(RCC_CR,ra);
while(1) if(GET32(RCC_CR)&(1<<17)) break;
ra=GET32(RCC_CFGR);
ra&=~3;
ra|=1;
PUT32(RCC_CFGR,ra);
while(1) if(((GET32(RCC_CFGR)>>2)&3)==1) break;
return(0);
}
static int uart2_init ( void )
{
unsigned int ra;
ra=GET32(RCC_AHB1ENR);
ra|=1<<0; //enable port A
PUT32(RCC_AHB1ENR,ra);
ra=GET32(RCC_APB1ENR);
ra|=1<<17; //enable USART2
PUT32(RCC_APB1ENR,ra);
ra=GET32(GPIOA_MODER);
ra&=~(3<<4); //PA2
ra&=~(3<<6); //PA3
ra|=2<<4; //PA2
ra|=2<<6; //PA3
PUT32(GPIOA_MODER,ra);
ra=GET32(GPIOA_OTYPER);
ra&=~(1<<2); //PA2
ra&=~(1<<3); //PA3
PUT32(GPIOA_OTYPER,ra);
ra=GET32(GPIOA_OSPEEDR);
ra|=3<<4; //PA2
ra|=3<<6; //PA3
PUT32(GPIOA_OSPEEDR,ra);
ra=GET32(GPIOA_PUPDR);
ra&=~(3<<4); //PA2
ra&=~(3<<6); //PA3
PUT32(GPIOA_PUPDR,ra);
ra=GET32(GPIOA_AFRL);
ra&=~(0xF<<8); //PA2
ra&=~(0xF<<12); //PA3
ra|=0x7<<8; //PA2
ra|=0x7<<12; //PA3
PUT32(GPIOA_AFRL,ra);
ra=GET32(RCC_APB1RSTR);
ra|=1<<17; //reset USART2
PUT32(RCC_APB1RSTR,ra);
ra&=~(1<<17);
PUT32(RCC_APB1RSTR,ra);
//PUT32(USART2_CR1,(1<<13));
//8000000/(16*115200) = 4.34 4+5/16
PUT32(USART2_BRR,0x45);
PUT32(USART2_CR1,(1<<3)|(1<<2)|(1<<13));
return(0);
}
static void uart2_send ( unsigned int x )
{
while(1) if(GET32(USART2_SR)&(1<<7)) break;
PUT32(USART2_DR,x);
}
static void hexstrings ( unsigned int d )
{
//unsigned int ra;
unsigned int rb;
unsigned int rc;
rb=32;
while(1)
{
rb-=4;
rc=(d>>rb)&0xF;
if(rc>9) rc+=0x37; else rc+=0x30;
uart2_send(rc);
if(rb==0) break;
}
uart2_send(0x20);
}
static void hexstring ( unsigned int d )
{
hexstrings(d);
uart2_send(0x0D);
uart2_send(0x0A);
}
void tim5_handler ( void )
{
uart2_send(0x55);
PUT32(TIM5_SR,0);
PUT32(NVIC_ICPR1,0x00040000);
}
int notmain ( void )
{
unsigned int ra;
unsigned int rb;
clock_init();
uart2_init();
hexstring(0x12345678);
ra=GET32(RCC_APB1ENR);
ra|=1<<3; //enable TIM5
PUT32(RCC_APB1ENR,ra);
if(0)
{
PUT32(TIM5_CR1,0x0000);
PUT32(TIM5_DIER,0x0000);
PUT32(TIM5_PSC,0x0000);
PUT32(TIM5_ARR,16000000-1);
PUT32(TIM5_CNT,16000000-1);
PUT32(TIM5_CR1,0x0001);
PUT32(TIM5_SR,0);
ra=GET32(TIM5_SR);
hexstring(ra);
while(1)
{
rb=GET32(TIM5_SR);
if(rb!=ra)
{
ra=rb;
hexstring(ra);
PUT32(TIM5_SR,0);
}
}
}
if(0)
{
PUT32(TIM5_CR1,0x0000);
PUT32(TIM5_DIER,0x0001);
PUT32(TIM5_PSC,0x0000);
PUT32(TIM5_ARR,16000000-1);
PUT32(TIM5_CNT,16000000-1);
PUT32(TIM5_CR1,0x0001);
PUT32(TIM5_SR,0);
while(1)
{
ra=GET32(NVIC_ICPR1);
if(ra)
{
hexstring(ra);
PUT32(TIM5_SR,0);
PUT32(NVIC_ICPR1,ra);
}
}
}
if(0)
{
PUT32(TIM5_CR1,0x0000);
PUT32(TIM5_DIER,0x0001);
PUT32(TIM5_PSC,0x0000);
PUT32(TIM5_ARR,16000000-1);
PUT32(TIM5_CNT,16000000-1);
PUT32(TIM5_CR1,0x0001);
PUT32(TIM5_SR,0);
while(1)
{
ra=GET32(NVIC_ICPR1);
if(ra)
{
hexstring(ra);
PUT32(TIM5_SR,0);
PUT32(NVIC_ICPR1,ra);
}
}
}
if(1)
{
PUT32(TIM5_CR1,0x0000);
PUT32(TIM5_DIER,0x0001);
PUT32(TIM5_PSC,0x0000);
PUT32(TIM5_ARR,16000000-1);
PUT32(TIM5_CNT,16000000-1);
PUT32(TIM5_CR1,0x0001);
PUT32(TIM5_SR,0);
PUT32(NVIC_ICPR1,0x00040000);
PUT32(NVIC_ISER1,0x00040000);
while(1)
{
DOWFI();
uart2_send(0x56);
}
}
return(0);
}
flash.ld
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
build
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -o notmain.elf -T flash.ld flash.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary
can change the cortex-m0s to cortex-m4s.
The cortex-m4 architectural reference manual shows the address of the NVIC registers, once you understand how the peripheral sets its interrupt status, then you can enable and poll the various NVIC interrupt pending registers until you see one set. Then figure out what interrupt number it is, and look at the ST documentation and it should match, in this case bit 18 in the second register (bit 50 if you count from the beginning to end of all the registers) is timer 5, looking at the ST documentation interrupt 50 is timer 5 so that matches. The st documentation also tells us it is address 0x108, which happened to match my counting them by hand.
80000fc: 08000137
8000100: 08000137
8000104: 08000137
8000108: 08000169
800010c: 08000137
8000110: 08000137
8000114: 08000137
Once I could see the pending register changing and confirming with the documentation that was the correct interrupt, then you can set the same bit in the corresponding set enable register to finally let the interrupt hit the cpu.
Build and copy notmain.bin to the virtual nucleo drive and it will print UV every second as the interrupt fires and the wfi wakes up. Naturally you normally dont want to print stuff out the uart in an interrupt service routine, but in this case we know that it is once a second, nothing else is going on to interfere with the peripheral so it is safe in this specific case.
/dev/ttyACM0 on linux or whatever the equivalent is on windows is where the uart output is from the NUCLEO debug board. You can easily change this to blink leds. Note I removed the protection around the clock init, messing with the clocks can very quickly brick a chip. The STM32 family has an internal bootloader and strap pin, so you can get unbricked using that, but before diving into clock init code, be very very careful and take it one slow step at a time, ideally get the uart up using the RC clock so you can see what is going on, just like the above watching what happens with the interrupts.
You dont initially have to mess with priorities on the NVIC. There are set enable registers and clear enable registers each when read will tell you want is enabled. There are set pending and clear pending, either will tell you what is pending when read. With interrupts in general on any system you ideally want to know how to clear the pending interrupt at the source, then work your way toward the processor, some chip designs when you clear it at the source, it clears all the way through, some like this one latch it, so you have to clear it in both places.
There are 16 NVIC regstiers of each type so 512 possible separate interrupts, like I said they mad the cortex-m almost trivial, you dont have a single interrupt line and then you have to wade through to see who caused it and deal with others coming in while you clear the first one in line. You could have the one peripheral have more than one interrupt but it is the one peripheral not all of them in the system. They also designed the cortex-m exception logic in a way that you can put a(n eabi compatible compiler) C function in the vector table directly, you dont need to wrap that code with saving the state on the stack and cleaning up and you dont use a special return from interrupt instruction. The cortex-m logic does all that for you, so understand you are a bit spoiled with this chip/family, but that is okay you can get your feet wet here then work on possibly more complicated MCU designs. Follow the same steps, though poll your way one step at a time where possible, take as many steps as you need to understand the peripheral before you actually interrupt the CPU, and then even there as needed depending on the cpu design, work through how to determine what was pending and how to clear that and check for other interrupts before returning, etc...
Upvotes: 6
Reputation: 2160
I did a quick read through of PM0214, it's shorter than I remember. Check section 4.3 for details on the NVIC. It talks about the registers themselves starting in section 4.3.2.
All the CMSIS libraries do is interact with those registers. If you very carefully follow along, you can very well build your own NVIC library for your specific application in just a few hours (hopefully!).
A faster way would be to simply browse the source of the STM32Fx CMSIS libraries. I did this to implement a faster GPIO toggle method than the one included in CMSIS.
Because of this I think it can be worth your while. I found many of the STM32Fx libraries to be very unwieldy and I did not like their syntax.
I haven't done this in many years so I may be completely off. Let me know if this works for you!
Upvotes: 3