ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

printf重定向的学习总结

2022-05-09 19:34:25  阅读:149  来源: 互联网

标签:总结 文件 ITM 重定向 int __ printf


简介

实现printf重定向有多种方式,下面一一介绍。

linux环境下

虽然linux系统的默认标准输出设备是显示器,但是我们可以把printf打印输出的内容重定向到其他设备或文件。方法如下:

方法1:

打开一个普通文件,把它的文件描述符指定为标准输出的文件描述符,这样printf打印输出的数据会重定向到这个普通文件。

示例如下:

//实现printf打印输出重定向功能示例1

#include <stdio.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	
	printf("hello,zzc\n");

	//保存标准输出的文件描述符	
	int stdout_fd = dup(STDOUT_FILENO);

	//打开一个文件,获取文件描述符
	int fd = open("./2.c", O_RDWR|O_APPEND, 0666);
	if(fd < 0)
	{
		printf("open a file fail\n");
	}

	//指定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 restored\n");

	return 0;
}

方法2:

使用freopen函数把文件关联到标准输出,这样printf打印输出的数据会重定向到该文件。

示例如下:

//实现printf打印输出重定向功能示例2

#include <stdio.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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 changed\n");
	
	//恢复为默认的标准输出
	p = freopen("/dev/pts/0", "r+", stdout);
	if(NULL == p)
	{
		perror("freopen fail");
		return -1;
	}
	
	printf("standard output has restored\n");

	return 0;
}

另外,如果想刷新标准IO缓冲区,可以在printf 之后调用fflush。

MCU环境下(以stm32为例)

在不同的开发环境下,我们有多种手段可以对printf打印输出的数据进行重定向,目的是方便我们调试程序。

首先我们要确定开发环境是什么样的,是软件仿真,是硬件仿真,还是产品功能运行。不同的开发环境,有不同的重定向printf内容的方法。

在实时性上,RTT > SWO >串口 >半主机模式;本文不会讲述RTT、半主机模式,有兴趣的朋友请自行查阅资料。

1、软件仿真

在软件仿真的环境下,以keil mdk工程为例,我们使用微库,然后把printf函数重定向到usart串口。

printf重定向代码如下所示:

#include <stdio.h>
int fputc(int ch, FILE * f)
{
    //等待串口数据发送完毕
    while((USART1->SR & USART_FLAG_TC) == 0);
    
    //发送下一个字符
    USART1->DR = (uint8_t)ch;

    return ch;
}

运行效果如下图所示:
1234567

2、硬件仿真

目标开发板通过仿真器(JLINK、ULINK、STLINK等)连接到PC调试主机,在这种环境下,我们可以把printf输出的数据重定向到串口;也可以重定向到ITM端口,通过SWO线把数据发送给PC。

串口重定向的方法和上述软件仿真一样,这里不再复述,下面着重介绍ITM方式。

重定向到ITM:
在Cortex-M3\M4\M7系列MCU中,内核的调试组件有一个仪器跟踪宏单元(ITM) 。请注意如果你的芯片是 Cortex-M0 或者其他ARM内核,不支持ITM。

下面介绍如何把printf打印输出的数据重定向到ITM端口。

硬件连接:

我们都知道SWD接口正常使用是四根线。而使用ITM机制需要多用SWD的一根线:SWO。
先找到link接口SWO引脚的位置,再找到目标开发板上SWO引脚的位置,然后用杜邦线把两个引脚连接起来。

软件配置

第一步: 添加重定向文件

新建一个文件(文件名自定义),添加到mdk工程,文件的内容如下:

#include <stdio.h>  
  
#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; /* Add whatever you need here */ };  
    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的配置,可以参考以下描述:
123
更多详细描述请参阅stm32有关的参考手册。

第二步:新建一个配置文件(STM32DBG.ini),用于stm32 debugger初始化,把这个文件放在mdk工程下。文件内容如下:

/******************************************************************************/  
/* STM32DBG.INI: STM32 Debugger Initialization File                           */  
/******************************************************************************/  
// <<< Use Configuration Wizard in Context Menu >>>                           //   
/******************************************************************************/  
/* This file is part of the uVision/ARM development tools.                    */  
/* Copyright (c) 2005-2007 Keil Software. All rights reserved.                */  
/* This software may only be used under the terms of a valid, current,        */  
/* end user licence from KEIL for a compatible version of KEIL software       */  
/* development tools. Nothing else gives you the right to use this software.  */  
/******************************************************************************/  
  
  
FUNC void DebugSetup (void) {  
// <h> Debug MCU Configuration  
//   <o1.0>    DBG_SLEEP     <i> Debug Sleep Mode  
//   <o1.1>    DBG_STOP      <i> Debug Stop Mode  
//   <o1.2>    DBG_STANDBY   <i> Debug Standby Mode  
//   <o1.5>    TRACE_IOEN    <i> Trace I/O Enable   
//   <o1.6..7> TRACE_MODE    <i> Trace Mode  
//             <0=> Asynchronous  
//             <1=> Synchronous: TRACEDATA Size 1  
//             <2=> Synchronous: TRACEDATA Size 2  
//             <3=> Synchronous: TRACEDATA Size 4  
//   <o1.8>    DBG_IWDG_STOP <i> Independant Watchdog Stopped when Core is halted  
//   <o1.9>    DBG_WWDG_STOP <i> Window Watchdog Stopped when Core is halted  
//   <o1.10>   DBG_TIM1_STOP <i> Timer 1 Stopped when Core is halted  
//   <o1.11>   DBG_TIM2_STOP <i> Timer 2 Stopped when Core is halted  
//   <o1.12>   DBG_TIM3_STOP <i> Timer 3 Stopped when Core is halted  
//   <o1.13>   DBG_TIM4_STOP <i> Timer 4 Stopped when Core is halted  
//   <o1.14>   DBG_CAN_STOP  <i> CAN Stopped when Core is halted  
// </h>  
_WDWORD(0xE0042004, 0x00000027);  // DBGMCU_CR  
_WDWORD(0xE000ED08, 0x20000000);   // Setup Vector Table Offset Register  
}  
  
DebugSetup();                       // Debugger Setup  

第三步:配置mdk工程,如下图:

配置初始化文件
1234
选择SW接口(我这里没有接link,所以有些信息没有显示)
12345

core clock需要设置为你的系统时钟频率,如果你的cpu主频是72MHz,那就设置为72MHz;
跟踪功能需要使能,另外ITM端口默认使用端口0,当然你也可以使用其他端口。

123

第四步: 烧录程序,启动调试

打开debug viewer,你会发现printf打印输出的数据会显示在这个窗口上。那是因为printf重定向到了ITM端口,然后再通过仿真器的SWO线把数据传回PC。
1234
打印出乱码是因为我打印了中文。

注意事项

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; /* Add whatever you need here */ };  

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)  
{  
    /* Your implementation of ferror */  
    return EOF;  
}  
  
void _ttywrch(int c)  
{  
    fputc(c, 0);  
}  
  
int __backspace()  
{  
    return 0;  
}  
void _sys_exit(int return_code)  
{  
label:  
    goto label;  /* endless loop */  
}  

keil工程编译之后运行效果如下:(先从PC键盘输入一个整数,然后打印出这个整数)
123

需要说明以下几点:

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 <stdio.h>
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。方法还是很多的,需要大家一起探索。

实际上,还是要根据自己的开发环境来选择合适的方法,技术在不断发展,以后肯定会出现更多更好的调试方法,方便我们调试、提高工作效率才是最终的目的。

参考资料

参考手册

标签:总结,文件,ITM,重定向,int,__,printf
来源: https://www.cnblogs.com/lxyjrx/p/16250517.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有