串口中断方式实现任意字节的收发操作
本次实验采用STM32F103ZET6主芯片的开发板,使用HAL库开发。
本次实验的目的是实现任意字节数据的收发,实验现象为通过串口发送数据,串口返回发送的数据。
实现原理
在串口的寄存器中有一位是判断串口是否空闲的位,可以利用这个位来判断串口此时的状态,在串口发生中断后,在中断回调函数中实现空闲中断函数,获取函数空闲中断位并判断是否空闲,如果此时已经空闲,表示数据已经发送完成,然后使函数终止,并执行终止回调函数。最后在主函数中实现接收之后再发送。
CobeMX设置
打开串口一(我的这块开发板是串口一和USB转串口连接的,所以我这里选择串口一),然后开启串口一中断即可。
完成后生成代码。
代码实现
首先因为本身HAL库中的函数需要填入收发数据的长度,但是此次实现的是不定长的数据,所以我们可以使用指针来指向数据的起点和终点。我们先在usart.h文件中创建一个结构体变量用来储存这两个指针。
// LCB是每次接收和发送的缓冲区的位置
typedef struct
{
uint8_t *start; // 起始位置
uint8_t *end; // 结束位置
}LCB;
但是只有起点和终点可能会产生数据存入后没有及时读取出来,下次再发送又会覆盖前面的内容,所以我们可以利用一个数组存放这些数据,再使用两个指针分别表示输入和输出的位置。在usart.h文件中创建一个总控结构体变量用来储存这些指针和数组。
// UCB是这个接收发送的总控结构体
typedef struct
{
uint32_t RxCounter; // 接收计数器(接收的数值)
uint32_t TxCounter;
uint8_t TxState; // 表示发送是否正在忙
LCB RxLocation[10]; // 接收的数组
LCB TxLocation[10];
LCB *RxInPtr; // 接收的位置(在数组的位置)
LCB *RxOutPtr; // 读取接收数组的位置
LCB *RxEndPtr; // 数组的总位置
LCB *TxInPtr;
LCB *TxOutPtr;
LCB *TxEndPtr;
UART_HandleTypeDef huart;
}UCB;
然后我们就可以实现函数收发的具体操作了。进入usart.c文件。
我们需要重新实现之前的串口初始化,因为我们将串口的定义也加入到总控结构体中了:
uint8_t Rxbuffer[Rx_Size];
uint8_t Txbuffer[Tx_Size];
UCB uart;
void MX_USART1_UART_Init(void)
{
uart.huart.Instance = USART1;
uart.huart.Init.BaudRate = 115200;
uart.huart.Init.WordLength = UART_WORDLENGTH_8B;
uart.huart.Init.StopBits = UART_STOPBITS_1;
uart.huart.Init.Parity = UART_PARITY_NONE;
uart.huart.Init.Mode = UART_MODE_TX_RX;
uart.huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart.huart.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&uart.huart) != HAL_OK)
{
Error_Handler();
}
}
先对我们之前的总控结构体实现初始化操作,在这个初始化函数中主要实现指针的指向位置和空闲中断的操作:
// 总控结构体初始化
void PtrInit(void)
{
uart.RxCounter = 0;
uart.RxEndPtr = &uart.RxLocation[9];
uart.RxInPtr = &uart.RxLocation[0];
uart.RxOutPtr = &uart.RxLocation[0];
uart.RxInPtr->start = Rxbuffer;
uart.TxCounter = 0;
uart.TxEndPtr = &uart.TxLocation[9];
uart.TxInPtr = &uart.TxLocation[0];
uart.TxOutPtr = &uart.TxLocation[0];
uart.TxInPtr->start = Txbuffer;
/*-------------初始化完成-------------*/
// 打开空闲中断
__HAL_UART_ENABLE_IT(&uart.huart,UART_IT_IDLE);
// 使能接收中断(永远不会产生接收回调函数)
HAL_UART_Receive_IT(&uart.huart,uart.RxInPtr->start,Max_Size);
}
初始化完成后实现空闲中断的处理,因为在CubeMX的中断函数中并没有对空闲中断进行判断和操作,所以我们需要自己在串口中断函数中实现空闲中断的操作,在这个中断中主要是实现接收的终止:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&uart.huart);
/* USER CODE BEGIN USART1_IRQn 1 */
// 实现空闲中断回调函数,在函数中只能使用这个回调函数判断数据的收发是否完成。
// 获取空闲中断标志位,反映是否产生空闲中断
if(__HAL_UART_GET_FLAG(&uart.huart, UART_FLAG_IDLE))
{
// 及时清除标志位
__HAL_UART_CLEAR_IDLEFLAG(&uart.huart);
// 新的计数值
uart.RxCounter += (Max_Size - uart.huart.RxXferCount);
// 终止接收
HAL_UART_AbortReceive_IT(&uart.huart);
}
/* USER CODE END USART1_IRQn 1 */
}
终止接收后要在终止接收的回调函数中完成对总控结构体中指针的指向、当前的计数值等完成更新,并且需要重新开启接收中断。
// 重新实现终止接收回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
uart.RxInPtr->end = &Rxbuffer[uart.RxCounter - 1];
uart.RxInPtr++;
// 判断指针是否指向最后一位
if(uart.RxInPtr == uart.RxEndPtr)
{
uart.RxInPtr = &uart.RxLocation[0];
}
if((Rx_Size-uart.RxCounter)<Max_Size)
{
uart.RxCounter = 0;
uart.RxInPtr->start = Rxbuffer;
}
else
uart.RxInPtr->start = &Rxbuffer[uart.RxCounter];
// 重新打开中断接收
HAL_UART_Receive_IT(&uart.huart,uart.RxInPtr->start,Max_Size);
}
我们本次实验是接收到数据后将其直接发送,所以我们需要将接收到的数据存入发送缓冲区(发送数组),然后发送:
// 实现接收函数
void Txdata(uint8_t *data, uint16_t data_len)
{
// 判断此次接收的数据能否放下
if((Tx_Size-uart.TxCounter) >= data_len)
{
uart.TxInPtr->start = &Txbuffer[uart.TxCounter];
}
else
{
uart.TxCounter = 0;
uart.TxInPtr->start = Txbuffer;
}
// 将数据存入发送缓冲区
memcpy(uart.TxInPtr->start,data,data_len);
uart.TxCounter += data_len;
uart.TxInPtr->end = &Txbuffer[uart.TxCounter-1];
uart.TxInPtr++;
if(uart.TxInPtr == uart.TxEndPtr)
{
uart.TxInPtr = &uart.TxLocation[0];
}
}
这些函数实现后准备工作就完成了。接下来在主函数中将之前实现的初始化函数声明,在while循环中实现函数的收发过程:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
PtrInit();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 实现的实验现象为接收什么直接将接收的数据发送
// 判断接收缓冲区是否有未发送的数据
if(uart.RxOutPtr != uart.RxInPtr)
{
Txdata(uart.RxOutPtr->start, uart.RxOutPtr->end - uart.RxOutPtr->start + 1);
uart.RxOutPtr++;
if(uart.RxOutPtr == uart.RxEndPtr)
{
uart.RxOutPtr = &uart.RxLocation[0];
}
}
if((uart.TxOutPtr != uart.TxInPtr)&&uart.TxState == 0)
{
uart.TxState = 1; // 表示发送在忙
// 发送
HAL_UART_Transmit_IT(&uart.huart,uart.TxOutPtr->start,uart.TxOutPtr->end-uart.TxOutPtr->start+1);
uart.TxOutPtr++;
if(uart.TxOutPtr == uart.TxEndPtr)
{
uart.TxOutPtr = &uart.TxLocation[0];
}
}
}
在while循环中有用于判断发送在忙的标志,这个标志的修改需要在发送的中断回调函数中实现:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
uart.TxState = 0;
}
至此所有的代码都已完成,为了方便移植,我这里将有代码更改的文件代码分别粘贴在下边
usart.c
/* Includes ------------------------------------------------------------------*/
#include "usart.h"
#include "string.h"
/* USER CODE BEGIN 0 */
uint8_t Rxbuffer[Rx_Size];
uint8_t Txbuffer[Tx_Size];
UCB uart;
/* USER CODE END 0 */
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
uart.huart.Instance = USART1;
uart.huart.Init.BaudRate = 115200;
uart.huart.Init.WordLength = UART_WORDLENGTH_8B;
uart.huart.Init.StopBits = UART_STOPBITS_1;
uart.huart.Init.Parity = UART_PARITY_NONE;
uart.huart.Init.Mode = UART_MODE_TX_RX;
uart.huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart.huart.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&uart.huart) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USART1 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
// 总控结构体初始化
void PtrInit(void)
{
uart.RxCounter = 0;
uart.RxEndPtr = &uart.RxLocation[9];
uart.RxInPtr = &uart.RxLocation[0];
uart.RxOutPtr = &uart.RxLocation[0];
uart.RxInPtr->start = Rxbuffer;
uart.TxCounter = 0;
uart.TxEndPtr = &uart.TxLocation[9];
uart.TxInPtr = &uart.TxLocation[0];
uart.TxOutPtr = &uart.TxLocation[0];
uart.TxInPtr->start = Txbuffer;
/*-------------初始化完成-------------*/
// 打开空闲中断
__HAL_UART_ENABLE_IT(&uart.huart,UART_IT_IDLE);
// 使能接收中断(永远不会产生接收回调函数)
HAL_UART_Receive_IT(&uart.huart,uart.RxInPtr->start,Max_Size);
}
// 重新实现终止接收回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
uart.RxInPtr->end = &Rxbuffer[uart.RxCounter - 1];
uart.RxInPtr++;
// 判断指针是否指向最后一位
if(uart.RxInPtr == uart.RxEndPtr)
{
uart.RxInPtr = &uart.RxLocation[0];
}
if((Rx_Size-uart.RxCounter)<Max_Size)
{
uart.RxCounter = 0;
uart.RxInPtr->start = Rxbuffer;
}
else
uart.RxInPtr->start = &Rxbuffer[uart.RxCounter];
// 重新打开中断接收
HAL_UART_Receive_IT(&uart.huart,uart.RxInPtr->start,Max_Size);
}
// 实现接收函数
void Txdata(uint8_t *data, uint16_t data_len)
{
// 判断此次接收的数据能否放下
if((Tx_Size-uart.TxCounter) >= data_len)
{
uart.TxInPtr->start = &Txbuffer[uart.TxCounter];
}
else
{
uart.TxCounter = 0;
uart.TxInPtr->start = Txbuffer;
}
// 将数据存入发送缓冲区
memcpy(uart.TxInPtr->start,data,data_len);
uart.TxCounter += data_len;
uart.TxInPtr->end = &Txbuffer[uart.TxCounter-1];
uart.TxInPtr++;
if(uart.TxInPtr == uart.TxEndPtr)
{
uart.TxInPtr = &uart.TxLocation[0];
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
uart.TxState = 0;
}
/* USER CODE END 1 */
usart.h
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
// 定义发送和接收缓冲区
#define Rx_Size 2048
#define Tx_Size 2048
#define Max_Size 256
// LCB是每次接收和发送的缓冲区的位置
typedef struct
{
uint8_t *start; // 起始位置
uint8_t *end; // 结束位置
}LCB;
// UCB是这个接收发送的总控结构体
typedef struct
{
uint32_t RxCounter; // 接收计数器(接收的数值)
uint32_t TxCounter;
uint8_t TxState; // 表示发送是否正在忙
LCB RxLocation[10]; // 接收的数组
LCB TxLocation[10];
LCB *RxInPtr; // 接收的位置(在数组的位置)
LCB *RxOutPtr; // 读取接收数组的位置
LCB *RxEndPtr; // 数组的总位置
LCB *TxInPtr;
LCB *TxOutPtr;
LCB *TxEndPtr;
UART_HandleTypeDef huart;
}UCB;
extern UCB uart;
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_USART1_UART_Init(void);
/* USER CODE BEGIN Prototypes */
// 总控结构体初始化
void PtrInit(void);
// 重新实现终止接收回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart);
// 实现接收函数
void Txdata(uint8_t *data, uint16_t data_len);
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __USART_H__ */
main.c
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
PtrInit();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 实现的实验现象为接收什么直接将接收的数据发送
// 判断接收缓冲区是否有未发送的数据
if(uart.RxOutPtr != uart.RxInPtr)
{
Txdata(uart.RxOutPtr->start, uart.RxOutPtr->end - uart.RxOutPtr->start + 1);
uart.RxOutPtr++;
if(uart.RxOutPtr == uart.RxEndPtr)
{
uart.RxOutPtr = &uart.RxLocation[0];
}
}
if((uart.TxOutPtr != uart.TxInPtr)&&uart.TxState == 0)
{
uart.TxState = 1; // 表示发送在忙
// 发送
HAL_UART_Transmit_IT(&uart.huart,uart.TxOutPtr->start,uart.TxOutPtr->end-uart.TxOutPtr->start+1);
uart.TxOutPtr++;
if(uart.TxOutPtr == uart.TxEndPtr)
{
uart.TxOutPtr = &uart.TxLocation[0];
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
stm32f1xx_it.c(CobeMX中断函数)
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&uart.huart);
/* USER CODE BEGIN USART1_IRQn 1 */
// 实现空闲中断回调函数,在函数中只能使用这个回调函数判断数据的收发是否完成。
// 获取空闲中断标志位,反映是否产生空闲中断
if(__HAL_UART_GET_FLAG(&uart.huart, UART_FLAG_IDLE))
{
// 及时清除标志位
__HAL_UART_CLEAR_IDLEFLAG(&uart.huart);
// 新的计数值
uart.RxCounter += (Max_Size - uart.huart.RxXferCount);
// 终止接收
HAL_UART_AbortReceive_IT(&uart.huart);
}
/* USER CODE END USART1_IRQn 1 */
}
最后这个我只是粘出了自己实现的部分,没有自动生成的部分。
这里我只是粗略地实现了这个函数,后续需要在其他地方使用可以将其封装为一个函数,可以更便于移植和更改。