zl程序教程

您现在的位置是:首页 >  后端

当前栏目

嵌入式系统重定向printf的三种方法

2023-09-11 14:21:25 时间

对printf()进行重定向的三种方法

  • 方法1: 使用MircoLib并重定义fputc
  • 方法2: 停用半主机模式,在MDK中使用标准库重定向printf()
  • 方法3: 在Gcc中使用标准库重定向printf

1. MDK使用MircoLib并重定义fputc

printf()函数实际上是调用了fputc()根据 format 字符串给出的格式打印输出到 stdout(标准输出)中。这两个函数都定义在<stdio.h>中:

int printf(const char *format, ...);
...
int fputc(int ch, FILE *stream);

fputc() 函数写入字符 ch 到给定输出流 streamprintf()函数在调用fputc() 时,会向stream参数传入stdout从而打印数据到标准输出。
KEIL-MDK中有一个Use MicroLIB选项,MicroLib是缺省c库的备选库,它可装入少量内存中,与嵌入式应用程序配合使用,且这些应用程序不在操作系统中运行。MicroLib提供了一个有限的stdio子系统,它仅支持未缓冲的stdinstdoutstderr
所以我们只需要勾选Use MicroLIB选项,并重写fputc() 函数就可以将printf()从串口输出了。
在这里插入图片描述

int fputc(int ch, FILE* f)
{
    USART_SendData(USART1, (unsigned char) ch);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
    {
    }
    return ch;
}

2. 在MDK中使用标准库重定向printf()

如果不想使用MicroLib,也可以使用标准库,但是需要停用半主机模式。下面我们提供另外一种重定义fputc()的写法。

STM32或者其他类似的ARM芯片(比如航顺HK32系列)的USART串口外设有一个 ISR 寄存器,全名 Interrupt and status register, 用来指示当前串口的状态,可以通过判断该位来判断串口当前是否处于发送状态,代码如下:

while((USART1->ISR & 0X40) == 0);

为了提高发送效率,直接使用寄存器来操作串口发送字符ch:

USART1->TDR = (uint8_t) ch;
#include <stdio.h>

/* 告知连接器不从C库链接使用半主机的函数 */
#pragma import(__use_no_semihosting)

/* 定义 _sys_exit() 以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

/* 标准库需要的支持类型 */
struct __FILE
{
    int handle;
};

FILE __stdout;
/*重定义fputc的另外一种方式*/
int fputc(int ch, FILE *stream)
{
    /* 堵塞判断串口是否发送完成 */
    while((USART1->ISR & 0X40) == 0);

    /* 串口发送完成,将该字符发送 */
    USART1->TDR = (uint8_t) ch;

    return ch;
}

3. 在Gcc中使用标准库重定向printf()

如果你不使用MDK而是Gcc来编译项目,那么上述两种方法不可行。
那么需要怎么做呢:

  • 在使用Gcc编译器的时候,需要重新定义_write函数而不是fputs()函数。
  • 只能使用标准库,因为Gcc中没有MicroLib。
    代码如下:
#include <stdio.h>

int _write(int fd, char *ptr, int len)  
{  
  HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
  return len;
}