本文会介绍学习stm32中断原理和编程方法,并使用stm32tubemx
和HAL
库分别完成实操中断点灯、串口通信
一、中断介绍
1.中断概念
中断通常被定义为一个事件,该事件能够改变处理器执行指令的顺序。这样的事件与 CPU 芯片内外部硬件电路产生的电信号相对应。
中断分为同步中断和异步中断。
同步中断——同步中断是当指令执行时由 控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后 CPU 才会发出中断
异步中断——异步中 断是由其他硬件设备依照 CPU 时钟信号随机 产生的。
通常我们所说的中断指的是异步中断,我们将同步中断称为异常。(异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的)
2.中断全过程
3.中断的作用
跟据中断的定义,我们可以通过中断 使处理器转而去优先运行正常控制流之外的代 码。
当 某个中断信号达到肘, CPU 必须停止它当前正在傲的事情,并且切换到一个另一个 活动。为了做到这 ~-就要在内核态堆钱保存程序计数器的当前值 (ftn eip cs 存器的内容) ,并把与中断类型相关的 个地址放进程序计数量.
4.中断优先级
多个中断同时出现时,处理器先响应高优先级的中断
低优先级中断的ISR执行时,可以被高优先级中断再次打断
ISR比App Code拥有更高的执行优先级
二、通过STM32和CubeMX外部中断模式点灯
1.实验要求
用stm32F103
核心板的GPIOA
端一管脚接一个LED,GPIOB
端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。
2.通过STMCube配置项目
(1)新建项目
进入页面,新建一个项目
选择芯片为STM32F103C8
(2)外设设置
这里我设置指示灯LED引脚PB5,设置引脚模式为输出模式GPIO_Output
设置按键引脚PA1,设置引脚为外部中断功能,PA1与外部中断线EXIT1连接GPIO_EXIT1
设置LED对应的PB5
管脚,名字设为LED,其余保持默认设置
对于开关对应管脚PA1,设置其触发方式为上升沿触发
External Interrupt Mode with Rising edge trigger detection
上升沿
External Interrupt Mode with Falling edge trigger detection
下降沿
External Interrupt Mode with Rising/Falling edge trigger detection
上升沿和下降沿
使能对应的外部中断线,点击Enabled
配置中断优先级时钟设置
这里设置了36M
生成工程文件,设置文件名称,把Toolchain/IDE
修改为MDK-ARM
工程文件生成完成。
(3)在keil中设置代码
在keil中打开工程文件在Keil文件中的gpio.c文件可以找到中断服务函数
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
当捕获到上升沿,触发中断,就会进入到这个函数里面
然后就会执行HAL_GPIO_EXTI_Callback(GPIO_Pin
)函数,此函数为回调函数,我们打开可以发现前面有个weak
。
前面的 __weak
表示此函数为虚函数,需要用户重写的。
那么我们在main.c文件中找个地方重新写一下。
位置在main函数下方。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if( GPIO_Pin == A1_EXTI_Pin)//判断外部中断源
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);//翻转LED状态
}
}
编译完成,记得勾选生成hex
文件
3.连接烧录
(1)硬件连接
① 烧录连接
② 电路连接
GPIO默认是3.3V高电平,当按下按键,GPIO变为低电平,此过程是下降沿。松开按键,GPIO又变为高电平,此过程为上升沿。因为设置的是上升沿中断,也就是松开按键的时候,灯会变化。
LED引脚配置,默认设为高电平,处于熄灭状态。
由于手头没有开关,我直接用杜邦线替代
LED长脚——3V3
LED短脚——PB5
PA1——3V3——亮灯
PA1——GND——熄灯
(2)程序烧录
我使用FlyMcu对程序进行烧录
打开生成的.hex文件
读取器件信息成功
然后点击开始编程
程序烧录完成
4.运行效果
三、中断方式串口通信
1.实验要求
采用串口中断方式重做上次的串口通信作业,分别实现:
1)当stm32接收到字符“
s”
时,停止持续发送“hello windows!”
; 当接收到字符“t”
时,持续发送“hello windows!”
(提示:采用一个全局标量做信号灯);
2)当stm32接收到字符“stop stm32!”
时,停止持续发送“hello windows!”
; 当接收到字符“go stm32!”
时,持续发送“hello windows!”
2.stm32接收到字符“
s”
和“t”
(1)通过STMCube配置项目
① 新建项目
进入页面,新建一个项目
选择芯片为STM32F103C8
② 工程设置
设置RCC
设置高速外部时钟HSE,选择外部时钟源
设置SYS,为
Senial Wire
设置USART为Asynchronous
设置NVIC,使用USART global interrupt
创建项目
设置项目名称
修改为MDK-ARM
勾选Generate periphreal.... ...
③ 在keil中设置代码
1.打开通过CubeMX生成的项目
2.在main函数前定义全局变量
char c;//指令 0:停止 1:开始
char message[]="hello Windows\n";//输出信息
char tips[]="CommandError\n";//提示1
char tips1[]="Start.....\n";//提示2
char tips2[]="Stop......\n";//提示3
int flag=0;//标志 0:停止发送 1.开始发送
3.在main函数中设置接收中断
函数说明:
- 函数原型
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
- 功能
功能:串口中断接收,以中断方式接收指定长度数据。
大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。
接收到数据时,会触发串口中断。
再然后,串口中断函数处理,直到接收到指定长度数据
而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)
- 参数
UART_HandleTypeDef *huart UATR的别名
huart1 *pData 接收到的数据存放地址
Size 接收的字节数
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
4.main函数中的while循环里面添加传输代码
if(flag==1){
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
}
5…在main函数下面重写中断处理函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为s时,发送提示并改变flag
if(c=='s'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为t时,发送提示并改变flag
else if(c=='t'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
//当输入不存在指令时,发送提示并改变flag
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
//重新设置中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
6.main函数全部代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>
void SystemClock_Config(void);
char c;//指令 0:停止 1:开始
char message[]="hello Windows\n";//输出信息
char tips[]="CommandError\n";//提示1
char tips1[]="Start.....\n";//提示2
char tips2[]="Stop......\n";//提示3
int flag=0;//标志 0:停止发送 1.开始发送
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
//设置接受中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
//当flag为1时,每秒发送一次信息
//当flag为0时,停止
while (1)
{
if(flag==1){
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为s时,发送提示并改变flag
if(c=='s'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为t时,发送提示并改变flag
else if(c=='t'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
//当输入不存在指令时,发送提示并改变flag
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
//重新设置中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
/* USER CODE END 4 */
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
如图所示:
编译成功,并生成hex文件
(2)连接烧录
① 电路连接
硬件连接只需将stm32芯片与转换器连接后插入电脑,方式见上
② 程序烧录
我使用FlyMcu对程序进行烧录
打开生成的.hex文件
(3)运行效果
运行成功!
3.stm32接收到字符“stop stm32!”
和“go stm32!”
接收到字符“stop stm32!”
和“go stm32!”
与接收到字符“s”
和“t”
的区别在于要将接收到的连续字符保存到一个字符数组里,进行判别匹配。
所以要写一个接收字符串的函数
所以只需要修改keil中的代码,其余步骤与接收字符“s”
和“t”
相同
(1)在keil中设置代码
① 打开通过CubeMX生成的项目
② 在main函数前定义全局变量
int i=0;int ch;
uint8_t ucTemp[20];//定义了字符数组指令 "go stm32!"开始,"stop stm32!"停止
char message[]="hello Windows\n";//输出信息
char tips[]="CommandError\n";//提示1
char tips1[]="Start.....\n";//提示2
char tips2[]="Stop......\n";//提示3
int flag=0;//标志 0:停止发送 1.开始发送
** ③ 在main函数中设置接收中断**
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
** ④ main函数中的while循环里面添加传输代码**
if(flag==1){
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
}
** ⑤ 在main函数下面重写中断处理函数**
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为“sto stm32!"时,发送提示并改变flag
if(ucTemp[10] == '!'&&ucTemp[9] == '2'&&ucTemp[8] == '3'&&ucTemp[7] == 'm'&&ucTemp[6] == 't'&&ucTemp[5] == 's'
&&ucTemp[4] == ' '&&ucTemp[3] == 'p'&&ucTemp[2] == 'o'&&ucTemp[1] == 't'&&ucTemp[0] == 's'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为"go stm32!"时,发送提示并改变flag
else if(ucTemp[8] == '!'&&ucTemp[7] == '2'&&ucTemp[6] == '3'&&ucTemp[5] == 'm'&&ucTemp[4] == 't'&&ucTemp[3] == 's'
&&ucTemp[2] == ' '&&ucTemp[1] == 'o'&&ucTemp[0] == 'g'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
//当输入不存在指令时,发送提示并改变flag
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
//重新设置中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&ucTemp[20], 1);
}
(2)运行效果
编译烧录连接与之前相同
四、总结
中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据。三种串口通讯方式:查询,中断和DMA。在实际的通讯过程当中,我们可以根据需求进行选择,在传输比较大的数据的时候采用DMA方式,可以解放CPU,让CPU进行别的工作。中断方式是通过中断服务函数不断将数据放到串口进行传送。每一种方式都有优缺点,我们学习完这些方法后要学会在不同场景下进行使用。
五、参考资料
https://blog.csdn.net/qq_46467126/article/details/121076618?spm=1001.2014.3001.5501
https://blog.csdn.net/qq_46467126/article/details/121055475?spm=1001.2014.3001.5501
https://blog.csdn.net/qq_47281915/article/details/121053903
串口通信第九章
https://blog.csdn.net/as480133937/article/details/104827639/