Программирование NVIC на STM32 (без библиотек)

Я тщательно изучил техническое описание и руководство пользователя для микроконтроллера STM32F4, который я использую (включая PM0214 для микроконтроллеров STM32F4xx), а также информацию в Интернете об общих микроконтроллерах, чтобы понять, как можно программировать прерывания без библиотеки... но безрезультатно. Является ли NVIC настолько тесно связанным с аппаратным обеспечением, что программирование прерывания и указание адреса ISR и аббревиатуры для функции в настоящее время непрактично без какой-либо библиотеки? В каждом посте и документации я вижу что-то вроде:

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

но что, если кто-то захочет запрограммировать ISR с нуля в учебных целях?

Какие шаги необходимо предпринять? Где подтверждающая документация? Стоит ли/рекомендуется ли мне это делать?


person spooksmus    schedule 28.03.2017    source источник
comment
Упомянутые вами функции предоставляются в виде исходного кода — все, что вам нужно сделать, это посмотреть в них, чтобы увидеть, что они делают. Они банально просты. Однако я бы не стал заморачиваться — это стандартные функции CMSIS, доступные для всех устройств Cortex-M, поэтому для целей обучения вы мало что изучите.   -  person Clifford    schedule 28.03.2017
comment
NVIC является стандартной частью устройств Cortex-M, поэтому он описан в документации ARM, а не в документации ST. infocenter.arm.com/help /index.jsp?topic=/. Документация ST будет содержать особенности реализации, такие как количество реализованных битов уровня приоритета и перечисление периферийных IRQ.   -  person Clifford    schedule 28.03.2017


Ответы (2)


Полный пример, без заголовочных файлов, без библиотек. Плата NUCLEO-F411RE. Некоторые STMF4 практически одинаковы, cortex-m4 будет таким же. На любом системном микроконтроллере или в другом случае вы должны работать над прерыванием процессора как можно медленнее, по одному слою / шагу за раз. Так гораздо проще.

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 показаны адреса регистров 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

Как только я увидел изменение регистра ожидания и подтверждение документацией того, что это было правильное прерывание, вы можете установить тот же бит в соответствующем регистре включения установки, чтобы, наконец, прерывание попало в процессор.

Создайте и скопируйте notmain.bin на виртуальный ядерный диск, и он будет печатать UV каждую секунду, когда срабатывает прерывание и просыпается wfi. Естественно, вы обычно не хотите распечатывать содержимое uart в процедуре обслуживания прерывания, но в этом случае мы знаем, что это происходит раз в секунду, ничто другое не мешает периферийному устройству, поэтому в этом конкретном случае это безопасно.

/dev/ttyACM0 в Linux или что-то подобное в Windows — это то место, где вывод uart находится на отладочной плате NUCLEO. Вы можете легко изменить это, чтобы мигать светодиодами. Заметьте я убрал защиту круглосуточного init, возня с часами может очень быстро замуровать чип. Семейство STM32 имеет внутренний загрузчик и ремешок, поэтому вы можете разблокировать его с помощью этого, но прежде чем углубляться в код инициализации часов, будьте очень осторожны и делайте это медленно, шаг за шагом, в идеале запускайте UART с помощью часов RC. чтобы вы могли видеть, что происходит, точно так же, как выше, наблюдая за тем, что происходит с прерываниями.

Вам изначально не нужно возиться с приоритетами на NVIC. Существуют установленные регистры включения и чистые регистры включения, каждый из которых при чтении скажет, что вы хотите, чтобы он был включен. Есть установленные ожидающие и четкие ожидающие, либо сообщит вам, что находится на рассмотрении, когда будет прочитано. С прерываниями в целом в любой системе вы в идеале хотите знать, как сбросить ожидающее прерывание в источнике, а затем продвигаться к процессору, некоторые конструкции микросхем, когда вы очищаете его в источнике, он очищается полностью, некоторые как этот защелка, так что вы должны очистить его в обоих местах.

Существует 16 регистров NVIC каждого типа, поэтому 512 возможных отдельных прерываний, как я уже сказал, сводят с ума кору — почти тривиально, у вас нет ни одной линии прерывания, и тогда вам нужно пробираться, чтобы увидеть, кто вызвал это, и разобраться с другими. в то время как вы очищаете первый в очереди. У вас может быть одно периферийное устройство с более чем одним прерыванием, но это одно периферийное устройство, а не все из них в системе. Они также разработали логику исключения cortex-m таким образом, что вы можете поместить функцию C (компилятор, совместимый с eabi) непосредственно в таблицу векторов, вам не нужно обертывать этот код сохранением состояния в стеке и очисткой, и вы не используйте специальный возврат из инструкции прерывания. Логика cortex-m делает все это за вас, так что поймите, что вы немного избалованы этим чипом/семейством, но ничего страшного, вы можете намочить здесь ноги, а затем работать над, возможно, более сложными конструкциями MCU. Выполняйте те же шаги, хотя и опрашивайте свой путь пошагово, где это возможно, выполняйте столько шагов, сколько вам нужно, чтобы понять периферийное устройство, прежде чем вы действительно прервете ЦП, а затем даже там, где это необходимо, в зависимости от конструкции ЦП, работайте над тем, как чтобы определить, что было ожидающим и как это очистить, и проверить наличие других прерываний перед возвратом и т. д.

person old_timer    schedule 28.03.2017

Я быстро прочитал PM0214, он короче, чем я помню. Подробную информацию о NVIC см. в разделе 4.3. О самих регистрах говорится начиная с раздела 4.3.2.

Все библиотеки CMSIS взаимодействуют с этими регистрами. Если вы очень внимательно будете следовать инструкциям, вы вполне сможете создать собственную библиотеку NVIC для своего конкретного приложения всего за несколько часов (надеюсь!).

Более быстрым способом было бы просто просмотреть исходный код библиотек STM32Fx CMSIS. Я сделал это, чтобы реализовать более быстрый метод переключения GPIO, чем тот, который включен в CMSIS.

Поэтому я думаю, что это может того стоит. Я обнаружил, что многие библиотеки STM32Fx очень громоздки, и мне не нравится их синтаксис.

Я не делал этого много лет, так что я могу быть полностью выключен. Дайте мне знать, если это работает для вас!

person Forest Kunecke    schedule 28.03.2017
comment
Я понимаю, что оптимизация для конкретного приложения возможна путем написания собственного, но приведенные примеры включения и установки приоритета прерывания вряд ли выиграют от оптимизации. - person Clifford; 28.03.2017
comment
Спасибо. Регистры в PM0214 дают мне возможность вручную изменять приоритет, а также включать и отключать прерывания и читать/очищать ожидающие флаги, где это необходимо. Ницца. Однако управление всем этим осуществляется через номер прерывания, который соответствует определенному событию прерывания и адресу прерывания в NVIC. Что если я хочу написать ISR для EXTI4 (например); как я могу указать, какой функцией будет ISR? Всегда ли он должен иметь имя EXTI4_Handler (или что-то еще)? Есть ли способ изменить это? Что мне делать с этим адресом в NVIC? Тай - person spooksmus; 29.03.2017