【蓝桥杯嵌入式】四、各种外设驱动(八)USART+DMA通信方式和串口通信协议的设计与使用

温馨提示:本文不会重复之前提到的内容,如需查看,请参考附录

【蓝桥杯嵌入式】附录

目录

重点提炼:

一、需求分析

1、需要的外设资源分析:

 2、外设具体分析:

CubeMX配置中,我们需要改动的参数:

USART1:

DMA:

二、软件配置

按照分析配置:

然后配置USART1 的 DMA Settings :

打开并设置中断优先级:

三、程序功能实现

用到的函数:

程序流程:

在MDK中编写代码:

四、运行测试


重点提炼:

用到的函数:

个人认为DMA模式更简单,不用DMA的话需要对接收到的数据做一些处理,而且不能连续接收数据。

一、需求分析

        我们将设计一个示例项目USART_DMA_Demo,进行计算机和开发板之间的串口通信。
        本示例要使用USART1和对应的DMA,还要使用RTC的周期唤醒功能。示例的功能和操作流程如下。

  • 在RTC周期唤醒中断里,读取当前时间后在LCD上显示,将时间转换为字符串之后,通过串口发送给计算机。
  • 在计算机上使用串口监视软件查看接收的数据,并且可以向开发板发送指令数据。
  • MCU 持续以DMA方式进行串口数据接收,接收到一条指令后就解析并执行指令的任务,例如修改当前时间。

        在计算机与开发板的串口通信中,我们一般将计算机称为上位机,将开发板称为下位机。串口的硬件层实现了数据的收发,发送的数据具体是什么意义,需要规定上位机和下位机之间的通信协议。这种通信协议就是传输数据的格式规范及其意义。在本示例中,上位机向开发板发送的串口数据的格式定义如下表所示:

上位机向开发板发送数据的格式定义(xx表示两个位数的数字)
上位机发送的指令字符串指令功能
#Hxx;设置小时,将RTC时间的小时改为xx
#Mxx设置分钟,将RTC时间的分钟改为xx
#Sxx设置秒,将RTC时间的秒改为xx
#U01;或#U00;设置上传数据,或不上传数据

        上位机发送的指令数据固定为5字节,每个指令以#开始,以;结束。紧跟在#后面的一个字母表示指令类型,例如,H表示修改小时,M表示修改分钟。类型字符后面是两位数字,表示指令的参数,例如,"#H13;"表示要将RTC的当前时间的小时数修改为13。
        在计算机上,我们需要使用一个串口通信软件与开发板之间进行串口通信测试。比赛时会提供这样的工具。当波特率设置的和MCU串口的波特率相同时才可以进行通信。串口工具使用起来很简单,只需要设置好串口号和波特率就行,如图:

1、需要的外设资源分析:

  • USART1 (占用引脚PA9和PA10);
  • DMA2;
  • LCD;
  • RTC;

 2、外设具体分析:

查看原理图,和手册。

原理图:

CubeMX配置中,我们需要改动的参数:
USART1:
  • Mode:工作模式。一般设置为Asynchronous(异步);
  • Baud Rate:波特率。CubeMX默认为115200bit/s,那就设置成115200bit/s即可。
  • Data Direction:数据传输方向。
DMA:

在有DMA功能的外设中都有对DMA的设置,找到USART1的DMA设置模块,点击Add即可添加一个DMA通道。

  • Channel: DMA 通道。每个DMA 请求可用的 DMA 通道会自动列出,选择一个即可。
  • Direction:传输方向。也就是DMA传输模式,会根据DMA请求的特性列出可选项。USARTI RX 是USARTI的DMA 数据输入请求,是将USART1接收的数据存入缓冲区,所以方向是Peripheral To Memory(外设到存储器);USART1 TX是USARTI的DMA 数据输出请求,是将缓冲区的数据用 USART1 输出,所以方向是Memory to Peripheral(存储器到外设)。
  • Priority:优先级别。DMA 流的软件优先级别有Low、Medium、

然后点击 Select 选择通道请求,串口有两个请求。

选择一个DMA流对象后,会弹出DMA请求设置栏:

  • Mode:DMA模式。普通模式和循环模式
  • Data Width: 数据宽度。外设和存储器需要单独设置数据宽度,数据宽度选项有Byte.
    Half Word 和 Word。串口传输数据的基本单位是字节,缓冲区的基本单位也是字节。
  • Increment Address:地址自增。这是指DMA传输个基本数据单位后,外设或存储器的地址是否自动增加,地址增量的大小就等于数据宽度。

二、软件配置

参考附录的内容,建立名为“USART_DMA_Demo”的项目。

按照分析配置:

LCD的设置参见附录。

RTC的配置参见上一篇文章的按照分析配置:

除了不打开闹钟外,其他配置相同。

这里只需要设置USART1 的模式为Asynchronous即可,其余保持。

!!容易漏掉的步骤:选择PA9和PA10作为USART1的信号引脚

默认情况下,CubeMX会使用其他引脚,所以需要需改为开发板上的PA9、PA10

在右侧的界面找到PA9和PA10,选择USART1对应模式,如图:

然后配置USART1 的 DMA Settings :

Add两个DMA通道,会自动设置通道号和传输方向;将RX的优先级设置为中优先级,模式设置为循环模式;TX保持默认。如图所示:

打开并设置中断优先级:

        为DMA请求配置DMA 流之后,用到的DMA 流的中断会自动打开。要对DMA流的中断进行响应和处理,就必须开启USART1的全局中断。在NVIC组件里设置中断的优先级,如图所示:

        将USART1和两个DMA 流的中断抢占优先级都设置为1,因为在它们的中断处理函数里会用到函数HAL_Delay()。

生成项目文件后,打开MDK;

导入LCD驱动程序文件。

三、程序功能实现

用到的函数:

中断服务函数:

RX对应的通道的中断服务函数:

DMA1_Channel1_IRQHandler(void)

拓展:DMA模式的中断回调函数其实映射了对应外设的中断回调函数,这里使用中断回调函数更加合理。但由于比赛涉及的中断不多,不会引发冲突,所以这种直接在中断服务函数里实现代码的方式也是可行的。

USART函数:

//txBuffer:要发送的数据指针
HAL_UART_Transmit_DMA(&huart1 ,txBuffer ,strlen(txBuffer));

//rxBuffer:接收数据缓冲区
HAL_UART_Receive_DMA(&huart1 ,rxBuffer ,strlen(rxBuffer));

自定义函数:

解析指令函数

程序流程:

  1. 在初始化HAL库后初始化LCD,之前实验发现中断可能会影响LCD初始化。
  2. 在main.c中定义一些全局变量,在main.h中引入这些变量。
  3. 用DMA方式发送一个“hello”,表示串口正常。
  4. 开启DMA循环接收。
  5. 写RTC中断服务函数(见附录)
  6. 写指令处理函数,为了方便管理,所有的私有函数都预定义在main.h中
  7. 写RX对应的通道的中断服务函数
    1. 复制数据缓冲区并返回给上位机。
    2. 指令解析处理,改变时间值。
    3. 在LCD上显示

在MDK中编写代码:

先在main.c中对应代码段定义全局变量:

/* USER CODE BEGIN PV */
uint16_t cmdLen=5;
uint8_t proBuffer[10]="#S00;\n";
uint8_t rxBuffer[10]="#S00;\n";
uint8_t isUploadTime=1;
/* USER CODE END PV */

main.h中 对应代码段进行引入、函数预定义、引用外部变量。

/* USER CODE BEGIN Includes */
#include "lcd.h"
#include <stdio.h>
#include <string.h>    //用到strlen
/* USER CODE END Includes */
/* USER CODE BEGIN EFP */
void updataRTCTime(void);
/* USER CODE END EFP */
/* USER CODE BEGIN Private defines */
extern uint16_t cmdLen;
extern uint8_t proBuffer[10];
extern uint8_t rxBuffer[10];
extern uint8_t isUploadTime;
/* USER CODE END Private defines */

 初始化HAL库后初始化LCD:

main.c 的 /* USER CODE BEGIN WHILE */ 代码段,编写以下代码

  /* USER CODE BEGIN WHILE */
	uint8_t hello[]="Hello,DMA transmit\n";
	HAL_UART_Transmit_DMA(&huart1,hello,sizeof(hello));
	HAL_UART_Receive_DMA(&huart1,rxBuffer,cmdLen);
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

stm32g4xx_it.c 中找到RTC的中断服务函数:

在如下代码段编写程序:

 代码如下,这里只显示时间,所以注释掉日期显示:

  /* USER CODE BEGIN RTC_WKUP_IRQn 1 */
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sData;
	
	if(HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN) == HAL_OK)
	{
		HAL_RTC_GetDate(&hrtc,&sData,RTC_FORMAT_BIN);
		char str[20];
		//sprintf(str,"Data:20%2d-%2d-%2d",sData.Year,sData.Month,sData.Date);
		//LCD_DisplayStringLine(Line3,str);
		sprintf(str,"Time:%2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds);
		LCD_DisplayStringLine(Line5,str);
		if(isUploadTime)
		{
			sprintf(str,"Time:%2d:%2d:%2d\n",sTime.Hours,sTime.Minutes,sTime.Seconds);
			HAL_UART_Transmit_DMA(&huart1,str,strlen(str));
			HAL_Delay(10);
		}
	}
  /* USER CODE END RTC_WKUP_IRQn 1 */

找到RX对应的通道的中断服务函数,我设置的是通道1:

  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
	for(uint16_t i=0;i<cmdLen;i++)
			proBuffer[i]=rxBuffer [i];
	
	HAL_UART_Transmit_DMA(&huart1 ,rxBuffer ,strlen(rxBuffer));
	HAL_Delay(10);
	updataRTCTime();
	LCD_DisplayStringLine(Line7,proBuffer);
  /* USER CODE END DMA1_Channel1_IRQn 1 */

在 stm32g4xx_it.c 最后面编写自定义函数

/* USER CODE BEGIN 1 */
void updataRTCTime(void)
{
	if(proBuffer[0] != '#')
		return;
	uint8_t timeSection=proBuffer[1];
	uint8_t tmp10=proBuffer[2]-0x30;
	uint8_t tmp1=proBuffer[3]-0x30;
	uint8_t val=10*tmp10+tmp1;
	if(timeSection == 'U')
	{
		isUploadTime=val;
		return ;
	}
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
	if(HAL_RTC_GetTime(&hrtc ,&sTime ,RTC_FORMAT_BIN)==HAL_OK )
	{
		HAL_RTC_GetDate(&hrtc ,&sDate ,RTC_FORMAT_BIN );
		if(timeSection == 'H')
			sTime.Hours = val;
		else if (timeSection == 'M')
			sTime.Minutes = val;
		else if (timeSection == 'S')
			sTime.Seconds = val;
		HAL_RTC_SetTime(&hrtc ,&sTime ,RTC_FORMAT_BIN );
	}
}
/* USER CODE END 1 */

四、运行测试

编译、下载(见附录)。

我的实验一直失败,经过两个小时的排查(额,胡乱排查了一通,,,),最后通过观察官方的示例程序中的串口驱动程序,发现了问题。

最终发现原因如下:

USART1的信号引脚有多组,而默认的选项不是PA9和PA10。我没有修改信号引脚,所以一直没能成功。(想来可笑)

实验结果如下:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值