0%

实现printf()函数

2018年1月31日 下午1:15

实现arm中的printf函数源代码(注释是重点):

.c文件

  1. my_printf.c
  2. uart.c
  3. main.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    my_printf.c 
    #include "my_printf.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) )

    //逗号表达式功能:(表达式1,表达式2)运算顺序是表达式1-->表达式2,返回表达式2的结果
    //下面两种写法作用一致
    //#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'};

    // 输出显示char
    static int outc(int c)
    {
    __out_putchar(c);
    return 0;
    }

    // 输出显示string
    static int outs (const char *s)
    {
    while (*s != '\0')
    __out_putchar(*s++);
    return 0;
    }

    // 输出显示number:传入n,进制,前导位,最大宽度。最后要转化成字符串输出!!
    //这个函数得打断点看输出,我没有看!
    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);
    }

    //my_vprintf函数,通过调整指向+步长去完成printf的功能
    /*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++)
    {
    //初始化
    lead=' ';
    maxwidth=0;

    if (*fmt != '%') {
    outc(*fmt);
    continue;
    }

    //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++;
    }

    //根据列类型符:先取值,再移动指针。
    //va_arg的作用是:取出当前ap指向的数据,然后ap再根据“步长”去移动到下一个位置
    //这样做的好处:这时的“步长”就是当前取出这个数据的长度,牛逼🐂!
    //va_arg的“返回值”=当前ap指向的数据
    //out_num()函数的参数: 当前ap指向的数据 +
    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;
    }

    // 重点:实现printf
    //reference : int printf(const char *format, ...);
    int printf(const char *fmt, ...)
    {
    /*这个指针ap,我觉得倒是很有话说
    *因为指针体现出了printf这个函数的本质,最精华的地方:所有的取值都是通过ap这一个指针来实现的!!
    *通过调整指针的指向+步长,来最终确定我们的操作。
    */
    va_list ap;

    //va_start这个宏,就是一个赋值语句。作用:给ap赋值,初始化ap指针的指向为第一个参数fmt
    va_start(ap, fmt);
    //my_vprintf函数,通过调整指向+步长去完成printf的功能
    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;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
uart.c
#include "s3c2440_soc.h"

// 初始化UART芯片
/* 115200,8n1 */
void uart0_init()
{
//这是根据电路原理图来设定的,本身GPIO就是一个通用口
/* 设置引脚用于串口 */
/* GPH2,3用于TxD0, RxD0 */
GPHCON &= ~((3<<4) | (3<<6));
GPHCON |= ((2<<4) | (2<<6));

GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */

//manual中专门以小结将波特率,直接告诉你使用那些寄存器,怎么使用,公式是啥,写的很完善。
/* 设置波特率 */
/* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
* UART clock = 50M
* UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
*/
UCON0 = 0x00000005; /* PCLK,中断/查询模式 */
UBRDIV0 = 26;

/* 设置数据格式 */
ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */

/* */

}

// 向pc显示器发送显示数据,按char
int putchar(int c)
{
/* UTRSTAT0 */
/* UTXH0 */
//为啥是UTRSTAT0,这里的0是哪里来的?Each UART channel contains two 64-byte FIFOs for receiver and transmitter.
//这个问题应该一开始就问,根本原因是原理图中使用的是TxD0,RxD0。版子的厂家电路就是这样焊好的!!!你要咋吧
while (!(UTRSTAT0 & (1<<2)));

//manual中总体介绍中,并没有提到UTXH0寄存器,但是在手册中最后说明寄存器中有:There are three UART transmit buffer registers including UTXH0, UTXH1 and UTXH2 in the UART block.
//也许像这种数据寄存器,就当成默认有的吧,所以manual就直接写在寄存器部分!!
UTXH0 = (unsigned char)c;

}
// 拿回从pc显示器输入的值
int getchar(void)
{
while (!(UTRSTAT0 & (1<<0)));
return URXH0;
}

// 向pc显示器发送显示数据,按字符串
int puts(const char *s)
{
while (*s)
{
putchar(*s);
s++;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
main.c
// 这里就是这节函数牛逼的地方,不用像stdio.h这种系统函数库,全是自己写的
#include "s3c2440_soc.h"
#include "uart.h"
#include "my_printf.h"

int main(void)
{
unsigned char c;

//初始化uart
uart0_init();
//pc显示字符串:Hello, world!
puts("Hello, world!\n\r");
//测试printf函数
my_printf_test();

//pc与arm的交互行为:又来有往
while(1)
{
c = getchar();
if (c == '\r')
{
putchar('\n');
}

if (c == '\n')
{
putchar('\r');
}

putchar(c);
}
return 0;
}

.h文件

1
2
3
4
5
6
7
8
9
10
uart.h
#ifndef _UART_H
#define _UART_H
//声明本身的函数
void uart0_init();
int putchar(int c);
int getchar(void);
int puts(const char *s);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
my_printf.h
#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H
// 说明my_printf.c依赖于uart.c
#include "uart.h"
// 宏定义
#define __out_putchar putchar

#define MAX_NUMBER_BYTES 64
// 声明本身的所带有的函数
extern int my_printf_test(void);
int printf(const char *fmt, ...);

#endif /* _MY_PRINTF_H */