可能会遇到的问题:
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{
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{
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。