本文开发环境:
- MCU型号:STM32F051R8T6
- IDE环境: MDK 5.25
- 代码生成工具:STM32CubeMx 5.0.1
- HAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series)
本文内容:
- STM32CubeMx 配置异步串口初始化代码
- 串口数据的收发
- 重定义printf
- 调试建议
- 串口性能测试
所需工具:
硬件:USB转串口模块
软件:串口助手
串口是MCU最常用的外设之一,通常用来和传感器等通讯或作为调试打印输出口,在学习笔记(2)中通过IO口的初始化,也介绍了新建一个工程的流程,这里我们演示在一个已经存在的工程上,进一步配置串口。首先我们打开工程所在目录:
DemoPro是建立工程时候的名字,它的文件类型为STM32CubeMx,双击打开后就可以开始配置串口了(通常都是使用异步模式,这里也以异步串口为例)。在弹出的串口中,默认应该就是打开Pinout&Configuration选项,我们选择USART1,然后配置Mode,最后设置好基本参数,基本参数的配置要根据具体情况,当通讯的双方需要配置一致才可以正常的通讯。具体操作如下图所示:
这里还建议串口的发送IO口配置为上拉模式(如果你的电路没有外部上拉),否则可能电平不稳定情况。比如如果使用默认的No pull-up no pull Down模式,那么开启中断后,可能会出现误触发情况:
最后配置完毕,直接点击右上角 GENERATE CODE 即可生成代码。
我们打开工程文件中stm32f0xx_hal_uart.h文件,在1291行开始,可以发现HAL库给我们提供了不少的API函数,堵塞收发,中断收发,DMA,回调函数等等:
/* IO operation functions *****************************************************/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
... ...
/* Transfer Abort functions */
HAL_StatusTypeDef HAL_UART_Abort(UART_HandleTypeDef *huart);
HAL_StatusTypeDef HAL_UART_AbortTransmit(UART_HandleTypeDef *huart);
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
... ...
HAL库的注释似乎标准库详细准确很多,每一个函数在定义处都会给出使用方法,这里就不一一介绍,仅以常用的接收发送函数举例:
while (1)
{
uint8_t recv_data[1]; //接收数据
uint8_t send_data[1]; //发送数据
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(!HAL_UART_Receive(&huart1,recv_data,1,500)) //如果接收到一个数据
{
HAL_UART_Transmit(&huart1,recv_data,1,500); //返回接收到的数据
}
}
其它的工程保持不变,我在while(1)中添加了以上2行代码,它用来把收到的数据返回出去。
HAL_UART_Receive()函数拥有四个参数:
所以 HAL_UART_Receive(&huart1,recv_data,1,500) 表示每次使用接收异步串口1,接收1个字节,存放在data为首的地址中,超时时间为500ms。超时以后则返回一个表示超时的错误码,程序不再等待,继续往下执行,避免卡死。
HAL_UART_Transmit()函数通用拥有四个参数,其原理和HAL_UART_Receive()一样,只不过是发送。
在STM32F051中,我测试接受比较大的数据时,参数Size为1并不是一个好的选择,由于HAL函数封装程度高,效率也有所影响,频繁的调用函数容易造成数据溢出,在上文代码中,如果每次发送的数据较多,就要多次调用HAL_UART_Receive()函数,所以如果你要每次要接收256个字节,建议使用Size为256的函数:
HAL_UART_Receive(&huart1,recv_data,256,5000)
这样,当你收到256个字节整后,函数才会被调用一次,同时建议:每次发送数据的间隔最好控制在超时时间内,也就是说尽量不要让该函数超时,在测试过程中,这很容易引起丢包现象。
我们重定义了fputc()函数,这样就可以方便的使用printf来打印调试数据了,毕竟M0/M0+ 系列没有ITM,具体代码如下所示,我代码放在了main.c中:
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1,(unsigned char *)&ch,1,0x200);
return ch;
}
这样当我们使用printf的时候,就可以在串口助手出看到打印信息。
理论上来讲,串口的配置是简单的,函数也很简单,一般都可以一次性调通。但是实际操作中,我们可能发现初始化后,使用发送函数发送数据,但是串口助手观察不到数据,这是有几种情况造成的:我们首先要保证我们调试的工具是正常使用的:
将串口转USB模块中插入电脑,在设备管理器中看到有串口驱动,如下图所示:
(该窗口可由 我的电脑 右键 管理 调出)
如果出现感叹号,或插入USB转串口模块后显示无法设别,那就应该安装驱动,假如你不知道如何查找驱动,可以尝试使用驱动精灵或询问卖家索要一份驱动。
接着我们短接USB转串口模块的RX和TX,这样使用串口助手发送的数据,它会原封不动的返回给串口助手,如果你可以正常的收到发出的数据,那么证明串口模块和串口助手都是正常工作的。
如果在排除硬件和上位机软件的原因后,可以尝试复位MCU,有时候下载选项没有勾选**“Reset and Run”**导致每次下载程序后都需要复位,当然似乎有时候勾选了,还是需要复位才可以正常运行。
如果以上方式都已经排除,那么则很可能是代码的原因,我们需要重新检查一下代码,如果你有一个参考的demo,则会方便很多。
建议通讯之前,可以先了解串口的性能,比如在测试过程中,可以通过不断发送接收到的数据来测试串口。我测试时的函数如下:
while (1)
{
uint8_t recv_data[512];
uint8_t send_data[512];
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(!HAL_UART_Receive(&huart1,recv_data,512,5000))
{
HAL_UART_Transmit(&huart1,recv_data,512,5000);
}
}
由于每次都是512个字节,所以在串口助手发送处,我也输入了512个字节的数据,然后使用定时发送,每200ms或150ms发送一次,最后观察发送和接受数据统计是否一致。在我的测试中,200ms没有测出问题,但是120ms或100ms很快就出现数据溢出,这时候就会观察到统计数据中,发送数据一直在增加,但是接收数据已经不再增加了。
了解外设的性能是重要的,这是为了在设计程序的时候提供一些有助于稳定性的指导。