本文介绍学习stm32中断原理和编程方法,实操中断点灯、串口通信。
一、题目要求
学习stm32中断原理和编程方法。使用stm32tubemx和HAL库完成以下练习:
-
用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。
-
采用串口中断方式重做的串口通信。
二、中断介绍
什么是中断
中断通常被定义为一个事件,该事件能够改变处理器执行指令的顺序。这样的事件与 CPU 芯片内外部硬件电路产生的电信号相对应。
中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。
中断分为同步中断和异步中断。
- 同步中断——同步中断是当指令执行时由 控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后 CPU 才会发出中断
- 异步中断——异步中断是由其他硬件设备依照 CPU 时钟信号随机产生的。
通常我们所说的中断指的是异步中断,我们将同步中断称为异常。(异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的)
应用程序不必关心中断的发生与处理
中断服务程序不必关心应用程序的执行状态
中断是“上层应用”与“底层代码”的“分割边界”
中断的作用
跟据中断的定义,我们可以通过中断使处理器转而去优先运行正常控制流之外的代码。
当中断信号达到肘, CPU 必须停止它当前正在做的事情,并且切换到一个另一个活动。为了做到这就要在内核态堆钱保存程序计数器的当前值 (寄存器的内容) ,并把与中断类型相关的地址放进程序计数量。
中断处理是指CPU响应中断,转入中断处理程序,系统开始处理中断。
中断响应是指CPU收到中断请求后转向相应的事件处理程序。
开中断后,系统就可以响应其他的中断了,关中断后,系统不响应其他的中断除非优先级高的中断。
中断屏蔽是指在中断请求产生后,系统用软件方式有选择地封锁部分中断而允许其余部分中断仍能得到响应。
中断的类型及优先级
中断的类型
- 硬中断:通过处理器中断信号线产生的中断
- 软中断:通过非法指令或特殊指令触发的中断
中断优先级
- 多个中断同时出现时,处理器先响应高优先级的中断
- 低优先级中断的ISR执行时,可以被高优先级中断再次打断
- ISR比App Code拥有更高的执行优先级
三、CubeMX中断方式点灯
1、题目分析
用stm32F103核心板的GPIOA
端一管脚接一个LED
,GPIOB
端口一引脚接一个开关
(用杜邦线模拟代替)。采用中断模式
编程,当开关接高电平时,LED亮灯
;接低电平时,LED灭灯
。
这里我选用的核心板为STM32F103C8T6
最小核心板
设置PA1
端接开关,PB5
接LED
对于按键电路,设置上拉式按键
- 按键按下,引脚PA1读到低电平
- 按键释放,引脚PA1读到高电平
LED灯的触发方式
- 按键按下瞬间,形成下降沿
- 按键释放瞬间,形成上升沿
因此这里设置上升沿触发
,即松开按键时灯亮,因为题目要求为开关接高高电平亮灯。
2、CubeMX工程设置
新建操作不再赘述,来到管脚定义界面
外设设置
设置指示灯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
上升沿和下降沿
User Label
处设置名字为 A1_EXTI
使能对应的外部中断线,点击Enabled
配置中断优先级
(大多数情况下不必设置中断优先级,直接使用中断编号设置的默认中断优先级)
时钟设置
这里设置了36M
之后生成工程文件即可
3、代码撰写
在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状态
}
}
编译一下,没有错
将代码烧录到C8T6核心板中
4、电路连接
GPIO默认是3.3V高电平,当按下按键,GPIO变为低电平,此过程是下降沿。松开按键,GPIO又变为高电平,此过程为上升沿。因为设置的是上升沿中断,也就是松开按键的时候,灯会变化。
LED引脚配置,默认设为高电平,处于熄灭状态。
由于手头没有开关,我直接用杜邦线替代
LED长脚——3V3
LED短脚——PB5
PA1——3V3——亮灯
PA1——GND——熄灯
由于C8T6直接接到面包板时,点灯发现接触不良,因此采取下策,用杜邦线两次连接管脚(不推荐)
四、中断方式串口通信
1、题目要求
完成一个STM32的USART串口通讯程序(查询方式即可,暂不要求采用中断方式),要求:
1)设置波特率为115200,1位停止位,无校验位;
2)STM32系统给上位机(win10)连续发送“hello windows!”。win10采用“串口助手”工具接收。
根据题目,我设置,串口自动发送Hello windows!,如果接收到其他数据,则返回其他数据,之后继续发送Hello windows
2、工程设置
设置RCC
设置高速外部时钟HSE,选择外部时钟源
设置串口
1)点击USART1
2)设置MODE
为异步通信
3)基础参数:波特率为115200 Bits/s
。传输数据长度为8 Bit
。奇偶检验无,停止位1,接收和发送都使能
4)GPIO引脚设置 USART1_RX/USART_TX
(这里一般自动设置好了)
5) NVIC Settings 一栏使能接收中断
时钟设置
之后导出Keil文件并打开。
3、代码撰写
printf函数设置
在main.c和usart.c中添加头文件#include "stdio.h"
之后,在usart.c文件中,添加如下代码,进行重定义
/* USER CODE BEGIN 1 */
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#if 1
//#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0x0001);
return ch;
}
#endif
/* USER CODE END 1 */
在main.c主函数中,添加发送数据
/* USER CODE END WHILE */
printf("Hello windows!\r\n");
HAL_Delay(500);
/* USER CODE BEGIN 3 */
在main.c中添加如下定义,用来接收串口数据
uint8_t aRxBuffer; //接收中断缓冲
uint8_t Uart1_RxBuff[256]; //接收缓冲
uint8_t Uart1_Rx_Cnt = 0; //接收缓冲计数
uint8_t cAlmStr[] = "数据溢出(大于256)\r\n";
添加开启接收中断的语句
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
/* USER CODE END 2 */
在main.c下部添加中断回调函数
/* USER CODE BEGIN 4 */
/**
* @brief Rx Transfer completed callbacks.
* @param huart pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
if(Uart1_Rx_Cnt >= 255) //溢出判断
{
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff));
HAL_UART_Transmit(&huart1, (uint8_t *)&cAlmStr, sizeof(cAlmStr),0xFFFF);
}
else
{
Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer; //接收数据转存
if((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
{
HAL_UART_Transmit(&huart1, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //清空数组
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
}
/* USER CODE END 4 */
编译后,不报错,烧录即可
注意烧录是boot0接1
4、串口助手通信
进行通信,boot0接0
利用XCOM进行通信
串口每隔0.5s输出Hello windows
当下方发送数据,例如good,串口会进入中断,发送good,之后回到原循环,继续发送Hello windows。
五、HAL库UART函数库介绍
UART结构体定义
UART_HandleTypeDef huart1;
1、串口发送/接收函数
HAL_UART_Transmit();串口发送数据,使用超时管理机制
HAL_UART_Receive();串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT();串口中断模式发送
HAL_UART_Receive_IT();串口中断模式接收
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收
说明:
串口发送数据
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
功能:串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)
参数:
- UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef
huart1; 别名就是huart1 - *pData 需要发送的数据
- Size 发送的字节数
- Timeout 最大发送时间,发送数据超过该时间退出发送
示例:
HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);
//串口发送三个字节数据,最大传输时间0xffff
2、串口中断函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)
HAL_UART_ErrorCallback();串口接收错误函数
串口接收中断回调函数
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
功能:HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。
参数:
- UART_HandleTypeDef *huart UATR的别名
- 如 : UART_HandleTypeDef huart1; 别名就是huart1
举例:
HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 }
串口中断处理函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
功能:对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用
如果接收数据,则会进行接收中断处理函数
/* UART in mode Receiver
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
UART_Receive_IT(huart);
}
如果发送数据,则会进行发送中断处理函数
/* UART in mode Transmitter
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
3、串口查询函数
HAL_UART_GetState(); 判断UART的接收是否结束,或者发送数据是否忙碌
举例
while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)
//检测UART发送结束
六、总结
中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据。
通过本次学习,了解了中断的相关知识,并实操了点灯和串口通信。
受益匪浅!
参考
[1] https://blog.youkuaiyun.com/qq_20233867/article/details/72236576
[2] https://blog.youkuaiyun.com/qq_44918248/article/details/114579472
[3] https://blog.youkuaiyun.com/as480133937/article/details/99073783