STM32:从串口接收数据的精彩之旅

STM32 —— 串口接收数据

我们已经在前面的博客中讲过了串口通信中发送数据和中断的一些基本原理,这里主要介绍串口接收数据的相关内容

定长字符串的接收

当接收单字节时,我们就可以使用最简单的接收方式即可,这种接收方式,我们只需要调用对应接口的中断函数,每一个串口都有对应的中断函数,每次中断只能接收一串定长数据,然后利用接收的函数 USART_ReceiveData ,以及接收的标志位状态,当我们的接收状态没有在的时候,我们就可以接收字符了,接收字符我们可以定义一个数组用来接收收到的字符,同时接收完字符后,就要清除这个标志位状态,这样我们才是一个接收完整的流程。

接收字符串主要有两种方法,一种是对中断函数进行改造,另一种是对接收回调函数进行改造

在讲解这两种方法之前,我们需要了解一个函数:

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

该函数的作用是用户自定义一个缓冲区(即参数 pData),接收一定(固定)数量(参数 Size 决定)的字符串存入缓冲区,同时,参数 Size 数量还决定着进入回调函数的频率,即每接收 Size 个字符,就进入一次回调函数

Size 只决定进入回调函数的频率,而不能影响进入接收中断的频率,无论 Size 是多少,每接收完成一个字符都会进入一次接收中断

改造回调函数

首先在主函数中进入主循环前的位置调用一次 HAL_UART_Receive_IT 函数,定义一个字符数组 getBuffer[] 作为缓冲区,参数Size 设定为 10。即每接收 10 个字符,就进入一次回调函数

image

重写中断函数,在 stm32f1xx_it.c 中找到 void USART1_IRQHandler(void) 函数,重写为:

void USART1_IRQHandler(void) {
	HAL_UART_IRQHandler(&huart1); //该函数会清空中断标志,取消中断使能,并间接调用回调函数
}

在文件 stm32l4xx_hal_uart.h 中,我们可以看到串口接收回调函数的定义。使用 _weak 关键字定义的函数,其具有如下特性:

一般情况下和一般函数相同,但是当有一个同名函数但是不带 __weak 被定义时,所有对这个函数的调用都是指向后者(不带 __weak 那个)。也就是说,ST 官方提供的这个回调函数需要我们自己进行改写:

#define COUNTOF(a) (sizeof(a)/sizeof(*(a)))	// 计算字符串 / 数组长度
uint8_t myBuffer[] = "I have gotten your message: ";	//用户提示信息
uint8_t Enter[] = "\r\n";	//回车换行
uint8_t getBuffer[100];	//用户自定义的缓冲区
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart1)
{
	while(HAL_UART_Transmit(huart1, (uint8_t*)myBuffer, COUNTOF(myBuffer), 5000)!= HAL_OK);	//发送字符串,用户提示信息
	while(HAL_UART_Transmit(huart1, (uint8_t*)getBuffer, 10, 5000)!= HAL_OK);	//发送用户自定义缓冲区中的数据
	while(HAL_UART_Transmit(huart1, (uint8_t*)Enter, COUNTOF(Enter), 5000)!= HAL_OK);	//发送回车换行
}

以上代码的作用是把用户发送给单片机数据再返回给用户。运行效果如下图:

image

方法改进

我们可以看到,用户向单片机发送了 10 个字符,单片机向串口助手返回了这 10 个数据。但是以上程序只能实现一次,当我们再次向单片机发送数据时,单片机却不再返回数据。这是因为我们在中断函数中取消了中断使能,所以导致了进入一次中断后,中断被关闭,无法再次进入中断的现象。为了实现多次数据返回,我们要在中断处理函数中添加一行代码:

void USART1_IRQHandler(void)
{
	HAL_UART_IRQHandler(&huart1); //该函数会清空中断标志,取消中断使能,并间接调用回调函数
	HAL_UART_Receive_IT(&huart1, (uint8_t *)getBuffer,10);  //添加的一行代码
}

这样就可以实现多次数据返回了,新的执行结果如下图:

image

可见,函数 HAL_UART_Receive_IT 还有中断使能的作用。这一功能的实现我们可以在 HAL_UART_Receive_IT 函数中找到

改造中断处理函数

首先在主函数中进入主循环前的位置调用一次 HAL_UART_Receive_IT 函数,定义一个字符 value 作为缓冲区,参数 Size 设定为1 。即每接收 1 个字符,就进入一次回调函数。使得进入回调函数的频率与进入中断处理函数的频率相同。这样,我们就可以直接在中断函数中对接收的数据进行处理了

image

重写中断函数

#define COUNTOF(a) (sizeof(a)/sizeof(*(a)))	// 计算字符串 / 数组长度
uint8_t myBuffer[] = "I have gotten your message: ";
uint8_t getBuffer[10];
uint8_t Enter[] = "\r\n";
int countofGetBuffer = 0;
void USART1_IRQHandler(void)
{
	HAL_UART_IRQHandler(&huart1); //该函数会清空中断标志,取消中断使能,并间接调用回调函数
	getBuffer[countofGetBuffer++] = value;
	if(countofGetBuffer == 10)
	{
		while(HAL_UART_Transmit(&huart1, (uint8_t*)myBuffer, COUNTOF(myBuffer), 0xFFFF)!= HAL_OK);
		while(HAL_UART_Transmit(&huart1, (uint8_t*)getBuffer, countofGetBuffer, 0xFFFF)!= HAL_OK);
		while(HAL_UART_Transmit(&huart1, (uint8_t*)Enter, COUNTOF(Enter), 0xFFFF)!= HAL_OK);
		countofGetBuffer = 0;
	}
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&value,1);  //由于接收中断是每接收一个字符便进入一次,所以这一行代码必须添加,否则只能接收一个字符,而无法接收整个字符串
}

以上代码的作用是接收每个来自用户的字符,并依次存入用户自定义的缓冲区中,数量达到 10 个后,将缓冲区中的所有数据返回给用户,同时清空计数,准备接下来 10 个字符的接收。运行效果如下图:

image

回调函数与中断函数关系

其实是这样的,单片机每完成接收一个字符,就会进入一次中断处理函数,而在中断处理函数中,我们又调用了函数 void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) ,该函数会间接调用回调函数,也就是说回调函数是由中断处理函数间接调用的

而函数 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) 决定了中断处理函数调用回调函数的频率,若 Size 为 1 ,则每进入一次中断处理函数都会调用一次回调函数;若 Size 为 10 ,则每第十次进入中断处理函数时,才会调用回调函数。方法 2 使用了标准库中断处理数据的思想

不定长但固定发送内容字符串的接收

前面我们已经提到了改造中断处理函数的接收数据方法,我们接收已知内容字符串的方法与之十分相似

前面在改造中断处理函数方法中,我们已知的是字符串的长度,所以我们判断的是符串的长度,这里我们已知的是字符串的内容,所以,这里我们直接判断字符串的内容即可

image

uint8_t myBuffer[] = "I have gotten your message: ";
char getBuffer[100];
uint8_t Enter[] = "\r\n";
char value;
char str1[] = "ppqppl"; 
char str2[] = "Hello ppqppl!"; 
uint8_t out1[] = "str1";
uint8_t out2[] = "str2";
int countofGetBuffer = 0;
void USART1_IRQHandler(void)
{
	HAL_UART_IRQHandler(&huart1); //该函数会清空中断标志,取消中断使能,并间接调用回调函数
	getBuffer[countofGetBuffer++] = value;
	if(strcmp(str1,getBuffer) == 0)	// 这里把判断长度改为判断内容
	{
		while(HAL_UART_Transmit(&huart1, (uint8_t*)myBuffer, COUNTOF(myBuffer), 0xFFFF)!= HAL_OK);
		while(HAL_UART_Transmit(&huart1, (uint8_t*)out1, COUNTOF(out1), 0xFFFF)!= HAL_OK);
		while(HAL_UART_Transmit(&huart1, (uint8_t*)Enter, COUNTOF(Enter), 0xFFFF)!= HAL_OK);
		countofGetBuffer = 0;
		memset(getBuffer,0,COUNTOF(getBuffer));
	}
	else if(strcmp(str2,getBuffer) == 0)
	{
		while(HAL_UART_Transmit(&huart1, (uint8_t*)myBuffer, COUNTOF(myBuffer), 0xFFFF)!= HAL_OK);
		while(HAL_UART_Transmit(&huart1, (uint8_t*)out2, COUNTOF(out2), 0xFFFF)!= HAL_OK);
		while(HAL_UART_Transmit(&huart1, (uint8_t*)Enter, COUNTOF(Enter), 0xFFFF)!= HAL_OK);
		countofGetBuffer = 0;
		memset(getBuffer,0,COUNTOF(getBuffer));
	}
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&value,1);  //由于接收中断是每接收一个字符便进入一次,所以这一行代码必须添加,否则只能接收一个字符,而无法接收整个字符串
}

这里在使用 strcmp 的时候需要引用 string.h 头文件

这样我们就能根据不同的内容执行不通过的命令,但是这种方法有一个弊端,就是输入的字符串必须在判断范围内,否则将再无相应,只能手动 reset

运行效果如下:

image

这种方法只适合有选择性的输入(设置只能输入固定的语句1或语句2),而不适合开放性输入,如果是开放性输入,则会出现前面输入错误,导致必须手动 reset 的效果,十分的不方便

不定长字符串的输入

这里涉及到 DMA 相关内容,请看我的另一篇博客:STM32 —— 串口不定长数据接收 DMA 详解

参考文档

  1. STM32串口接收中断——基于HAL库

  2. STM32_串口接收中断_实现定长数据接收(1)

  3. 记录STM32中的函数

物联沃分享整理
物联沃-IOTWORD物联网 » STM32:从串口接收数据的精彩之旅

发表评论