蓝桥杯CT117E_M4(G431)-USART+DMA
1、功能实现及思路
功能:串口控制LED灯
思路:在串口接收回调函数中存储收到的数据,并根据\r\n来判断数据是否接收完整,然后根据相应指令开关灯
2、STM32CubeMX配置
2.1、USART1配置
串口模式选择异步通讯
DMA设置通过下面的Add添加两个DMA通道
2.2、时钟配置
配置系统时钟为80MHz
3、相关函数
3.1、HAL_UART_Receive_IT()
- 函数功能:用于开启串口接收(需要注意每次接收完成后需要重新调用开启接收)
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
-
huart:UART句柄
-
pData:指向数据缓冲区(u8或u16数据元素)的指针(即你自己定义的数组或者变量)
-
Size:要接收的数据元素(u8或u16)的数量
只有当接收到Size个数据后才会进入回调函数HAL_UART_RxCpltCallback()
深入分析:该函数会将数据缓冲区的地址存储在UART句柄中的pRxBuffPtr变量中,当有数据进入后,其实还是跟标准库一样,会首先进入USART1_IRQHandler()这个中断服务函数,然后在中断服务函数中在调用HAL库的中断服务函数HAL_UART_IRQHandler(&huart1),然后在这个函数中去读取收到的数据并通过pRxBuffPtr地址往我们指定的数据缓冲区(即数组)里写入数据,并且在最后才会进入回到函数 HAL_UART_RxCpltCallback() 中,因此在HAL库开发中不像在标准库需要我们自己去读取寄存器的值并存储下来,而是可以直接在回调函数中去处理缓冲区的值。
3.2、HAL_UART_Receive_DMA()
- 函数功能:用于开启DMA串口接收(需要注意每次接收完成后需要重新调用开启接收)
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
-
huart: UART句柄
-
pData: 指向数据缓冲区(u8或u16数据元素)的指针(即你自己定义的数组或者变量)
-
Size: 要接收的数据元素(u8或u16)的数量
这个函数与上面3.1的函数区别在于,该函数通过DMA进行串口数据传输和保存,当串口传输的数据量较大时,通过DMA来保存接收到的数据可以释放CPU资源以执行其他任务,通过这个函数开启接收不会进入串口中断服务函数,这是与上面3.1函数最大的区别,但是最后都会进入回调函数HAL_UART_RxCpltCallback() 中进行数据处理。
3.3、HAL_UART_Transmit_IT()
- 函数功能:串口发送函数
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
参数与上面接收一致,只不过把接收变成了发送
3.4、HAL_UART_Transmit_DMA()
- 函数功能:将要发送的数据通过DMA传给串口进行发送
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
3.5、HAL_UART_RxCpltCallback()
-
接收完成回调函数
-
函数功能:串口接收回调函数,当数据接收完成后会进入该回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
该函数需要重写,并再函数中对收到的数据进行分析处理
3.6、HAL_UART_RxHalfCpltCallback()
- 半接收完成回调函数
- 函数功能:当UART接收到一个字节的数据时,该函数会被HAL库自动调用,用于处理接收到一半数据包的情况
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
与上面函数不同在于,只要有数据进来就会进入该回调函数,而3.1则在数据接收完成才会进入
举个栗子:
int main()
{
HAL_UART_Receive_DMA(&huart1,&Rx_Buffer[0],2); //开启串口接收
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
/* 点亮PC8 */
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_UART_Receive_DMA(&huart1,&Rx_Buffer[0],2); //继续接收
}
}
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
/* 点亮PC15 */
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
}
在该栗子中,当通过串口发送一个数据时会点亮PC15,只有发送了两个数据才会点亮PC8,因为在开启接收函数中设置了要接收的数据长度为2
4、编写回调函数
在通过STM32CubeMX生成代码之后,我们只需要在代码中重写回调函数即可,然后通过串口给单片机发送"LED1 OFF"即可点亮LED1,注意串口助手需要勾选发送新行,即每次发送数据后面都会跟\r\n,通过判断接收\r\n来表示数据是否发送完成
uint8_t Rx_Buffer[200];
uint8_t Rx,Rx_Length=0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
if(Rx == '\n')
{
Rx_Buffer[Rx_Length-1] = 0;//把保存的\r换行符去掉
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
if(strcmp((char*)Rx_Buffer,"LED1 OFF")==0)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_UART_Transmit_DMA(&huart1,(uint8_t*)"LED1已打开",strlen("LED1已打开"));
}
else if(strcmp((char*)Rx_Buffer,"LED1 ON")==0)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);
HAL_UART_Transmit_DMA(&huart1,(uint8_t*)"LED1已关闭",strlen("LED1已关闭"));
}
if(strcmp((char*)Rx_Buffer,"LED2 OFF")==0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_9,GPIO_PIN_RESET);
else if(strcmp((char*)Rx_Buffer,"LED2 ON")==0)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_9,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
memset(Rx_Buffer,0,Rx_Length);
Rx_Length = 0;
}
else
Rx_Buffer[Rx_Length++] = Rx;
HAL_UART_Receive_DMA(&huart1,&Rx,1);
}
}
主函数中只需要启动第一次接收即可
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1,&Rx,1);//开启串口接收
while (1)
{
}
}
串口助手
特别的
1、strcmp()
#include <stdio.h>
#include <string.h>
int main() {
unsigned char p[20] = "hello 1";
p[5] = 0;
if (strcmp("hello", (char *)p)==0)
printf("相等");
else
printf("不相等");
}
判断字符串p和“hello”是否相等,strcmp会比较str1和str2每一个字符,当出现字符不同时比较大小,str1字符比str2大则返回一个大于0的数,小则返回一个小于0的数
结果为相等,当字符串出现有字符为0则判断比较结束,两个字符串相等
2、memset()
#include <stdio.h>
#include <string.h>
int main() {
unsigned char p[5];
unsigned int t[5];
memset(p, 1, 5);
memset(t, 1, 5);
printf("%d\n", p[0]);
printf("%d\n", t[0]);
printf("%x", t[0]);
}
void *memset(void *str, int c, size_t n)
通过memset设置数组数值,memset用于给指定数组设置某个c值,并可以选择设置要修改多少个值,需要注意的是memset的修改规则是按字节进行修改,即将数组中的每一个字节都修改成设定的c值,因此当对大于1个字节的数组进行修改时需要注意
结果为: