嵌入式开发--STM32用DMA+IDLE中断方式串口接收不定长数据

回顾

之前讲过用 利用IDLE空闲中断来接收不定长数据 ,但是没有用到DMA,其实用DMA会更加的高效,MCU也可以腾出更多的性能去处理应该做的事情。

原理简介

IDLE顾名思义,就是空闲的意思,即当监测到串口空闲超过1个串口的数据帧时,会使状态寄存器(SR或ISR)的IDLE位置位,如果此时控制寄存器(CR或CR1)的IDLEIE为1,则会触发IDLE中断。

DMA搬运数据,则是一边接收数据,一边将串口接收到的数据搬运到内存中,这个过程不需要MCU参与,等到IDLE中断到来的时候,直接去内存中取数据即可。

DMA中断在CubeMX中是默认开启的,可以手动将其关闭,等IDLE中断到来的时候,直接操作读取数据即可。当DMA设置为NORMAL模式时,这个中断是完全用不到的,因为当IDLE到来时,置标志位,然后数据处理,此时需要重启DMA,而DMA的接收缓冲区又大于不定长的数据帧,这样DMA中断就永远不会触发了。

知道原理了,就好操作了。

CubeMX操作

开启调试接口

开启USART1异步方式,波特率、数据长度和校验方式根据需要设置

开启DMA,内存自增,循环方式

开启串口中断

设置好时钟

然后是 Generate Code生成代码

手动修改代码

中断函数中,置标志位,读取SR和DR寄存器,以清除IDLE中断标识,并获得本次接收的数据长度

c 复制代码
//485发送数据
//由于485发送数据时,接收端会同步接收到发送的数据,这会造成数据解析的错乱。
//所在在485换向之前,先关闭DMA,等发送完成,再转换到输出状态以后,再开启DMA 
//也就是说,作为从机来说,不应当主动发起数据传输请求,否则当主机开始发送数据时,从机会丢失数据帧
void LL_myuart_send(u8 *pdata, u16 len)
{
  HAL_UART_DMAStop(&huart1);    //先关闭DMA
  
  RS485_OUT();
  HAL_Delay(1);
  HAL_UART_Transmit(&huart1, pdata, len, 1000);
  RS485_IN();
  HAL_Delay(1);

  rs485_receive_pos = 0;
  rs485_receive_len = 0;
  HAL_UART_Receive_DMA(&huart1,rs485_receive_data, RS485_BUF_LEN);  //等发送完成,再转换到输出状态以后,再开启DMA 
}
c 复制代码
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  u8 temp;

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  //长时间未接收到数据时,会发生IDLE中断,此时意味着数据接收完成
  //不同的内核,清除IDLEIE的方式不同,请查阅手册
  if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
  {
    //读SR和DR寄存器,以清除IDLE和RXNE中断标志
    temp = huart1.Instance->SR;
    temp = huart1.Instance->DR;
    rs485_idle_flag = 1;
    
    rs485_receive_pos += rs485_receive_len;
    if(rs485_receive_pos >= RS485_BUF_LEN)  //如果起始位置超出缓冲区,则减去缓冲区长度,也就意味着数据从头开始循环记录
      rs485_receive_pos -= RS485_BUF_LEN;
    
    //DMA接收串口数据,
    //设置为NORMAL方式时,需要在每一次接收完成后,再次使能,否则DMA的接收缓冲区满了以后就不会再接收
    //设置为CIRCLAR方式时,不需要再次使能,但编程会麻烦些

    //如果长度大于缓冲区长度
    if(RS485_BUF_LEN < (__HAL_DMA_GET_COUNTER(&hdma_usart1_rx) + rs485_receive_pos))
      rs485_receive_len = RS485_BUF_LEN*2 - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx) - rs485_receive_pos; 
    else
      rs485_receive_len = RS485_BUF_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx) - rs485_receive_pos; 
    
  }
  /* USER CODE END USART1_IRQn 1 */
}

在主循环开始前进行初始设置

开启串口和DMA,使能IDLE中断

c 复制代码
  HAL_UART_Receive_DMA(&huart1,rs485_receive_data,RS485_BUF_LEN);
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);	//启动IDLE中断

主循环内,清标志位,然后发送 receive OK,由于我用的是485总线,需要进行接收和发送方式的切换,故将整个发送过程做了封装,不再具体列出。

c 复制代码
  while (1)
  {

    if(rs485_idle_flag != 0)  //接收到一帧数据
    {
      rs485_idle_flag = 0;
      //HAL_UART_Receive_DMA(&huart1,rs485_receive_data,RS485_BUF_LEN); //如果DMA采用NORMAL方式,需要激活本句,以使能DMA
      LL_myuart_send((u8*)"receive OK\r\n", 12); 
      rs485_idle_flag = 0;
    }

    /* USER CODE END WHILE */
  }

实测


下图为缓冲区写满时,从头开始循环写入的情况

在主程序中,从rs485_receive_pos开始,读取rs485_receive_len长度的数据即可,如果超出缓冲区,则减去缓冲区长度,从头开始读取,不再列出代码。

注意事项

1 DMA接收串口数据

设置为NORMAL方式时,需要在每一次接收完成后,再次使能,否则DMA只接收一次就不会再接收

设置为CIRCLAR方式时,不需要再次使能,但可能会由于各种延时导致数据来不及处理的问题

2 需要开启串口的总中断

在CubeMX中需要开启串口的总中断,否则会不进中断。

3 清中断标识

对于F1系列来说,清IDLE中断标志需要读取SR和DR寄存器,否则会一直进IDLE中断。

不同的内核,清IDLE标志的方法不同,这个需要查询芯片手册

4 非ST家的芯片,本代码不兼容

亲测航顺芯片就不兼容,需要改代码。

5 DMA操作

DMA的计数是连续的,每次接收都是在上次接收完成的位置,继续进行下一次接收,缓冲区装满时,继续从头开始接收,这给编程带来一些麻烦。

还有一种方式简单粗暴:首先接收缓冲区的长度 > 2倍的最大帧长度,然后在每次接收完成后,停止并重新开启DMA,这个过程可以放在主程序中,处理数据时进行。这样每次数据都是从缓冲区的0偏移开始接收数据,所以编程时缓冲区数据不会循环写入,编程上有便利,如果数据传输量不大,而且传输间隔也比较长,可以用这种方式。

相关推荐
板栗焖小鸡11 分钟前
STM32-PWM驱动无源蜂鸣器
stm32·学习
智者知已应修善业2 小时前
【51单片机用数码管显示流水灯的种类是按钮控制数码管加一和流水灯】2022-6-14
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
智商偏低8 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen10 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森12 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白12 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D12 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术15 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt16 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘16 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件