自定义串口DMA打印函数和串口接收缓冲区

本文介绍了如何在STM32单片机上使用自定义的串口DMA功能,包括使用环形缓冲区管理接收数据,以避免数组越界问题,并详细描述了串口发送和接收中断回调函数的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义串口DMA打印函数

volatile uint8_t usart_dma_tx_over = 1;  //指示串口1的DMA发送完成

int myprintf(const char *format,...)   //定义串口1的DMA打印
{
  va_list arg;
  static char SendBuff[200] = {0};
  int rv;
  while(!usart_dma_tx_over);//等待前一次DMA发送完成

  va_start(arg,format);
  rv = vsnprintf((char*)SendBuff,sizeof(SendBuff)+1,(char*)format,arg);  
	//rv = vsnprintf((char*)U0_TxBuff,sizeof(U0_TxBuff)+1,(char*)format,arg);
  va_end(arg);

  //HAL_UART_Transmit_DMA(&huart1,(uint8_t *)U0_TxBuff,rv);
	usart_dma_tx_over = 0;//清0全局标志,发送完成后重新置1
	HAL_UART_Transmit_DMA(&huart1,(uint8_t *)SendBuff,rv);
 

  return rv;
}


void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)  //串口1的发送完成中断回调函数
{
	if(huart->Instance==USART1)
	{
   usart_dma_tx_over = 1;
	}

}

串口空闲中断DMA接收不定长数据

DMA数据接收满缓冲区了或者串口空闲了传输就会停止,然后知道串口空闲中断触发对接收的数据进行处理;

uint8_t U0_RxBuff[U0_RX_SIZE];
uint8_t U0_TxBuff[U0_TX_SIZE];


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 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */
  HAL_UARTEx_ReceiveToIdle_DMA(&huart1, U0_RxBuff, sizeof(U0_RxBuff));  //开启串口2的DMA接收中断
	__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);  //取消接收数据过半中断
  /* USER CODE END USART1_Init 2 */

}



void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)     //usart1的接收中断回调函数
{
	 if(huart->Instance==USART1)
    {
			
			//这里编写串口接收数据后对其进行处理的代码
			
				HAL_UARTEx_ReceiveToIdle_DMA(&huart1, U0_RxBuff, sizeof(U0_RxBuff));  //一次处理完成之后,重新开启接收中断
			__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);  //取消接收数据过半中断
    }
}

串口DMA接收缓冲区

简单数组:由于HAL库DMA的接收是设定一个目标地址再设定一个接收长度,如果要接收多次DMA的数据,该目标地址则会不断向数组后面移动,如果目标地址+数据长度超过了数组最后一位的地址的话就会造成数组越界,这是我们不希望看到的。

环形数组:通过设置指针作为DMA接收目标地址和数据最大接受长度,每次接受完数据都判断该指针+数据最大接收长度是否超过数组末尾地址,不超过就将接收指针后移,如果超过就执行回卷操作,即将该目标地址指针重新指向缓冲区起始地址。

具体实现代码如下:

#define U0_RX_SIZE 2048                 //接收缓冲区长度
#define U0_RX_MAX  256                  //单次接收最大量
#define NUM        10                   //se指针对结构体数组长度

typedef struct{
	uint8_t *start;                      //s用于标记起始位置
	uint8_t *end;                        //e用于标记结束位置
}UCB_URxBuffptr;                       //se指针对结构体

typedef struct{
	uint16_t URxCounter;                 //累计接收数据量
	UCB_URxBuffptr URxDataPtr[NUM];      //se指针对结构体数组 ,可以通过回溯指针对来知晓至多前NUM-1个数据包的内容
	UCB_URxBuffptr *URxDataIN;           //指针 用于标记接收数据
	UCB_URxBuffptr *URxDataOUT;          //OUT指针 用于提取接收的数据
	UCB_URxBuffptr *URxDataEND;          //IN和OUT指针的结尾标志
}UCB_CB;                               //串口控制结构体


UCB_CB  U0CB;
uint8_t U0_RxBuff[U0_RX_SIZE];





void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)     //usart1的接收中断回调函数
{
	 if(huart->Instance==USART1)
    {
			
			//GET_COUNTER宏定义可以获得DMA缓冲区剩余的数据大小
			U0CB.URxCounter += (U0_RX_MAX+1) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);       //+=操作,将本次的接收数量,累计到URxCounter变量
			U0CB.URxDataIN->end = &U0_RxBuff[U0CB.URxCounter-1];                             //IN指针指向的结构体中的e指针记录本次接收的结束位置
		  U0CB.URxDataIN++;                                                                //IN指针后移
			if(U0CB.URxDataIN == U0CB.URxDataEND){                                           //如果IN指针后移到了END标记的位置,进入if
				U0CB.URxDataIN = &U0CB.URxDataPtr[0];                                        //回卷,重新指向指针对0号成员    
			}
			if(U0_RX_SIZE - U0CB.URxCounter >= U0_RX_MAX){                                   //判断,如果剩余的空间大于等于单次接收最大量,进入if
				U0CB.URxDataIN->start = &U0_RxBuff[U0CB.URxCounter];                         //IN指针指向的结构体中的s指针记录下次接收的起始位置
			}else{                                                                           //判断,如果剩余的空间小于单次接收最大量,进入else
				U0CB.URxDataIN->start = U0_RxBuff;                                           //剩余的空间不用了,回卷,下次的接收位置返回缓冲区起始位置,这样做可以防止数组越界
				U0CB.URxCounter = 0;                                                         //累计值也清零
			}
			
				HAL_UARTEx_ReceiveToIdle_DMA(&huart1, U0CB.URxDataIN->start , U0_RX_MAX+1);  //一次处理完成之后,重新开启接收中断,并且重新设置DMA数据在缓冲区中的起始接收位置
			__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);  //取消接收数据过半中断
    }
}

NUM表示你之多可以知晓前面几次的传输内容,这样设置缓冲区的好处个人认为可以将多次DMA不定长数据一起处理。当然我认为还有一种方法就是设置一个普通数组,其长度大于数据最大接收长度,然后目标地址一直为数组首地址,每次接收数据后都把数据粘贴到另一个更大的数组中,在另一个大数组对数据进行处理即可,这样似乎要方便许多。

该串口缓冲区设置来源于哔站up主:超子说物联网

视频链接:【【开源】GD32/STM32单片机4G物联网入门教程—从0开始手把手编写OTA远程升级BootLoader程序】https://www.bilibili.com/video/BV1a84y1J7CD?p=7&vd_source=d1e91e9e17a031e2d6e11ed04c3ab205

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值