Skip to content

LV060-串口格式化函数

一、概述

上一节中,我们实现了 UART1 基本的数据收发功能,虽然可以用来调试程序,但是功能太单一了,只能输出字符。如果需要输出数字的时候就需要我们自己先将数字转换为字符,非常的不方便。学习 STM32 串口的时候我们都会将 printf 函数映射到串口上,这样就可以使用 printf 函数来完成格式化输出了,使用非常方便。那 printf 这样的格式化函数能移植到 I.MX6U-ALPHA 开发板上吗?当然也可以啦。

格式化函数说的是 printf、 sprintf 和 scanf 这样的函数,分为格式化输入和格式化输出两类函数。学习 C 语言的时候常常通过 printf 函数在屏幕上显示字符串,通过 scanf 函数从键盘获取输入。这样就有了输入和输出了,实现了最基本的人机交互。在 I.MX6U-ALPHA 开发板上也可以将 printf 和 scanf 映射到串口上,这样就可以使用 MobaXterm 作为开发板的终端,完成与开发板的交互。也可以使用 printf 和 sprintf 来实现各种各样的格式化字符串,方便我们后续的开发。

二、printf 移植

1. 从 uboot 移植

这里直接用了正点原子的资料,他为我们提供了一个 stdio 文件夹

image-20230729153948757

stdio 里面的文件其实是从 uboot 里面移植过来的。其实都是可以从 uboot 源码还有交叉编译工具链安装目录里面找出相应的文件,完成格式化函数的移植。这里要注意一点, stdio 中并没有实现完全版的格式化函数,比如 printf 函数并不支持浮点数,但是基本够我们使用了。 移植完成后,直接编译可能会报下边的警告:

shell
In file included from project/main.c:5:0:
bsp/uart/bsp_uart.h:12:6: 警告: conflicting types for built-in function ‘putc’
 void putc(unsigned char c);
      ^
bsp/uart/bsp_uart.h:13:6: 警告: conflicting types for built-in function ‘puts’
 void puts(char *str);
      ^

这个表示“putc”、“puts”这两个函数与内建函数冲突,在编译的时候加入选项“-fno-builtin”表示不使用内建函数就可以了。另外还需要添加在编译 C 文件的时候添加选项“-Wa,-mimplicit-it = thumb”,否则的话会有如下类似的错误提示:

shell
thumb conditional instruction should be in IT block -- `addcs r5,r5,#65536

移植完成后,可以看 gitee 仓库:02_UART/03_printf · sumumm/imx6ull-bare-demo

2. 自己实现一个?

自己实现的这个是参考的韦东山教程的逻辑开发教程,所以这里写一下基于的工程情况吧。可以看这里:02_UART/04_my_printf · sumumm/imx6ull-bare-demo。需要注意的是这里的 printf 后面的换行只能是 \n\r,换成 \r\n 就会报以下问题:

image-20240119004011983

具体原因还不清楚,上面直接移植 uboot 中相关文件的版本倒是没问题,后面知道了再补充。

2.1 基础工程结构

2.1.1 目录结构
image-20240116231150136
2.1.2 imx6ull.lds
assembly
SECTIONS {
    . = 0x80100000;

    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(.rodata) }

    . = ALIGN(4);
    .data : { *(.data) }

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}
2.1.3 main.c
c
#include "uart.h"

int  main()
{
	unsigned char cTestData ;       /*用于测试发送的数据*/
 	Uart_Init()	 ;

	PutStr("Hello, world!\n\r");	/*发送字符串*/
	
	while(1)
	{	
		cTestData = GetChar() ;				/*等待从串口获取数据*/
		
			if (cTestData == '\r')  		/*添加回到行首\r*/
			{ 
				PutChar('\n');
			}

			if (cTestData == '\n')			/*换行\n*/
			{
				PutChar('\r');
			}
				
		PutChar(cTestData)    ;				/*从串口发送数据*/
	}
					
	return 0;
}
2.1.4 start.S
assembly
.text
.global  _start
_start: 				

	/* 设置栈 */
	ldr  sp,=0x80200000
	/* 清除bss段 */
	bl clean_bss
	/* 跳转到主函数 */
	bl main

halt:
	b  halt 

clean_bss:
	ldr r1, =__bss_start	// 将链接脚本变量__bss_start变量保存于r1
	ldr r2, =__bss_end      // 将链接脚本变量__bss_end变量保存于r2
	mov r3, #0
clean:
	strb r3, [r1]		// 将当前地址下的数据清零
	add r1, r1, #1		// 将r1内存储的地址+1
	cmp r1, r2		// 相等:清零操作结束;否则继续执行clean函数清零bss段
	bne clean
	
	mov pc, lr
2.1.5 uart.c
c
#include "uart.h"


static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA ;
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA	;
static volatile unsigned int *IOMUXC_UART1_RX_DATA_SELECT_INPUT ;

void Uart_Init(void)	 				
{
	
	IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA		= (volatile unsigned int *)(0x20E0084);
	IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA		= (volatile unsigned int *)(0x20E0088);
	IOMUXC_UART1_RX_DATA_SELECT_INPUT		= (volatile unsigned int *)(0x20E0624);

	*IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA = 0;
	*IOMUXC_UART1_RX_DATA_SELECT_INPUT = 3;
	*IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA = 0;

	UART1->UCR1 |= (1 << 0) ;		/*关闭当前串口*/ 
	
	/* 
	 *  设置 UART 传输格式:
	 *  UART1 中的 UCR2 寄存器关键 bit 如下
	 *  [14]:	1:忽略RTS引脚
	 *  [8] :	0: 关闭奇偶校验 默认为 0,无需设置
	 *  [6] :	0: 停止位 1 位	    默认为 0,无需设置
	 *  [5] :	1: 数据长度 8 位
	 *  [2] :	1: 发送数据使能
	 *  [1] :	1: 接收数据使能
	 */
	
	UART1->UCR2 |= (1<<14) |(1<<5) |(1<<2)|(1<<1);

	/*
	 *  UART1 中的 UCR3 寄存器关键 bit 如下
	 *  [2]:  1:根据官方文档表示,IM6ULL的UART用了这个MUXED模型,提示要设置	
	 */
	
	UART1->UCR3 |= (1<<2);
	
	/*
	 * 设置波特率
	 * 根据芯片手册得知波特率计算公式:
	 * Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1))
	 * 当我们需要设置 115200 的波特率
	 * UART1_UFCR [9:7] = 101,表示不分频,得到当前 UART 参考频率 Ref Freq :80M ,
	 * 带入公式:115200 = 80000000 /(16*(UBMR + 1)/(UBIR+1))
	 * 
	 * 选取一组满足上式的参数:UBMR、UBIR 即可
	 *	
	 * UART1_UBIR    = 71
	 * UART1_UBMR = 3124  
	 */
	 
    UART1->UFCR = 5 << 7;       /* Uart 的时钟 clk:80MHz */
    UART1->UBIR = 71;
    UART1->UBMR = 3124;

	UART1->UCR1 |= (1 << 0);		/*使能当前串口*/ 
}

void PutChar(int c)						
{
	while (!((UART1->USR2) & (1<<3))); /*等待上个字节发送完毕*/
	UART1->UTXD = (unsigned char)c;		
}

unsigned char GetChar(void)						
{	
	while (!(UART1->USR2 & (1<<0)));  /*等待接收数据*/
	return (unsigned char)UART1->URXD;
}

void PutStr(const char *s)				
{
	while (*s)
	{
		PutChar(*s);
		s++;
	}
}
2.1.6 uart.h
c
#ifndef _UART_H_
#define _UART_H_


/*UART1 的寄存器的基地址*/
#define UART1_BASE          (0x2020000u)

#define UART1    ((UART_Type *)UART1_BASE)

/*根据 IMX6ULL 芯片手册 <<55.15 UART Memory Map/Register Definition> > 的 3608 页,定义 UART 的结构体,*/
typedef struct {
  volatile unsigned int  URXD;               /**< UART Receiver Register, offset: 0x0 	           串口接收寄存器,偏移地址 0x0     */
  		   unsigned char RESERVED_0[60];		
  volatile unsigned int  UTXD;               /**< UART Transmitter Register, offset: 0x40          串口发送寄存器,偏移地址 0x40*/
  		   unsigned char RESERVED_1[60];		
  volatile unsigned int  UCR1;               /**< UART Control Register 1, offset: 0x80 	       串口控制寄存器 1,偏移地址 0x80*/
  volatile unsigned int  UCR2;               /**< UART Control Register 2, offset: 0x84 	       串口控制寄存器 2,偏移地址 0x84*/
  volatile unsigned int  UCR3;               /**< UART Control Register 3, offset: 0x88            串口控制寄存器 3,偏移地址 0x88*/
  volatile unsigned int  UCR4;               /**< UART Control Register 4, offset: 0x8C            串口控制寄存器 4,偏移地址 0x8C*/
  volatile unsigned int  UFCR;               /**< UART FIFO Control Register, offset: 0x90         串口 FIFO 控制寄存器,偏移地址 0x90*/
  volatile unsigned int  USR1;               /**< UART Status Register 1, offset: 0x94             串口状态寄存器 1,偏移地址 0x94*/
  volatile unsigned int  USR2;               /**< UART Status Register 2, offset: 0x98             串口状态寄存器 2,偏移地址 0x98*/
  volatile unsigned int  UESC;               /**< UART Escape Character Register, offset: 0x9C     串口转义字符寄存器,偏移地址 0x9C*/
  volatile unsigned int  UTIM;               /**< UART Escape Timer Register, offset: 0xA0         串口转义定时器寄存器 偏移地址 0xA0*/
  volatile unsigned int  UBIR;               /**< UART BRM Incremental Register, offset: 0xA4      串口二进制倍率增加寄存器 偏移地址 0xA4*/
  volatile unsigned int  UBMR;               /**< UART BRM Modulator Register, offset: 0xA8 	   串口二进制倍率调节寄存器 偏移地址 0xA8*/
  volatile unsigned int  UBRC;               /**< UART Baud Rate Count Register, offset: 0xAC      串口波特率计数寄存器 偏移地址 0xAC*/
  volatile unsigned int  ONEMS;              /**< UART One Millisecond Register, offset: 0xB0      串口一毫秒寄存器 偏移地址 0xB0*/
  volatile unsigned int  UTS;                /**< UART Test Register, offset: 0xB4                 串口测试寄存器 偏移地址 0xB4*/		
  volatile unsigned int  UMCR;               /**< UART RS-485 Mode Control Register, offset: 0xB8  串口 485 模式控制寄存器 偏移地址 0xB8*/
} UART_Type;
		  			 		  						  					  				 	   		  	  	 	 
void Uart_Init(void);		
void PutChar(int c);

 #endif
2.1.7 Makefile
makefile
PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

uart.img : start.S  uart.c main.c
	$(CC) -nostdlib -g -c -o start.o start.S
	$(CC) -nostdlib -g -c -o uart.o uart.c	
	$(CC) -nostdlib -g -c -o main.o main.c	
	
	$(LD) -T imx6ull.lds -g start.o uart.o main.o -o uart.elf 
	
	$(OBJCOPY) -O binary -S uart.elf  uart.bin
	$(OBJDUMP) -D -m arm  uart.elf  > uart.dis	
	./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d uart.bin uart.imx
	dd if=/dev/zero of=1k.bin bs=1024 count=1
	cat 1k.bin uart.imx > uart.img

clean:
	rm -f uart.dis  uart.bin uart.elf uart.imx uart.img *.o

2.2 移植 printf

2.2.1 raise 函数

在 uart.c 中加入 raise 函数,用于防止编译失败。

c
int raise(int signum)/* raise 函数,防止编译报错 */
{
    return 0;
}
2.2.2 修改 Makefile

my_printf.c 中用到除法的求模运算,需要提供除法库。一般的交叉工具链里都提示有基本的数学运算,它们位于 libgcc.a 中。我们需要把 libgcc.a 也链接进程序里,需要修改 Makefile。

注意:链接指令中,每个“ -L”表示库在哪里,即它的目录;“ -l”表示哪个库,即库的名称, -lgcc 表示会链接“libgcc.a”库。

对 Makefile 作如下修改:

(1)增加 $(CC) -nostdlib -g -c -o my_printf.o my_printf.c

(2)在 $(LD) -T imx6ull.lds -g start.o uart.o main.o my_printf.o -o my_printf.elf 后 添加 -lgcc – L<libgcc.a 的路径>

例如:

makefile
$(LD) -T imx6ull.lds -g start.o uart.o main.o my_printf.o -o my_printf.elf -lgcc -L/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_armlinux-gnueabihf/lib/gcc/arm-linux-gnueabihf/6.2.1
2.2.3 变参数函数移植

函数参数列表包括了字符串( format)和变参(…)组合而成, 在 vc6.0 的头文件 stdarg.h 中找到 typedef char * va_list:

c
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

① _INTSIZEOF(n) 用于获取其中一个变参类型占用的空间长度。

② va_start(ap, v) 令 ap 指向第一个变参地址。

③ va_arg(ap, t) 取出一个变参,同时指针指向下一个变参。

④ va_end(ap) 将指针指向 NULL,防止野指针 。

我们移植以上的代码,编写一个属于自己的 printf。参考 int printf(const char *format, ...)库函数,实现 my_printf。我们创建 my_printf.c :

c
//reference :  int printf(const char *format, ...); 
int printf(const char *fmt, ...) 
{
	va_list ap;

	va_start(ap, fmt);
	my_vprintf(fmt, ap);	
	va_end(ap);
	return 0;
}
2.2.4 编写 my_vprintf(fmt, ap)

参考 int vprintf(const char *format, va_list ap)实现 my_vprintf ,在 my_printf.c 中添加:

c
/*reference :   int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap) 
{
	char lead=' ';
	int  maxwidth=0;
	
	 for(; *fmt != '\0'; fmt++)		
	 {
		if (*fmt != '%') {			
			outc(*fmt);				
			continue;
		}

		 lead=' ';
		 maxwidth=0;
		 
		//format : %08d, %8d,%d,%u,%x,%f,%c,%s 
		    fmt++;
		if(*fmt == '0'){
			lead = '0';
			fmt++;	
		}

		
		while(*fmt >= '0' && *fmt <= '9'){
			maxwidth *=10;
			maxwidth += (*fmt - '0');
			fmt++;
		}
		
		switch (*fmt) {
		case 'd': out_num(va_arg(ap, int),          10,lead,maxwidth); break;
		case 'o': out_num(va_arg(ap, unsigned int),  8,lead,maxwidth); break;				
		case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
		case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
		case 'c': outc(va_arg(ap, int   )); break;		
		case 's': outs(va_arg(ap, char *)); break;		  		
				
		default:  
			outc(*fmt);
			break;
		}
	}
	return 0;
}
2.2.5 编写 out_c, outs 与 out_num 函数
  • (1)利用之前我们实现的单字节打印函数 void PutChar(int c)实现 out_c,outs 与 out_num 函数 outc 用于格式化输出中的%c 的输出。
c
static int outc(int c) 
{
	PutChar(c);
	return 0;
}
  • outs 用于格式化输出中的%s 的输出。
c
static int outs (const char *s)
{
	while (*s != '\0')	
		PutChar(*s++);
	return 0;
}
  • out_num 用于格式化输出中的%d, %o, %u, %x 的输出。
c
static int out_num(long n, int base,char lead,int maxwidth) 
{
	unsigned long m=0;
	char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
	int count=0,i=0;
			

	*--s = '\0';
	
	if (n < 0){
		m = -n;
	}
	else{
		m = n;
	}
	
	do{
		*--s = hex_tab[m%base];
		count++;
	}while ((m /= base) != 0);
	
	if( maxwidth && count < maxwidth){
		for (i=maxwidth - count; i; i--)	
			*--s = lead;
}

	if (n < 0)
		*--s = '-';
	
	return outs(s);
}
2.2.6 测试函数
c
int my_printf_test(void)
{
	printf("This is www.100ask.org   my_printf test\n\r") ;	
	printf("test char           =%c,%c\n\r", 'A','a') ;	
	printf("test decimal number =%d\n\r",    123456) ;
	printf("test decimal number =%d\n\r",    -123456) ;	
	printf("test hex     number =0x%x\n\r",  0x55aa55aa) ;	
	printf("test string         =%s\n\r",    "www.100ask.org") ;	
	printf("num=%08d\n\r",   12345);
	printf("num=%8d\n\r",    12345);
	printf("num=0x%08x\n\r", 0x12345);
	printf("num=0x%8x\n\r",  0x12345);
	printf("num=0x%02x\n\r", 0x1);
	printf("num=0x%2x\n\r",  0x1);

	printf("num=%05d\n\r", 0x1);
	printf("num=%5d\n\r",  0x1);

	return 0;
}

2.3 移植后的工程结构

2.3.1 目录结构
image-20240116233107762
2.3.2 imx6ull.lds
assembly
SECTIONS {
    . = 0x80100000;

    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(.rodata) }

    . = ALIGN(4);
    .data : { *(.data) }

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}
2.3.3 main.c
c
#include "my_printf.h"
#include "uart.h"
int  main()
{	Uart_Init();
	my_printf_test();			
	return 0;
}
2.3.4 start.S
assembly
.text
.global  _start
_start: 				

	/* 设置栈 */
	ldr  sp,=0x80200000
	/* 清除bss段 */
	bl clean_bss
	/* 跳转到主函数 */
	bl main

halt:
	b  halt 

clean_bss:
	ldr r1, =__bss_start	// 将链接脚本变量__bss_start变量保存于r1
	ldr r2, =__bss_end      // 将链接脚本变量__bss_end变量保存于r2
	mov r3, #0
clean:
	strb r3, [r1]		// 将当前地址下的数据清零
	add r1, r1, #1		// 将r1内存储的地址+1
	cmp r1, r2		// 相等:清零操作结束;否则继续执行clean函数清零bss段
	bne clean
	
	mov pc, lr
2.3.5 uart.c
c
#include "uart.h"

static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA ;
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA	;
static volatile unsigned int *IOMUXC_UART1_RX_DATA_SELECT_INPUT ;

void Uart_Init(void)	 				
{
	
	IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA		= (volatile unsigned int *)(0x20E0084);
	IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA		= (volatile unsigned int *)(0x20E0088);
	IOMUXC_UART1_RX_DATA_SELECT_INPUT		= (volatile unsigned int *)(0x20E0624);

	*IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA = 0;
	*IOMUXC_UART1_RX_DATA_SELECT_INPUT = 3;
	*IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA = 0;

	UART1->UCR1 |= (1 << 0) ;		/*关闭当前串口*/ 
	
	/* 
	 *  设置 UART 传输格式:
	 *  UART1 中的 UCR2 寄存器关键 bit 如下
	 *  [14]:	1:忽略RTS引脚
	 *  [8] :	0: 关闭奇偶校验 默认为 0,无需设置
	 *  [6] :	0: 停止位 1 位	    默认为 0,无需设置
	 *  [5] :	1: 数据长度 8 位
	 *  [2] :	1: 发送数据使能
	 *  [1] :	1: 接收数据使能
	 */
	
	UART1->UCR2 |= (1<<14) |(1<<5) |(1<<2)|(1<<1);

	/*
	 *  UART1 中的 UCR3 寄存器关键 bit 如下
	 *  [2]:  1:根据官方文档表示,IM6ULL的UART用了这个MUXED模型,提示要设置	
	 */
	
	UART1->UCR3 |= (1<<2);
	
	/*
	 * 设置波特率
	 * 根据芯片手册得知波特率计算公式:
	 * Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1))
	 * 当我们需要设置 115200 的波特率
	 * UART1_UFCR [9:7] = 101,表示不分频,得到当前 UART 参考频率 Ref Freq :80M ,
	 * 带入公式:115200 = 80000000 /(16*(UBMR + 1)/(UBIR+1))
	 * 
	 * 选取一组满足上式的参数:UBMR、UBIR 即可
	 *	
	 * UART1_UBIR    = 71
	 * UART1_UBMR = 3124  
	 */
	 
    UART1->UFCR = 5 << 7;       /* Uart 的时钟 clk:80MHz */
    UART1->UBIR = 71;
    UART1->UBMR = 3124;

	UART1->UCR1 |= (1 << 0);		/*使能当前串口*/ 
}

void PutChar(int c)						
{
	while (!((UART1->USR2) & (1<<3))); /*等待上个字节发送完毕*/
	UART1->UTXD = (unsigned char)c;		
}

unsigned char GetChar(void)						
{	
	while (!(UART1->USR2 & (1<<0)));  /*等待接收数据*/
	return (unsigned char)UART1->URXD;
}

void PutStr(const char *s)				
{
	while (*s)
	{
		PutChar(*s);
		s++;
	}
}
int raise(int signum)/* raise 函数,防止编译报错 */
{
    return 0;
}
2.3.6 uart.h
c
#ifndef _UART_H_
#define _UART_H_

/*UART1 的寄存器的基地址*/
#define UART1_BASE          (0x2020000u)

#define UART1    ((UART_Type *)UART1_BASE)

/*根据 IMX6ULL 芯片手册 <<55.15 UART Memory Map/Register Definition> > 的 3608 页,定义 UART 的结构体,*/
typedef struct {
  volatile unsigned int  URXD;               /**< UART Receiver Register, offset: 0x0 	           串口接收寄存器,偏移地址 0x0     */
  		   unsigned char RESERVED_0[60];		
  volatile unsigned int  UTXD;               /**< UART Transmitter Register, offset: 0x40          串口发送寄存器,偏移地址 0x40*/
  		   unsigned char RESERVED_1[60];		
  volatile unsigned int  UCR1;               /**< UART Control Register 1, offset: 0x80 	       串口控制寄存器 1,偏移地址 0x80*/
  volatile unsigned int  UCR2;               /**< UART Control Register 2, offset: 0x84 	       串口控制寄存器 2,偏移地址 0x84*/
  volatile unsigned int  UCR3;               /**< UART Control Register 3, offset: 0x88            串口控制寄存器 3,偏移地址 0x88*/
  volatile unsigned int  UCR4;               /**< UART Control Register 4, offset: 0x8C            串口控制寄存器 4,偏移地址 0x8C*/
  volatile unsigned int  UFCR;               /**< UART FIFO Control Register, offset: 0x90         串口 FIFO 控制寄存器,偏移地址 0x90*/
  volatile unsigned int  USR1;               /**< UART Status Register 1, offset: 0x94             串口状态寄存器 1,偏移地址 0x94*/
  volatile unsigned int  USR2;               /**< UART Status Register 2, offset: 0x98             串口状态寄存器 2,偏移地址 0x98*/
  volatile unsigned int  UESC;               /**< UART Escape Character Register, offset: 0x9C     串口转义字符寄存器,偏移地址 0x9C*/
  volatile unsigned int  UTIM;               /**< UART Escape Timer Register, offset: 0xA0         串口转义定时器寄存器 偏移地址 0xA0*/
  volatile unsigned int  UBIR;               /**< UART BRM Incremental Register, offset: 0xA4      串口二进制倍率增加寄存器 偏移地址 0xA4*/
  volatile unsigned int  UBMR;               /**< UART BRM Modulator Register, offset: 0xA8 	   串口二进制倍率调节寄存器 偏移地址 0xA8*/
  volatile unsigned int  UBRC;               /**< UART Baud Rate Count Register, offset: 0xAC      串口波特率计数寄存器 偏移地址 0xAC*/
  volatile unsigned int  ONEMS;              /**< UART One Millisecond Register, offset: 0xB0      串口一毫秒寄存器 偏移地址 0xB0*/
  volatile unsigned int  UTS;                /**< UART Test Register, offset: 0xB4                 串口测试寄存器 偏移地址 0xB4*/		
  volatile unsigned int  UMCR;               /**< UART RS-485 Mode Control Register, offset: 0xB8  串口 485 模式控制寄存器 偏移地址 0xB8*/
} UART_Type;

void Uart_Init(void);		
void PutChar(int c);

 #endif
2.3.7 Makefile
makefile
PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

my_printf.img : start.S  uart.c main.c my_printf.c
	$(CC) -nostdlib -g -c -o start.o start.S 
	$(CC) -nostdlib -g -c -o uart.o uart.c	
	$(CC) -nostdlib -g -c -o main.o main.c	
	$(CC) -nostdlib -g -c -o my_printf.o my_printf.c
	
	$(LD) -T imx6ull.lds -g start.o uart.o main.o my_printf.o -o my_printf.elf -lgcc -L/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/6.2.1
	
	$(OBJCOPY) -O binary -S my_printf.elf  my_printf.bin
	$(OBJDUMP) -D -m arm  my_printf.elf  > my_printf.dis	
	./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d my_printf.bin my_printf.imx
	dd if=/dev/zero of=1k.bin bs=1024 count=1
	cat 1k.bin my_printf.imx > my_printf.img

clean:
	rm -f my_printf.dis  my_printf.bin my_printf.elf my_printf.imx my_printf.img *.o
2.3.8 mp_printf.c
c
#include  "my_printf.h"
#include  "uart.h"

//==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==
typedef char *  va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
//#define va_arg(ap, t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_arg(ap,t)    ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )

//==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==
		  			 		  						  					  				 	   		  	  	 	  
unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
		                 '8','9','a','b','c','d','e','f'};


static int outc(int c) 
{
	PutChar(c);
	return 0;
}

static int outs (const char *s)
{
	while (*s != '\0')	
		PutChar(*s++);
	return 0;
}

static int out_num(long n, int base,char lead,int maxwidth) 
{
	unsigned long m=0;
	char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
	int count=0,i=0;
			

	*--s = '\0';
	
	if (n < 0){
		m = -n;
	}
	else{
		m = n;
	}
	
	do{
		*--s = hex_tab[m%base];
		count++;
	}while ((m /= base) != 0);
	
	if( maxwidth && count < maxwidth){
		for (i=maxwidth - count; i; i--)	
			*--s = lead;
}

	if (n < 0)
		*--s = '-';
	
	return outs(s);
}
   

/*reference :   int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap) 
{
	char lead=' ';
	int  maxwidth=0;
	
	 for(; *fmt != '\0'; fmt++)		
	 {
		if (*fmt != '%') {			
			outc(*fmt);				
			continue;
		}

		 lead=' ';
		 maxwidth=0;
		 
		//format : %08d, %8d,%d,%u,%x,%f,%c,%s 
		    fmt++;
		if(*fmt == '0'){
			lead = '0';
			fmt++;	
		}

		
		while(*fmt >= '0' && *fmt <= '9'){
			maxwidth *=10;
			maxwidth += (*fmt - '0');
			fmt++;
		}
		
		switch (*fmt) {
		case 'd': out_num(va_arg(ap, int),          10,lead,maxwidth); break;
		case 'o': out_num(va_arg(ap, unsigned int),  8,lead,maxwidth); break;				
		case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
		case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
		case 'c': outc(va_arg(ap, int   )); break;		
		case 's': outs(va_arg(ap, char *)); break;		  		
				
		default:  
			outc(*fmt);
			break;
		}
	}
	return 0;
}

//reference :  int printf(const char *format, ...); 
int printf(const char *fmt, ...) 
{
	va_list ap;

	va_start(ap, fmt);
	my_vprintf(fmt, ap);	
	va_end(ap);
	return 0;
}

int my_printf_test(void)
{
	printf("This is www.100ask.org   my_printf test\n\r") ;	
	printf("test char           =%c,%c\n\r", 'A','a') ;	
	printf("test decimal number =%d\n\r",    123456) ;
	printf("test decimal number =%d\n\r",    -123456) ;	
	printf("test hex     number =0x%x\n\r",  0x55aa55aa) ;	
	printf("test string         =%s\n\r",    "www.100ask.org") ;	
	printf("num=%08d\n\r",   12345);
	printf("num=%8d\n\r",    12345);
	printf("num=0x%08x\n\r", 0x12345);
	printf("num=0x%8x\n\r",  0x12345);
	printf("num=0x%02x\n\r", 0x1);
	printf("num=0x%2x\n\r",  0x1);

	printf("num=%05d\n\r", 0x1);
	printf("num=%5d\n\r",  0x1);

	return 0;
}
2.3.9 my_printf.h
c
#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H

#define  MAX_NUMBER_BYTES  	64
extern int my_printf_test(void);
int printf(const char *fmt, ...);
		  			 		  						  					  				 	   		  	  	 	  
#endif /* _MY_PRINTF_H */

2.4 测试效果

不出意外,一切顺利的话我们应该会得到以下输出:

shell
This is www.100ask.org my_printf test
test char =A,a
test decimal number =123456
test decimal number =-123456
test hex number =0x55aa55aa
test string =www.100ask.org
num=00012345
num= 12345
num=0x00012345
num=0x 12345
num=0x01
num=0x 1
num=00001
num= 1

串口终端如果打印以上信息,证明实验成功!

2.5 地址打印

我们来看一下上面移植好后,对地址的打印情况:

c
#include "my_printf.h"
#include "uart.h"

char g_charA = 'A';       // 存储在 .data 段
const char g_charB = 'B'; // 存储在 .rodata 段
const char g_charC; // 存储在 .bss 段
int g_intA = 0;     // 存储在 .bss 段
int g_intB;         // 存储在 .bss 段

int main(int argc, const char * argv[])
{
	int c = 9;
 	Uart_Init();
	my_printf_test();	
	printf("\n\r");//反过来会报错,具体原因未知,还没有深究
	printf("g_charA=%d &g_charA=0x%x\n\r", g_charA, &g_charA);
	printf("g_charB=%d &g_charB=0x%x\n\r", g_charB, &g_charB);
	printf("g_charC=%d &g_intA=0x%x\n\r", g_charC, &g_charC);
	printf("g_intA=%d &g_intA=0x%x\n\r", g_intA, &g_intA);
	printf("g_intB=%d &g_intB=0x%x\n\r", g_intB, &g_intB);
	printf("c=%d &c=0x%x\n\r", c, &c);
	return 0;
}

不出意外的话,我们会得到以下打印输出:

image-20240119003227288

会发现全是错的,具体原因可能还是哪里移植的不太对,后面进行了修改,修改后的工程可以直接看这里:imx6ull-bare-demo/02_UART/04_my_printf。修改完后打印信息如下:

image-20240119003653270