前面几篇文章涉及到stm32编程需要了解的一些基础知识,本篇在记录一些编程中常用到的基础概念。
本篇记录的是NVIC(Nested Vectored Interrupt Controller嵌套向量中断控制器),NVIC属于Cortex内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理(SYSTICK不是由NVIC来控制的)。
Cortex™-M3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。但STM32并没有使用CM3内核的全部东西,而是只用了它的一部分,STM32F103系列上面,只有60个可屏蔽中断、16个内核中断,具有16级可编程的中断优先级(使用了4位中断优先级)。
NVIC 的寄存器以存储器映射的方式来访问,除了包含控制寄存器和中断处理的控制逻辑之外,NVIC 还包含了MPU的控制寄存器、SysTick 定时器以及调试控制。本小节仅记录中断相关的内容。
《STM32中文手册》60个可屏蔽中断如下图:
STM32中断有5组,即组0、组1、…、组4。每个中断设置一个抢占优先级和一个响应优先级。如下表(通过SCB->AIRCR寄存器对中断优先级进行配置)
表中的AIRCR的10到8位 用以表示分组,组0到组4。
表中的IP 的7到4位 用以表示 优先级。IP全称为Interrupt Priority Registers
抢占优先级是指 当 优先级更高的中断来了可以打断 低优先级的中断。
响应优先级是指 抢占优先级相同的情况下,若多个中断同时发生,高响应优先级的先运行,注意该优先级的高的不能打断正在运行的低优先级的中断。
//misc.h
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
#define AIRCR_VECTKEY_MASK ((uint32_t)0x05FA0000)
/**中断分组设置函数
* @brief Configures the priority grouping: pre-emption priority and subpriority.
* @param NVIC_PriorityGroup: specifies the priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
* 4 bits for subpriority
* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
* 3 bits for subpriority
* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
* 2 bits for subpriority
* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
* 1 bits for subpriority
* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
* 0 bits for subpriority
* @retval None
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
//core_cm3.h
/** @addtogroup CMSIS_CM3_SCB CMSIS CM3 SCB
memory mapped structure for System Control Block (SCB)
@{
*/
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
__IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;
//main.c
//设置中断分组为 组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
中断优先级的设置 NVIC为我们提供了 寄存器,其寄存器机构如下:
typedef struct
{
__IO uint32_t ISER[8]; //中断使能寄存器组
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; //中断失能寄存器组
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; //中断挂起寄存器组
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; //中断解挂寄存器组
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; //中断激活标志位寄存器组
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; //中断优先级控制的寄存器组
uint32_t RESERVED5[644];
__O uint32_t STIR;
} NVIC_Type;
①IP[240](中断优先级控制器组)
中断优先级的设置 使用的是 IP[240],看结构体定义我们知道 系统定义了240个8位寄存器,每个中断使用一个寄存器来确定优先级。
stm32f10x系列一共有60个可屏蔽中断,使用IP[0]~IP[59]。每个IP寄存器仅用到了高四位,低4位不用。
②ISER[8](中断使能寄存器组)
用来使能中断,32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ISER[0]和ISER[1]。ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~27对应中断32~59;
③ICER[8](中断失能寄存器组)
用来失能中断,32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ICER[0]和ICER[1]。ICER[0]的bit0~bit31分别对应中断0~31。ICER[1]的bit0~27对应中断32~59;
①②③使用库函数 void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);进行配置
④ISPR[8](中断挂起控制寄存器组)
⑤ICPR[8](中断解挂控制寄存器组)
④⑤使用到的库函数有:
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn);
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
⑥IABR [8](中断激活标志位寄存器组)
作用:只读,通过它可以知道当前在执行的中断是哪一个。
如果对应位为1,说明该中断正在执行。
该寄存器配置涉及到的库函数为 static __INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)
中断优先级设置步骤如下:
①系统运行后先设置中断优先级分组。调用函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); 整个系统执行过程中,只设置一次中断分组。
②针对每个中断,设置对应的抢占优先级和响应优先级:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
③如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可。
//设置中断分组为 组2 ,仅设置一次
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化NVIC寄存器
本小节定义的延时函数使用到了Systick定时器。Cortex™-M3,Cortex™-M4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。
misc.h为我们定义了Systick寄存器,如下
/******************************************************************************/
/* */
/* SystemTick */
/* */
/******************************************************************************/
/***************** Bit definition for SysTick_CTRL register *****************/
#define SysTick_CTRL_ENABLE ((uint32_t)0x00000001) /*!< Counter enable */
#define SysTick_CTRL_TICKINT ((uint32_t)0x00000002) /*!< Counting down to 0 pends the SysTick handler */
#define SysTick_CTRL_CLKSOURCE ((uint32_t)0x00000004) /*!< Clock source */
#define SysTick_CTRL_COUNTFLAG ((uint32_t)0x00010000) /*!< Count Flag */
/***************** Bit definition for SysTick_LOAD register *****************/
#define SysTick_LOAD_RELOAD ((uint32_t)0x00FFFFFF) /*!< Value to load into the SysTick Current Value Register when the counter reaches 0 */
/***************** Bit definition for SysTick_VAL register ******************/
#define SysTick_VAL_CURRENT ((uint32_t)0x00FFFFFF) /*!< Current value at the time the register is accessed */
/***************** Bit definition for SysTick_CALIB register ****************/
#define SysTick_CALIB_TENMS ((uint32_t)0x00FFFFFF) /*!< Reload value to use for 10ms timing */
#define SysTick_CALIB_SKEW ((uint32_t)0x40000000) /*!< Calibration value is not exactly 10 ms */
#define SysTick_CALIB_NOREF ((uint32_t)0x80000000) /*!< The reference clock is not provided */
从上面可看出Cortex™-M3内核设计有4个Systick寄存器
1)CTRL
CTRL是SysTick控制和状态寄存器
对于STM32,外部时钟源是 HCLK(AHB总线时钟)的1/8
内核时钟是 HCLK时钟
配置函数:SysTick_CLKSourceConfig();
2)LOAD
LOAD是SysTick重装载数值寄存器
3)VAL
VAL是SysTick当前值寄存器
4)CALIB
CALIB是SysTick校准值寄存器
常用SysTick相关函数
①SysTick_CLKSourceConfig() //Systick时钟源选择 misc.c文件中
/** @defgroup SysTick_clock_source **/
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
((SOURCE) == SysTick_CLKSource_HCLK_Div8))
/**
* @brief Configures the SysTick clock source.
* @param SysTick_CLKSource: specifies the SysTick clock source.
* This parameter can be one of the following values:
* @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
* @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
* @retval None
*/
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
②SysTick_Config(uint32_t ticks) //初始化systick,时钟为HCLK,并开启中断
//core_cm3.h/core_cm4.h文件中
/**
* @brief Initialize and start the SysTick counter and its interrupt.
*
* @param ticks number of ticks between two interrupts
* @return 1 = failed, 0 = successful
*
* Initialise the system tick timer and its interrupt and start the
* system tick timer / counter in free running mode to generate
* periodical interrupts.
*/
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
③void SysTick_Handler(void) //中断服务函数
下面的程序即是一个简单的使用SysTick利用中断实现的delay延时,如下:
static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
int main(void)
{ …
if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms,我们知道SystemCoreClock在system_stm32f10x.c文件总定义为uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz;SystemClock=7200000,SystemCoreClock/1000=7200,而系统频率使72MHz,即周期T=1/(72*1000*1000)s,则SystemCoreClock/1000*T=1/1000s=1ms
{
while (1);
}
while(1)
{
Delay(200);//2ms
…
}
}
端口复用就是指用GPIO复用为内置外设引脚。如stm32f103的PA9、PA10可以不作为GPIO而复用为串口1的发送接收引脚。
端口复用配置过程如下:
①GPIO端口时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
②复用外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
③端口模式配置
GPIO_Init();
下面以串口1复用为例,我们从《STM32中文参考手册V10》P110的表格“8.1.11外设的GPIO配置”可知GPIO作为串口使用时 GPIO的模式为什么,如下表
//①IO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//②外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//③初始化IO为对应的模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9//复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 PA.10 浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在STM32中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
为了使不同器件封装的外设IO功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。STM32中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。
重映射操作步骤
①使能被重新映射的I/O端口时钟
②使能被重映射的外设时钟
③使能AFIO功能的时钟
④进行重映射
如串口1重映射步骤如下:
①使能GPIO时钟(重映射后的IO);
②使能功能外设时钟(例如串口1);
③使能AFIO时钟。重映射必须使能AFIO时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
④开启重映射。
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
根据第一个参数,来确定是部分重映射还是全部重映射
上面看到两个词 部分重映射、全部重映射,下面说一下这两个的概念
在stm32f10x_gpio.h中定义了重映射的类别,下面是串口的定义
#define GPIO_Remap_USART1 ((uint32_t)0x00000004) /*!< USART1 Alternate Function mapping */
#define GPIO_Remap_USART2 ((uint32_t)0x00000008) /*!< USART2 Alternate Function mapping */
#define GPIO_PartialRemap_USART3 ((uint32_t)0x00140010) /*!< USART3 Partial Alternate Function mapping */
#define GPIO_FullRemap_USART3 ((uint32_t)0x00140030) /*!< USART3 Full Alternate Function mapping */
端口重映射寄存器
对用使用AFIO寄存器需要首先开启AFIO时钟