目录
一、知识点
1. 外部中断知识点
中断优先级问题,高级中断会打断低级中断
- 编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器 设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。输 入线一般是存在电平变化的信号。
- 编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿 触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为 信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号 0。而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过 程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
- 编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自 软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER允许我们通过程序控制就可以启动中断 /事件线,这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入 随便一个有有效信号 1 就可以输出 1 给编号 4 和编号 6 电路。
- 编号 4 电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽 寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设 置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0; 如果 EXTI_IMR设置为 1时,最终编号 4电路输出的信号才由编号 3电路的输出信号决定, 这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号 会被保存到挂起寄存器(EXTI_PR)内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应 位置 1。
- 编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
HAL库 GPIO函数库讲解
在正常使用中,除了STM32CubeMX配置之外,我们有时候还需要自己配置一些东西,学习并理解HAL库,也是我们必须要学习的一个地方
首先打开stm32f4xx_hal_gpio.h 发现一共定义有8个函数
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
功能: GPIO初始化
实例:HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);
功能:在函数初始化之后的引脚恢复成默认的状态,即各个寄存器复位时的值
实例:HAL_GPIO_Init(GPIOC, GPIO_PIN_4);
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:读取引脚的电平状态、函数返回值为0或1
实例:HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4);
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
功能:引脚写0或1
实例:HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4,0);
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:翻转引脚的电平状态
实例:HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_4); 常用在LED上
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:锁住引脚电平,比如说一个管脚的当前状态是1,当这个管脚电平变化时保持锁定时的值。
实例:HAL_GPIO_LockPin(GPIOC, GPIO_PIN_4);
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);
功能: 外部中断服务函数,清除中断标志位
实例:HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
功能: 中断回调函数,可以理解为中断函数具体要响应的动作。
实例:HAL_GPIO_EXTI_Callback(GPIO_PIN_4);
1.2串口通信
基础设置可参考
基于stm32CubeMX(Hal库)的stm32串口通信_Laul Ken-Yi的博客-CSDN博客
HAL库UART函数库介绍
1.2.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_Receive_IT(&huart1,(uint8_t *)&value,1); //中断接收一个字符,存储到value中
中断接收数据函数格式:
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能:串口中断接收,以中断方式接收指定长度数据。
大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。
再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData 接收到的数据存放地址
Size 接收的字节数
举例: HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 }
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.3 串口DMA
DMA定义:
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
DMA传输方式:
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
- 外设到内存 Peripheral To Memory
- 内存到外设 Memory To Peripheral
- 内存到内存 Memory To Memory
- 外设到外设 Peripheral To Peripheral
- DMA传输时外设对DMA控制器发出请求。
- DMA控制器收到请求,触发DMA工作。
- DMA控制器从AHB外设获取ADC采集的数据,存储到DMA通道中
- DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC采集的数据经由DMA通道存放到SRAM中,这个数据的传输过程中,完全不需要内核的参与,也就是不需要CPU的参与
串口DMA发送数据:
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能:串口通过DMA发送指定长度的数据。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData 需要发送的数据
Size 发送的字节数
举例:
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff)); //串口发送Senbuff数组
串口DMA接收数据:
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能:串口通过DMA接受指定长度的数据。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
*pData 需要存放接收数据的数组
Size 接受的字节数
举例:
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Recbuff, sizeof(Recbuff)); //串口发送Senbuff数组
串口DMA恢复函数
HAL_UART_DMAResume(&huart1)
作用: 恢复DMA的传输
返回值: 0 正在恢复 1 完成DMA恢复
二、外部中断,实现LED亮熄
基础设置可参考基于stm32CubeMX(Hal库)的stm32串口通信_Laul Ken-Yi的博客-CSDN博客
以下为不同点
1. 创建工程
1.1 选择芯片
1.2 设置led,PA6引脚和外部中断EXTI1,PB1
1.3 给PA6,PB1取名,则在程序中可快速引用和查看。
1.4 给中断设置下降沿触发,并且上拉电阻。
如果不设置上拉电阻或下拉电阻,led会不停的闪烁,但也可以实现中断。
1.5 配置中断优先级,因为此处只有一个中断,因此它的优先级为0,为最高,如果有多个中断则可设为0,1,2,3。
1.6 配置时钟,设时钟频率为72MHZ
然后自动生成程序。
2. 编译与写程序
可以看到生成的中断服务函数 void EXTI1_IRQHandler(void)
可以看到该函数又调用了HAL_GPIO_EXTI_IRQHandler(),于是继续跳转到下一函数
可以看到在这之中调用了HAL_GPIO_EXTI_Callback(),接下来的函数可以看到是_weak开头,则需要用户自己写函数。
此时到main.c中书写callback程序,用到的库函数是HAL_GPIO_TogglePin(),该函数的作用是翻转电平,即中断一产生,则翻转一次电平。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
{HAL_GPIO_TogglePin(led1_GPIO_Port,led1_Pin);
}
}
接着进行编译。
3. 烧录
烧完后记得在通电下将boot0置0,然后再次reset后可运行。
4.实验结果
此处因为stm32c8t6没有按键,因此选择使用杜邦线代替开关,每接触一次接地,则产生一次中断。
如果遇到多次接触后再响应,可能是因为面包板接触不良的问题。
三、串口中断实现串口通信
1.创建工程
RCC和SYS和CLOCK设置如上面一样
下面设置串口USART1,在MODE下选择Asynchronous(异步通信模式),并且使得USART1中断使能
接着就可以直接生成工程了。
2.重定向printf和scanf
之后可以直接使用重定向的函数
- 在 stm32f1xx_hal.c中包含#include <stdio.h>
#include <stdio.h>
extern UART_HandleTypeDef huart1; //声明串口
- 在 stm32f1xx_hal.c 中重写fget和fput函数
/**
* 函数功能: 重定向c库函数printf到DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/**
* 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
- 在main.c中添加
#define RXBUFFERSIZE 256
char RxBuffer[RXBUFFERSIZE];
printf("hello world\n");
HAL_Delay(1000);
在target勾选Use MicroLIB,需要调用微型库
接着就可以编译加烧录
3. 运行结果
4. UART接收中断
4.1 原理:
具体流程:
- 初始化串口
- 在main中第一次调用接收中断函数
- 进入接收中断,接收完数据 进入中断回调函数
- 修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,
- 回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中
4.2 代码实现:
并在main.c中添加下列定义:
#include <string.h>
#define RXBUFFERSIZE 256 //最大接收字节数
char RxBuffer[RXBUFFERSIZE]; //接收数据
在main()主函数中,调用一次接收中断函数
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, RxBuffer, 1);
/* USER CODE END 2 */
在main.c下方添加中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)
{
HAL_UART_Transmit(&huart1,RxBuffer,1,0xff);
}
HAL_UART_Receive_IT(&huart1, RxBuffer, 1);
}
4.3 实验成果
此处需要使用XCOM调试助手,之前的sscom在发送时有一点问题
下载链接:
链接:https://pan.baidu.com/s/16rmNViVvAvLx3diG1G-xiw
提取码:qwer
可以看到串口回显。
四、串口DMA接收发数据
1.创建工程
其余设置参考上面串口设置
以下为不同点
进入DMA Settings
点击add,选择USART_RX USART_TX 传输速率设置为中速
DMA传输模式为正常模式
DMA内存地址自增,每次增加一个Byte(字节)
右侧点击System Core 点击DMA
点击add添加MENTOMEN
Normal:正常模式
当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
DMA指针递增设置
Increment Address:地址指针递增(上方有介绍)。
左侧Src Memory 表示外设地址寄存器
功能:设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,
右侧Dst Memory 表示内存地址寄存器
功能:设置传输数据时候内存地址是否递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,
这个Src Memory一样,只不过针对的是内存。
然后即可生成工程
2. 测试例程1
在main.C中添加:
/* USER CODE BEGIN Init */
uint8_t Senbuff[] = "HELLO WORLD"; //定义数据发送数组
/* USER CODE END Init */
while循环:
while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart1, Senbuff, sizeof(Senbuff));
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
3. 测试结果
4. 测试例程2
STM32 IDLE 接收空闲中断
STM32的IDLE的中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断
代码:
uart.c
volatile uint8_t rx_len = 0; //接收一帧数据的长度
volatile uint8_t recv_end_flag = 0; //一帧数据接收完成标志
uint8_t rx_buffer[100]={0}; //接收数据缓存数组
/* USER CODE BEGIN USART1_Init 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
//DMA接收函数,此句一定要加,不加接收不到第一次传进来的实数据,是空的,且此时接收到的数据长度为缓存器的数据长度
HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);
/* USER CODE END USART1_Init 2 */
uart.h
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
/* USER CODE BEGIN Private defines */
#define BUFFER_SIZE 100
extern volatile uint8_t rx_len ; //接收一帧数据的长度
extern volatile uint8_t recv_end_flag; //一帧数据接收完成标志
extern uint8_t rx_buffer[100]; //接收数据缓存数组
工程里没有.h文件,需要在文件夹里寻找
main.c
/*
*********************************************************************************************************
* 函 数 名: DMA_Usart_Send
* 功能说明: 串口发送功能函数
* 形 参: buf,len
* 返 回 值: 无
*********************************************************************************************************
*/
void DMA_Usart_Send(uint8_t *buf,uint8_t len)//串口发送封装
{
if(HAL_UART_Transmit_DMA(&huart1, buf,len)!= HAL_OK) //判断是否发送正常,如果出现异常则进入异常中断函数
{
Error_Handler();
}
}
/*
*********************************************************************************************************
* 函 数 名: DMA_Usart1_Read
* 功能说明: 串口接收功能函数
* 形 参: Data,len
* 返 回 值: 无
*********************************************************************************************************
*/
void DMA_Usart1_Read(uint8_t *Data,uint8_t len)//串口接收封装
{
HAL_UART_Receive_DMA(&huart1,Data,len);//重新打开DMA接收
}
这里需要定义在main函数前面,否则在main函数里调用会报错,也可以在main之前先声明函数。
while循环
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(recv_end_flag == 1) //接收完成标志
{
DMA_Usart_Send(rx_buffer, rx_len);
rx_len = 0;//清除计数
recv_end_flag = 0;//清除接收结束标志位
// for(uint8_t i=0;i<rx_len;i++)
// {
// rx_buffer[i]=0;//清接收缓存
// }
memset(rx_buffer,0,rx_len);
}
HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}
stm32f1xx_it.c中
#include "usart.h"
void USART1_IRQHandler(void)
{
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
//temp = huart1.Instance->SR; //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
//temp = huart1.Instance->DR; //读取数据寄存器中的数据
//这两句和上面那句等效
HAL_UART_DMAStop(&huart1); //
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
//temp = hdma_usart1_rx.Instance->NDTR;//读取NDTR寄存器 获取DMA中未传输的数据个数,
//这句和上面那句等效
rx_len = BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
recv_end_flag = 1; // 接受完成标志位置1
}
HAL_UART_IRQHandler(&huart1);
}
5. 测试程序
五、心得
从延时的方式转换到了中断与串口DMA通信,可以提高程序的运行效率,从而不浪费芯片的内存,DMA可以直接访问存储器,而直接绕过CPU,为cpu减负,熟悉了串口通信的基本函数和DMA通信的基本函数,不过还有许多需要学习的,希望越来越会吧。
参考文献:
【STM32】HAL库 STM32CubeMX教程三----外部中断(HAL库GPIO讲解)_Z小旋-CSDN博客
【STM32】HAL库 STM32CubeMX教程四---UART串口通信详解_Z小旋-CSDN博客_hal_uart_transmit