×
嵌入式 > 嵌入式开发 > 详情

STM32串口DMA方式接收数据

发布时间:2020-08-12 发布时间:
|
一直以来都为串口接收数据所困扰:
1:如果用接收中断的话,每接收1byte就得中断一次。这样太消耗CPU资源!
2:如果用DMA方式接收数据,那么如何确定接收数据的长度又不好确定了。(比如GPRS模块AT命令的接收!)
3:DMA方式接收+定时器的超时中断。这样处理也比较复杂,需要开定时器,关定时器。。。。个人不喜欢!(ATMEL的ARM系列的串口倒是有硬件超时中断可以直接使用。我现在用AT91SAM7系列处理GPRS的AT命令就采用这种方式,挺好用。但是STM32就没有了,需要自己加定时器,还要硬件处理:RXD连接定时器的一个触发引脚!)。

所以之前用STM32接收串口数据都是采用接收中断,然后写入一个FIFO队列。然后在主函数里面去查询队列缓冲中是否有数据需要处理。但是这样的话,串口中断服务函数始终是很大的硬件开销。比如我现在用串口下载STM32的升级固件的时候,数据量较大。

废话完毕,今天突然脑子发热想要把DMA和环形的FIFO队列结合一下使用。把想法跟同事交流一下,觉得有可行性!马上动手实验。经过半天调试,结果令人满意。
说说我的思路(本人表达能力有限,描述不清楚的希望大家跟帖):关在在于让DMA来实现“环形队列中往缓冲区写入1byte”的功能!剩下的读取队列就跟普通环形队列没多大区别了。这样我们的程序中拥有了一个不占用CPU资源的“环形队列”后,我们就不用担心CPU频繁中断,我们只需要在适当的时间读取队列中的数据然后慢慢分析处理数据!
A:串口初始化配置串口为DMA方式接收数据。具体配置请看:

DMA1_Channel5->CCR = DMA_CCR5_PL //通道优先级最高
| DMA_CCR5_MINC //MEM地址增量使能
| DMA_CCR5_CIRC //接收缓冲区循环模式
| DMA_CCR5_TCIE //传输完成中断
;
DMA1->IFCR |= 0x000F0000;
DMA1_Channel5->CPAR = USART1_BASE + 4;
// Enable the DMA1_CH5 Interrupt
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

关键,开启DMA循环模式,这样接收完之后会自动回到FIFO缓冲区开头地方,这样能省不少事情。
当然,考虑到可能把缓冲区撑爆的情况,所以开启通道传输完成标志位,在传输完成中断中查询一下队列中有多少数据没有读取出来,如果太多数据没有读取,那么在中断里面处理读取FIFO数据并做相应处理!

B:关于FIFO的一些声明:
#define FIFO_OK 0
#define FIFO_ERROR_PARAM -1
#define FIFO_ERROR_MEM -2
#define FIFO_ERROR_FULL -3
#define FIFO_ERROR_EMPTY -4
#define FIFO_ERROR_BUSY -5

typedef struct _FIFO_TYPE_
{
INT32U size; //FIFO缓冲区大小
INT32U front; //FIFO下一读取位置
INT32U staraddr; //FIFO缓冲区起始地址
INT32U endaddr; //FIFO缓冲区结束地址
INT8U buffer[1]; //实际长度由初始化分配内存!(memloc的时候确定)
}FIFOTYPE;


C:关于FIFO队列的初始化,具体配置
//
//函数:FIFO_Init
//参数:FIFO类型的指针地址,队列大小
//返回:>=0初始化成功
//描述:初始化FIFO队列
//
Int32S FIFO_Init(FIFOTYPE * *fifo,INT32U fifosize)
{
volatile INT32U da;
if(fifo==NULL || fifosize == 0)
{
return FIFO_ERROR_PARAM;
}
(*fifo) = malloc(16+fifosize);
if((*fifo) == NULL)
{
//已经在堆里面申请了地址
return FIFO_ERROR_MEM;
}
(*fifo)->size = fifosize;
(*fifo)->staraddr = (INT32U)(&(*fifo)->buffer[0]); //记录FIFO缓冲区起始地址
(*fifo)->endaddr = (INT32U)(&(*fifo)->buffer[fifosize-1]); //记录FIFO缓冲区结束地址
(*fifo)->front = (*fifo)->staraddr; //FIFO下一读取数据地址
memset((*fifo)->buffer,0,(*fifo)->size); //清除缓冲区里面的数据,可省略

DMA1_Channel5->CCR &= ~DMA_CCR5_EN;
DMA1_Channel5->CMAR = (INT32U)(*fifo)->staraddr; //配置DMA传输地址
DMA1_Channel5->CNDTR = (*fifo)->size; //配置DMA传输数据量
da = USART1->DR;
da = da;
DMA1->IFCR |= 0x000F0000;
DMA1_Channel5->CCR |= DMA_CCR5_EN;

return FIFO_OK;
}

D:清空队列缓冲区函数
//
//函数:FIFO_Clear
//参数:无
//返回:无
//描述:清空FIFO队列
//
Int32S FIFO_Clear(FIFOTYPE *fifo)
{
volatile INT32U da;
if(fifo == NULL)
return FIFO_ERROR_PARAM;
fifo->front = fifo->staraddr; //将下一读取地址设置为FIFO缓冲开始
DMA1_Channel5->CCR &= ~DMA_CCR5_EN;
DMA1_Channel5->CMAR = fifo->staraddr; //重新配置DMA地址
DMA1_Channel5->CNDTR = fifo->size; //重新配置DMA传输数据量
memset(fifo->buffer,0,fifo->size);
da = USART1->DR;
da = da;
DMA1->IFCR |= 0x000F0000;
DMA1_Channel5->CCR |= DMA_CCR5_EN;

return FIFO_OK;
}

E:读取FIFO缓冲区,这个跟标准的环形队列基本没区别
//
//函数:FIFO_Read
//参数:队列指针,1byte数据指针
//返回:>=0读取成功
//描述:从FIFO队列中读出1byte数据
//
Int32S FIFO_Read(FIFOTYPE *fifo,INT8U *data)
{
if(fifo == NULL )
return FIFO_ERROR_PARAM;
if(FIFO_Status(fifo)==0)
{
return FIFO_ERROR_EMPTY;
}
*data = (INT8U)(*((INT8U *)(fifo->front)));
if(fifo->front == fifo->endaddr)
{
fifo->front = fifo->staraddr;
}
else
{
fifo->front++;
}
return FIFO_OK;
}

F:获取缓冲区的数据量
//
//函数:FIFO_Status
//参数:队列指针
//返回:>0队列中有未读出数据
//描述:获取FIFO队列状态
//
INT32S FIFO_Status(FIFOTYPE *fifo)
{
INT32S res;
INT32S nextsave = (INT32S)fifo->endaddr + 1 - (INT32S)DMA1_Channel5->CNDTR;
res = nextsave- (INT32S)(fifo->front);
if(res < 0)
{
res = ( (INT32S)(fifo->endaddr)+1 - (INT32S)(fifo->front) ) + (nextsave - (INT32S)fifo->staraddr);
}
return res;
}
说明:
1:STM32的DMA_CMAR传输地址寄存器不会随传输数据量的变化而真正的指向下一个存储位置(AT91SAM就是总是指向下一个存储地址的)。所以我需要根据传输数量寄存器DMA_CNDTR来推算下一传输位置寄存器!
2:需要考虑环形队列写入指针已经重新回到缓冲区开头了,而读取指针还在缓冲区尾部的情况!


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
NXP推出Wi-Fi 6E三频段SOC 充分释放6GHz频谱潜力