您的位置:首页 > 其它

stm32串口dma加空闲中断 实现fifo接收数据 串口高效收发思路

2020-06-03 05:14 1691 查看

我做这个串口数据接收 dma+空闲中断 加fifo  实现串口的高效收发  ,主要是串口接收的数据长度不定长,时间超时也不好做,还要串口收发的效率要高,采用串口数据的接收 dma+空闲中断+fifo的方式  速度快和效率高,占用cpu的时间短

对比了其他几种方式

1:采用串口中断的话,每接收1byte就得中断一次。这样太消耗CPU资源! 频繁进中断,占用中断,特别是对时间和时序要求比较严格的时候 串口频繁进入中断导致其他中断时序有影响

 

2:采用DMA方式接收数据,接收的数据长度必须是固定的  对于接收数据长度不固定就不怎么好弄了,特别像gprs通信,接收长度不固定,这些都是困扰我

 

3:采用dma方式接收数据+定时器超时中断,这样来确定一帧数据完成,需要开关定时器,操作比较复杂,超时时间还不太好设置,stm32f1和f4 没有超时中断还只能采用定时器或者把rxd引脚接到stm32定时器触发引脚上来实现超时, 

4:stm32串口dma方式接收数据+空闲中断或者超时中断+fifo 这种分内事来实现不定长的数据和高效的串口数据接收   效率比其他的方式要快,消耗cpu的时间比较少,这样应用可以做数据超时

所以我采用了stm32串口dma方式接收数据+空闲中断或者超时中断+fifo方式来实现

像stm32f103和stm32f407芯片没有时间超时中断  需要定时器来做超时 比较麻烦,nxp的部分芯片串口有超时中断,atmel的部分芯片串口有超时中断,stm32的h7和f7系列才有超时中断

要实现stm32串口dma方式接收数据+空闲中断或者超时中断+fifo方式来实现

第一步 stm32的串口dma配置 ,串口的初始化,还有串口的空闲中断

1,通过stm32的cubemx软件来生成串口+dma配置的初始化

cubemx生成代码都是HAL库的模式,我就以stm32f4的HAL库的方式实现

[code]UART_HandleTypeDef   huart1={0}; 

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)
  {
  }

dma的配置可以通过cubemx软件串口+dma方式来生成代码

[code]  /* USER CODE BEGIN USART1_MspInit 0 */

/* USER CODE END USART1_MspInit 0 */
/* Peripheral 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_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/* USART1 DMA Init */
/* USART1_RX Init */
hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
Error_Handler();
}

__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);

/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);

2,stm32生成的代码是不支持空闲中断的,需要自己增加空闲中断和空闲中断的处理

[code]    __HAL_UART_ENABLE_IT(uart->h, UART_IT_IDLE);//使能空闲中断HAL代码
    __HAL_UART_CLEAR_IDLEFLAG(uart->h);//使能空闲中断HAL代码

串口中断的处理

[code]void USART1_IRQHandler(void)
{ 

 HAL_UART_IRQHandler(&huart1); 

if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) ? SET : RESET) == SET)

{

//串口空闲中断数据处理 

}

}

第二步 了解环形fifo的buff特点,通过分析环形buff的特点,其实串口的dma接收 dma模式设置循环模式 就是环形buff

环形buff的说明讲网站:https://blog.csdn.net/jiejiemcu/article/details/80563422

转载环形fifo说明出处:    STM32进阶之串口环形缓冲区实现

以下内容是引用了原文的内容 

转载原文链接:https://blog.csdn.net/jiejiemcu/article/details/80563422 

 

实现的原理:初始化的时候,列队头与列队尾都指向0,当有数据存储的时候,数据存储在‘0’的地址空间,列队尾指向下一个可以存储数据的地方‘1’,再有数据来的时候,存储数据到地址‘1’,然后队列尾指向下一个地址‘2’。当数据要进行处理的时候,肯定是先处理‘0’空间的数据,也就是列队头的数据,处理完了数据,‘0’地址空间的数据进行释放掉,列队头指向下一个可以处理数据的地址‘1’。从而实现整个环形缓冲区的数据读写。

  看图,队列头就是指向已经存储的数据,并且这个数据是待处理的。下一个CPU处理的数据就是1;而队列尾则指向可以进行写数据的地址。当1处理了,就会把1释放掉。并且把队列头指向2。当写入了一个数据6,那么队列尾的指针就会指向下一个可以写的地址。

如果你懂了环形队列,那就跟着一步步用代码实现吧:

是不是跟dma的circular的模式跟这个环形队列非常相似呢

第3步就开始写串口dma+空闲中断或超时中断+fifo的代码啦

重要的代码 串口dma的fifo的数据长度的处理

[code]//假设dma的接收缓存大小为512       环形buff的位置为 500   
//接收到数据长度为48 这个时候的环形buff的位置为548                       
// 548-500=48      但是dma环形buff的特性,
//dma实际的长度为36 这个时候如何计算长度           
// 这个算法如512-500 12      548-512       36
rev_dma_lens =  DMA接收的数据长度
//buff_index-----串口接收和app的buff的序列号
//环形数组
if (puart->ndtr_last != rev_dma_lens) //上次与这次不同,表示有新数据
{

        start_addr = start_addr + len;//环形数据地址偏移量
        //
        if (start_addr >= 512)
        {
            puart->start_addr = start_addr - 512;
        }
        //
        if (rev_dma_lens > ndtr_last)
        {
            len = rev_dma_lens - ndtr_last; //接收数据长度=上次长度-这次长度
        }
        else
        {
            len =  512 - ndtr_last + rev_dma_lens; //环形数据到头后,总数-这次剩余+上次剩余
        }
        //

       ndtr_last = rev_dma_lens;

}

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: