自定义串口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