/***********************************************
描述:基于CubeMX+HAL库的STM32串口发送、接收配置大全,详细内容可查看下方目录。
功能:各种常用的配置大全,可以方便的挑选合适的配置快速开发
平台:STM32F723-DISCO,除F7特有的自适应波特率外,其余配置对各种含有串口的STM32单片机均适用
作者:Miss_若星
时间:2019/10/20
工程:博客不包含代码工程,所作更改都是基于前面的步骤
说明:因作者水平有限,不能保证100%的正确性,以下内容仅供大家参考使用,有不当之处还请指出。
**************************************************/
目录
一、串口发送
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、同样的方法,使用其他波特率也同样适用。
HAL_UART_Transmit( &huart6 , (uint8_t *)"hello DISCO\r\n" , sizeof("hello DISCO\r\n"), 0xFFFF);
/* 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); } }
My_String_Printf("hello DISCO\r\n");
#include "stdio.h"
#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;
}
printf("hello DISCO\r\n");
在嵌入式系统中,通过串口打印log是非常重要的调试手段,但是直接调用底层驱动打印信息非常不方便,在c语言中一般使用printf打印基本的显示信息,而默认printf的结果不会通过串口发送,解决方法主要有两种:
半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如 printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和键盘。
简单的来说,半主机模式就是通过仿真器实现开发板在电脑上的输入和输出。和半主机模式功能相同的是ITM调试机制。ITM是ARM在推出semihosting之后推出的新一代调试机制。这两种机制的运行均需要仿真器,否则无法运行。
使用微库的话,不会使用半主机模式.
microlib 是缺省 C 库的备选库。 它用于必须在极少量内存环境下运行的深层嵌入式应用程序。 这些应用程序不在操作系统中运行。
#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;
}
模式选择:
FIFO:
注:我使用的优先级分组为4:
HAL_UART_Transmit_DMA( &huart6, (uint8_t *)"hello DISCO by DMA\r\n", sizeof("hello DISCO by DMA\r\n") );
但是如果不打开串口中断的话,程序只能发送一次数据,之后便无法将串口数据发送出来,调试看到husart中的gState位没有被复位,一直不是READY状态:
在嵌入式系统中,printf是一个相对比较占资源的函数,调试过程中如果过多地使用printf甚至会影响程序本来的结果。
stm32具有DMA,可以把程序运行过程中的调试信息通过DMA的方式打印出来,无疑提高了程序运行的效率。要通过DMA方式把串口的内容格式化打印出来,重定向是不可能的了,唯一办法是自己实现一个printf函数。
#include "stdarg.h"
其中使用了C语言的标准库函数
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);
}
使用方法和标准库的printf函数一样。
void DMA_printf(const char *format, ...);
DMA_printf("hello DISCO by DMA\r\n");
在主函数的主循环中添加测试代码,最后一个输入参数1为超时时间,单位为ms,表示1
ms内如果没有收到数据,函数就退出,返回超时。
if( HAL_OK == HAL_UART_Receive( &huart6, &pData, 1, 1) )
DMA_printf("Receive Data:%c\r\n",pData);
这种轮询的方法非常占用CPU,一般不使用。
在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);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if( huart == &huart6 )
{
HAL_UART_Transmit( &huart6, &pData, 1, 20);
HAL_UART_Receive_IT( &huart6, &pData, 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);
}
}
if( BSP_PB_GetState( BUTTON_USER ) == GPIO_PIN_SET )
{
HAL_Delay(500);
BSP_LED_Toggle(LED_RED);
for(i=0;i
(第2.3.7步为循环模式):
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);
}
}
但一次发送50个数据时,便出现了严重的数据丢失现象:
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);
}
}
接收文件数据也是可行的:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if( huart == &huart6 )
{
UART6_RXD_BUFF[UART6_RXD_COUNT] = TEMP_BUFF;
UART6_RXD_COUNT++;
}
}
使用模式三,0x55帧确定当前通信波特率。
生成代码后发现初始化函数中的波特率有一个预设值为:115200
GUI_DispDecAt ( 108000000/(huart6.Instance->BRR) , 0, 20, 7);
因为USART6是挂载在APB2总线上的,所以它的工作时钟为108MHz
从官方的参考手册中可以得知:USARTDIV 是一个存放在 USARTx_BRR 寄存器中的无符号定点数。即为分频值,按照给出的公式,可以计算出当前的波特率。
可以看到屏幕上显示115138,十分接近115200的默认波特率。
在波特率为256000的情况下,发送一串以字母U为开头的字符串,可以看到显示屏上的数字变成了257142。
缺点:配置成模式3,在每次上电之后都需要让对方先发送一个0x55的字符,才可以确定波特率,之后一直保持此波特率不变,否则串口不会正常工作。