s3c2440学习之路-008 uart实现printf函数

最新更新时间:2021-10-14来源: eefocus关键字:s3c2440  uart  printf函数 手机看文章 扫描二维码
随时随地手机看文章

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

[1] [2]
关键字:s3c2440  uart  printf函数 编辑:什么鱼 引用地址:s3c2440学习之路-008 uart实现printf函数

上一篇:s3c2440学习之路-009 nor flash的简单配置
下一篇:s3c2440学习之路-007uart的简单配置

推荐阅读

printf()是如何与UART外设驱动函数“勾搭”起来的?
这绝对是一篇好文章,打印这个函数有时候比什么调试工具都有用,内核的prink更加是神器中的神器,但是printf这个函数是怎么和uart驱动联系在一起的,这篇文章会给你解答,点赞,转发,收藏起来需要的时候看看吧。今天给大家分享的是IAR下调试信息输出机制之硬件UART外设。在嵌入式世界里,输出打印信息是一种非常常用的辅助调试手段,借助打印信息,我们可以比较容易地定位和分析程序问题。在嵌入式应用设计里实现打印信息输出的方式有很多,本系列将以 IAR 环境为例逐一介绍 ARM Cortex-M 内核 MCU 下打印信息输出方法。本篇是第一篇,我们先介绍最常见的输出打印信息方式,即利用 MCU 芯片内的硬件 UART 外设。本篇其实并不是
发表于 2023-03-27
单片机下串口(UART)协议包接收程序
代码编写在51单片机上,用于从接收缓冲区中提取有用数据(协议包)。基本逻辑亦可用于其他语言,其他情况下的接收程序。数据的转移主要分了3步,接收缓冲区=》待处理数据=》有用数据。接收缓冲区提取到待处理数据:由于可能下一个包马上就要过来,所以应把数据提取出来再做处理,以免直接处理的过程中收到新的数据造成混乱。待处理数据提取到有用数据:例如在某些环境下,会收到其他协议的包(由其他程序处理),或者带有地址区分是否发给本机的包,所以需要设置一些条件来提取真正有用的数据。单片机串口UART在接收中断程序中写入接收缓冲区,一定时间没有收到数据则标志为接收空闲(完成一个包的接收)。本段代码先判断串口处于接收空闲状态,然后判断包太小则放弃,认为是错误
发表于 2023-03-24
单片机UART升级固件流程
单片机是嵌入式系统中最基础和常用的芯片种类之一。随着技术的不断发展,单片机的功能和性能得到了越来越大的提升,同时固件的规模和复杂度也在不断增加。为了保障单片机的正常运行,需要对其进行固件升级。本文将介绍单片机UART升级固件的基本流程。1. 确定升级方式单片机的固件升级方式通常有多种,包括串口、USB、SD卡等。其中,串口升级是最为常用的一种方式,可以通过引脚上的TX和RX两个信号线实现数据传输,具有简单、方便、快捷等优点。2. 准备工作在进行单片机UART升级之前,需要进行一些准备工作。首先,需要准备好电脑、单片机芯片以及相关的软硬件工具。其次,需要查看单片机芯片的数据手册,确定需要升级的固件版本和升级方法,并将升级相关的程序代码
发表于 2023-03-24
STM32F0单片机快速入门六 用库操作串口(UART)原来如此简单
1.从 GPIO 到 UART前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。我们用 Keil 打开下面这个工程:STM32Cube_FW_F0_V1.11.0ProjectsSTM32F030R8-NucleoExamplesUARTUART_TwoBoards_ComPollingMDK-ARMProject.uvprojx这个代码配置串口为 9600,8 N
发表于 2023-03-17
STM32F0单片机快速入门七 串口(UART)操作从轮询到中断
1.从轮询到中断很多同学都不喜欢用中断,而偏爱用轮询的操作方式。这是不是和我们的天性有关呢?每个人都喜欢一切尽在掌握中,肯定都不喜欢被打断。我们常常都有这样的经验:正在跟别人说一件事,然后突然有个电话打进来,Call打完后突然记不起来刚才讲到哪了!这种糟糕的体验对我们影响是如此深刻,以至于我们认定机器可能也是这样吧,频繁的中断会不会把事情搞乱呢?好在机器虽然大部分时间都比人笨一些,但在处理这种问题上却能做到一丝不苟。机器在中断来的时候总会老老实实地先把当前正在做的记录下来,然后转去处理中断事件,中断处理完后分毫不差地恢复原来的工作。仔细想一想,我们是不是也可以在接电话前先用个小本儿记录一下正在讲的事情呢?我们为什么没有这么做呢?一个
发表于 2023-03-17
U-boot在S3C2440上的移植详解(四)
一、移植环境主 机:VMWare--Fedora 9开发板:Mini2440--64MB Nand,Kernel:2.6.30.4编译器:arm-linux-gcc-4.3.2.tgzu-boot:u-boot-2009.08.tar.bz2二、移植步骤在这一篇中,我们首先让开发板对CS8900或者DM9000X网卡的支持,然后再分析实现u-boot怎样来引导Linux内核启动。因为测试u-boot引导内核我们要用到网络下载功能。7)u-boot对CS8900或者DM9000X网卡的支持。u-boot-2009.08版本已经对CS8900和DM9000X网卡有比较完善的代码支持(代码在drivers/net/目录下),而且在S3C2
发表于 2023-02-07
U-boot在<font color='red'>S3C2440</font>上的移植详解(四)

推荐帖子

【学习心得-DLP三】如何开发DLP微投产品
产品开发这一集实际给出了很多好的借鉴,我相信大部分产品开发都可以遵循这样的一个过程。 首先就是一个产品定位与使用场景(单纯还是附属),这一方面会决定所选的DLP光学引擎的功耗、分辨率、尺寸等等特性。当光学引擎可控制平台定下来后,则要去考虑主控-DLP控制电路-光学引擎之间的接口与控制方式。另一方面则会影响到视频的来源(内置存储的,通过标准视频接口、网络、无线传送等等),这个来源会极大的影响视频前端的接受方案。事实上,到底支持哪些接口会影响到开发时间、产品体积和成本,这些都是需要权衡的。当然
johnrey TI技术论坛
为 FPGA 供电的技巧
糟糕。将现场可编程门阵列(FPGA)连接到DC/DC转换器的输出,现在DC/DC无法启动。当使用示波器观察电路时,看到图1所示的情形。输出电压未进入调压模式。哪里发生故障了呢?图1:由于该FPGA具有较高的启动负载和极高的去耦电容,DC/DC转换器无法使其输出电压进入调压模式FPGA对其电源提出了一些独特的挑战。例如,FPGA供应商通常需要其输入电源拥有数百或甚至数千微法拉(µF)的去耦电容,以便在FPGA产生的瞬变的不同频率之间维持FPGA电源电压所需的调节,并减少电源
qwqwqw2088 模拟与混合信号
全志R16搭配NAND FLASH
正泽环球有限公司(简称“J&G”)成立于2006年,位于广东省深圳市。我们是销售各种韩国内存(RAM和ROM)的韩国代理商。现有代理品牌JSC,Fidelix,ATOSOLUTION,Neowine,Netsol等。我们的主要产品有:SPINANDFlash,SLCNANDFlash,MCP,PSRAM,LPSDRAM,LPDDR1,LPDDR2,DDR3,加密芯片IC等。1、Fidelix品牌NANMCP和低功耗内存PSRAM,LPSDRAM,LPDDR1,LPDDR2,DD
dctmonica ARM技术
功率变换开关技术(修订版)电力电子的核心理论
电力电子技术在电力系统、新能源发电、电动汽车、电力牵引以及家用电器等众多领域快速发展,电力电子化已成为一种发展趋势,然而现在大多数电力电子技术教材是由既成技术的集合体形成的,从而造成教材内容缺少理论及体系化的现实,在此背景下,编写一本具备理论及体系化且适应电力电子技术发展需求和人才培养需求的教科书,显得尤为重要。本书首先概述电力电子变换技术的基础原理及概况,其次说明半导体开关器件原理及其用法,对功率变换方式进行了分类,并形成体系化。然后对直流-直流变换、直流-交流变换、交流-直流变换和交流-交流
arui1999 下载中心专版
市场上的串口屏,一般用哪种单片机做的主控芯片?
现在市场上有好多种类的串口屏,主控芯片都被打磨了,想问一下串口屏,一般用哪种单片机作为主控合适呢???市场上的串口屏,一般用哪种单片机做的主控芯片?
WKfirst 单片机
技术演变正在进行:V2X 架构
​3GPP计划在V2X中使用5G技术以及汽车级别的射频前端模块(FEM),相比于当前专用短距离通信或其他C-V2X协议,该技术具有明显的优势。新兴车联网应用(如V2X)需要满足延迟、数据速率、可靠性和通信范围方面严格的服务质量(QoS)要求。在开发自动驾驶汽车过程中,通常会使用以下三种传感器技术:摄像头、雷达和激光雷达。然而,现有的另一种无线技术,即车对万物(V2X),可大大提高自动驾驶汽车的价值。V2X是指各种交通运输相关传感器之间进行的高带宽、低延迟可
btty038 RF/无线
小广播
设计资源 培训 开发板 精华推荐

何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2023 EEWORLD.com.cn, Inc. All rights reserved