实现printf重定向有多种方式,下面一一介绍。
linux环境下虽然linux系统的默认标准输出设备是显示器,但是我们可以把printf打印输出的内容重定向到其他设备或文件。方法如下:
方法1:打开一个普通文件,把它的文件描述符指定为标准输出的文件描述符,这样printf打印输出的数据会重定向到这个普通文件。
示例如下:
//实现printf打印输出重定向功能示例1 #include方法2:#include #include #include #include int main(void) { printf("hello,zzcn"); //保存标准输出的文件描述符 int stdout_fd = dup(STDOUT_FILENO); //打开一个文件,获取文件描述符 int fd = open("./2.c", O_RDWR|O_APPEND, 0666); if(fd < 0) { printf("open a file failn"); } //指定fd为标准输出的文件描述符 dup2(fd, STDOUT_FILENO); printf("standard output file descriptor has changed"); //刷新标准输出的IO缓冲区 fflush(stdout); //恢复为默认的标准输出,有两种方式 //方式一,把之前保存的文件描述符重新指定为标准输出的文件描述符 dup2(stdout_fd, STDOUT_FILENO); //方式二,打开当前的控制终端设备文件(文件路径通过tty命令获取),获取文件描述符 int tty_fd = open("/dev/pts/0", O_RDWR, 0666); dup2(tty_fd, STDOUT_FILENO); printf("standard output file descriptor has restoredn"); return 0; }
使用freopen函数把文件关联到标准输出,这样printf打印输出的数据会重定向到该文件。
示例如下:
//实现printf打印输出重定向功能示例2 #include#include #include #include #include int main(void) { FILE *p = NULL; //freopen()函数的主要用途是更改与标准文本流(stderr、stdin或stdout)相关联的文件。 p = freopen("./2.c", "a", stdout); if(NULL == p) { perror("freopen ./2.c fail"); return -1; } printf("standard output has changedn"); //恢复为默认的标准输出 p = freopen("/dev/pts/0", "r+", stdout); if(NULL == p) { perror("freopen fail"); return -1; } printf("standard output has restoredn"); return 0; }
另外,如果想刷新标准IO缓冲区,可以在printf 之后调用fflush。
MCU环境下(以stm32为例)在不同的开发环境下,我们有多种手段可以对printf打印输出的数据进行重定向,目的是方便我们调试程序。
首先我们要确定开发环境是什么样的,是软件仿真,是硬件仿真,还是产品功能运行。不同的开发环境,有不同的重定向printf内容的方法。
在实时性上,RTT > SWO >串口 >半主机模式;本文不会讲述RTT、半主机模式,有兴趣的朋友请自行查阅资料。
1、软件仿真在软件仿真的环境下,以keil mdk工程为例,我们使用微库,然后把printf函数重定向到usart串口。
printf重定向代码如下所示:
#includeint fputc(int ch, FILE * f) { //等待串口数据发送完毕 while((USART1->SR & USART_FLAG_TC) == 0); //发送下一个字符 USART1->DR = (uint8_t)ch; return ch; }
运行效果如下图所示:
目标开发板通过仿真器(JLINK、ULINK、STLINK等)连接到PC调试主机,在这种环境下,我们可以把printf输出的数据重定向到串口;也可以重定向到ITM端口,通过SWO线把数据发送给PC。
串口重定向的方法和上述软件仿真一样,这里不再复述,下面着重介绍ITM方式。
重定向到ITM:
在Cortex-M3M4M7系列MCU中,内核的调试组件有一个仪器跟踪宏单元(ITM) 。请注意如果你的芯片是 Cortex-M0 或者其他ARM内核,不支持ITM。
下面介绍如何把printf打印输出的数据重定向到ITM端口。
硬件连接:
我们都知道SWD接口正常使用是四根线。而使用ITM机制需要多用SWD的一根线:SWO。
先找到link接口SWO引脚的位置,再找到目标开发板上SWO引脚的位置,然后用杜邦线把两个引脚连接起来。
软件配置
第一步: 添加重定向文件
新建一个文件(文件名自定义),添加到mdk工程,文件的内容如下:
#include#define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n))) #define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n))) #define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n))) #define DEMCR (*((volatile unsigned long *)(0xE000EDFC))) #define TRCENA 0x01000000 struct __FILE { int handle; }; FILE __stdout; FILE __stdin; int fputc(int ch, FILE *f) { if (DEMCR & TRCENA) { while (ITM_Port32(0) == 0); ITM_Port8(0) = ch; } return(ch); }
说明:
1、这个文件用于重定义fputc函数;因为printf函数的底层实现就是fputc,所以需要重定义这个函数,在这个函数里面把printf打印输出的数据重定向到ITM端口
2、上述文件中默认使用ITM的port0,当然可以使用其他的端口。
关于ITM的配置,可以参考以下描述:
更多详细描述请参阅stm32有关的参考手册。
第二步:新建一个配置文件(STM32DBG.ini),用于stm32 debugger初始化,把这个文件放在mdk工程下。文件内容如下:
// <<< Use Configuration Wizard in Context Menu >>> //
FUNC void DebugSetup (void) {
// Debug MCU Configuration
// DBG_SLEEP Debug Sleep Mode
// DBG_STOP Debug Stop Mode
// DBG_STANDBY Debug Standby Mode
// TRACE_IOEN Trace I/O Enable
// TRACE_MODE Trace Mode
// <0=> Asynchronous
// <1=> Synchronous: TRACEDATA Size 1
// <2=> Synchronous: TRACEDATA Size 2
// <3=> Synchronous: TRACEDATA Size 4
// DBG_IWDG_STOP Independant Watchdog Stopped when Core is halted
// DBG_WWDG_STOP Window Watchdog Stopped when Core is halted
// DBG_TIM1_STOP Timer 1 Stopped when Core is halted
// DBG_TIM2_STOP Timer 2 Stopped when Core is halted
// DBG_TIM3_STOP Timer 3 Stopped when Core is halted
// DBG_TIM4_STOP Timer 4 Stopped when Core is halted
// DBG_CAN_STOP CAN Stopped when Core is halted
//
_WDWORd(0xE0042004, 0x00000027); // DBGMCU_CR
_WDWORd(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register
}
DebugSetup(); // Debugger Setup
第三步:配置mdk工程,如下图:
配置初始化文件
选择SW接口(我这里没有接link,所以有些信息没有显示)
core clock需要设置为你的系统时钟频率,如果你的cpu主频是72MHz,那就设置为72MHz;
跟踪功能需要使能,另外ITM端口默认使用端口0,当然你也可以使用其他端口。
第四步: 烧录程序,启动调试
打开debug viewer,你会发现printf打印输出的数据会显示在这个窗口上。那是因为printf重定向到了ITM端口,然后再通过仿真器的SWO线把数据传回PC。
打印出乱码是因为我打印了中文。
注意事项
1、如果使用微库,不需要关闭半主机模式,因为并不会进入半主机模式。
2、如果使用MDK提供的标准库(不勾选微库),就需要关闭半主机模式。方法就是上述重定向文件中添加下面这句话:
#pragma import(__use_no_semihosting_swi)
这句话意思是告知连接器不从C库链接使用半主机的函数。
如果你使用的是AC5编译器,是没有问题的;如果你使用的是AC6编译器,你可能会遇到问题:编译器会报错提示 #pragma import(__use_no_semihosting_swi) 这个命令AC6并不支持。
你可以添加下面这句话来解决这个问题:
__ASM (".global __use_no_semihosting");
3、开发板独立运行(不带仿真器)
不接仿真器,开发板独立运行,这种场景下可以使用串口重定向,这里不再复述。
拓展一、输入输出重定向
我们也可以重定向输入,本来是从标准输入设备输入,但是开发板没有这个东西,所以我们可以从PC的标准输入设备输入。
新建一个重定向文件,文件内容如下:(对标准输入和输出都做了重定向)
#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; };
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f)
{
return ITM_SendChar(ch);
}
volatile int32_t ITM_RxBuffer;
int fgetc(FILE *f)
{
while (ITM_CheckChar() != 1) __NOP();
return (ITM_ReceiveChar());
}
int ferror(FILE *f)
{
return EOF;
}
void _ttywrch(int c)
{
fputc(c, 0);
}
int __backspace()
{
return 0;
}
void _sys_exit(int return_code)
{
label:
goto label;
}
keil工程编译之后运行效果如下:(先从PC键盘输入一个整数,然后打印出这个整数)
需要说明以下几点:
1、ITM_SendChar、ITM_CheckChar、ITM_ReceiveChar这几个函数都是在core_cmX.h文件中定义的
2、scanf依赖的函数共有两个,fgetc和__backspace都需要实现,如果缺少__backespace函数,则scanf无法从Debug Viewer Dialog 窗口获取输入
3、如果编译报错,缺少以上某些函数,那就需要添加这几个函数
二、在GCC中使用标准库重定向printf
在Gcc中重定向printf函数时要注意以下两点:
- 与重定义fputs()函数一样,在使用gcc编译器的时候,需要重新定义_write函数;
- gcc中没有MicroLib,只能使用标准库;
重定向代码如下所示:
#include总结int _write(int fd, char *ptr, int len) { int ret = len; while(len) { USART_SendData(USART1, *(char *)ptr); len--; } return ret; }
本文汇总了printf函数在linux系统下和在mcu环境下重定向的几种方法,比如重定向到串口、重定向到ITM端口、重定向到文件等,其实还可以重定向到RTT。方法还是很多的,需要大家一起探索。
实际上,还是要根据自己的开发环境来选择合适的方法,技术在不断发展,以后肯定会出现更多更好的调试方法,方便我们调试、提高工作效率才是最终的目的。
参考资料参考手册



