#1.前言

我做一个模块的时候,需要通过串口接收消息,一开始使用的是串口中断接收,一次中断接收一个字节,后续因为串口接收消息总是消息接收不完全,所以想到了使用DMA搬运串口数据到内存中。在做的时候发现这个串口寄存器没有空闲中断,后续我使用了串口接收中断加定时器中断实现了伪空闲中断。

#2.程序

#1.踩的坑

在这个初始化我就踩坑了,这个厂家的串口使能DMA的库函数居然是有bug的,就是需要操作这个

UARTn_IDE 寄存器的时候
它对应的库函数
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位置。不要过分相信库函数。

Logo

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

更多推荐