串口通信-中断方式
1 中断方式的串口通信
串口中断方式的特点:
发送数据时,将一字节数据放入数据寄存器DR;接收数据时,将DR的内容存放到用户存储区;
中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据;
在传输数据量较大,且通信波特率较高(大于38400)时,如果采用中断方式,每收发一个字节的数据,CPU都会被打断,造成CPU无法处理其他事务。因此在批量数据传输,通信波特率较高时,建议采用DMA方式。
-
串口中断方式发送函数:HAL_UART_Transmit_IT
函数原型 HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size) 功能描述 在中断方式下发送一定数量的数据 入口参数1 huart:串口句柄的地址 入口参数 pData:待发送数据的首地址 入口参数3 Size:待发送数据的个数 入口参数4 Timeout:超时等待时间, 以ms为单位, HAL MAX DELAY表示无限等待 返回值 HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用; 注意事项 1. 函数将使能串口发送中断2. 函数将置位TXEIE和TCIE,使能发送数据寄存器空中断和发送完成中断。完成指定数量的数据发送后,将会关闭发送中断,即清零TXEIE和TCIE。因此用户采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启发送中断3. 当指定数量的数据发送完成后,将调用发送中断回调函数HAL_UART_TxCpltCallback进行后续处理4. 该函数由用户调用 -
串口中断方式接收函数:HAL_UART_Receive_IT
函数原型 HAL_StatusTypeDef HAL_UART_Receive_IT(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size, uint 32_t Timeout) 功能描述 在中断方式下接收一定数量的数据 入口参数1 huart:串口句柄的地址 入口参数2 pData:存放数据的首地址 入口参数3 Size:待接收数据的个数 入口参数4 Timeout:超时等待时间, 以ms为单位, HAL MAX DELAY表示无限等待 返回值 HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用; 注意事项 1. 函数将使能串口接收中断2. 函数将置位RXNEIE,使能接收数据寄存器非空中断RXNE。完成指定数量的数据接收后,将会关闭接收中断,即清零RXNEIE。因此用户采用中断方式连续接收数据时,要重复调用该函数,以重新开启接收中断3. 当指定数量的数据接收完成后,将调用接收中断回调函数HAL_UART_RxCpltCallback进行后续处理4. 该函数由用户调用 -
串口中断通用处理函数:HAL_UART_IRQHandler
函数原型 void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) 功能描述 作为所有串口中断发生后的通用处理函数 入口参数 htim:定时器句柄的地址 返回值 无 注意事项 1. 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成对应的中断处理2. 该函数由CubeMX自动生成 -
串口发送中断回调函数:HAL_UART_TxCpltCallback
函数原型 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) 功能描述 回调函数,用于处理所有串口的发送中断,用户在该函数内编写实际的任务处理程序 入口参数 htim:定时器句柄的地址 返回值 无 注意事项 1. 函数由串口中断通用处理函数HAL_UART_IRQHandler调用,完成所有注意事项2.串口的发送中断任务处理函数内部需要根据串口句柄的实例来判断是哪一个串口产生的发送中断3. 函数由用户根据具体的处理任务编写 -
串口接收中断回调函数:HAL_UART_RxCpltCallback
函数原型 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 功能描述 回调函数,用于处理所有串口的接收中断,用户在该函数内编写实际的任务处理程序 入口参数 htim:定时器句柄的地址 返回值 无 注意事项 1. 函数由串口中断通用处理函数HAL_UART_IRQHandler调用,完成所有注意事项2.串口的发送中断任务处理函数内部需要根据串口句柄的实例来判断是哪一个串口产生的接收中断3. 函数由用户根据具体的处理任务编写 -
串口中断使能函数:__HAL_UART_ENABLE_IT
函数原型 __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) 功能描述 使能对应的串口中断类型 入口参数 __INTERRUPT __ :串口中断类型,该参数几个常用的取值如下UART_IT_TXE :发送数据寄存器空中断UART_IT_TC :发送完成中断UART_IT_RXNE:接收数据寄存器非空中断UART_IT_IDLE :线路空闲中断 返回值 无 注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 函数需要由用户调用,用于使能对应的串口中断类型 -
串口中断标志查询函数:__HAL_UART_GET_FLAG
函数原型 __HAL_UART_GET_FLAG (__HANDLE__, __INTERRUPT__) 功能描述 查询对应的串口中断类型 入口参数 __INTERRUPT __ :串口中断类型,该参数几个常用的取值如下UART_IT_TXE :发送数据寄存器空中断UART_IT_TC :发送完成中断UART_IT_RXNE:接收数据寄存器非空中断UART_IT_IDLE :线路空闲中断 返回值 中断标志的状态值:SET表示中断标志置位;RESET表示中断标志没有置位 注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 函数需要由用户调用,用于查询对应的串口中断类型 -
空闲中断标志清除函数:__HAL_UART_CLEAR_IDLEFLAG
函数原型 __HAL_UART_CLEAR_IDLEFLAG 功能描述 清除串口的空闲中断标志 入口参数 HANDLE :串口句柄的地址 返回值 无 注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 函数需要由用户调用,用于清除对应的串口空闲中断标志
2 HAL库串口中断处理过程:
HAL_UART_Receive_IT:开启中断,在中断方式下接收一定数量的数据。
USART2_IRQHandler:串口2的中断服务程序,调用串口中断通用处理函数HAL_UART_IRQHandler。
HAL_UART_IRQHandler:在函数 HAL_UART_IRQHandler内部通过判断中断类型是否为接收完成中断,确定是否调用UART_Receive_IT。
函数UART_Receive_IT的作用是把每次中断接收到的字符保存在串口句柄的缓存指针pRxBuffPtr中,同时每次接收一个字符,其计数器 RxXferCount 减 1,直到接收完成 RxXferSize 个字符之后 RxXferCount设置为0,同时调用接收完成回调函数 HAL_UART_RxCpltCallback进行处理。
HAL_UART_RxCpltCallback:函数由串口中断通用处理函数UART_Receive_IT调用,完成所有串口的接收中断任务处理,函数内部需要根据串口句柄的实例来判断是哪一个串口产生的接收中断,函数由用户根据具体的处理任务编写。
3 任务实践2
利用串口调试助手,从PC上发送10个字符到开发板,开发板收到后原样发回到PC。
前后台编程模式: 前台程序为中断服务程序,一旦数据接收完成,则设置一个标志位为1;后台程序为while(1)的死循环,在循环中不断检测标志位是否为1。如果为1,表明数据接收完成,并存放在接收缓冲区中。然后进行后续处理:先清除标志位,再把接收的数据原样发回。
串口外设配置
异步模式,无硬件流控
设置通信参数:波特率115200,8位数据位,无奇偶校验,1位停止位,使能接收和发送,16倍过采样(CubeMX默认配置)
使能串口中断
编写代码
printf和scanf重定向:略
// -----------------------------------------------------------------------//
/* USER CODE BEGIN PD */
#define LENGTH 10 // 接收数据缓冲区大小
/* USER CODE END PD */
// -----------------------------------------------------------------------//
/* USER CODE BEGIN PV */
uint8_t RxBuffer[LENGTH]; // 接收缓冲区
uint8_t RxFlag = 0; // 接收完成标志,0未完成,1完成
/* USER CODE END PV */
// -----------------------------------------------------------------------//
/* USER CODE BEGIN 2 */
// 打印提示信息
printf("UART Communication Using ITn");
printf("Please enter 10 characters:n");
HAL_UART_Receive_IT(&huart1, (uint8_t *)RxBuffer, LENGTH); // 使能接收中断
/* USER CODE END 2 */
// -----------------------------------------------------------------------//
while (1)
{
/* USER CODE BEGIN 3 */
if (RxFlag == 1) // 判断接收是否完成
{
RxFlag = 0; // 接收完成,清除标志位
printf("Receive Successfully!n"); // 打印提示信息
HAL_UART_Transmit_IT(&huart1, (uint8_t*)RxBuffer, LENGTH); // 将接收的字符原样发回
}
}
/* USER CODE END 3 */
// -----------------------------------------------------------------------//
/* USER CODE BEGIN 4 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart- >Instance == USART1) // 判断发生接收中断的串口
{
RxFlag = 1; // 置位接收完成b标志
HAL_UART_Receive_IT(&huart1, (uint8_t*)RxBuffer, LENGTH); // 使能接收中断
}
}
/* USER CODE END 4 */
// -----------------------------------------------------------------------//
-
实验现象
4 任务实践3
实现简单的帧格式通信:PC按照自定义的帧格式发送指令开启或关闭开发板上的LED1。
帧格式的概念:
帧(Frame)是数据传输的一种单位。一帧数据由多个字符组合而成,不同字段的字符代表不同的含义,执行不同的功能;
在实际的工程应用中,数据的传输常常以帧为单位来进行,如工控领域中最常用的Modbus通信协议中的消息帧;
发送方按照规定的帧格式发送一帧数据,接收方接收下这一帧数据后,再按照帧格式进行解析,最后完成后续的处理。
Modbus消息帧格式:
起始符 | 设备地址 | 功能代码 | 数据 | 校验 | 结束符 |
---|---|---|---|---|---|
1个字符 | 2个字符 | 1个字符 | n个字符 | 2个字符 | 1个字符 |
起始符:表示一帧数据的开始
设备地址:用于指定需要进行信息传递的设备
功能代码:用于指定需要完成的操作
数据:表示需要传输的数据
校验:用于通信中的错误校验
结束符:表示一帧数据的结束
自定义的帧格式设定:
帧头 | 设备码 | 功能码 | 帧尾 |
---|---|---|---|
0xaa | 1个字符(8bit) | 1个字符(8bit) | 0x55 |
帧头 :0xaa表示一帧数据的开始
设备码:0x01表示指示灯
功能码:0x00表示关闭指示灯,0x01表示开启指示灯
帧尾 :0x55表示一帧数据的结束
串口配置同任务实践1
配置PA1为GPIO_Output模式
编写代码
// -----------------------------------------------------------------------//
/* USER CODE BEGIN PV */
uint8_t RxBuffer[4]; // 接收缓冲区
uint8_t RxFlag = 0; // 接收完成标志,0位完成,1完成
uint8_t ErrFlag = 0; // 指令错误标志,0正确,1错误
/* USER CODE END PV */
// -----------------------------------------------------------------------//
/* USER CODE BEGIN 2 */
// 打印提示信息
printf("***** Communication Frame *****n");
printf("Please enter instruction:n");
printf("Head- >0xaa, Device- >0x01, Operation- >0x00/0x10, Tail- >0x55n");
HAL_UART_Receive_IT(&huart1, (uint8_t*)RxBuffer, 4); // 使能接收中断
/* USER CODE END 2 */
// -----------------------------------------------------------------------//
while(1)
{
/* USER CODE BEGIN 3 */
// Determine if reception is complete
if (RxFlag == 1) // 判断接收是否完成
{
RxFlag = 0; // 完成,清除标志位
// 帧格式解析
printf("head = RxBuffer[0] = %xn", RxBuffer[0]);
printf("tail = RxBuffer[3] = %xn", RxBuffer[3]);
printf("device = RxBuffer[1] = %xn", RxBuffer[1]);
printf("function = RxBuffer[2] = %xn", RxBuffer[2]);
if (RxBuffer[0] == 0xaa && RxBuffer[3] == 0x55) // 判断帧头帧尾
{
if (RxBuffer[1] == 0x01) // 判断设备码
{
if (RxBuffer[2] == 0x00) // 判断功能码
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
printf("LED1 is close!n");
}
else if (RxBuffer[2] == 0x01) // 判断功能码
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
printf("LED1 is open!n");
}
else // 功能码错误
{
ErrFlag = 3;
}
}
else // 设备码错误
{
ErrFlag = 2;
}
}
else // 帧头帧尾错误
{
ErrFlag = 1;
}
// 发送错误提示信息
switch (ErrFlag)
{
case 1:
printf("Head and tail error! Please send again!n");
break;
case 2:
printf("Device code error! Please send again!n");
break;
case 3:
printf("Function code error! Please send again!n");
break;
default:
break;
}
// 清除接收缓冲区和错误标志,准备下一次接收
ErrFlag = 0;
RxBuffer[0] = 0;
RxBuffer[1] = 0;
RxBuffer[2] = 0;