串口利用DMA接收数据——M0芯片(无空闲中断)
1.可以在没有串口空闲中断的情况下,使用接收中断及定时器中断去完成类似于空闲中断效果。从而去判断一帧的数据是否接收完毕
#1.前言
我做一个模块的时候,需要通过串口接收消息,一开始使用的是串口中断接收,一次中断接收一个字节,后续因为串口接收消息总是消息接收不完全,所以想到了使用DMA搬运串口数据到内存中。在做的时候发现这个串口寄存器没有空闲中断,后续我使用了串口接收中断加定时器中断实现了伪空闲中断。
#2.程序
#1.踩的坑
在这个初始化我就踩坑了,这个厂家的串口使能DMA的库函数居然是有bug的,就是需要操作这个


void UART_DMACmd(UART_TypeDef* UARTx, uint16_t UART_DMAReq, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_UART_ALL_PERIPH(UARTx));
assert_param(IS_UART_DMAREQ(UART_DMAReq));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if(NewState != DISABLE)
{
/* Enable the DMA transfer for selected requests by setting the DMAT and/or
DMAR bits in the UART IDE register */
UARTx->UART_IDE |= UART_DMAReq;
}
else
{
/* Disable the DMA transfer for selected requests by clearing the DMAT and/or
DMAR bits in the UART IDE register */
UARTx->UART_IDE &= (uint16_t)~UART_DMAReq;
}
}
就是调用这个库函数来初始化这个寄存器的时候,这个库函数没有去将使能第六位或者第七位。
我是直接粗暴的赋值解决这个问题的,为了找出这个问题我花了一天时间。解决就半分钟0.0。
UART1->UART_IDE|=0x42;
#2.串口初始化及DMA初始化
我做的这款芯片的初始化,仅供参考:
void DMA_UART1_Init(void)
{
/*UART1_Init write here*/
RCC_APB0Cmd(ENABLE); //开启APB0时钟
RCC_APB0PeriphClockCmd(RCC_APB0Periph_UART1, ENABLE); //开启UART1时钟
UART_InitTypeDef UART_InitStruct;
UART_InitStruct.UART_BaudRate = 115200;// 115200;// 4000000;
UART_InitStruct.UART_ClockFrequency = 72000000;
UART_InitStruct.UART_Mode = UART_Mode_10B;
UART_Init(UART1, &UART_InitStruct);
UART_TXCmd(UART1, ENABLE);
UART_RXCmd(UART1, ENABLE);
UART_ITConfig(UART1,UART_IT_EN|UART_IT_RX,ENABLE);
/*配置UART1接受DMA采集*/
UART_DMACmd(UART1,UART_IT_RX,ENABLE);
UART1->UART_IDE|=0x42;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA, ENABLE);
/*DMA1初始化*/
DMA_InitTypeDef DMA_InitStruct; //定义DMA初始化结构体变量
DMA_StructInit(&DMA_InitStruct);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA, ENABLE);
DMA_InitStruct.DMA_CircularMode = DMA_CircularMode_Disable; /* 使能DMA循环模式 */
DMA_InitStruct.DMA_DataSize = DMA_DataSize_Byte; /* 传输宽度为字节 */
DMA_InitStruct.DMA_TargetMode = DMA_TargetMode_INC; /* 目标地址递增 */
DMA_InitStruct.DMA_SourceMode = DMA_SourceMode_FIXED; /* 源地址不变 */
DMA_InitStruct.DMA_SrcAddress = (uint32_t)&UART1->UART_DATA; /* 源地址为UART接收寄存器 */
DMA_InitStruct.DMA_DstAddress = (uint32_t)uartbuff_rx; /* 目标地址为用户接收缓存区数组 */
DMA_InitStruct.DMA_Request = DMA_Request_UART1_RX; /* DMA触发源为UART1 RX中断 */
DMA_InitStruct.DMA_BufferSize = 128; /* DMA数据量 */
DMA_Init(DMA1, &DMA_InitStruct); /* 配置DMA1 */
DMA_Cmd(DMA1, ENABLE); /* 使能DMA1 */
}
#3.中断服务函数
这个就是我想说的重点了,由于没有串口空闲中断,我怎么去得知一个串口消息是否接收完毕。
我的做法是开启了串口接收中断UART_IT_RX。然后注册更新定时器,直到定时器过期,在定时器中断里去认定这个数据帧接收完毕。
(就是只要在一次RX中断后5ms后未接收到RX中断来更新定时器,定时器便会到期)
RX中断服务函数
void UART1_IRQHandler(void)
{
if(UART_GetFlagStatus(UART1,UART_Flag_RX) != RESET)
{
uart_callback(); //向定时器更新注册
UART_ClearFlag(UART1, UART_Flag_RX);
}
}
然后初始化了一个1ms的定时器。
定时器内的处理函数
if(uart_receive_delay>0)
{
--uart_receive_delay;
if(uart_receive_delay==0)
{
uart_rx_count = RX_BUFF_SIZE-(u16)DMA_GetCurrDataCounter(DMA1);
DMA_Cmd(DMA1,DISABLE);
DMA_Cmd(DMA1,ENABLE);
uart_RECE_Flag = SET;
}
这个uart_receive_delay这个变量就是往定时器里注册的变量。每次串口接收中断,就将这个值设置为5,当它从1变成0就是认为串口接收完一帧完整的数据,然后去获取DMA搬运了多少个数,去得到这帧数据的长度。然后重启DMA,等待下一次搬运数据。
# 3.总结
1.可以在没有串口空闲中断的情况下,使用接收中断及定时器中断去完成类似于空闲中断效果。从而去判断一帧的数据是否接收完毕
2.要多去看芯片手册,看每个寄存器的BIT位置。不要过分相信库函数。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)