硬汉嵌入式论坛

 找回密码
 立即注册
查看: 8869|回复: 7
收起左侧

[有问必答] stm32f407的串口和DMA不定长接收问题

[复制链接]
回帖奖励 5 个金币 回复本帖可获得 5 个金币奖励! 每人限 1 次

32

主题

115

回帖

211

积分

高级会员

积分
211
发表于 2021-4-20 15:22:14 | 显示全部楼层 |阅读模式
例程目的:通过串口6和DMA,开启串口6的空闲中断,接收不定长数据

采用串口6空闲中断和DMA进行不定长接收,如果DMA选择正常模式,代码如下
usart配置
  1. void RS232_USART_Config(void)
  2. {
  3.   GPIO_InitTypeDef GPIO_InitStructure;
  4.   USART_InitTypeDef USART_InitStructure;
  5.                
  6.   RCC_AHB1PeriphClockCmd( RS232_USART_RX_GPIO_CLK|RS232_USART_TX_GPIO_CLK, ENABLE);

  7.   /* 使能 UART 时钟 */
  8.   RCC_APB2PeriphClockCmd(RS232_USART_CLK, ENABLE);
  9.   
  10.   /* 连接 PXx 到 USARTx_Tx*/
  11.   GPIO_PinAFConfig(RS232_USART_RX_GPIO_PORT,RS232_USART_RX_SOURCE, RS232_USART_RX_AF);

  12.   /*  连接 PXx 到 USARTx__Rx*/
  13.   GPIO_PinAFConfig(RS232_USART_TX_GPIO_PORT,RS232_USART_TX_SOURCE,RS232_USART_TX_AF);

  14.   /* 配置Tx引脚为复用功能  */
  15.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  16.   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  17.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  18.   GPIO_InitStructure.GPIO_Pin = RS232_USART_TX_PIN  ;
  19.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  20.   GPIO_Init(RS232_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  21.   /* 配置Rx引脚为复用功能 */
  22.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  23.   GPIO_InitStructure.GPIO_Pin = RS232_USART_RX_PIN;
  24.   GPIO_Init(RS232_USART_RX_GPIO_PORT, &GPIO_InitStructure);
  25.                        
  26.   /* 配置串口RS232_USART 模式 */
  27.   USART_InitStructure.USART_BaudRate = RS232_USART_BAUDRATE;
  28.   USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  29.   USART_InitStructure.USART_StopBits = USART_StopBits_1;
  30.   USART_InitStructure.USART_Parity = USART_Parity_No ;
  31.   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  32.   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  33.   USART_Init(RS232_USART, &USART_InitStructure);
  34.        
  35.         NVIC_Configuration();
  36.         /*配置串口接收中断手册上说,使用DMA接收,不要使能RXNEIE*/
  37. //        USART_ITConfig(RS232_USART, USART_IT_RXNE, ENABLE);
  38.         USART_ITConfig(RS232_USART, USART_IT_IDLE, ENABLE);
  39.        
  40.   USART_Cmd(RS232_USART, ENABLE);
  41. }
复制代码
DMA配置
  1. void RS232_DMA_Cfg(void)
  2. {
  3.         DMA_InitTypeDef DMA_InitStructure;
  4.        
  5.         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  6.        
  7.         DMA_InitStructure.DMA_BufferSize                =        256;//是缓冲数组的2倍
  8.         /*usart6发送是数据流6通道5,接收是数据流1通道5*/
  9.         DMA_InitStructure.DMA_Channel                        =        DMA_Channel_5;
  10.         DMA_InitStructure.DMA_DIR                                =        DMA_DIR_PeripheralToMemory;
  11.         DMA_InitStructure.DMA_FIFOMode                        =        DMA_FIFOMode_Disable;
  12.         DMA_InitStructure.DMA_FIFOThreshold                =        DMA_FIFOThreshold_1QuarterFull;//FIFO没用,这个没影响
  13.         DMA_InitStructure.DMA_Memory0BaseAddr        =        (u32)(&RX_Buff);
  14.         DMA_InitStructure.DMA_MemoryBurst                =        DMA_MemoryBurst_Single;
  15.         DMA_InitStructure.DMA_MemoryDataSize        =        DMA_MemoryDataSize_Byte;
  16.         DMA_InitStructure.DMA_MemoryInc                        =        DMA_MemoryInc_Enable;
  17.         DMA_InitStructure.DMA_Mode                                =        DMA_Mode_Normal;
  18.         DMA_InitStructure.DMA_PeripheralBaseAddr=        (u32)(USART6_BASE+0x04);//USART6->DR;
  19.         DMA_InitStructure.DMA_PeripheralBurst        =        DMA_PeripheralBurst_Single;
  20.         DMA_InitStructure.DMA_PeripheralDataSize=        DMA_PeripheralDataSize_Byte;
  21.         DMA_InitStructure.DMA_PeripheralInc                =        DMA_PeripheralInc_Disable;
  22.         DMA_InitStructure.DMA_Priority                        =        DMA_Priority_Medium;
  23.        
  24.        
  25.         /*USART6在DMA2的通道5,数据流1和数据流2*/
  26.         DMA_Init(DMA2_Stream1, &DMA_InitStructure);
  27.        
  28.         DMA_Cmd(DMA2_Stream1, ENABLE);
  29.        
  30.         while(DMA_GetCmdStatus(DMA2_Stream1) != SET);
  31. }
复制代码
串口6的中断服务函数
  1. void RS232_USART_IRQHandler(void)
  2. {
  3.         u8 data;
  4. //没有RXNE中断,直接IDLE中断

  5.         if(USART_GetITStatus(RS232_USART,  USART_IT_IDLE) != RESET)
  6.         {
  7.                
  8.                 //清除空闲标志位
  9.                 data = USART6->SR;
  10.                 data = USART6->DR;
  11.                 /*得到接收到的数值 = 缓冲区总量 - 缓冲区剩余*/
  12.                 RXBuffCNT = BUFFLEN - DMA_GetCurrDataCounter(DMA2_Stream1);
  13.                 RXFlag = 1;
  14.                 /*把缓冲区的内容发送出去,也可以放在main函数中实验*/
  15.                
  16.                
  17.                 DMA_Cmd(DMA2_Stream1, DISABLE);   //先关闭DMA,才能设置它
  18.                 while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}        //等待传输结束       
  19.                 USART_DMACmd( RS232_USART, USART_DMAReq_Rx,DISABLE);
  20. //                        /*        设置DMA为循环模式时,这一句加不加没有影响,可以正常接收;
  21. //                                DMA正常模式,添加这句,第一次正常,后续回显第一个字符
  22. //                                DMA正常模式,去掉这一句,第一次正常,后续发送不同内容,但是缓冲区的数据不变
  23. //                        */
  24. //                DMA_SetCurrDataCounter(DMA2_Stream1,BUFFLEN);//可以去掉设置传输数据长度
  25.                 DMA_Cmd(DMA2_Stream1, ENABLE);                      //开启DMA
  26.                 //DMA工作在正常模式下需要清除这些标志位,否则有问题
  27.                 DMA_ClearFlag(DMA2_Stream1,DMA_FLAG_TCIF1 |DMA_FLAG_FEIF1 | DMA_FLAG_DMEIF1 | DMA_FLAG_TEIF1 |  DMA_FLAG_HTIF1);//
  28.                 USART_DMACmd( RS232_USART, USART_DMAReq_Rx,ENABLE);
  29.         }
  30. }
复制代码
有如下问题:
  • 如果选择正常模式,需要清除一堆DMA的标志位,不知道为什么虽然我接收的数据只有几位,外设到内存的内存空间有256字节,正常来说这些标志位都没有影响的,但是不清除就不行
  • DMA工作模式normal,当第二次发送的内容和第一次不同时,串口回显的内容不会立刻改变,而是下一次才开始改变,如图,之前都是bbbbbbb当发送qqqqqq时,在main函数中的发送函数表明接收到的还是bbbbbbbbb;第二次,之前都是qqqqq,我发送yyyyy时,在main函数中的发送函数表明接收到的还是qqqqqqq,等到下一次才显示正常。
DMA收发不一致.png
这个更明显,通过调试发现,我发送的是b,但是缓冲数组里还是之前的a,感觉DMA的缓冲慢半拍,不知道大家有没有碰到这样的问题,谢谢大家。



DMA接收发送慢一拍.png

回复

使用道具 举报

32

主题

115

回帖

211

积分

高级会员

积分
211
 楼主| 发表于 2021-4-20 15:32:02 | 显示全部楼层
如果把DMA设置成circle模式,则没有这个慢半拍的效果,而且中断函数里也不需要清除DMA相关的标志位。
很奇怪,normal模式下已经关闭,再开启DMA,相当于手动的  circle模式了,为什么差别这么大呢,谢谢大家。解答了问题,可以发小红包表示感谢。
回复

使用道具 举报

0

主题

290

回帖

290

积分

高级会员

积分
290
发表于 2021-4-20 19:14:11 | 显示全部楼层
需要在串口的接收完成中断中执行DMA的flush,不然DMA的FIFO中的数据是不会传输到缓冲区的,除非满了。
回复

使用道具 举报

32

主题

115

回帖

211

积分

高级会员

积分
211
 楼主| 发表于 2021-4-20 21:10:15 | 显示全部楼层
regbbs 发表于 2021-4-20 19:14
需要在串口的接收完成中断中执行DMA的flush,不然DMA的FIFO中的数据是不会传输到缓冲区的,除非满了。

flush是什么意思?你的意思是,需要在中断里把缓冲区的内容用memcpy复制到外面的数组里吗?
回复

使用道具 举报

32

主题

115

回帖

211

积分

高级会员

积分
211
 楼主| 发表于 2021-4-21 08:00:41 | 显示全部楼层
  1. int main(void)
  2. {
  3.         u8 buff2[BUFFLEN]={0};
  4.         u8 pos = 0;
  5.         u16 revnum = 0;
  6.         DEBUG_USART_Config();
  7.         RS232_USART_Config();
  8.         RS232_DMA_Cfg();
  9.         USART_DMACmd( RS232_USART, USART_DMAReq_Rx,ENABLE);
  10.         while(1)
  11.         {
  12.                 if(RXFlag ==1)
  13.                 {
  14.                         RXFlag = 0;
  15.                         memcpy(buff2,RX_Buff,RXBuffCNT);
  16.                         /*加上之后会发出0,先复制再清空,buff2不该是0*/
  17.                         //memset(RX_Buff,0,RXBuffCNT);
  18.                         Usart_SendStr_length(USART6,buff2,RXBuffCNT);               
  19.                 }                       
  20.         }       
  21. }
复制代码

这是主函数的回显
回复

使用道具 举报

0

主题

290

回帖

290

积分

高级会员

积分
290
发表于 2021-4-21 08:27:49 | 显示全部楼层
伊森亨特 发表于 2021-4-20 21:10
flush是什么意思?你的意思是,需要在中断里把缓冲区的内容用memcpy复制到外面的数组里吗?

不是复制,是先禁用DMA,然后再启用DMA。
DMA有FIFO,参考407 RM的DMA章节。    __HAL_DMA_DISABLE(huart->hdmarx);  禁用DMA,DMA的FIFO中的数据就会传输到缓冲区
    __HAL_DMA_SET_COUNTER(huart->hdmarx, RX_BUFF_LENGTH);  重设DMA要传输的数据项数目
--------------复制数据或对数据处理----------
    __HAL_DMA_ENABLE(huart->hdmarx);启用DMA

DMA

DMA


回复

使用道具 举报

32

主题

115

回帖

211

积分

高级会员

积分
211
 楼主| 发表于 2021-4-21 09:30:49 | 显示全部楼层
regbbs 发表于 2021-4-21 08:27
不是复制,是先禁用DMA,然后再启用DMA。
DMA有FIFO,参考407 RM的DMA章节。    __HAL_DMA_DISABLE(huar ...

我写了,加上设置数量的话,效果不对,我就把那条语句注释掉了
  1. DMA_Cmd(DMA2_Stream1, DISABLE);   //先关闭DMA,才能设置它
  2.                 while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}        //等待传输结束      
  3.                 USART_DMACmd( RS232_USART, USART_DMAReq_Rx,DISABLE);
  4. //                        /*        设置DMA为循环模式时,这一句加不加没有影响,可以正常接收;
  5. //                                DMA正常模式,添加这句,第一次正常,后续回显第一个字符
  6. //                                DMA正常模式,去掉这一句,第一次正常,后续发送不同内容,但是缓冲区的数据不变
  7. //                        */
  8. //                DMA_SetCurrDataCounter(DMA2_Stream1,BUFFLEN);//可以去掉设置传输数据长度
  9.                 DMA_Cmd(DMA2_Stream1, ENABLE);        
复制代码
回复

使用道具 举报

0

主题

290

回帖

290

积分

高级会员

积分
290
发表于 2021-4-21 12:06:16 | 显示全部楼层
伊森亨特 发表于 2021-4-21 09:30
我写了,加上设置数量的话,效果不对,我就把那条语句注释掉了

我用了cmsis_os2,代码是附件的,删除了部分头文件,测试的话需要自己添加下。

rs485mb.c

4.21 KB, 下载次数: 30

收发部分

usart.c

4.14 KB, 下载次数: 28

初始化

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-8-16 15:05 , Processed in 0.046697 second(s), 27 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表