STM32串口中断方式实现任意字节的收发操作

串口中断方式实现任意字节的收发操作

本次实验采用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 */

}

最后这个我只是粘出了自己实现的部分,没有自动生成的部分。

这里我只是粗略地实现了这个函数,后续需要在其他地方使用可以将其封装为一个函数,可以更便于移植和更改。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用环形队列实现串口收发,可以有效地解决数据丢失、串口阻塞等问题。下面是一个基于STM32的环形队列串口收发实现示例: 首先,定义一个环形队列结构体: ``` typedef struct{ uint8_t *buffer; // 数据缓冲区 uint16_t head; // 队头指针 uint16_t tail; // 队尾指针 uint16_t size; // 队列大小 } RingBuffer_t; ``` 然后,在串口中断回调函数中,将接收到的数据放入环形队列中: ``` void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { static uint8_t data; if(huart->Instance == USARTx){ data = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF); if(RingBuffer_Write(&rxBuffer, &data, 1) != 1){ // 队列已满,处理溢出情况 } HAL_UART_Receive_IT(&huart, &data, 1); // 重新开启接收中断 } } ``` 在发送函数中,从队列中读取数据发送: ``` void UART_SendData(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t size) { uint16_t i; for(i = 0; i < size; i++){ while(RingBuffer_IsEmpty(&txBuffer)); // 等待队列非空 RingBuffer_Read(&txBuffer, pData + i, 1); } HAL_UART_Transmit_IT(huart, pData, size); // 发送数据 } ``` 最后,实现环形队列的读写操作: ``` int RingBuffer_Write(RingBuffer_t *buffer, uint8_t *data, uint16_t size) { uint16_t i; for(i = 0; i < size; i++){ if(RingBuffer_IsFull(buffer)){ return i; // 队列已满,返回写入的字节数 } buffer->buffer[buffer->head] = data[i]; buffer->head = (buffer->head + 1) % buffer->size; } return i; // 返回写入的字节数 } int RingBuffer_Read(RingBuffer_t *buffer, uint8_t *data, uint16_t size) { uint16_t i; for(i = 0; i < size; i++){ if(RingBuffer_IsEmpty(buffer)){ return i; // 队列已空,返回读取的字节数 } data[i] = buffer->buffer[buffer->tail]; buffer->tail = (buffer->tail + 1) % buffer->size; } return i; // 返回读取的字节数 } int RingBuffer_IsFull(RingBuffer_t *buffer) { return ((buffer->head + 1) % buffer->size) == buffer->tail; } int RingBuffer_IsEmpty(RingBuffer_t *buffer) { return buffer->head == buffer->tail; } ``` 这样,就可以在STM32上使用环形队列实现串口收发了。需要注意的是,缓冲区大小应该根据实际需求进行调整,避免溢出或浪费内存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值