使用STM32 HAL实现串口printf重定向

一、C语言的格式化输出

C语言的printf是一个标准库函数,用于将格式化的数据输出到标准的输出设备(通常是终端)

  • 基本语法:
  • int printf(const char *format, ...);
    

    其中的第一个参数const char *format表示输出格式,后面的参数是可变参数,用于填充格式化字符串中的占位符。

  • 字符输出原理:
    1. 格式化字符串处理:printf函数将第一个参数 const char *format 中的格式占位符解析出来,然后根据占位符的类型和顺序依次取可变参数中的值,将这些值转换为字符串,并将其按照格式化字符串中的顺序和样式组合成最终的输出字符串。
    2. 输出字符串存储:printf函数将格式化后的输出字符串存储在内存缓冲区中。
    3. 输出字符串显示:printf函数将内存缓冲区中的输出字符串显示到标准输出设备上,通常是终端。

    在学习C语言的时候,在调用头文件#include "stdio.h"的时候,就可以使用printf函数进行格式化打印

    #include <stdio.h>
    
    int a = 10;
    char str[] = "hello,world!";
    int main(void)
    {
        printf("%s\n",str);
        printf("a = %d",a);  
        return 0;
    }
    
    [result]
    
    hello,world!
    a = 10
    

    但是在Keil中,在stm32的使用中是不能直接使用C语言的打印函数的,需要添加支持设置,即调用MDK的微库(MicroLib) 称之为printf的重定向。其实不仅仅可以把打印字符重定向,而且还可以将获取字符重定向。

    二、开发准备

  • 基于STM32L431RCT6的小熊派开发板

  • windows系统并安装Cubemx和Keil MDK的电脑

  • 三、初始化片上外设

    本次开发介绍的主要是串口的重定向,因此就是需要初始化串口外设。

    设置串口通信为异步通信,波特率115200

    生成代码,并选择keil-MDK打开该工程

    四、设置重定向

    4.1 点击魔术棒,然后勾选使用微库(Use MicroLIB)

    针对MicroLIB的介绍:

    ​ MicroLIB是Keil公司提供的一个C标准库,专为嵌入式系统设计而开发。相对于标准C库,MicroLIB库更加轻量级,代码量更小,适用于嵌入式系统等资源受限的环境。MicroLIB库支持ISO/ANSI C标准的大部分函数,并增加了一些嵌入式系统常用的函数,例如串口通信、GPIO控制等。在MDK的工程中,开发者可以选择使用MicroLIB库来进行开发,以减小程序的代码大小和占用内存的空间。

    ​ 需要注意的是,MicroLIB库并不是一个完整的C标准库,它只实现了一部分的C标准函数,并且一些函数的实现与标准C库可能存在差异。如果需要使用标准C库的函数或者功能更加完整的C标准库,开发者需要使用其他的C标准库,例如GNU C Library(glibc)等。

    4.2 添加串口重定向代码

    main.c函数中添加头文件:

    #include "stdio.h"
    

    main.c函数的/* USER CODE BEGIN 4 */内添加下面代码即可:

    int fputc(int c,FILE *f)
    {
        uint8_t ch;  //定义一个无符号8位整型变量ch 并将字符C赋值给它
        ch = c;
        HAL_UART_Transmit(&huart1,&ch,1,1000);
        // 调用HAL库的串口发送函数,将ch发送到USART1串口,等待时间为1000ms
        return c;
    }
    

    4.3 在主循环中添加代码进行测试

    /* USER CODE BEGIN 2 */
    	uint8_t str[] = "Hello GearLong!";
    	uint8_t num1 = 10;
    	float f = 3.1415926;
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    		printf("%s\r\n",str);
    		printf("num = %d\r\n",num1);
    		printf("f1 = %f\r\n",f);
    		printf("f2 = %.2f\r\n",f);
    		printf("f3 = %8.3f\r\n",f);
    		HAL_Delay(1000);		
      }
    

    4.4 编译、下载,然后打开串口调试助手查看执行

    记得将AT开关拨到AT_MCU

    4.5 不使用微库打印数据

    下面的代码来自正点原子的HAL库代码中的串口打印 ,将代码复制到usart.c的代码添加处,并取消勾选微库,并移除上面已经设置的重定向代码。

    usart.c函数中添加头文件:

    #include "stdio.h"
    

    usart.c函数/* USER CODE BEGIN 1 */内添加下面代码即可:

    /* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
    
    #if 1
    #if (__ARMCC_VERSION >= 6010050)                    /* 使用AC6编译器时 */
    __asm(".global __use_no_semihosting\n\t");          /* 声明不使用半主机模式 */
    __asm(".global __ARM_use_no_argv \n\t");            /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
    
    #else
    /* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
    #pragma import(__use_no_semihosting)
    
    struct __FILE
    {
        int handle;
        /* Whatever you require here. If the only file you are using is */
        /* standard output using printf() for debugging, no file handling */
        /* is required. */
    };
    
    #endif
    
    /* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
    int _ttywrch(int ch)
    {
        ch = ch;
        return ch;
    }
    
    /* 定义_sys_exit()以避免使用半主机模式 */
    void _sys_exit(int x)
    {
        x = x;
    }
    
    char *_sys_command_string(char *cmd, int len)
    {
        return NULL;
    }
    
    /* FILE 在 stdio.h里面定义. */
    FILE __stdout;
    
    /* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
    int fputc(int ch, FILE *f)
    {
        while ((USART1->SR & 0X40) == 0);               /* 等待上一个字符发送完成 */
        
        USART1->DR = (uint8_t)ch;                       /* 将要发送的字符 ch 写入到DR寄存器 */	
        return ch;
    }
    #endif
    

    注意在使用Cubmex生成的HAL库代码中,USART1的相关寄存器可能随着不同型号的MCU会发生变化,如果编译不通过及时修改即可。

    4.6 打印输出的结果是一致的

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32 HAL实现串口printf重定向

    发表评论