前言: 关于中断简单介绍
中断的流程:
中断流程图:
中断方式传送数据具有可以有效提高单片机工作效率, 适合于实时控制系统等优点, 相对于查询方式更为常用。
当CPU处理某件事情的时候, 外部发生的某一事件(如电平的改变、脉冲边沿跳变、定时器/计数器溢出等)请求CPU迅速处理, 于是CPU暂时中断当前的工作, 转去处理发生的事件。 处理完该事件后, 再回到原来中断处, 继续工作。 这样的过程称为中断 上图为中断流程图
这个中断的概念是不是有点晦涩难懂? 主要是这个是书上的内容, 所以不是很形象. 我再解释一遍:想象一个场景,
1、当有一天你正在和川建国同志吃饭(你和他吃饭这个事情就是主程序);
2、突然有个电话打给你, 说有个十五亿的小单子需要你去处理一下(这件事就是中断源),
3、 你停止吃饭, 川建国同志在这等你, 你去处理你的十五亿小合同(就是响应中断请求, 签合同过程就是中断服务程序),
4、处理完合同之后你又回到餐桌继续和他就餐(这就是返回主程序, 然后继续执行主程序).
这样解释是不是清晰一点.
关于STM32F407的中断介绍可以看一下原子的或者火哥的pdf, 如果实在看得下去, 看官方手册也行。 这里就不赘述, 通过Cube配置以及编程过程理解这个外部中断会好很多, 通过现象再回去看本质
同样的, 还使用前面两篇博客所用到的工程即可, 也可以自己新建一个, 当做对自己的测试
2-1. 使用核心板自带按键
操作简介
: 通过板子上的两个按钮控制LED灯的亮灭 WK_UP按键按下则进入中断, 并翻转LED0的状态, KEY0按下时翻转LED1的状态. 两者虽然功能一样, 但却有质的区别
这里要做的和按键那一篇一样, 只是把其中一个按键改为中断, 而不是作为GPIO_input 所以可以看完上一篇直接看这一篇继续。 点击下方蓝字可以看上一篇的博客
第一节补充: 按键操作(CubeMX加HAL库学STM32系列)
Step1
RCC&SYS配置这些都不用动, 时钟树的配置也不用动
(1) RCC&SYS以及时钟树配置不用改变
(2)更改一下PA0引脚配置:
把WKUP按键对应的PA0引脚模式由GPIO_input改为GPIO_EXIT0, 再把GPIO的配置更改一下即可 具体操作见下图
注 :
如果是用的原来的工程, 只改这个即可, 其他的LED引脚和按键引脚不用动, 如果是自己又新建了一个工程, 那其他引脚按照前面两篇的介绍配置, 然后这个PA0按照这一篇配置就好了, 问题不大
对应GPIO配置改为下图
(3)中断NVIC配置
我们设置了中断, 在NVIC里面要记得使能PA0引脚的中断
NVIC ( Nested Vectored Interrupt Controller ) : 中断向量控制器
在中断向量表里面使能EXIT line0中断
关于抢占优先级和子优先级: 当你使用多个中断的时候会用到这个。 就是为了防止多个中断冲突, 所以需要给他们每个中断排个号, 就不会乱了。 抢占优先级高的先执行, 若抢占优先级相同, 再看子优先级
(4)以上配置完之后就可以Generate CODE
Step2
<程序编写>
(1) 中断服务函数
stm32f4xx_it.c 这个文件里面看到我们要用的中断服务函数
我们要在中断里面做什么事情, 就要写在中断服务函数里面, 然后中断到来之后, 单片机就回去处理中断服务函数里面的工作
这个函数里面调用了 HAL_GPIO_EXTI_IRQHandler() 这个函数, 这个函数是处理GPIO外部中断的函数 可以看到里面的参数是GPIO_PIN_0, 因为我们用的是PA0即GPIOA的0引脚
Go to definition 一下, 可以看这个函数的定义
/**
* @brief This function handles EXTI interrupt request. // 这个功能是处理外部中断请求
* @param GPIO_Pin Specifies the pins connected EXTI line // GPIO_Pin指定连接EXTI线的引脚
* @retval None // 无返回值
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); // 清除这个引脚的中断标识位
HAL_GPIO_EXTI_Callback(GPIO_Pin); // 回调外部中断
}
}
综上:中断服务函数最终会执行中断回调函数 HAL_GPIO_EXTI_Callback()
(2) 中断回调函数
中断回调函数如下图 这个函数是空的, 所以我们可以自己重构这个函数, 在它内部实现我们要做的功能
我们需要重构中断回调函数
在main.c里面写入我们的代码 :
提示 :
不要忘了把代码写在 /* USER CODE BEGIN
/ /
USER CODE END */ 之间
/* USER CODE BEGIN 0 */
// 重构中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 判断是否为WKUP引脚(即GPIO_PIN_0)进入中断
if (WKUP_Pin == GPIO_Pin)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin); // 翻转LED0的的电平状态
/* 下面这一句话与上面一句是等价的, 因为LED0是我们给这个引脚起的别名, 在main.h文件里面有对应的宏定义 */
//HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
}
}
/* USER CODE END 0 */
(3) 主函数
在主函数里面用KEY0做一个一样的功能, 作为对比
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 在while(1)里面循环扫描, 判断读取的按键引脚状态
// 下面扫描KEY0按键的引脚信号
if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(10); // 延时10ms, 做一个软件的消抖, 防止因抖动而检测到按键按下
if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
{
// 做一个松手检测, 若KEY0一直是RESET(低电平),则一直在死循环
// 当KEY0位SET才会跳出,进而继续执行下面的对 LED1 的操作
while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET);
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
}
}
/* USER CODE END 3 */
(4) 编译下载到单片机, 看看单片机什么反应
左边的红色按键是WKUP, 左边的蓝色LED是LED0
仔细看一下动图中的效果可以发现, 中断的WKUP按键的功能并不是很完美, 这是因为没有消抖导致的, 在中断里面加个软件消抖的程序就可以了。
此外, 虽然两种方式实现的功能是一样的, 但是他们的区别就在于, KEY0翻转LED状态实在while(1)循环里面做的, 这就相当于主函数里面一直循环扫描这个按键的状态, 比较耗费资源.
而WKUP按键按下翻转LED是在中断里面做的,不影响主函数里面做其他事情. 如果以后做的东西要求写很多代码, 最好多多利用中断,这样会更高效。只有当事情来了CPU再去处理,其他时间主函数里面正常做其他事情。 这样既能提高MCU效率, 也不会让自己的代码全部写成一坨在主函数里面
程序编写>
|