目录
  1. 1. SysTick定时器和NVIC中断优先级管理
    1. 1.1. Systick定时器简介
    2. 1.2. 4个Systick寄存器
    3. 1.3. SysTick库函数
  2. 2. NVIC中断优先级管理
    1. 2.1. 中断管理方法
    2. 2.2. IO引脚复用和映射管理与配置
SysTick定时器,NVIC中断优先级和IO引脚复用

SysTick定时器和NVIC中断优先级管理

Systick定时器简介

Systick定时器,是一个简单的定时器,对于ST的CM3,CM4,CM7内核芯片,都有Systick定时

Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。

Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。Systick中断的优先级也可以设置。

4个Systick寄存器

CTRL SysTick 控制和状态寄存器

SysTick 控制和状态寄存器

LOAD SysTick 自动重装载除值寄存器

SysTick 自动重装载除值寄存器

VAL SysTick 当前值寄存器

SysTick 当前值寄存器

CALIB SysTick 校准值寄存器

四个寄存器的定义函数

1
2
3
4
5
6
7
typedef struct
{
__IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) SysTick Control and Status Register */
__IOM uint32_t LOAD; /*!< Offset: 0x004 (R/W) SysTick Reload Value Register */
__IOM uint32_t VAL; /*!< Offset: 0x008 (R/W) SysTick Current Value Register */
__IM uint32_t CALIB; /*!< Offset: 0x00C (R/ ) SysTick Calibration Register */
} SysTick_Type;

SysTick库函数

Systick时钟源有两种选择选择:SYSTICK_CLKSOURCE_HCLK和SYSTICK_CLKSOURCE_HCLK_DIV8

1
2
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SYSTICK_CLKSOURCE_HCLK) || \
((SOURCE) == SYSTICK_CLKSOURCE_HCLK_DIV8))

初始化systick,时钟为HCLK,并开启中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)  //systick定时器经过多少个ticks周期发生中断
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* MASK值最大是24位*/
}

SysTick->LOAD = (uint32_t)(ticks - 1); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* 开启中断使能IO口设定时钟源 */
return (0UL);
}

不带OS的系统延时

systick的频率为HCLK,fac_us=SYSCLK,

systick运行1us,要跑fac_us(SYSCLK)个节拍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static u32 fac_us=0;							//us延时倍乘数
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用
}
//延时nus
//nus为要延时的us数.
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow//这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
}

NVIC中断优先级管理

CM4/CM7 内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。STM32F42xx/STM32F43xx则总共有97个中断。10个内核中断,87个可屏蔽中断。

STM32具有16级可编程的中断优先级,而我们常用的就是这些可屏蔽中断。

《STM32F中文参考手册》中搜索向量表可以找到相应的中断说明。

中断管理方法

首先,对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值。分组配置是在寄存器SCB->AIRCR中配置:

AIRCR[10:8] IP bit[7:4]分配情况 分配结果
0 111 0:4 0位抢占优先级,4位响应优先级
1 110 1:3 1位抢占优先级,3位响应优先级
2 101 2:2 2位抢占优先级,2位响应优先级
3 100 3:1 3位抢占优先级,1位响应优先级
4 011 4:0 4位抢占优先级,0位响应优先级

抢占优先级 & 响应优先级区别:

高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。

抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。

抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。

如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;

举例

假定设置中断优先级组为2,然后设置

中断3(RTC中断)的抢占优先级为2,响应优先级为1。

中断6(外部中断0)的抢占优先级为3,响应优先级为0

中断7(外部中断1)的抢占优先级为2,响应优先级为0。

那么这3个中断的优先级顺序为:中断7>中断3>中断6。

中断优先级分组函数

1
2
3
4
5
6
7
8
9
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));

NVIC_SetPriorityGrouping(PriorityGroup);
}

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);

IO引脚复用和映射管理与配置

STM32F4 系列微控制器 IO 引脚通过一个复用器连接到内置外设或模块。该复用器一次只允 许一个外设的复用功能(AF)连接到对应的 IO 口。

这样可以确保共用同一个 IO 引脚的外设之 间不会发生冲突。 每个 IO 引脚都有一个复用器,该复用器采用 16 路复用功能输入(AF0 到 AF15),可通过 GPIOx_AFRL(针对引脚 0-7)和 GPIOx_AFRH(针对引脚 8-15)寄存器对这些输入进行配置,每四位控制一路复用:

端口复用映射示意图

0-7端口复用映射示意图

8-15端口复用映射示意图

AFRL复用功能低位寄存器

nAFRL复用功能低位寄存器

AFRH复用功能高位寄存器

AFRH复用功能高位寄存器

复用功能映射配置

复用功能映射配置1)复用功能映射配置2

复用功能映射配置3

端口复用为复用功能配置过程

-以PA9,PA10配置为串口1为例

① 首先,我们要使用 IO 复用功能,必须先打开对应的 IO 时钟和复用功能外设时钟,这里 我们使用了 GPIOA 以及 USART1,所以我们需要使能 GPIOA 和 USART1 时钟。方法如下:

1
2
__HAL_RCC_GPIOA_CLK_ENABLE();   //使能 GPIOA 时钟 
__HAL_RCC_USART1_CLK_ENABLE(); //使能 USART1 时钟

② 其次,我们在 GIPOx_MODER 寄存器中将所需 IO(对于串口 1 是 PA9,PA10)配置为复用 功能(ADC 和 DAC 设置为模拟通道)。

④ 最后,我们需要配置 GPIOx_AFRL 或者 GPIOx_AFRH 寄存器,将 IO 连接到所需的 AFx。 对于 PA9,PA10 复用为 USART1 的发送接收引脚,可知都需要连接 AF7。 上面三步,在我们 HAL 库中是通过 HAL_GPIO_Init 函数来实现的,参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟

GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为USART1 复用功能!!!
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9

GPIO_Initure.Pin=GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10

对于 GPIO 初始化结构体成员变量 Alternate 的取值范围,在 HAL 库中有详细定义,取值范围如下

1
2
3
#define IS_GPIO_AF(AF)
(((AF) == GPIO_AF0_RTC_50Hz)||((AF) == GPIO_AF9_TIM14) || \ ((AF) == GPIO_AF0_MCO) || ((AF) == GPIO_AF0_TAMPER) || \ ((AF) == GPIO_AF0_SWJ) || ((AF) == GPIO_AF0_TRACE) || \ ((AF) == GPIO_AF1_TIM1)|| ((AF) == GPIO_AF1_TIM2) || \ ...//此处省略部分代码
((AF) == GPIO_AF8_UART7)|| ((AF) == GPIO_AF8_UART8) || \ ((AF) == GPIO_AF12_FMC) || ((AF) == GPIO_AF6_SAI1) || \ ((AF) == GPIO_AF14_LTDC))
文章作者: jiangzuojiben
文章链接: http://jiangzuojiben.github.io/2019/11/01/SysTick%E5%AE%9A%E6%97%B6%E5%99%A8%EF%BC%8CNVIC%E4%B8%AD%E6%96%AD%E4%BC%98%E5%85%88%E7%BA%A7%E5%92%8CIO%E5%BC%95%E8%84%9A%E5%A4%8D%E7%94%A8/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 jiangzuojiben