/*x86平台标准输出printf()函数原型:int printf(const char * fmt, ...);
*测试平台:Ubuntu16.04(64位机)
*ARM平台:s3c2440A
*编译器:gcc
*程序功能:制作一个与x86平台标准printf()功能相同的arm平台printf()函数,当完全裸机时,依然可以实现printf()函数的部分功能,包括输出:字符,字符串,(不同数据格式的)整数,(暂无浮点数,结构体);
//===============================================================================================================================
一、arm平台上实现与printf()函数相同功能的my_printf()函数
难点:1、裸机UART0调试打印功能的程序与自动确定可变参数的标准printf()函数的结合问题2、如何判断printf()函数中字符串和输出格式字符的区别,及输出格式字符后面数据格式,还有变量的输出问题
3、单项数值/变量输出还是多项输出,答:多项输出
4、系统对 buf[64] 的前几位没有数据的数组元素如何处理? 答:在buf[]中有指针s,指向数据n的最高位或正负符号位,输出s指向的字符串,即为数据n
方法:归根到底,把数据当做字符串来处理,已然确定是的
*/
/* 2018-05-30 File: my_printf.h 功能:my_printf.c文件的函数集合声明 */ #ifndef _MY_PRINTF_H #define _MY_PRINTF_H int printf(const char * fmt, ...); int my_printf_test(void); #endif
/* 2018-05-29 File:my_printf.c 功能:制作一个与x86平台printf()功能相同的arm平台printf()函数 难点: 1、裸机UART0调试打印功能的程序与自动确定可变参数的标准printf()函数的结合问题 2、如何判断printf()函数中字符串和输出格式字符的区别,及输出格式字符后面数据格式,还有变量的输出问题 3、单项数值/变量输出还是多项输出,答:多项输出 4、系统对 buf[64] 的前几位没有数据的数组元素如何处理? 答:在buf[]中有指针s,指向数据n的最高位或正负符号位,输出s指向的字符串,即为数据n 方法:归根到底,把数据当做字符串来处理,已然确定是的 */ //#include "s3c2440_soc.h" #include "uart.h" #include "my_printf.h" //========================================== //#define _out_putchar putchar #define MAX_NUMBER_BYTES 64 //========================================== 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_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'}; //或 unsigned char hex_tab[] = "0123456789abcdef"; 两种写法打印效果完全一样;注意:hex_tab为地址常量,不可改变和被赋值 /* 函数说明: 功能:输出规定格式的数字n 方法:把数字n当做字符串处理 特征:static int out_num(参数列表)内部函数,只可在本文件调用 */ 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); //定义数组buf[64]作为盛放数字n的缓冲器,数字n最长8字节,二进制64位 //防止数据输出格式为二进制? int count = 0, i = 0; *--s = '\0'; //s = s - 1; *s = '\0';先自减,后赋值; 可做左值 if(n < 0) m = -n; else m = n; /*板块:do{...}while(); *功能: 1、把数字n写到字符数组buf[]中,(假若n为十进制)其中, '\0'→buf[63](末元素),n个位→buf[62],n十位→buf[61],n百位→buf[60] ..., n宽度最高位→*s(此时,s的值为本函数out_num()结束时s的值) * 2、数字n的实际有效位长度为count位 * 3、数字n为base进制 */ do { *--s = hex_tab[m%base]; count++; }while((m /= base) != 0); /*板块:if(){...} *用法: 1、printf格式字符为%8d, %08d时进入if() * 2、printf格式字符为%d, %c, %f, %s, %u, %x时进入if() *功能:把数字n的规定格式宽度中,实际有效位前面的剩余位赋值为0 */ if(maxwidth && count < maxwidth) { for(i = maxwidth - count; i; i--) *--s = lead; } /*板块:if(){...} *用法:当 n<0 时,进入if(){...}; *说明:此时,在数组buf[]的存储空间中,指针s指向数字n的格式长度的的首位或符号(±)位。 *此时,字符串s即printf()打印的数字n */ if(n < 0) *--s = '-'; return puts(s); } /* 函数说明: 功能:制作一个与x86平台printf()功能相同的arm平台printf()函数 特征:static int my_printf(参数列表); 内部函数,只可在本文件调用 */ static int my_printf(const char * fmt, va_list ap) //此时,ap已经指向(fmt指向的)字符串后第一个可变参数 { char lead = ' '; int maxwidth = 0; for(; *fmt != '\0'; fmt++) { /*板块说明: *功能:打印printf()函数参数列表中%前面的字符 */ if(*fmt != '%') { putchar(*fmt); continue; } //format: %08d, %8d, %d, %u, %c, %s, %f /* *板块功能:判断并确定数据的输出格式:前导码lead,数据宽度maxwidth */ fmt++; if(*fmt == '0') { lead = '0'; fmt++; } lead = ' '; maxwidth = 0; while(*fmt >= '0' && *fmt <= '9') { maxwidth *= 10; maxwidth += (*fmt - '0'); //-'0';不可省去,否则会造成打印的数据宽度>>32位 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': putchar(va_arg(ap, int)); break; case 's': puts(va_arg(ap, char *)); break; default: putchar(*fmt); break; } } return 0; } int printf(const char * fmt, ...) { va_list ap; va_start(ap, fmt); my_printf(fmt, ap); va_end(ap); return 0; } /*测试函数*/ int my_printf_test(void) { printf("This if 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 decimal number = 0x%x\n\r", 0x55aa56ff); printf("Test string = %s\n\r", "www.100ask.org"); printf("num = %08d\n\r", 12345); printf("num = %8d\n\r", 12345); printf("num = 0x%08d\n\r", 0x12345); printf("num = 0x%08d\n\r", 0x12345); printf("num = %05d\n\r", 12345); printf("num = %5d\n\r", 12345); printf("num1 = %05d, num1 = %05d\n\r", 0x12, 0x23); return 0; } /*========================================================== arm平台的开发板的程序处理结果通过UART0在PC机的打印结果为: Wakakaka This if www.100ask.org my_printf_test Test char = A, a Test decimal number = 123456 Test decimal number = -123456 Test decimal number = 0x55aa56ff Test string = www.100ask.org num = 12345 num = 12345 num = 0x 74565 num = 0x 74565 num = 12345 num = 12345 num1 = 18, num1 = 35 nihao woaho dajihao 结果:程序运行成功 ======================================================== 解析printf()函数: 1.未遇到%,直接输出'n', 'n', 'n', 'n', 2.遇到%,处理格式字符: %08d %8d 前导码: '0' (空格) 最大宽度: 8位 8位 进制: 十进制 十进制 */
/* 2018-05-27 FIle: main.c 功能:主函数 1、输出字符串 2、完成与printf()功能相同的的arm平台printf()函数的制作 3、用UART0作调试打印功能串口,并输入输出字符 */ #include "s3c2440_soc.h" #include "uart.h" #include "my_printf.h" int main(void) { unsigned char c; led_test(); uart0_init(); puts("Wakakaka\n\r"); my_printf_test(); while(1) { c = getchar(); if(c == '\n') putchar('\r'); if(c == '\r') putchar('\n'); putchar(c); } return 0; }
/* 2018-05-27 File: uart0_2>uart.c 功能: 把UART0设置成开发板和PC机的串口工具间的调试打印工具 步骤: 1、设置输入输出引脚GPH2,3,波特率,数据格式 2、设置字符输出函数,字符输入函数,字符串输出函数 */ #include "s3c2440_soc.h" /*115200, 8N1*/ void uart0_init(void) { //设置输入输出引脚 GPHCON &= ~((3<<6) | (3<<4)); GPHCON |= ((2<<6) | (2<<4)); GPHUP &= ~((1<<3)|(1<<2)); //波特率UCON0 = 0B0101,UBRDIVn = (int)( UART 时钟 / ( 波特率 × 16) ) –1 UCON0 = 0X00000005; UBRDIV0 = 26; //数据格式 ULCON0 = 0X00000003; } /*字符输出函数*/ int putchar(int c) { while(!(UTRSTAT0 & (1<<2))); UTXH0 = (unsigned char)c; } /*字符输入函数*/ int getchar(void) { while(!(UTRSTAT0 & (1<<0))); return URXH0; } /*字符串输出函数*/ int puts(const char * s) { while(*s) { putchar(*s); s++; } }
/*
程序lib1funcs.S, 略
*/
/* 2018-05-26 File: led.c 功能: 流水灯 GPF4,5,6 */ #include "s3c2440_soc.h" void delay(volatile int k) { volatile int i, j; for(i = 0; i < k; i++) for(j = 0; j < 110; j++); } void led_test(void) { int i = 30; int l = 4; GPFCON &= ~((3<<12)|(3<<10)|(3<<8)); GPFCON |= ((1<<12)|(1<<10)|(1<<8)); while(i) { GPFDAT |= (7<<4); GPFDAT &= ~(1<<l); l++; i--; delay(2000); if(l > 6) l = 4; } }
/* 2018-05-27 File: uart0_2>start.S 功能: 1、程序开始的地方 2、关闭看门狗 3、设置时钟频率 4、判断启动方式并设置栈地址 */ .text .global _start _start: /*关闭看门狗*/ ldr r0, = 0x53000000 mov r1, #0 str r1, [r0] /*设置时钟频率*/ //设置锁定时间计数寄存器LOCKTIME(0X4C000000) ldr r0, = 0x4c000000 ldr r1, = 0xffffffff str r1, [r0] //设置CLKDIVN = 0b101,使得T(fclk) : T(hclk) : T(pclk) = 1 : 4 : 8 ldr r0, = 0x4c000014 ldr r1, = 5 str r1, [r0] /* *此时HDIVN 不为 0,CPU 总线模式应该使用以下指令使其从快总线模式改变为异步总线模式(S3C2440 *不支持同步总线模式)。 */ MRC p15, 0, r0, c1, c0, 0 ORR r0, r0, #0xc0000000 MCR p15, 0, r0, c1, c0, 0 //设置多层锁相环寄存器MPLL,使得FCLK = 400MHz ldr r0, = 0x4c000004 ldr r1, =((92<<12)|(1<<4)|(1<<0)) str r1, [r0] /*判断启动方式并设置栈地址*/ mov r0, #0 ldr r1, [r0] str r0, [r0] ldr r2, [r0] cmp r0, r2 //先假设Norflash启动 ldr sp, = 0x40000000 + 4096 //再假设Norflash启动 moveq sp, #4096 streq r1, [r0] bl main halt: b halt
/* 2018-05-27 File: uart0_2>uart.h 功能: 函数声明集合 */ #ifndef _UART_H #define _UART_H void delay(volatile int k); void led_test(void); void uart0_init(void); int putchar(int c); int getchar(void); int puts(const char * s); int push_test(const char * format, ...); int uart0_test(void); #endif
/* 2018-05-31 File: my_printf.h 功能:自制printf()函数的函数声明集合 */ #ifndef _MY_PRINTF_H #define _MY_PRINTF_H int printf(const char * format, ...); int my_printf_test(void); #endif
all: arm-linux-gcc -c -o led.o led.c arm-linux-gcc -c -o start.o start.S arm-linux-gcc -c -o lib1funcs.o lib1funcs.S arm-linux-gcc -c -o my_printf.o my_printf.c arm-linux-gcc -c -o main.o main.c arm-linux-gcc -c -o uart.o uart.c arm-linux-ld -Ttext 0 -Tdata 0xe80 start.o led.o uart.o lib1funcs.o my_printf.o main.o -o uart.elf arm-linux-objcopy -O binary -S uart.elf uart.bin arm-linux-objdump -D uart.elf > uart.dis clean: rm *.o *.bin *.dis *.elf
//==========================================================================
arm平台的开发板的程序处理结果通过UART0在PC机的打印结果为:
Wakakaka
This if www.100ask.org my_printf_test
Test char = A, a
Test decimal number = 123456
Test decimal number = -123456
Test decimal number = 0x55aa56ff
Test string = www.100ask.org
num = 12345
num = 12345
num = 0x 74565
num = 0x 74565
num = 12345
num = 12345
num1 = 18, num1 = 35
nihao
woaahuo
dajiahao
结果:程序运行成功
//==========================================================================
补充:
/* 2018-04-23 FIle:s3c2440_soc.h 功能:宏定义头文件 */ #ifndef _S3C2440_SOC_H #define _S3C2440_SOC_H #define _REG(x) (*(volatile unsigned int *)(x)) //地址[0xx]所对应的寄存器(即内存空间) #define _REG_BYTE(x) (*(volatile unsigned char *)(x)) /*Memory Controllers*/ #define BWSCON _REG(0x48000000) //Bus width & wait status control #define BANKCON0 _REG(0x48000004) //Boot ROM control #define BANKCON1 _REG(0x48000008) //BANK1 control #define BANKCON2 _REG(0x4800000C) //BANK2 control #define BANKCON3 _REG(0x48000010) //BANK3 control #define BANKCON4 _REG(0x48000014) //BANK4 control #define BANKCON5 _REG(0x48000018) //BANK5 control #define BANKCON6 _REG(0x4800001C) //BANK6 control #define BANKCON7 _REG(0x48000020) //BANK7 control #define REFRESH _REG(0x48000024) //DRAM/SDRAM refresh control #define BANKSIZE _REG(0x48000028) //Flexible bank size #define MRSRB6 _REG(0x4800002C) //Mode register set for SDRAM BANK6 #define MRSRB7 _REG(0x48000030) //Mode register set for SDRAM BANK7 /*WATDOG Timer register*/ #define WATCON _REG(0x53000000) //WATDOG Timer control register /*I/O Points*/ #define GPACON _REG(0x56000000) //Port A control #define GPADAT _REG(0x56000004) //Port A data #define GPBCON _REG(0x56000010) //Port B control #define GPBDAT _REG(0x56000014) //Port B data #define GPBUP _REG(0x56000018) //Pull-up control B #define GPCCON _REG(0x56000020) //Port C control #define GPCDAT _REG(0x56000024) //Port C data #define GPCUP _REG(0x56000028) //Pull-up control C #define GPDCON _REG(0x56000030) //Port D control #define GPDDA1T _REG(0x56000034) //Port D data #define GPDUP _REG(0x56000038) //Pull-up control D #define GPECON _REG(0x56000040) //Port E control #define GPEDAT _REG(0x56000044) //Port E data #define GPEUP _REG(0x56000048) //Pull-up control E #define GPFCON _REG(0x56000050) //Port F control #define GPFDAT _REG(0x56000054) //Port F data #define GPFUP _REG(0x56000058) //Pull-up control F #define GPGCON _REG(0x56000060) //Port G control #define GPGDAT _REG(0x56000064) //Port G data #define GPGUP _REG(0x56000068) //Pull-up control G #define GPHCON _REG(0x56000070) //Port H control #define GPHDAT _REG(0x56000074) //Port H data #define GPHUP _REG(0x56000078) //Pull-up control H #define GPJCON _REG(0x560000D0) //Port J control #define GPJDAT _REG(0x560000D4) //Port J data #define GPJUP _REG(0x560000D8) //Pull-up control J #define MISCCR _REG(0x56000080) //Miscellaneous control #define DCLKCON _REG(0x56000084) //DCLK0/1 control #define EXTINT0 _REG(0x56000088) //External interrupt control register 0 #define EXTINT1 _REG(0x5600008C) //External interrupt control register 1 #define EXTINT2 _REG(0x56000090) //External interrupt control register 2 #define EINTFLT0 _REG(0x56000094) //? W R/W Reserved #define EINTFLT1 _REG(0x56000098) //Reserved #define EINTFLT2 _REG(0x5600009C) //External interrupt filter control register 2 #define EINTFLT3 _REG(0x560000A0) //External interrupt filter control register 3 #define EINTMASK _REG(0x560000A4) //External interrupt mask #define EINTPEND _REG(0x560000A8) //External interrupt pending #define GSTATUS0 _REG(0x560000AC) //R External pin status #define GSTATUS1 _REG(0x560000B0) //R/W Chip ID #define GSTATUS2 _REG(0x560000B4) //Reset status #define GSTATUS3 _REG(0x560000B8) //Inform register #define GSTATUS4 _REG(0x560000BC) //Inform register #define MSLCON _REG(0x560000CC) //Memory sleep control register /*UART*/ #define ULCON0 _REG(0x50000000) //UART 0 line control #define UCON0 _REG(0x50000004) //UART 0 control #define UFCON0 _REG(0x50000008) //UART 0 FIFO control #define UMCON0 _REG(0x5000000C) //UART 0 modem control #define UTRSTAT0 _REG(0x50000010) //UART 0 Tx/Rx status #define UERSTAT0 _REG(0x50000014) //UART 0 Rx error status #define UFSTAT0 _REG(0x50000018) //UART 0 FIFO status #define UMSTAT0 _REG(0x5000001C) //UART 0 modem status #define UTXH0 _REG_BYTE(0x50000020) //UART 0 transmission hold, 小端模式-高字节高地址,低字节低地址 #define URXH0 _REG_BYTE(0x50000024) //UART 0 receive buffer, 小端模式-高字节高地址,低字节低地址 #define UBRDIV0 _REG(0x50000028) //UART 0 baud rate divisor #define ULCON1 _REG(0x50004000) //UART 1 line control #define UCON1 _REG(0x50004004) //UART 1 control #define UFCON1 _REG(0x50004008) //UART 1 FIFO control #define UMCON1 _REG(0x5000400C) //UART 1 modem control #define UTRSTAT1 _REG(0x50004010) //UART 1 Tx/Rx status #define UERSTAT1 _REG(0x50004014) //UART 1 Rx error status #define UFSTAT1 _REG(0x50004018) //UART 1 FIFO status #define UMSTAT1 _REG(0x5000401C) //UART 1 modem status #define UTXH1 _REG_BYTE(0x50004020) //UART 1 transmission hold,小端模式-高字节高地址,低字节低地址 #define URXH1 _REG_BYTE(0x50004024) //UART 1 receive buffer,小端模式-高字节高地址,低字节低地址 #define UBRDIV1 _REG(0x50004028) //UART 1 baud rate divisor #define ULCON2 _REG(0x50008000) //UART 2 line control #define UCON2 _REG(0x50008004) //UART 2 control #define UFCON2 _REG(0x50008008) //UART 2 FIFO control #define UTRSTAT2 _REG(0x50008010) //UART 2 Tx/Rx status #define UERSTAT2 _REG(0x50008014) //UART 2 Rx error status #define UFSTAT2 _REG(0x50008018) //UART 2 FIFO status #define UTXH2 _REG_BYTE(0x50008020) //UART 2 transmission hold,小端模式-高字节高地址,低字节低地址 #define URXH2 _REG_BYTE(0x50008024) //UART 2 receive buffer,小端模式-高字节高地址,低字节低地址 #define UBRDIV2 _REG(0x50008028) //UART 2 baud rate divisor #endif