输出至串口STM32通过printf重定向输出到串口

最近用STM32CubeMX创建了一个demo工程,在调试过程中,printf打印功能一直不能正常打印,检查工程中也已将fputc函数进行了实现。奇怪的是用JTAG进行调试时打印恢复了正常。最后发现问题的原因是没有勾选MDK使用微库的配置,使用微库的话 ,不会使用半主机模式。
printf之类的函数,使用了半主机模式,MDK上开启半主机模式-需要SWO线(换言之,需要使用JTAG接线)当目标板脱离仿真器(jlink/ulink)单独运行时,不能使用半主机模式。

半主机是ARM的一种目标机制,它使得在ARM目标上跑的代码,如果主机电脑运行了调试器,那么该代码可以使用该主机电脑的输入输出设备。
这点非常重要,因为开发初期,可能开发者根本不知道该 ARM 器件上有什么输入输出设备,而半主基机制使得你不用知道ARM器件的外设,利用主机电脑的外设就可以实现输入输出调试。
一般单片机需要独立运行,使用时应去掉仿真器,把printf函数通过单片机的外设来实现,例如通过开发板的串口,lcd或者sd卡。首先要关掉半主机机制。然后再将输入输出重定向到ARM器件上,如 printf 和 scanf,需要重写fputc和fgetc函数。

printf 定义在 <stdio.h> 头文件中,如下:

int printf(const char *format, ...);

printf 函数根据 format 字符串给出的格式打印输出到 stdout(标准输出)中,当然,printf 函数是不会一个字符一个字符去输出,它会调用更底层的 I/O 函数:fputc去逐个字符打印
fputc 也定义于头文件 <stdio.h>中,如下:

int fputc(int ch, FILE *stream);

fputc 函数写入字符 ch 到给定输出流 stream,printf函数在调用该函数时,会向stream参数传入stdout从而打印数据到标准输出。
那么,要实现printf打印到串口就变得非常简单了,只需要重新定义fputc函数,在fputc的函数中将数据通过串口发送,称之为:fputc重定向或者printf重定向。

在MDK中使用重定向的方式:

使用微库

  • 勾选Use MicroLib
  • 重定义fputc到串口
  • #include <stdio.h>
    
    int fputc(int ch, FILE *stream)
    {
        while((USART1->ISR & UART_FLAG_TC) == 0);
        USART1->TDR = (uint8_t) ch;
        return ch;
    }
    

    使用标准库

    系统 IO 一般指的是 Linux/Unix 系统调用中关于 I/O 操作的统称,其中包括 open、read、write、close 等操作。
    与系统 IO 对应还有标准 IO,标准 IO 是 ISO 标准中 C 语言标准定义的 IO 访问接口,例如 fprintf/fgets 等 C 语言标准中定义的文件访问接口。

    在 Linux 系统中 open/read/write 等函数的底层实现是通过系统调用访问的,在 STM32 的裸机中没有操作系统,更没有这些系统调用。
    但是我们可以用一种其他的方式去实现这些系统 IO,而不需要操作系统。

    /* 为确保没有从 C 库链接使用半主机的函数,如果仍然链接了使用半主机的函数,则链接器会报告错误 */   
    #pragma import(__use_no_semihosting) 
    
    /* 标准库需要的支持函数 */        
    struct __FILE {
        int handle; 
    }; 
    
    /* FILE 在stdio.h文件 */    
    FILE __stdout;    
    
    /* 定义_sys_exit()以避免使用半主机模式 */                       
    void _sys_exit(int x)                    
    {  
        x = x;
    }         
    
    int fputc(int ch, FILE *stream)
    {
        while((USART1->ISR & UART_FLAG_TC) == 0);
        USART1->TDR = (uint8_t) ch;
        return ch;
    }
    

    microlib 进行了高度优化以使代码变得很小。 它的功能比缺省 C 库少,并且根本不具备某些 ISO C 特性。某些库函数的运行速度也比较慢,例如,memcpy()。

    不同的编译器对于C库的底层实现机制是不同的,所以上面两种在MDK中的实现方法,在使用Gcc编译器的时候是不可行的。

    GCC使用标准库重定向

    #include <stdio.h>
    
    int _write(int fd, char *ptr, int len)  
    {  
        HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
        return len;
    }
    

    与microlib 方式不同的是,GCC重定向是一次写入多个字符,而fputc 中是一次写入一个字符。

    在需要使用printf 频繁写入大量字符时,建议使用半主机模式或者使用GCC 编译器。GCC使用打印时要注意行缓冲。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 输出至串口STM32通过printf重定向输出到串口

    发表评论