/*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