历史上的今天
返回首页

历史上的今天

今天是:2024年09月08日(星期日)

2021年09月08日 | s3c2440学习之路-008 uart实现printf函数

2021-09-08 来源:eefocus

1.基本原理

因为程序目前处于裸板阶段,只能输出字符串,没有C语言的printf函数可以调用。但是在调试程序时,想像C语言一样调用printf来调试,因此只能自己来实现了。


C语言中,printf函数的原型为:


int printf(const char *format, ...);


1.1 可变参数"…"

参数有2个, “const char *format” 和 “…”,这个“…”就是可变参数,下面先讲解一下如何识别这个可变参数。

参数的传递会顺序的放到栈里面,而printf函数可获取的参数只有 “const char *format” 和 “…”,不过format刚好指向了第1个参数的首地址,因此可以通过format地址的移动来找到后续的几个参数。


先通过1个简单的程序了解如何通过format这个参数找到后续的参数。请结合程序下面的图片来理解,必须理解这个程序才可继续往后看,否则会懵逼。


程序是实现自己的printf函数,把传进去的字符串、整数、结构体、字符、浮点数依次打印出来


01_printf.c


#include


struct person{

char *name;

int age;

char score;

int id;

};


int my_printf(const char *format, ...)

{

    /* 指针p指向format的地址 */

char *p = (char *)&format;

    int arg2 = 0;

struct person arg3;

char arg4 = 0;

double arg5;// must be duoble


    /* format指向了第1个参数的首地址

      所以直接输出format就可以"abcd" */

printf("arg1:%sn", format);


    /* 第1参数是字符串 "abcd", p的值加上sizeof(char *)就可以移动到第2个参数 */

p += sizeof(char *);

arg2 = *((int *)p);

printf("arg2:%dn", arg2);


    /* 第2个参数是整数 123, p的值加上sizeof(int)就可以移到第3个参数  */

p += sizeof(int);

arg3 = *((struct person *)p);

printf("arg3:name:%s,age:%d,score:%c, id:%dn", arg3.name, arg3.age, arg3.score, arg3.id);


    /* 第3个参数是结构体, p的值加上sizeof(struce person)就可以移到第4个参数  */

    p += sizeof(struct person);

    arg4 = *((char *)p);

    printf("arg4:%cn", arg4);


    /* 第4个参数是字符 C, 这里需要4对齐,所以加上((sizeof(char) + 3) & ~3)

     就可以移到第5个参数  */

    p += ((sizeof(char) + 3) & ~3);

    /* 这里需要转化成double, 否则数据会出错 */

    arg5 = *((double *)p);

    printf("arg5:%lfn", arg5);


return 0;

}


int main(int argc, char *argv)

{

struct person per = {"Black", 26, 'A', 53};


            //字符串  int  结构体  char double

    my_printf("abcd", 123, per,    'C', 4.321);


return 0;

}

在这里插入图片描述

在这里插入图片描述

1.2 获取地址的替代函数

01_printf.c 中,获取参数的都分成2个小步骤:

1)通过sizeof来偏移地址,如 p += sizeof(char *);

2)通过指针强制转换来取值, 如arg2 = *((int *)p);


是否有办法将2个步骤合二为一,这里可以通过va_start, va_end, va_arg来实现,所需要的头文件为#include


void va_start(va_list ap, last);

type va_arg(va_list ap, type);

void va_end(va_list ap);


01_printf.c 中,一开始我们用char *p = (char *)&format; 来获得format的首地址。这里可以通过

va_start 来代替。

1)先定义va_list 变量:va_list p;

2)将format和定义好的va_list p带入va_start: va_start(p, format);

char *p = (char *)&format 就等价于 va_start(p, format);


不过va_start事实上还会把p的值指向下一个参数,所以va_start等于做了2个步骤

1)char *p = (char *)&format

2)p += sizeof(char *);


获取第2个参数的2个小步骤可由va_arg来代替:

1)p += sizeof(char *);

2)arg2 = *((int *)p);

等价于 arg2= vs_arg(p, int);


不过对于第2个参数来说,地址的偏移是由va_start(p, format)完成了,所以并不需要再做p += sizeof(char *)了,而是把地址偏移到下个参数。所以vs_arg(p, int) 实际等价于:

1)arg2 = *((int *)p);

2)p += sizeof(int);


va_star和va_arg的函数可能有点绕,但总结起来就是2个功能:

1)获取当前参数的值

2)把地址偏移到下1个参数

调用va_start(p, format)时,p已经指向到了第2个参数的位置(第1个参数就是format)

调用arg2= vs_arg(p, int)时,p已经指向到了第3个参数的位置

下面贴出整体替换的代码,自己好好分析一下


03_printf.c


#include

#include


struct person{

char *name;

int age;

char score;

int id;

};


//use va_list va_start va_arg va_end 

int my_printf(const char *format, ...)

{

va_list p;

    int arg2 = 0;

struct person arg3;

char arg4 = 0;

double arg5;// must be duoble


//arg1

printf("arg1:%sn", format);

//char *p = (char *)&format;

    //p += sizeof(char *);

va_start(p, format);


//arg2

//arg2 = *((int *)p);

//p += sizeof(int);

arg2 = va_arg(p, int);

printf("arg2:%dn", arg2);


//arg3

//arg3 = *((struct person *)p);

    //p += sizeof(struct person);

arg3 = va_arg(p, struct person);

printf("arg3:name:%s,age:%d,score:%c, id:%dn", arg3.name, arg3.age, arg3.score, arg3.id);


//arg4

    //arg4 = *((char *)p);

    //p += ((sizeof(char) + 3) & ~3);

arg4 = va_arg(p, int); //must be int, because alignment is 4

    printf("arg4:%cn", arg4);


//arg5

    //arg5 = *((double *)p);

arg5 = va_arg(p, double);

    printf("arg5:%lfn", arg5);


va_end(p);


return 0;

}


int main(int argc, char *argv)

{

struct person per = {"Black", 26, 'A', 53};


    my_printf("abcd", 123, per, 'C', 4.321);


return 0;

}


1.3 arg_xx的函数原型分析

由于跑裸板程序时,并没有C库可以调用,所以va_star, va_arg, va_end这3个函数需要自己实现。在网上收到这3个函数的原型,原来全是宏,下面对这些宏进行分析说明


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 )


#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ):

为了实现sizeof(int)对齐也就是4对齐,所以才有(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) 这一串操作。就等价于实现了4对齐的一个sizeof宏。


#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ):

如同前面说的,va_start实现了把ap偏移到下1个参数的功能。


这里先讲1个前提知识,请看下面test.c的代码,最终A的值是2。如果是#define A (1, 2, 3) 那么最终A的值会是3,C语言就有这么一个小规则。


test.c


#include

#define A (1,2)


int main(int argc, char *argv)

{

    printf("A=%dn", A);

    return 0;

}

在这里插入图片描述

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ):

这个宏实现了2个功能,第1就是参数强制转换成对应的类型,第2就是将地址偏移到下1个参数。


*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))可以拆开成2个步骤:

ap = ap + _INTSIZEOF(t)

*(t *)(ap - _INTSIZEOF(t))

将这2个步骤带到va_arg(ap,t)中


#define va_arg(ap,t)    (ap = ap + _INTSIZEOF(t), *(t *)(ap - _INTSIZEOF(t)))


ap = ap + _INTSIZEOF(t) 把ap地址偏移到了下1个参数

*(t *)(ap - _INTSIZEOF(t)) 把ap地址减去_INTSIZEOF(t)再强制转换,此时ap还是偏移在下1个参数不过 *(t *)(ap - _INTSIZEOF(t)) 整体作为了宏的值


还可以写成这样


#define va_arg(ap,t)    (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))


2.源码

__out_putchar©函数就是就是putchar,也就是前篇文章实现的函数

https://blog.csdn.net/lian494362816/article/details/85083263

最后printf函数的实现我就不讲解了,自己看看源码分析分析。


#define __out_putchar putchar


int putchar(int c)

{

    while (!(UTRSTAT0 & 0x4))

    {

        //nothing

    }


    UTXH0 = (unsigned char )c;

}


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) )

//#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 )


static 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)

{

    __out_putchar(c);

    return 0;

}


static int outs (const char *s)

{

    while(*s != '')

    {

        __out_putchar(*s++);

    }


    return 0;

}


static int out_num(long n, int base, char lead, int maxwidth)

{

    unsigned long num = 0;

    char buf[MAX_NUMBER_BYTES];

    char *s = buf + sizeof(buf);

    int count = 0;

    int i = 0;


    *--s = '';


    num = n;

    if(n < 0)

    {

        num = -n;

    }


    do{

        *--s = hex_tab[num % base];

        count ++;

    }while((num /= base) != 0);


    if (maxwidth && count < maxwidth)

    {

        for (i = maxwidth - count; i; i--)

        {

            *--s = lead;

        }

    }


    if(n < 0)

    {

        *--s = '-';

    }


    outs(s);


    return 0;

}


static int my_vprintf(const char *fmt, va_list ap)

{

    char lead = ' ';

    int maxwidth = 0;


    for (; *fmt != ''; fmt++)

    {

        if (*fmt != '%')

        {

            outc(*fmt);

            continue;

        }


        /*format : %08d, %8d,%d,%u,%x,%f,%c,%s*/

        fmt++;


        if ('0' == *fmt)

        {

            lead = '0';

            fmt ++;

        }



        lead = ' ';

        maxwidth = 0;


        while(*fmt >= '0' && *fmt <= '9')

        {

            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);

推荐阅读

史海拾趣

Diplohmatic A/S公司的发展小趣事

Diplohmatic A/S公司最初是一家小型电子元件分销商,但其创始人对技术创新的执着追求引领了公司的转型。在一次偶然的机会中,公司研发团队发现了一种新型半导体材料,这种材料在能效和稳定性上远超市场同类产品。经过数月的研发和测试,公司成功将这种材料应用于新型电源管理芯片中,并迅速获得了市场的认可。这一创新不仅为公司带来了丰厚的利润,也奠定了其在电子行业的技术领先地位。

FCI connector [富加宜连接器]公司的发展小趣事

自FCI成立以来,公司凭借其专业的设计、精密的制造工艺和创新精神,迅速在全球连接器市场上崭露头角。通过不断的技术创新和产品升级,FCI逐渐成为了全球领先的连接器制造商之一。其产品线涵盖了通讯、电源、医疗等多个领域,为全球众多知名品牌提供了优质的连接器解决方案。

德索五金(dosinconn)公司的发展小趣事

发展历程:2016年,公司荣获国家高新技术企业认证,并累计获得了30余项专利。这些荣誉的获得不仅彰显了公司在技术创新方面的实力,也为企业赢得了更多的市场机会。

成果与影响:技术沉淀和荣誉的获得使德索五金电子在连接器制造领域树立了良好的品牌形象,也为公司的长远发展提供了坚实的技术支持。

以上五个故事框架简要概述了德索五金电子在电子行业中的发展历程和关键成就。每个故事都突出了公司在不同阶段的发展重点和取得的成果,为读者提供了关于德索五金电子成长历程的全面了解。

Eby Electro Inc公司的发展小趣事

随着公司规模的扩大和产品质量的提升,Eby Electro Inc开始寻求拓展国际市场。公司积极参加国际电子产品展览,与国际知名企业建立合作关系,将产品销往全球各地。在国际市场的竞争中,Eby Electro Inc凭借卓越的产品质量和良好的售后服务,赢得了客户的信赖和好评。同时,公司也积极学习国际先进的管理经验和技术,不断提升自身的竞争力。

格瑞宝(GP)公司的发展小趣事

随着产品线的拓展和技术实力的增强,格瑞宝开始积极拓展国内外市场。公司不仅在消费电子和工业类电子领域取得了显著成绩,还努力将产品和服务拓展到通信、汽车电子等新兴领域。同时,格瑞宝注重品牌建设,通过提供优质的产品和服务,赢得了广大客户的信赖和好评。公司的品牌知名度和影响力不断提升,为公司的长远发展奠定了良好基础。

DMC Tools公司的发展小趣事

DMC Tools公司自创立之初,就注重技术创新。早期,公司研发团队针对电子行业对高精度工具的需求,成功开发出一系列具有自主知识产权的精密切割工具。这些工具不仅提高了生产效率,还降低了生产成本,迅速在市场上获得了认可。随着技术的不断进步,DMC Tools公司不断推出新产品,满足电子行业日益增长的需求,逐步在市场上树立了自己的品牌。

问答坊 | AI 解惑

学习单片机需要掌握的硬件问题,大家一起分享

学习单片机需要掌握的硬件问题,大家一起分享 就单片机学习过程中硬件设计方面的几个基本问题一起分享 1、电阻电容的封装形式如何选择,有没有什么原则?比如,同样是 104 的电容有 0603、0805 的封装,同样是 10uF 电容有 3216、0805、3528 ...…

查看全部问答>

施耐德触摸屏XBTGT5330之COM1或COM2口进行串口通讯得进

想利用XBTGT5330的COM口(com1或com2口)与第三方厂家的仪表进行串行通讯 该第三方的通讯协议符合modbus协议。其具体协议数据格式为: 开始符       指令代码          ...…

查看全部问答>

高人求救

请问A/D转换显示 S3C44BO 4路LCD显示曲线 屏幕显示三分钟 超过三分钟将右半屏移到左半屏 每秒采样60次,外接5V电压 请高手指点…

查看全部问答>

+++++++++GPRS数据串口转以太网

我的ARM开发板通过串口连接的GPRS MODEM上网,它也有以太网口,我想把接收的GPRS数据转发到以太网口上(它所在的局域网),请问难不难啊。…

查看全部问答>

单片机的汇编指令使用基础

1 .MOV A,Rn 寄存器内容送入累加器2 .MOV A,direct 直接地址单元中的数据送入累加器3 .MOV A,@Ri (i=0,1)间接RAM 中的数据送入累加器4 .MOV A,#data 立即数送入累加器5 .MOV Rn,A 累加器内容送入寄存器6 .MOV Rn,direct 直接地址单元中的数据送入寄 ...…

查看全部问答>

C6455 TCP2中TCPIC0~15有一半的寄存器写不进去

在CCS3.3下,查看寄存器的值,发现TCP2中TCPIC0~TCPIC15中,有一半的寄存器不能write,分别是TCPIC(1,3,5,7,9,11,13,15),是因为哪个控制器锁住了吗? 情况紧急,请求各位帮助,谢谢!…

查看全部问答>

LPC1500体验+(4)使用SWM将功能动态分配到任意引脚

本帖最后由 mars4zhu 于 2014-9-23 11:14 编辑 LPCXpresso1549试用报告——(3)使用SWM将功能动态分配到任意引脚 文档编号AN-0001-A0关键字LPCXpresso1549, LPCOpen, LPC1549, Keil MDK, ARM Cortex-M, SWM, 功能分配任意引脚摘要本文记 ...…

查看全部问答>

Android新功能:用谷歌搜索寻找丢失手机

谷歌本周宣布推出一项新功能,帮助用户通过桌面平台的搜索引擎去寻找丢失的Android手机。如果希望使用这一功能,用户需要知道自己的电脑在何处。谷歌同时表示,用户需要安装最新版谷歌Android应用才能使用这一功能。在升级之后,当用户在谷歌搜索引 ...…

查看全部问答>

关于程序计数器PC的理解

51单片机的程序计数器PC的16位的也就是65536 ARM的程序计数器是32位也就是4G的空间地址 如何理解这写内容呢? PC指针是否就限制了程序的大小呢,或者说是程序的长短。 以STC89C52为例 程序存储空间是8K   PC的最大数是65536。 …

查看全部问答>

Micropython Timer回调函数的疑问

import pyb import time from pyb import Pin,Timer from DHT11 import DHT11 S=DHT11(\'Y2\') A=\'\'    def f(t):         global A,S         pyb.LED(1).toggle()     ...…

查看全部问答>