linux uart dma模式,DMA和UART的深刻认识--串口接收的3种工作方式(附STM32F4代码)...

可能会遇到的问题:

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

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

8b823fddacf0b197e95e59a3dee537b3.png

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);只是读取一个字节?难道是每个字节的接收都会启动一次中断?

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

5788d1cc83342e33171a890a40e3ccb0.png

本次的实验使用的是: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{

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的配置

50a72c163ce2b217b31c4c3092163460.png

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{

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依据接收的值再发送回串口。乱码为本程序的其它操作可不比在意。

50dd1bba13d342e2e74094c479c2574b.png

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值