DMA和UART的深刻认识--串口接收的3种工作方式(附STM32F4代码)
阅读原文时间:2021年04月20日阅读:1

可能会遇到的问题:

1.能实现接收但不发送 注意是否是识别函数出错

2.DMA单次传输模式要求再初始化,否者出现第二次中断不执行。使用循环模式出现的问题是要结合配置公式:

3.DMA再次初始化不完全,会出现接收一次成功,再来一次不行。第三次能接收的问题

4.串口调试连续点击的次数太快,会使的里面的发送程序出错

一.串口uart中断接收

 遇到的问题:

1、串口调试接收引脚坏掉

2.接收数据识别,使用的库函数出错

串口设置的一般步骤可以总结为如下几个步骤:
1) 串口时钟使能, GPIO 时钟使能。
2) 设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。
3) GPIO 初始化设置:要设置模式为复用功能。
4) 串口参数初始化:设置波特率,字长,奇偶校验等参数。
5) 开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。
6) 使能串口。
7) 编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。

其中串口中断服务程序的解析(正点原子):

  void USART1_IRQHandler(void)                    //串口1中断服务程序
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
    {
        Res =USART_ReceiveData(USART1);//(USART1->DR);  //读取接收到的数据

        if((USART_RX_STA&0x8000)==0)//接收未完成 相当于一个循环 一开始肯定进入这里 因为赋初值为0
        {
            if(USART_RX_STA&0x4000)//接收到了0x0d
             {
                if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
                else USART_RX_STA|=0x8000;  //接收完成了 
             }
            else //还没收到0X0D
            {   
                if(Res==0x0d)USART_RX_STA|=0x4000;
                else
                {
                    USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
                    USART_RX_STA++;
                    if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收      
                }   
            }
    }
}

上面的程序接收部分为正点原子编写的一段程序,具体的思路是:

     定义了接收状态寄存器USART_RX_STA总共16位,由于串口接收到数据开始产生中断,在中断函数里启动接收指令,一次读取一个字节,根据标志位收到倒数第二个字节0x0d 和最后一个字节0x0a代表数据接收完。因此在上面的中断处理函数中它做的判断是:依据读取的数据判断接收是否完成,或者接收到数据了若接收到14位了表明之前已经判断是接收到0x0d,并将该位置位。若这次没有收到数据0x0a说明接收出错。所以两个变量的判断很关键:USART_RX_STA和Res(接收的单个字节);

如果接收的字节还没有到14位 而且当前的res不是0x0d,则接收缓冲区数组递增,通过USART_RX_STA++的方式。若是累加的数目要大于已知的数据长度,则说明多接收了,肯定出问题,但为什么是减一,原因是USART_RX_STA是从0开始的,也就是累加到2时已经代表3个数据了(C语言的知识)同时将USART_RX_STA=0,重新开始接收。

但是问题是:中断接收字符串,使用Res =USART_ReceiveData(USART1);只是读取一个字节?难道是每个字节的接收都会启动一次中断?

答:这和我们启动的中断方式有关,下图为串口中断的方式:

本次的实验使用的是:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);因此数据寄存器非空即来中断。

因此每个字节的接收都会来一次中断。因此后面我们才会要使用DMA的方式。

发现这位作者写的不错http://blog.sina.com.cn/s/blog_776077610102vgqg.html 

在主函数内的操作:

1.中断优先级分组 NVIC_Configuration();

2.调用初始化函数 uart_init(9600);  

串口中断完成,接收数据正常。

为了方便查看正确的状态,启用串口发送模式

串口发送,在上面我们已经启动了收发模式

为此发送时判断上次传输完成以后:

while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);  //等待上次传输完成。

启动发送命令:

USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]);        //发送数据到串口1

一次发一个字节。

判断发送数组的长度,并开启循环发送:如下所示:

void u3_printf(char* fmt,...) 
{  
    u16 i,j;
    va_list ap;
    va_start(ap,fmt);
    vsprintf((char*)USART1_TX__ins_BUF,fmt,ap);
    va_end(ap);//以上的语句可以对输入的字符串按照一定的格式存储
    i=strlen((const char*)USART1_TX__ins_BUF);//此次发送数据的长度
    for(j=0;j<i;j++)//循环发送数据
    {
      while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);  //等待上次传输完成 
        USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]);   //发送数据到串口1
    }
}

发送部分完成,可以结合接收的数据发送指定的字符;在这里就会涉及到对接收字符的识别问题:1.可利用正则表达式 2.库函数 3. 指定连续字符条件判断

其中库函数这个作者写的不错:https://blog.csdn.net/u013071074/article/details/27692933 

需要注意的是:strcmp库函数实现的是两个字符串的比较,相等才为0,为此串口发送的数据为带有回车换行0x0d 和0x0a。

二、串口DMA中断接收

配置成uart+DMA的方式,那么uart的收模式不配置中断,同时配置DMA中断,固定字节接收。(即是搬运数据满了才DMA中断)

1.uart的初始化去掉NVIC的配置

2.DMA配置

  void MYDMA_Config_Rx(DMA_Stream_TypeDef*DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr) 

  (1)使能DMA的时钟 并等待数据流可配置(这里注意你要使用的DMA时钟为哪个1或2)

  (2).设置外设地址

  (3).设置存储器地址

  (4).设置传输数据量

  (5).设置DMA数据流的配置信息

  (6)使能DMA数据流,启动传输

  (7)中断配置

 注意的是根据是发送和接收选择:外设到内存 or内存到外设方向

 正常传输模式和循环模式要注意,正常传输则传输完一次后DMA结束,下次要重启DMA的配置信息才能再次使用。可以选用循环模式,但是有一个问题是当接收的数据超过指定的接收长度时会出错。

中断的选择为:

DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);//使能DMA2流2的传输完成中断

3.DMA的中断服务函数

void DMA2_Stream2_IRQHandler(void)
{
    uint16_t pro=0;
    Blue_receive_Flag = 1;
if(Blue_receive_Flag == 1)      //
{
 Receive_data_process();//接收的数据判断 DMA接收 下一步通过串口
 Blue_receive_Flag = 0;
//发送应答 同上面的DMA 发送
}     
    if(DMA_GetITStatus(DMA2_Stream2,DMA_IT_TCIF2)!= RESET)     //DMA传输完成标志
    {
//        DMA_Cmd(DMA2_Stream2, DISABLE);                        //关闭USART1 RX DMA2 所指示的通道  
//        pro =  DMA_GetCurrDataCounter(DMA2_Stream2);           //获取DMA通道的DMA缓存的大小
        DMA_Cmd(DMA2_Stream2, ENABLE);                            //使能USART1 RX DMA2 所指示的通道        
        DMA_ClearITPendingBit(DMA2_Stream2,DMA_IT_TCIF2);      //清除中断标志  
              USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收    
          MYDMA_Enable(DMA2_Stream2,USART_REC_LEN);     //开始一次DMA传输!  
    }
}

4.在主函数中需要初始化uart  MYDMA_Config_Rx

uart(9600);
MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)USART_RX_BUF,USART_REC_LEN);

5.启动uart 和DMA 

DMA_Cmd(DMA_Streamx, ENABLE);
 USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收

6.关闭DMA才可以设置

DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输 

while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}    //确保DMA可以被设置  

DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //数据传输量  

//设置完成再次启动即可

DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输

直至完成了串口DMA接收的配置

而且在上面中指定DMA搬送的缓存区是USART_RX_BUF,字节长度USART_REC_LEN。因此在处理函数中可以直接操作此数组USART_RX_BUF例如识别数组中含有的字符串,并作出相应的判断比如发送接收的内容或者其它指定的字符。

三、不定长度数据的DMA接收

采用UART中断+DMA搬运

1.uart初始化

 加上uart的中断配置同时开启空闲中断

USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断 修改使用的是空闲中断

Usart1NVIC 配置

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0x02;        //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器、

2.DMA配置去掉中断

 开启DMA_Cmd(DMA2_Stream2, ENABLE);  //正式驱动DMA传输

3.中断服务函数void USART1_IRQHandler(void) 

判断当uart产生空闲的中断时,从uart读值以清除中断标志

 (1)关键的一点是:

Usart1_Rec_Cnt = DMA_REC_LEN-DMA_GetCurrDataCounter(DMA2_Stream2);    //算出接本帧数据长度

其中的DMA_GetCurrDataCounter获得了当前剩余缓冲区的大小

(2)并将接收的数据再次发送出去:

len为Usart1_Rec_Cnt buf[t]为缓冲区DMA_Rece_Buf

for(t=0;t<len;t++)        //循环发送数据
{           
 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);      
 USART_SendData(USART1,buf[t]);
}    

(3)由于是单次传输,因此传输完成一次,要再次初始化DMA并清除中断标志

USART_ClearITPendingBit(USART1, USART_IT_IDLE);         //清除中断标志
MYDMA_Enable(DMA2_Stream2,DMA_REC_LEN);//可以实现实时调节数据传输量
MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)DMA_Rece_Buf, DMA_REC_LEN);

4.在主函数中初始化DMA和uart即可

完成

结果:其中可看到接收到22字节 和29字节的,原因是先使用串口调试助手发送给MCU,MCU依据接收的值再发送回串口。乱码为本程序的其它操作可不比在意。

参考http://www.openedv.com/thread-63849-1-1.html 改进,这里的程序单次可接收中断,但是下一次时不行,原因是没有再次初始化DMA。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章