基于CubeMX HAL库的STM32串口发送、接收配置

/***********************************************

描述:基于CubeMX+HAL库的STM32串口发送、接收配置大全,详细内容可查看下方目录。

功能:各种常用的配置大全,可以方便的挑选合适的配置快速开发

平台:STM32F723-DISCO,除F7特有的自适应波特率外,其余配置对各种含有串口的STM32单片机均适用

作者:Miss_若星

时间:2019/10/20

工程:博客不包含代码工程,所作更改都是基于前面的步骤

说明:因作者水平有限,不能保证100%的正确性,以下内容仅供大家参考使用,有不当之处还请指出。

**************************************************/


目录

扫描二维码关注公众号,回复: 16703971 查看本文章

一、串口发送

1.1、普通发送模式

1.1.1、模式配置:

1.1.2、中断配置:

1.1.3、生成代码,打开工程,在主函数的循环前面添加测试语句,输出字符串:

1.1.4、编译下载,打开串口调试助手,复位:

1.2、使用自定义printf函数

1.2.1、编写函数用于发送字符串

1.2.2、在主函数中调用:

1.2.3、测试结果:

1.3、使用标准的printf函数

1.3.1、在usart.h文件中包含头文件:

1.3.2、在用户代码区添加输出重定向代码:

1.3.3、主函数中调用:

1.3.4、测试结果同样是可以使用

1.4、半主机模式与C库

1.4.1、半主机模式

1.4.2、微库

1.4.3、标准库,禁用半主机模式,添加重定向

1.4.4、另外的调试方法

1.5、DMA发送模式

1.5.1、配置串口发送DMA

1.5.2、在NVIC菜单下配置他的中断优先级为5和6

1.5.3、生成代码,打开工程,在主函数中添加代码:

1.5.4、编译下载,复位:

1.6、DMA方式使用printf函数

1.6.1、在usart.c文件中包含头文件:

1.6.2、在用户代码区定义一个新的函数DMA_printf:

1.6.3、在头文件中声明函数,在主函数中调用。

1.6.4、下载验证:

二、串口接收

2.1、轮询接收模式

2.1.1、在CubeMX中配置

2.1.2、生成代码

2.2、中断接收模式

2.2.1、打开串口的全局中断:

2.2.2、在NVIC选项里修改它的优先级:

2.2.3、生成代码

2.2.4、编译下载

2.2.5、收发速度测试

2.3、DMA接收模式

2.3.1、配置CubeMX为DMA接收,方式选择Normal

2.3.2、设置DMA接收中断优先级为4,打开串口接收全局中断:

2.3.3、中断回调函数配置如下所示:

2.3.4、测试10个数据时没有数据丢失现象:

2.3.5、修改中断回调函数只保存,不发送

2.3.6、可以发现最终实现的效果还是很理想的

2.3.7、修改DMA为连续工作方式

2.3.8、则接收回调函数可以写成如下内容,不需要每次都重新打开DMA。

2.3.9、测试文件传输结果如下,没有数据丢失现象:

三、自动波特率

3.1.1、在CubeMX上配置为自动波特率

3.1.2、在主函数中添加一句显示当前波特率的程序:

3.1.3、编译下载

3.1.4、使用串口助手测试

3.1.5、按下按键可以看到数据成功返回:

3.1.6、同样的方法,使用其他波特率也同样适用。



一、串口发送

1.1、普通发送模式

1.1.1、模式配置:

  1. 数据长度:为包含了奇偶校验位的数据长度,上图设置为8,奇偶校验位为0.所以数据位宽就是8.
  2. 过采样:见下图
  3. 单个采样点:见下图

  1. Overrun数据溢出检测:见下图:

  1. 接收错误时禁止DMA:详细解释参考下图:

1.1.2、中断配置:

1.1.3、生成代码,打开工程,在主函数的循环前面添加测试语句,输出字符串:

HAL_UART_Transmit( &huart6 , (uint8_t *)"hello DISCO\r\n" , sizeof("hello DISCO\r\n"), 0xFFFF);

1.1.4、编译下载,打开串口调试助手,复位:

1.2、使用自定义printf函数

1.2.1、编写函数用于发送字符串

/*  used for a string send by usart */ void My_String_Printf( uint8_t* String ) {  while( *String != '\0' ){   HAL_UART_Transmit( &huart6 , (uint8_t *)(String++), 1, 0xFFFF);  } }

1.2.2、在主函数中调用:

My_String_Printf("hello DISCO\r\n");

1.2.3、测试结果:

1.3、使用标准的printf函数

1.3.1、在usart.h文件中包含头文件:

#include "stdio.h"

1.3.2、在用户代码区添加输出重定向代码:

#ifdef __GNUC__
  /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
	HAL_UART_Transmit( &huart6 , (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}

1.3.3、主函数中调用:

printf("hello DISCO\r\n");

1.3.4、测试结果同样是可以使用

1.4、半主机模式与C库

在嵌入式系统中,通过串口打印log是非常重要的调试手段,但是直接调用底层驱动打印信息非常不方便,在c语言中一般使用printf打印基本的显示信息,而默认printf的结果不会通过串口发送,解决方法主要有两种:

1.4.1、半主机模式

半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如 printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和键盘。

简单的来说,半主机模式就是通过仿真器实现开发板在电脑上的输入和输出。和半主机模式功能相同的是ITM调试机制。ITM是ARM在推出semihosting之后推出的新一代调试机制。这两种机制的运行均需要仿真器,否则无法运行。

1.4.2、微库

使用微库的话,不会使用半主机模式.

microlib 是缺省 C 库的备选库。 它用于必须在极少量内存环境下运行的深层嵌入式应用程序。 这些应用程序不在操作系统中运行。

1.4.3、标准库,禁用半主机模式,添加重定向

#pragma import(__use_no_semihosting)    	//不使用半主机模式
         
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 
 
FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	USART_SendData(USART1,ch);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);  
	return ch;
}   

1.4.4、另外的调试方法

  1. Jlink自带的RTT:速度更快,也不依赖编译工具,只要有JLink即可。
  2. JScope:图形化显示数据调试。

1.5、DMA发送模式

1.5.1、配置串口发送DMA

模式选择:

  1. Normal正常模式,DMA发送一次就停止发送;
  2. Circular循环模式,会一直发送数据;

FIFO:

  1. DMA每个数据流都有一个独立的4字FIFO,阈值级别可由软件配置为1/4、1/2、3/4或满。可以用于字、半字、字节之间的转换。如下图所示:

  1. 打开DMA通道后,会自动开启DMA的一个中断,此处应该将串口全局中断也打开:

1.5.2、在NVIC菜单下配置他的中断优先级为5和6

注:我使用的优先级分组为4:

1.5.3、生成代码,打开工程,在主函数中添加代码:

HAL_UART_Transmit_DMA( &huart6, (uint8_t *)"hello DISCO by DMA\r\n", sizeof("hello DISCO by DMA\r\n") );

1.5.4、编译下载,复位:

但是如果不打开串口中断的话,程序只能发送一次数据,之后便无法将串口数据发送出来,调试看到husart中的gState位没有被复位,一直不是READY状态:

1.6、DMA方式使用printf函数

在嵌入式系统中,printf是一个相对比较占资源的函数,调试过程中如果过多地使用printf甚至会影响程序本来的结果。

stm32具有DMA,可以把程序运行过程中的调试信息通过DMA的方式打印出来,无疑提高了程序运行的效率。要通过DMA方式把串口的内容格式化打印出来,重定向是不可能的了,唯一办法是自己实现一个printf函数。

1.6.1、在usart.c文件中包含头文件:

#include "stdarg.h"

1.6.2、在用户代码区定义一个新的函数DMA_printf:

其中使用了C语言的标准库函数 <stdarg.h>,参考资料:

https://www.runoob.com/cprogramming/c-standard-library-stdarg-h.html

uint8_t DMA_PRINTF_BUFF[100];
void DMA_printf(const char *format, ...)
{
	uint32_t length;
    va_list args;
		
    va_start(args, format);
    length = vsnprintf((char *)DMA_PRINTF_BUFF, sizeof(DMA_PRINTF_BUFF), (char *)format, args);
    va_end(args);

    HAL_UART_Transmit_DMA(&huart6, (uint8_t *)DMA_PRINTF_BUFF, length);
}

1.6.3、在头文件中声明函数,在主函数中调用。

使用方法和标准库的printf函数一样。

void DMA_printf(const char *format, ...);

DMA_printf("hello DISCO by DMA\r\n");

1.6.4、下载验证:

二、串口接收

2.1、轮询接收模式

2.1.1、在CubeMX中配置

2.1.2、生成代码

在主函数的主循环中添加测试代码,最后一个输入参数1为超时时间,单位为ms,表示1

ms内如果没有收到数据,函数就退出,返回超时。

if( HAL_OK == HAL_UART_Receive( &huart6, &pData, 1, 1) )
	DMA_printf("Receive Data:%c\r\n",pData);

这种轮询的方法非常占用CPU,一般不使用。

2.2、中断接收模式

2.2.1、打开串口的全局中断:

2.2.2、在NVIC选项里修改它的优先级:

2.2.3、生成代码

在usart.c中添加接收完成回调函数:

uint8_t pData = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if( huart == &huart6 )
	{
		DMA_printf("Receive Data:%c\r\n",pData);
		HAL_UART_Receive_IT( &huart6, &pData, 1);
	}
}

头文件中声明外部变量:

extern uint8_t pData;

主函数开头设置初始化:

HAL_UART_Receive_IT( &huart6, &pData, 1);

2.2.4、编译下载

2.2.5、收发速度测试

  1. 将接收回调函数配置成收到数据立刻发送的状态:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if( huart == &huart6 )
	{
		HAL_UART_Transmit( &huart6, &pData, 1, 20);
		HAL_UART_Receive_IT( &huart6, &pData, 1);
	}
}
  1. 设置串口助手一次发送10个字节,可以看到串口返回来的数据也是10个字节,没有数据丢失,设置自动发送时间200ms,数据同样没有丢失。

  1. 当一次发送180个字节的时候,就会出现数据丢失的现象:

  1. 考虑出现此问题的原因可能是因为接收速度不够,或者是因为串口发送数据的速度不够,所以修改代码如下,测试是否是因为串口发送数据太慢限制了收发性能,设置接收回调函数中只对数据进行保存,不发送,在主函数中判断按键是否按下,按下了就将收到数据发送出去。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if( huart == &huart6 )
	{
		UART6_RXD_BUFF[UART6_RXD_COUNT] = pData;
		UART6_RXD_COUNT++;
		HAL_UART_Receive_IT( &huart6, &pData, 1);
	}
}
  1. 同时在主循环中添加如下的扫描按键的程序

if( BSP_PB_GetState( BUTTON_USER ) == GPIO_PIN_SET )
{
	HAL_Delay(500);
	BSP_LED_Toggle(LED_RED);
	for(i=0;i<UART6_RXD_COUNT;i++)
	{
		HAL_UART_Transmit( &huart6 , (uint8_t *)&UART6_RXD_BUFF[i] , 1, 0xFFFF);
		UART6_RXD_BUFF[i] = UART6_RXD_BUFF[i];
	}
	UART6_RXD_COUNT=0;
}
  1. 测试,可以看到,数据收发的数量是一致的,说明使用中断方式接收串口数据的速度是挺快的,只要在回调函数中不运行过多的代码,就可以保证数据完整传输。

  1. 附带测试一个文件传输,以此程序的main.c为例,将其发送到单片机。可以看到收发数据是一致的。

2.3、DMA接收模式

2.3.1、配置CubeMX为DMA接收,方式选择Normal

(第2.3.7步为循环模式):

2.3.2、设置DMA接收中断优先级为4,打开串口接收全局中断:

2.3.3、中断回调函数配置如下所示:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if( huart == &huart6 )
	{
		HAL_UART_Transmit( &huart6 , (uint8_t *)&pData , 1, 0xFFFF);
		HAL_UART_Receive_DMA( &huart6, &pData, 1);
	}
}

2.3.4、测试10个数据时没有数据丢失现象:

但一次发送50个数据时,便出现了严重的数据丢失现象:

2.3.5、修改中断回调函数只保存,不发送

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if( huart == &huart6 )
	{
		UART6_RXD_BUFF[UART6_RXD_COUNT] = pData;
		UART6_RXD_COUNT++;
		//HAL_UART_Transmit( &huart6 , (uint8_t *)&pData , 1, 0xFFFF);
		HAL_UART_Receive_DMA( &huart6, &pData, 1);
	}
}

2.3.6、可以发现最终实现的效果还是很理想的

接收文件数据也是可行的:

2.3.7、修改DMA为连续工作方式

2.3.8、则接收回调函数可以写成如下内容,不需要每次都重新打开DMA。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if( huart == &huart6 )
	{
		UART6_RXD_BUFF[UART6_RXD_COUNT] = TEMP_BUFF;
		UART6_RXD_COUNT++;
	}
}

2.3.9、测试文件传输结果如下,没有数据丢失现象:

三、自动波特率

3.1.1、在CubeMX上配置为自动波特率

使用模式三,0x55帧确定当前通信波特率。

生成代码后发现初始化函数中的波特率有一个预设值为:115200

3.1.2、在主函数中添加一句显示当前波特率的程序:

GUI_DispDecAt ( 108000000/(huart6.Instance->BRR)  , 0, 20, 7);

因为USART6是挂载在APB2总线上的,所以它的工作时钟为108MHz

从官方的参考手册中可以得知:USARTDIV 是一个存放在 USARTx_BRR 寄存器中的无符号定点数。即为分频值,按照给出的公式,可以计算出当前的波特率。

3.1.3、编译下载

可以看到屏幕上显示115138,十分接近115200的默认波特率。

3.1.4、使用串口助手测试

在波特率为256000的情况下,发送一串以字母U为开头的字符串,可以看到显示屏上的数字变成了257142。

3.1.5、按下按键可以看到数据成功返回:

3.1.6、同样的方法,使用其他波特率也同样适用。

缺点:配置成模式3,在每次上电之后都需要让对方先发送一个0x55的字符,才可以确定波特率,之后一直保持此波特率不变,否则串口不会正常工作。

猜你喜欢

转载自blog.csdn.net/qq_37147721/article/details/102645679