STM32上NVIC编程(无库)

Programming NVIC on STM32 (without libraries)

我已经彻底搜索了我正在使用的 STM32F4 MCU 的数据表和用户手册(包括 STM32F4xx MCU 的 PM0214),甚至还搜索了有关一般 MCU 的在线信息,以了解如何在没有中断的情况下进行编程中断一个图书馆......但无济于事。 NVIC 是否与硬件紧密相关,以至于在没有某种库的情况下编程中断并指定 ISR 地址和函数的首字母缩略词在当今不切实际?在每个 post 和文档中,我都会看到类似的内容:

NVIC_EnableIRQ(IRQn_Type IRQn)
NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)

但是,如果有人想从头开始编写 ISR 以进行学习怎么办?

涉及哪些步骤?支持文档在哪里?我 advised/worth 是时候这样做了吗?

我快速通读了 PM0214,它比我记得的要短。有关 NVIC 的详细信息,请查看第 4.3 节。它从 4.3.2 节开始讨论寄存器本身。

CMSIS 库所做的就是与这些寄存器进行交互。如果您非常认真地遵循,您可以在短短几个小时内为您的特定应用构建您自己的 NVIC 库(希望如此!)。

一种更快的方法是简单地浏览 STM32Fx CMSIS 库的源代码。我这样做是为了实现比 CMSIS 中包含的方法更快的 GPIO 切换方法。

正因为如此,我认为可以值得您光顾。我发现许多 STM32Fx 库非常笨拙,我不喜欢它们的语法。

我已经很多年没有这样做了,所以我可能会完全放弃。让我知道这是否适合您!

一个完整的例子,没有头文件没有库。 NUCLEO-F411RE 板。许多 STMF4 几乎相同,cortex-m4 将是相同的。在任何系统 MCU 或其他系统上,您应该努力尽可能慢地中断 cpu,一次一个 layer/step。这样就容易多了。

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
}

建造

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

可以将 cortex-m0s 更改为 cortex-m4s。

cortex-m4 architectural reference manual 显示了NVIC寄存器的地址,一旦你了解外设如何设置它的中断状态,你就可以启用和轮询各种NVIC中断挂起寄存器,直到你看到一组。然后找出它是什么中断号,并查看 ST 文档,它应该匹配,在这种情况下,第二个寄存器中的第 18 位(如果从所有寄存器的开始到结束计数,则为第 50 位)是定时器 5,看在 ST 文档中断 50 是定时器 5,以便匹配。 st 文档还告诉我们它是地址 0x108,这恰好与我手工计数相匹配。

 80000fc:   08000137
 8000100:   08000137
 8000104:   08000137
 8000108:   08000169
 800010c:   08000137
 8000110:   08000137
 8000114:   08000137

一旦我看到挂起的寄存器发生变化并通过文档确认是正确的中断,那么您可以在相应的设置启用寄存器中设置相同的位,最终让中断命中 cpu。

构建并复制 notmain.bin 到虚拟核驱动器,它会在中断触发和 wfi 唤醒时每秒打印一次 UV。自然地,您通常不想在中断服务例程中打印出 uart 的内容,但在这种情况下,我们知道它是每秒一次,没有其他事情会干扰外围设备,因此在这种特定情况下它是安全的。

linux 上的

/dev/ttyACM0 或 windows 上的任何等效项是来自 NUCLEO 调试板的 uart 输出。你可以很容易地改变 这使 LED 闪烁。注意我删除了全天候的保护, 弄乱时钟会很快使芯片变砖。 STM32 系列有一个内部引导加载程序和 strap pin,所以你可以使用它来解除阻塞,但在深入时钟初始化代码之前,要非常非常小心,一次慢慢来,最好使用 RC 时钟启动 uart这样你就可以看到发生了什么,就像上面观察中断发生了什么一样。

您最初不必在 NVIC 上弄乱优先级。有设置启用寄存器和清除启用寄存器,每个读取时都会告诉您想要启用。有set pending 和clear pending 两种,读的时候都会告诉你什么是pending。对于任何系统上的一般中断,您理想情况下想知道如何从源头清除挂起的中断,然后朝着处理器的方向努力,一些芯片设计当您在源头清除它时,它会一直清除,有些像这个锁住它,所以你必须在两个地方都清除它。

每种类型有 16 个 NVIC 注册器,因此有 512 个可能的独立中断,就像我说的,它们让 cortex-m 发疯几乎微不足道,你没有一条中断线,然后你必须艰难地查看是谁造成的并在清除队列中的第一个时处理其他进来的人。您可以让一个外围设备有多个中断,但它是一个外围设备,而不是系统中的所有外围设备。他们还以一种方式设计了 cortex-m 异常逻辑,您可以将(与 eabi 兼容的编译器)C 函数直接放入向量 table 中,您不需要通过在堆栈上保存状态来包装该代码,并且清理并且您不使用中断指令中的特殊 return 。 cortex-m 逻辑为您完成了所有这些工作,所以请理解您对这个 chip/family 有点宠坏了,但这没关系,您可以在这里尝试一下,然后处理可能更复杂的 MCU 设计。遵循相同的步骤,尽管尽可能一次轮询您的方式,在实际中断 CPU 之前采取尽可能多的步骤了解外围设备,然后根据需要根据 cpu 设计,研究如何确定挂起的内容以及如何清除它并在 returning 等之前检查其他中断...