STM32串口使用(HAL库)指南

零、为什么写这篇博客?

写程序的时候串口时陪伴我们DEBUG的好伙伴,同时也在一些项目中充当着数据上传的主要通道。因为它用得太频繁了,所以把一些常用的代码和使用心得写在这里,在新建项目的时候可以很方便的抄代码和规避错误。(本文主要针对HAL库)

一、串口的几种使用方法

1、轮询方式

发送和接收主要使用下面两个HAL库的函数。

/*串口发送*/
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/*串口接收*/
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

如果填充了Size个字节,就会立即接收/发送,如果超时没有接收/发送完毕Size个字节的数据,就会向下执行。

2、中断方式

/*中断方式串口发送*/
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

 使用之后,系统可以去干别的事,系统通过中断将数据发送出去,每发送一个字节才会进入一次中断。它不能在执行完第一次发送之前进行下一次发送,否则下一次不会执行。如果硬要连续调用两次,需要用while循环来判断HAL_UART_Transmit_IT的返回值来进行等待,直到当HAL_UART_Transmit_IT的返回值为不忙的时候,才跳出循环。这样还不如使用轮询来得直观,反正时间上差不多。

/*中断方式串口接收*/
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

使用之后,系统可以去干别的事,之后会自动在每次收到字节的时候用中断来进行接收,当接收满了Size个字节长度的数据时,会产生一个接收完成的中断。在我们自己的程序中,通过在接收中断回调函数中放入一个标志位,来让相关的程序知道什么时候需要进行数据处理。但是这个函数有一个坏处就是不能接收不定长的数据,只有接收满了Size个字节才会进行中断,如果Size为1确实看上去可以接收不定长的数据,但大量的中断会占用很多时间。(可用FIFO解决)

3、DMA方式

/*DMA方式串口发送*/
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

 使用后,串口会使用DMA来发送数据。DMA和CPU是同时干活的,CPU只要一句话,DMA就可以不需要CPU控制就能进行数据搬运,大大节约了资源。但要注意的是:同样的,它不能在执行完第一次发送之前进行下一次发送,否则下一次不会执行。我前段时间使用串口DMA连续发送大量数据的时候,发现DMA只执行第一个,想了好久才找到这个问题,建议合理操控间隔,或者设置标志位进行等待。

/*DMA方式串口接收*/
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

使用后,串口会使用DMA来传输数据。

特别注意!DMA初始化必须在串口初始化之前,否则就不会工作,CubeMXyou一个缺点就是如果你一开始只开了串口,之后添加DMA再生成代码,DMA的初始化会在串口初始化之后。

二、串口重定向

1、串口重定向的代码(直接开抄!)

(1)STM32 HAL库(需要勾选MicroLib的方法)

/*可调用printf*/
int fputc(int ch,FILE *f)
{
    /*&huart1指的是串口1,如果用别的串口就修改数字*/
    HAL_UART_Transmit(&huart1 , (uint8_t *)&ch , 1 , 0xffff);
    return ch;
}

HAL_UART_Transmit这个函数的功能是通过串口1(huart1),把传入的无符号8位形参通过串口传出,传输长度为1,也就是一次发送一个字节。 0xffff是超时时间,因为你开串口的时候用的是systick,所以是毫秒为单位(HAL库中只有少量的TimeOut是us级别,大部分都是ms级别),如果在这个和时间之内没有接收到相应长度(这里为1)的字节,就会向后执行。该方法需要在编译器中勾选微库(MicroLib),在使用printf的文件中要包含stdio.h

//可调用scanf及getchar
int fgetc(FILE *f)
{
int ch;
/* 等待串口输入数据 */
/*&huart1指的是串口1,如果用别的串口就修改数字*/
while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == RESET);
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return (ch);
}

(2)STM32 HAL库(不需要勾选微库MicroLib的方法)

#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       

//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
}
 
//加入2行代码,即可解决报错
//__use_no_semihosting was requested, but _ttywrch was 
void _ttywrch(int ch)
{
    ch = ch;
}

//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0){};//循环发送,直到发送完毕   
    USART1->DR = (uint8_t) ch;      
	return ch;
}
#endif

这种方法依然需要在使用的文件中包含stdio.h

(3)STM32标准库

//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(USARTx, (uint8_t) ch);
		/* 等待发送完毕 */
		while (!USART_GetFlagStatus(USARTx, USART_FLAG_TXE));	
		return (ch);
}
 
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (!USART_GetFlagStatus(USARTx, USART_FLAG_RXNE));
 
		return (int)USART_ReceiveData(USARTx);
}

2、为什么要串口重定向?

因为单片机编程条件下没有现成的从串口发送出数据的函数,因为单片机是硬件,不同的单片机有不同的寄存器配置方法将数据从串口发送出去。所以我们要对其中的某些部分进行重写,把正在使用的单片机的串口输出的代码和printf融合在一起。

总所周知,printf其实底层也是调用了fputc来一个一个char进行输出的,所以我们要重写这个程序,这样printf调用fputc的时候,fputc就会执行我们写在里面的程序一个一个char的从单片机串口输出了。

物联沃分享整理
物联沃-IOTWORD物联网 » STM32串口使用(HAL库)指南

发表评论