1. 问题:
- 1、空闲中断和别的接收完成(一个字节)中断,发送完成(发送寄存器控)中断的一样是串口中断;
- 2、空闲中断是接收到一个数据以后,接收停顿超过一字节时间 认为桢收完,总线空闲中断是在检测到在接收数据后,数据总线上一个字节的时间内,没有再接到数据后发生。也就是RXNE位被置位之后,才开始检测,只被置位一次,除非再次检测到RXNE位被置位,然后才开始检测下一次的总线空闲。一次RXNE位被置位只进行一次。
2. 实现思路:
- 确定串口,并配置成空闲中断模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。
- 那么初始化完成之后,当外部给单片机发送数据的时候,假设这帧数据长度是100个字节,那么在单片机接收到一个字节的时候(RXNE位被置位)并不会产生串口中断,而是DMA在后台把数据默默地搬运到你指定的缓冲区里面。当整帧数据发送完毕之后(接收停顿超过一字节时间)串口才会产生一次中断,此时可以利用DMA_GetCurrDataCounter();函数计算出本次的数据接受长度,从而进行数据处理。
- 就是在串口空闲中断中使用 Usart1RecLen = DMARecLen-DMA_GetCurrDataCounter(DMA1_Channel5); //算出接本帧数据长度 = DMA缓存的大小 - 剩余 DMA缓存大小
3. 配置时候注意:
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断
DMA_InitStructure.DMA_BufferSize = DMA_Rec_Len; //DMA通道的DMA缓存的大小
4. 乒乓缓存(DMA常常搭配乒乓缓存进行)
优势:
- 乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
- 节省缓冲区空间
- 可以使用低速模块处理高速数据
附:DMA部分示意代码
串口之DMA初始化步骤:(在代码中对比着看,加深理解)
RXEN = 1:移位寄存器中的内容已经转移到RDR
RXNEIE =1:产生中断
串口使用DMA进行发送,可以通过设置USART_CR3寄存器上的DMAT位激活。只要TXE位被置起,就从配置成使用DMA外设的SRAM区装载数据到USART_DR寄存器。为 USART的发送分配一个DMA通道的步骤如下(x表示通道号):
1. 在DMA控制寄存器上将USART_DR寄存器地址配置成DMA传输的目的地址。在每个TXE事件后,数据将被传送到这个地址。
2. 在DMA控制寄存器上将存储器地址配置成DMA传输的源地址。在每个TXE事件后,数据将从此存储器区传送到USART_DR寄存器。
3. 在DMA控制寄存器中配置要传输的总的字节数。
4. 在DMA寄存器上配置通道优先级。
5. 根据应用程序的要求配置在传输完成一半还是全部完成时产生DMA中断。
6. 在DMA寄存器上激活该通道。
7. 当DMA控制器中指定的数据量传输完成时,DMA控制器在该DMA通道的中断向量上产生一中断。在中断服务程序里,软件应将USART_CR3寄存器的DMA位清零。 \
注意: 如果DMA被用于发送,不要使能TXEIE位。
串口使用DMA进行接收,可以通过设置USART_CR3寄存器的DMAR位激活。只要接收到一个字节,数据就从USART_DR寄存器放到配置成使用DMA的SRAM区(参考DMA技术说明)。为USART的接收分配一个DMA通道步骤如下(x表示通道号):
1. 通过DMA控制寄存器把USART_DR寄存器地址配置成传输的源地址。在每个RXNE事件后此地址上的数据将传输到存储器。
2. 通过DMA控制寄存器把存储器地址配置成传输的目的地址。在每个RXNE事件后,数据将从USART_DR传输到此存储器区。
3. 在DMA控制寄存器中配置要传输的总的字节数。
4. 在DMA寄存器上配置通道优先级。
5. 根据应用程序的要求配置在传输完成一半还是全部完成时产生DMA中断。
6. 在DMA控制寄存器上激活该通道。通用同步异步收发器(USART)
7. 当DMA控制器中指定的传输数据量接收完成时,DMA控制器在该DMA通道的中断矢量上产生一中断。在中断程序里, USART_CR3寄存器的DMAR位应该被软件清零。
注意: 如果DMA被用来接收,不要使能RXNEIE位。
四.程序设计 这里摘取了部分代码讲解,完整代码及工程见附件下载哦。
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作源头
DMA_InitStructure.DMA_BufferSize = dma_len; //BUF大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 这两项的配置还是很好理解的,比如在这里我们是要将内存里边的东西发到USART1中去,每次发送8位,那么外设地址当然不能改变,而每一次发送内容都是不一样的,而且数组在内存中的存放就是递增的,所以内存地址寄存器要递增。
下边是设置数据宽度:
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常缓存模式(非连续传输) 一些基本定义:
#define dma_len 100 //定义串口DMA传输数据长度
//(如果串口一次接收数据没有达到dma_len个byte,则不会发生DMA中断)
extern u8 USART1_DMA_Buf1[dma_len];
//BUF1 extern u8 USART1_DMA_Buf2[dma_len];
//BUF2 typedef enum {BUF_NO1=0,BUF_NO2=1}BUF_NO;
extern BUF_NO Free_Buf_Now;
extern bool Buf_Ok;
乒乓思想在这里体现:
void DMA1_Channel5_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC5))
{
//DataCounter = DMA_GetCurrDataCounter(DMA1_Channel5); //获取剩余长度,一般都为0,调试用
DMA_ClearITPendingBit(DMA1_IT_GL5); //清除全部中断标志
//转换可操作BUF
if(Free_Buf_Now==BUF_NO1) //如果BUF1空闲,将DMA接收数据赋值给BUF1
{
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART1_DMA_Buf1;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
Free_Buf_Now=BUF_NO2;
}else //如果BUF2空闲,将DMA接收数据赋值给BUF2
{
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART1_DMA_Buf2;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
Free_Buf_Now=BUF_NO1;
}
Buf_Ok=TRUE;
}
}
这是主函数:
void main()
{
Delay_Init(72); //系统延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //初始化与按键连接的硬件接口
USART_Config( USART1,115200);
// USART_Config( USART2,115200); //com2与led1,led2 接口复用
USART_Config( USART3,115200); //com3
DMA_UARTToMemConfig();//串口DMA配置
NVIC_Config(); //
while (1)
{
//////////////////////////////////////////////////////////////////////////////////////
if(Buf_Ok==TRUE)//BUF可用 (在dma中断中置位)
{
Buf_Ok=FALSE;
x1=0;
x2=0;
if(Free_Buf_Now==BUF_NO1)//如果BUF1空闲
{
while(x1<dma_len)
{
USART_PutChar(USART1,USART1_DMA_Buf1[x1++]); //用串口1将BUF1中数据发送出去
}
} else //如果BUF2空闲
{
while(x2<dma_len)
{
USART_PutChar(USART1,USART1_DMA_Buf2[x2++]); //用串口1将BUF2中数据发送出去
}
}
}
网友评论