温馨提示:本文不会重复之前提到的内容,如需查看,请参考附录
【蓝桥杯嵌入式】附录
目录
重点提炼:
一、需求分析
1、需要的外设资源分析:
2、外设具体分析:
CubeMX配置中,我们需要改动的参数:
USART1:
DMA:
二、软件配置
按照分析配置:
然后配置USART1 的 DMA Settings :
打开并设置中断优先级:
三、程序功能实现
用到的函数:
程序流程:
在MDK中编写代码:
四、运行测试
重点提炼:
用到的函数:
一、需求分析
我们将设计一个示例项目USART_DMA_Demo,进行计算机和开发板之间的串口通信。
本示例要使用USART1和对应的DMA,还要使用RTC的周期唤醒功能。示例的功能和操作流程如下。
- 在RTC周期唤醒中断里,读取当前时间后在LCD上显示,将时间转换为字符串之后,通过串口发送给计算机。
- 在计算机上使用串口监视软件查看接收的数据,并且可以向开发板发送指令数据。
- MCU 持续以DMA方式进行串口数据接收,接收到一条指令后就解析并执行指令的任务,例如修改当前时间。
在计算机与开发板的串口通信中,我们一般将计算机称为上位机,将开发板称为下位机。串口的硬件层实现了数据的收发,发送的数据具体是什么意义,需要规定上位机和下位机之间的通信协议。这种通信协议就是传输数据的格式规范及其意义。在本示例中,上位机向开发板发送的串口数据的格式定义如下表所示:
上位机发送的指令字符串 | 指令功能 |
---|---|
#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即可,其余保持。
然后配置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函数:
HAL_UART_Transmit_DMA(设备句柄地址,数据指针,数据大小);
HAL_UART_Receive_DMA(设备句柄地址,缓冲区指针,数据大小);
自定义函数:
解析指令函数
程序流程:
- 在初始化HAL库后初始化LCD,之前实验发现中断可能会影响LCD初始化。
- 在main.c中定义一些全局变量
- 用DMA方式发送一个“hello”,表示串口正常。
- 开启DMA循环接收。
- 写RTC中断服务函数(见附录)
- 写指令处理函数,为了方便管理,所有的私有函数都预定义在main.h中
- 写RX对应的通道的中断服务函数
- 复制数据缓冲区并返回给上位机。
- 指令解析处理,改变时间值。
- 在LCD上显示
在MDK中编写代码:
在main.h中 对应代码段进行引入、函数预定义、引用外部变量。
/* USER CODE BEGIN Includes */
#include "lcd.h"
#include <stdio.h>
/* 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 */
在 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的中断服务函数:
在如下代码段编写程序:
代码如下,这里只显示时间,所以注释掉日期显示:
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);
}
找到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 ,cmdLen +1);
HAL_Delay(10);
updataRTCTime();
LCD_DisplayStringLine(Line7,rxBuffer);
/* 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];
uint8_t tmp1=proBuffer[3];
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 */
四、运行测试
编译、下载(见附录)。