4. 外部中断
PA0~PF0对应的中断线是EXTI0,……,PA15~PF15对应中断线EXTI15。
1) 使用IO口作为中断输入:
初始化IO口时钟(输入模式)。
开启
SYSCFG
时钟,设置 IO口与中断线的映射关系。
1
2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // 外部中断一定要使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); // 将GPIOA.0与EXTI1中断线连接
- 初始化线上中断,设置触发条件等。
1
2
3
4
5
6
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line4; // 中断线标号,外部中断是EXTI_Line0~EXTI_Line15
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式:EXTI_Mode_Interrupt或者EXTI_Mode_Event
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 触发方式
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断线
EXTI_Init(&EXTI_InitStructure); // 初始化外设EXTI 寄存器
- 配置中断分组(NVIC),并使能中断。
1
2
3
4
5
6
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //响应优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
- 编写中断函数
1
2
3
4
// 判断某个中断线上的中断是否发生(标志位是否置位)
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
// 清除某个中断线的中断标志位
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
2) main.c
:
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
33
34
35
36
37
38
39
40
#include "main.h"
#include "gpio.h"
#include "delay.h"
#include "usart.h"
void EXTI_Init(void) {
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE(); //启GPIOA时钟
GPIO_Initure.Pin = GPIO_PIN_0; //PA0是按键
GPIO_Initure.Mode = GPIO_MODE_IT_RISING; //上升沿触
GPIO_Initure.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
//中断0-PA0
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); //抢占优先级为2,子优先级为0
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线0,PA0中断线是EXTI0
}
void EXTI0_IRQHandler(void) {
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); //调用中断处理公用函数
}
//在HAL库中有的外部中断服务函数都会调用此函数
//GPIO_Pin:中断引脚
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
delay_ms(100); //消抖
// EXTI0可接PA0~PF0,
// 通过switch来判断是哪个引脚发生中断
switch (GPIO_Pin) {
case GPIO_PIN_0: // GPIO_PIN_0对应EXTI0中断线
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
break;
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
delay_init(168);
EXTI_Init();
while (1) {
delay_ms(500);
}
}
3) STM32CubeMX
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "main.h"
#include "gpio.h"
#include "delay.h"
#include "usart.h"
// 使用STM32CubeMX只需要编写此函数就可以了
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
delay_ms(100); //消抖
switch (GPIO_Pin) {
case GPIO_PIN_0:
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
delay_ms(100);
break;
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init(); // 配置LED灯PE9和按键PA0,设置中断
delay_init(168);
while (1) {
delay_ms(500);
}
}
5. 独立看门狗
用于系统重启检测,由内部低速时钟LSI提供驱动,需要导入stm32f4xx_iwdg.c
。
1
2
3
4
5
6
7
IWDG_HandleTypeDef IWDG_Handler; //独立看门狗句柄
IWDG_Handler.Instance=IWDG;
IWDG_Handler.Init.Prescaler=IWDG_PRESCALER_64; // 分频数是64
IWDG_Handler.Init.Reload=500; // 重载值500
HAL_IWDG_Init(&IWDG_Handler); // 喂狗时间:((4*2^Prescaler)*Reload)/32 ms,LSI时钟是32Khz
// 喂狗:
HAL_IWDG_Refresh(&IWDG_Handler); // 如果没有及时喂狗,看门狗会重启程序
窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障,WWDG需要在一个有限的时间窗口中喂狗。
6. 定时器
- 定时器可以来实现PWM,需要导入
stm32f4xx_hal_tim.c
。
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
33
34
35
36
37
TIM_HandleTypeDef TIM3_Handler; //定时器句柄
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz,要看时钟树
//这里使用的是定时器3!(定时器3挂在APB1上,时钟为HCLK/2)
void TIM3_Init(u16 arr,u16 psc) {
TIM3_Handler.Instance=TIM3; //通用定时器3
TIM3_Handler.Init.Prescaler=psc; //分频系数
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器
TIM3_Handler.Init.Period=arr; //自动装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
HAL_TIM_Base_Init(&TIM3_Handler); // 调用HAL_TIM_Base_MspInit
HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE
}
//定时器底册驱动,开启时钟,设置中断优先级
//此函数会被HAL_TIM_Base_Init()函数调用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) {
if(htim->Instance==TIM3) {
__HAL_RCC_TIM3_CLK_ENABLE(); //使能TIM3时钟
HAL_NVIC_SetPriority(TIM3_IRQn,1,3); //设置中断优先级,抢占优先级1,子优先级3
HAL_NVIC_EnableIRQ(TIM3_IRQn); //开启ITM3中断
}
}
//定时器3中断服务函数
void TIM3_IRQHandler(void) {
HAL_TIM_IRQHandler(&TIM3_Handler); // 调用HAL_TIM_PeriodElapsedCallback
}
//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim==(&TIM3_Handler)) {
LED1=!LED1; //LED1反转
}
}
// 在main.c中使用TIM3
TIM3_Init(5000-1,8400-1);
STM32CubeMX
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "main.h"
#include "tim.h"
#include "gpio.h"
static u8 leg_flag = 0;
// 编写TIM3中断函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) {
if (htim->Instance == TIM3) {
if (leg_flag) {
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
}
leg_flag = !leg_flag;
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM3_Init();
HAL_TIM_Base_Start_IT(&htim3); // 手动开启TIM3中断,htim3来自生成的tim.h里
while (1);
}
1
2
3
3. 定时器的`PWM`模式
单个定时器可以输出多路PWM信号,具体看数据手册,以TIM14的1路输出PWM为例:
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
TIM_HandleTypeDef TIM14_Handler; //定时器句柄
TIM_OC_InitTypeDef TIM14_CH1Handler; //定时器14通道1句柄
//TIM14 PWM部分初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void TIM14_PWM_Init(u16 arr, u16 psc) {
TIM14_Handler.Instance = TIM14; //定时器14
TIM14_Handler.Init.Prescaler = psc; //定时器分频
TIM14_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
TIM14_Handler.Init.Period = arr; //自动重装载值
TIM14_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&TIM14_Handler); //初始化PWM
TIM14_CH1Handler.OCMode = TIM_OCMODE_PWM1; //模式选择PWM1
TIM14_CH1Handler.Pulse = arr / 2; //设置比较值,此值用来确定占空比,默认比较值为自动重装载值arr的一半,即占空比为50%
TIM14_CH1Handler.OCPolarity = TIM_OCPOLARITY_LOW; //输出比较极性为低
HAL_TIM_PWM_ConfigChannel(&TIM14_Handler, &TIM14_CH1Handler, TIM_CHANNEL_1);//配置TIM14通道1
HAL_TIM_PWM_Start(&TIM14_Handler, TIM_CHANNEL_1);//开启PWM通道1
}
//定时器底层驱动,时钟使能,引脚配置,将LED灯PF9与定时器TIM14_CH1绑定
//此函数会被HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim) {
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM14_CLK_ENABLE(); //使能定时器14
__HAL_RCC_GPIOF_CLK_ENABLE(); //开启GPIOF时钟
GPIO_Initure.Pin = GPIO_PIN_9; //PF9
GPIO_Initure.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
GPIO_Initure.Speed = GPIO_SPEED_HIGH; //高速
GPIO_Initure.Alternate = GPIO_AF9_TIM14; //PF9复用为TIM14_CH1
HAL_GPIO_Init(GPIOF, &GPIO_Initure);
}
//设置TIM通道4的占空比
//compare:比较值
void TIM_SetTIM14Compare1(u32 compare) {
// 占空比是: (TIM14->CCR1) / 自动重装值
// TIM14->CCR1是TIM14的1路的捕获比较寄存器
TIM14->CCR1 = compare; // 值最大不超过自动重装值
}
int main(void) {
u8 dir = 1;
u16 led0pwmval = 0;
HAL_Init();
SystemClock_Config();
delay_init(168);
// 定时器时钟是84MHz,中断时间:Tout=((arr+1)*(psc+1))/Ft us = (500 * 84) / 84 us = 0.5ms
// 0.5ms定时器TIM14中断一次
// 自动重装值是500,时钟预分频数是84
TIM14_PWM_Init(500 - 1, 84 - 1);
// 呼吸灯
while (1) {
delay_ms(10);
if (dir)led0pwmval++; //dir==1 led0pwmval递增
else led0pwmval--; //dir==0 led0pwmval递减
if (led0pwmval > 200)dir = 0; //led0pwmval到达200后,方向为递减
if (led0pwmval == 0)dir = 1; //led0pwmval递减到0后,方向改为递增
TIM_SetTIM14Compare1(led0pwmval); //修改比较值,修改占空比:led0pwmval/自动重装值
}
}
- 定时器可做输入捕获。
7. 开启硬件浮点功能
STM32F4 属于 Cortex M4F 架构,带有 32 位单精度硬件 FPU,支持浮点指令集,相对于 Cortex M0 和Cortex M3 等,高出数十倍甚至上百倍的运算性能。 STM32F4 硬件上要开启 FPU 是很简单的,通过一个叫协处理器控制寄存器(CPACR)的寄存器设置即可开启 STM32F4 的硬件 FPU,定义全局宏定义标识符 __FPU_PRESENT(处理器是否带FPU功能)以及__FPU_USED(是否启动FPU功能)为 1,那么就可以开启硬件 FPU:
方法1:在头文件 STM32f4xx.h 中定义宏定义标识符__FPU_USED 的值为 1。
方法2:
8. DSP
ST给STM32F4提供一套DSP库: