STM32F103C8T6 HAL库 USART1 DMA方式接收数据

前言:        

        前面的两篇文章都说关于发送的,HAL库发送数据可以调用现成的函数,而接收数据,现成函数不太好用。这里为了记录了一下自己参考了网上几个大佬的代码,整理了一下USART1 DMA方式接受数据的代码,这里亲测了一下,传输比较稳定,也没有出现发送数据过快导致串口反应不过来的情况。

正文开始:

        Cubemx配置

        这里跟上一篇博客一样,我就不再赘述了

        额外注意的一点是记得勾选上Use MicroLIB

        代码编写:

        这里我是学习了大佬的博客,传送门

        我稍微做了一下改进,这里还是沿用上一篇文章创建的两个文件 USART_DMA.c和USART.h
按照大佬博客里教的。

        ①  定义一个结构体变量:存放接收的字节数、数据数组。        

        ②  开启DMA:让硬件自动接收数据放到缓存        

        ③  重写回调函数:当一帧数据接收好了,把缓存的数据,转存到全局结构体变量里,备用。        

        ④  在需要使用串口接收的地方,如在while中,判断接收字节数>0,  即为接收到新一帧数据了

         在USART_DMA.h中,我们声明一下我们的结构体

typedef struct						//声明一个结构体,方便管理变量
{						
	uint16_t		ReceiveNum;		//接受字节数;在中断回调函数中被自动赋值;只要字节数>0,即为接受到新一帧
	uint8_t		ReceiveData[512];	//接受到的数据
	uint8_t		BuffTemp[512];		//接受缓存;注意,这个数组只是一个缓存
									//临时缓存,在DMA空闲中断中将把一帧数据复制到ReceivedData[] 
}myUATR_TypeDef;

        然后在USART_DMA.c中定义一个自己的变量,如果要在其他.c文件引用的,extern一下就行

myUATR_TypeDef myUSART1 = {0};				//用来定义自己的变量

        我采用的方法是,直接将extern myUATR_TypeDef myUSART1;放在USART_DMA.h里面。 

        开启DMA,让硬件自动接收数据,.

        我们整个接收过程,仅使用到1个HAL库函数。只需在main()函数的初始化部分,调用HAL库函数:HAL_UARTEx_ReceiveToIdle_DMA (串口、缓存、字节数) ;        

        参数:串口、接收缓存区、最大接收字节数          

        作用:使能DMA、使能串口的空闲中断,正式进入接收状态。 

        我们需要在main.c中添加代码

HAL_UARTEx_ReceiveToIdle_DMA(&huart1, myUSART1.BuffTemp, sizeof(myUSART1.BuffTemp));  // 开启DMA空闲中断  

         调用函数后,硬件就会立刻进入自动接收状态:从RX引脚接收到的数据,会逐个字节顺序存放到指定缓存中,这里我们指定的缓存是:myUSART1.BuffTemp

        因为函数内部,开启了DMA中断、空闲中断,所以达成下列两个条件之一,就会触发中断:

        ①  DMA接收的字节数,达到了参数中的最大值      

        ②  串口发生空闲中断,即RX引脚,超过1字节的时间,没有新信号。

        当上述中断产生时,硬件自动调用其相关的中断服务函数,再继而调用回调函数。

         重写DMA空闲中断回调函数,(DMA完成中断、空闲中断,所调用的回调函数):         HAL_UARTEx_RxEventCallback(串口,接收到的字节数);      

        弱函数定义在stm32xx_hal_gpio.c文件的底部。

/******************************************************************************
 * 函  数: HAL_UARTEx_RxEventCallback
 * 功  能: DMA+空闲中断回调函数
 * 参  数: UART_HandleTypeDef  *huart   // 触发的串口
 *          uint16_t             Size    // 接收字节
 * 返回值: 无
 * 备  注: 1:这个是回调函数,不是中断服务函数。技巧:使用CubeMX生成的工程中,中断服务函数已被CubeMX安排妥当,我们只管重写回调函数
 *          2:触发条件:当DMA接收到指定字节数时,或产生空闲中断时,硬件就会自动调用本回调函数,无需进行人工调用;
 *          2:必须使用这个函数名称,因为它在CubeMX生成时,已被写好了各种函数调用、函数弱定义(在stm32xx_hal_uart.c的底部); 不要在原弱定义中增添代码,而是重写本函数
 *          3:无需进行中断标志的清理,它在被调用前,已有清中断的操作;
 *          4:生成的所有DMA+空闲中断服务函数,都会统一调用这个函数,以引脚编号作参数
 *          5:判断参数传进来的引脚编号,即可知道是哪个串口接收收了多少字节
******************************************************************************/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart == &huart1)                                                                    // 判断串口
    {
        __HAL_UNLOCK(huart);                                                                 // 解锁串口状态
 
        myUSART1.ReceiveNum  = Size;                                                          // 把接收字节数,存入结构体xUSART1.ReceiveNum,以备使用
        memset(myUSART1.ReceiveData, 0, sizeof(myUSART1.ReceiveData));                         // 清0前一帧的接收数据
        memcpy(myUSART1.ReceiveData, myUSART1.BuffTemp, Size);                                 // 把新数据,从临时缓存中,复制到xUSART1.ReceiveData[], 以备使用
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1, myUSART1.BuffTemp, sizeof(myUSART1.BuffTemp));   // 再次开启DMA空闲中断; 每当接收完指定长度,或者产生空闲中断时,就会来到这个
    }
}

        (1)xUSART1.ReceiveNum = Size;        

        把接收的字节数,存入结构体 xUSART1.ReceiveNum,以备使用 。            

        在程序的其它地方,判断 ReceivNum > 0, 就能知道是否收到新一帧数据了。

        (2)memset(xUSART1.ReceivedData, 0, sizeof(xUSART1.ReceivedData));        

        清0前一帧的数据缓存  

        (3) memcpy(xUSART1.ReceivedData, xUSART1.BuffTemp, Size);              

        把新数据,从临时缓存中,复制到xUSART1.ReceivedData[], 以备使用                

        从结构体和这段回调函数中,可以发现,这是一个双缓存的操作思路。                      .ReceivedData:用于存放接收后完整的一帧数据,对外使用 。              

        .BuffTemp:用于DMA接收过程,是一个中间缓存。

        (4)HAL_UARTEx_ReceiveToIdle_DMA(&huart1, xUSART1.BuffTemp, sizeof(xUSART1.BuffTemp));              

        再次开启DMA空闲中断,进入接收状态。              

        我们在main()函数的初始化部分,已调用过这个函数了,为什么要在回调函数中再次调用?              

        因为在DMA的中断服务函数里,会关闭DMA,即只接收一次。所以,在接收完一帧后,再次调用函数,就能让DMA开始工作接收下一帧。在这个位置调用 ,能让DMA不断地循环工作。              

        其实,在CubeMX配置中,DMA有一个选项 :Mode的circular, 可以让DMA进行连续地的工作,接收完成后,无需在回调函数里再次开启DMA 

         本篇的处理,是保存最后一帧数据。当有新一帧数据来了,会自动盖掉旧帧数据。

         接下来,为了验证,到底这个项目的程序到底好事使不好使,我在这里写了一个测试函数,其中的myprintf函数是用了我上一篇文章的写法

       

void USART_test()
{
	if(myUSART1.ReceiveNum)			//一旦接受到数据
	{
		myUSART1.ReceiveNum = 0;		//将数据清零
		if(strcmp((char *)myUSART1.ReceiveData,"hello\r\n") == 0)//相等,返回 0;
		{
			num++;
			myprintf("接受次数%d\r\n",num);
		}
	}
}

        将其放在while循环中

        

最终效果演示

        在串口助手这里,简单设置一下 ,一定要注意的是,如果点了发送新行之后,一定不要再额外在第一个箭头的后面加回车了,否则是接受不到的。

         接受的速度也很不错,没有出现过卡死的状况,100ms回应一次。

  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值