跳到主要内容

前言

前几天志坚哥哥在用接收dr16那套串口DMA双缓冲的方法接裁判系统的数据的时候引发了我的思考

为什么dr16官方(C板示例程序)用的是DMA双缓冲,而icra2021github开源的那套是只用了单缓冲

另外,官方接收uart6的驱动用的只是单单DMA+串口空闲中断/DMA半/溢传输中断+FIFO

下面内容部分参考一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制

如果你懒得看里面可以看我的非常精简版本(里面有很多原理图,建议看看

DMA

当我们的波特率设置在115200或者以下时,而且数据量不大的场景时,一般没必要使用DMA(嵌入式大杂烩说的嗷,我还没实际上systemview看发送/接收用时,或者说用DMA未能发挥出DMA的作用

发送端使用中断发送以115200波特率时,1s传输11520字节,大约69us需响应一次中断,如波特率再提高,将消耗更多CPU资源

An example showing the tip type prompt. {: .prompt-tip}

An example showing the info type prompt. {: .prompt-info}

An example showing the warning type prompt. {: .prompt-warning}

An example showing the danger type prompt. {: .prompt-danger}

为什么裁判系统的串口接收推荐使用FIFO?

我们是否考虑过在计算数据帧校验值的时候,数据源改变了的问题呢? {: .prompt-danger}

一般情况下,我们都不会写临界区保护在我们的代码里(至少我看过很多人都没写,中断都不关的)

但是,单缓冲下的接收一般都是用指针传参到处理函数的,一旦在处理函数的时候进入了中断,我们的数据源就会被新来的数据冲掉一部分,校验直接寄掉,裁判系统也没有重发机制。

那双缓冲能解决一丢丢这样的问题,因为你处理数据的时候旧数据是在缓冲区1,而缓冲区2在接收新数据,不会被冲掉。

但是裁判系统的CRC校验和数据处理我不确定他的计算时间真的是否能撑过两次的缓冲区刷新,最好采用FIFO的机制,而且裁判系统的实时性不用像dr16的那么高。

如图,使用dr16类似的小缓存+双缓冲机制真的很容易冲掉数据,但是,我们将缓冲区开大点,同时将数据分为一个包一个包来,环形队列缓存,减少被冲的可能,保持数据完整。

官方C板例程用的也是双缓...但是也是扔进去fifo的,说明也有可能

双缓冲+FIFO示例程序


/************************** Dongguan-University of Technology -ACE**************************
* @brief 理论上通用的双缓冲开启,因为板子不一样,就有不一样的串口(hal库的通用性大大提高了)//TODO:待测试
* 双缓冲模式自动开启循环接收,所以你cubeMX爱开不开
*
* @param huart
* @param rx1_buf
* @param rx2_buf
* @param dma_buf_num
************************** Dongguan-University of Technology -ACE***************************/
void usart_DoubleBuffer_init(UART_HandleTypeDef *huart,uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{

//enable the DMA transfer for the receiver and tramsmit request
//使能DMA串口接收和发送
SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);
SET_BIT(huart->Instance->CR3, USART_CR3_DMAT);

//enalbe idle interrupt
//使能空闲中断
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);

//disable DMA
//失效DMA
__HAL_DMA_DISABLE(huart->hdmarx);

while(huart->hdmarx->Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(huart->hdmarx);
}

__HAL_DMA_CLEAR_FLAG(huart->hdmarx, DMA_LISR_TCIF1);

huart->hdmarx->Instance->PAR = (uint32_t) & (huart->Instance->DR);
//memory buffer 1
//内存缓冲区1
huart->hdmarx->Instance->M0AR = (uint32_t)(rx1_buf);
//memory buffer 2
//内存缓冲区2
huart->hdmarx->Instance->M1AR = (uint32_t)(rx2_buf);
//data length
//数据长度
__HAL_DMA_SET_COUNTER(huart->hdmarx, dma_buf_num);

//enable double memory buffer
//使能双缓冲区
SET_BIT(huart->hdmarx->Instance->CR, DMA_SxCR_DBM);

//enable DMA
//使能DMA
__HAL_DMA_ENABLE(huart->hdmarx);


//disable DMA
//失效DMA
__HAL_DMA_DISABLE(huart->hdmarx);

while(huart->hdmarx->Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(huart->hdmarx);
}

huart->hdmarx->Instance->PAR = (uint32_t) & (huart->Instance->DR);

}

void Referee_IRQHadler(UART_HandleTypeDef huart)
{

static volatile uint8_t res;
if (huart.Instance->SR & UART_FLAG_IDLE)//这里的USART6->SR改成了huart.Instance->SR,提高适用性
{
__HAL_UART_CLEAR_PEFLAG(&huart);

static uint16_t this_time_rx_len = 0;

if ((huart.hdmarx->Instance->CR & DMA_SxCR_CT) == RESET)
{
__HAL_DMA_DISABLE(huart.hdmarx);
this_time_rx_len = USART_RX_BUF_LENGHT - __HAL_DMA_GET_COUNTER(huart.hdmarx);
__HAL_DMA_SET_COUNTER(huart.hdmarx, USART_RX_BUF_LENGHT);
huart.hdmarx->Instance->CR |= DMA_SxCR_CT;
__HAL_DMA_ENABLE(huart.hdmarx);
fifo_s_puts(&referee_fifo, (char *)usart_Referee_buf[0], this_time_rx_len);
// detect_hook(REFEREE_TOE);
}
else
{
__HAL_DMA_DISABLE(huart.hdmarx);
this_time_rx_len = USART_RX_BUF_LENGHT - __HAL_DMA_GET_COUNTER(huart.hdmarx);
__HAL_DMA_SET_COUNTER(huart.hdmarx, USART_RX_BUF_LENGHT);
huart.hdmarx->Instance->CR &= ~(DMA_SxCR_CT);
__HAL_DMA_ENABLE(huart.hdmarx);
fifo_s_puts(&referee_fifo, (char *)usart_Referee_buf[1], this_time_rx_len);
// detect_hook(REFEREE_TOE);
}
}
}