历史上的今天

今天是:2024年10月13日(星期日)

正在发生

2018年10月13日 | Tiny210裸机之实现printf功能

发布者:清晨微风 来源: eefocus关键字:Tiny210  printf功能 手机看文章 扫描二维码
随时随地手机看文章

注意:本节串口的printf等函数的实现方法可以参考OK6410的裸机“11th_uart_stdio”实验源码,该方法更实用。

start.S源码:

.global _start

    

_start:

    ldr sp, =0xD0030000    @初始化堆栈

    

    b main

===================================================================

main.c

#include "clock.h"

#include "led.h"

#include "uart.h"

#include "lib.h"

int main(void)

{

    led_init();     // 设置对应管脚为输出 

    clock_init(); // 初始化时钟 

    uart_init();   // 初始化UART

    wy_printf("\n********************************\n");

    wy_printf("                   wy_bootloader\n");

    wy_printf("                   vars: %d \n",2012);

    wy_printf("********************************\n");

    while (1)

    {

        char c = 'x';

        putchar('\n');

        

        c = uart_getchar();

        putchar(c);

        

        for (c = 'a'; c <= 'z'; c++)

            putchar(c);

        break;

    }

    while(1)

    {

        led_water();

    }

    return 0;

}

===================================================================

leds.c源码:


#include "lib.h"

#define GPJ2CON        (*(volatile unsigned int *)0xe0200280)

#define GPJ2DAT        (*(volatile unsigned int *)0xe0200284)

#define GPH2CON       (*(volatile unsigned int *)0xE0200C40)

#define GPH2DAT        (*(volatile unsigned int *)0xE0200C44)

void led_init(void)

{

    // 配置GPJ2_0为输出引脚 

    GPJ2CON = 0x1111;

}

void led_water(void)

{

    int i = 0;

    while (1)

    {        

        GPJ2DAT = i;

        i++;

        if (i == 16)

            i = 0;

        delay();

    }

}

===================================================================

clock.c源码:

#define APLL_CON      (*(volatile unsigned int *)0xe0100100) 

#define CLK_SRC0      (*(volatile unsigned int *)0xe0100200) 

#define CLK_DIV0      (*(volatile unsigned int *)0xe0100300) 

#define MPLL_CON      (*(volatile unsigned int *)0xe0100108)  

void clock_init(void)

{

    // 设置时钟为:

    // ARMCLK=1000MHz, HCLKM=200MHz, HCLKD=166.75MHz

    // HCLKP =133.44MHz, PCLKM=100MHz, PCLKD=83.375MHz, 

    // PCLKP =66.7MHz

    // SDIV[2:0]  : S = 1

    // PDIV[13:8] : P = 0x3

    // MDIV[25:16]: M = 0x7d

    // LOCKED [29]: 1 = 使能锁

    // ENABLE [31]: 1 = 使能APLL控制器

    // 得出FoutAPLL = 500MHz

    APLL_CON = (1<<31)|(1<<29)|(0x7d<<16)|(0x3<<8)|(1<<0);

    

    // 时钟源的设置

    // APLL_SEL[0] :1 = FOUTAPLL

    // MPLL_SEL[4] :1 = FOUTMPLL

    // EPLL_SEL[8] :1 = FOUTEPLL

    // VPLL_SEL[12]:1 = FOUTVPLL

    // MUX_MSYS_SEL[16]:0 = SCLKAPLL

    // MUX_DSYS_SEL[20]:0 = SCLKMPLL

    // MUX_PSYS_SEL[24]:0 = SCLKMPLL

    // ONENAND_SEL [28]:1 = HCLK_DSYS

    CLK_SRC0 = (1<<28)|(1<<12)|(1<<8)|(1<<4)|(1<<0);

    

    // 设置分频系数

    // APLL_RATIO[2:0]: APLL_RATIO = 0x0

    // A2M_RATIO [6:4]: A2M_RATIO  = 0x4

    // HCLK_MSYS_RATIO[10:8]: HCLK_MSYS_RATIO = 0x4

    // PCLK_MSYS_RATIO[14:12]:PCLK_MSYS_RATIO = 0x1

    // HCLK_DSYS_RATIO[19:16]:HCLK_DSYS_RATIO = 0x3

    // PCLK_DSYS_RATIO[22:20]:PCLK_DSYS_RATIO = 0x1

    // HCLK_PSYS_RATIO[27:24]:HCLK_PSYS_RATIO = 0x4

    // PCLK_PSYS_RATIO[30:28]:PCLK_PSYS_RATIO = 0x1

     CLK_DIV0 = (0x1<<28)|(0x4<<24)|(0x1<<20)|(0x3<<16)|(0x1<<12)|(0x4<<8)|(0x4<<4);

     

    // SDIV[2:0]  : S = 1

    // PDIV[13:8] : P = 0xc

    // MDIV[25:16]: M = 0x29b

    // VSEL   [27]: 0

    // LOCKED [29]: 1 = 使能锁

    // ENABLE [31]: 1 = 使能MPLL控制器

    // 得出FoutAPLL = 667MHz

    APLL_CON = (1<<31)|(1<<29)|(0x29d<<16)|(0xc<<8)|(1<<0);

}

===================================================================

uart.c源码:

#define GPA0CON     (*(volatile unsigned int *)0xE0200000) 

#define ULCON0      (*(volatile unsigned int *)0xE2900000) 

#define UCON0       (*(volatile unsigned int *)0xE2900004) 

#define UTRSTAT0    (*(volatile unsigned int *)0xE2900010)

#define UTXH0       (*(volatile unsigned char *)0xE2900020) 

#define URXH0       (*(volatile unsigned char *)0xE2900024) 

#define UBRDIV0     (*(volatile unsigned int *)0xE2900028) 

#define UDIVSLOT0   (*(volatile unsigned int *)0xE290002C)

void uart_init(void)

{

    // 设置对应GPIO用于UART0 

    GPA0CON |= 0x22;

            

    // 设置UART0寄存器 

    // bit[1:0]:0x3 = 8位数据位

    // 其他位默认,即1位停止位,无校验,正常模式

    ULCON0 |= (0x3<<0);

    // Receive Mode [1:0]:1 = 接收采用查询或者中断模式

    // Transmit Mode[3:2]:1 = 发送采用查询或者中断模式

    // bit[6]:1 = 产生错误中断

    // bit[10]:0 = 时钟源为PCLK

    UCON0 = (1<<6)|(1<<2)|(1<<0);

    

    // 设置波特率(详细信息请参考手册或者学习日记)

    // DIV_VAL = UBRDIVn + (num of 1's in UDIVSLOTn)/16

    // DIV_VAL = (PCLK / (bps x 16)) - 1

    UBRDIV0 = 0x23;

    UDIVSLOT0 = 0x808;

    return;

}

char uart_getchar(void)

{

    char c;

    

    // 查询状态寄存器,直到有有效数据 

    while (!(UTRSTAT0 & (1<<0)));

    

    c = URXH0; // 读取接收寄存器的值 

        

    return c;

}

void uart_putchar(char c)

{

    // 查询状态寄存器,直到发送缓存为空 

    while (!(UTRSTAT0 & (1<<2)));

    

    UTXH0 = c; // 写入发送寄存器 

    

    return;

}

===================================================================

lib.c源码:

#include "uart.h"

#define UTRSTAT0      (*(volatile unsigned int *)0xE2900010)

#define UTXH0            (*(volatile unsigned char *)0xE2900020) 

#define URXH0            (*(volatile unsigned char *)0xE2900024) 

void delay(void)

{

    volatile int i = 0x100000;

    while (i--);

}

void putchar_hex(char c)

{

    char * hex = "0123456789ABCDEF";

    

    uart_putchar(hex[(c>>4) & 0x0F]);

    uart_putchar(hex[(c>>0) & 0x0F]);

    return;

}

int putchar(int c)

{

    if(c == '\r')

    {

        while(!(UTRSTAT0&(1<<1)));

        UTXH0 = '\n';

    }

    

    if(c == '\n')

    {

        while(!(UTRSTAT0&(1<<1)));

        UTXH0 = '\r';

    }

    while(!(UTRSTAT0&(1<<1)));

    UTXH0 = c;

}

int puts(const char * s)

{

    while (*s)

        putchar(*s++);

        

    return 0;

}

void putint_hex(int a)

{

    putchar_hex( (a>>24) & 0xFF );

    putchar_hex( (a>>16) & 0xFF );

    putchar_hex( (a>>8) & 0xFF );

    putchar_hex( (a>>0) & 0xFF );

}

char * itoa(int a, char * buf)

{

    int num = a;

    int i = 0;

    int len = 0;

    

    do 

    {

        buf[i++] = num % 10 + '0';

        num /= 10;        

    } while (num);

    buf[i] = '\0';

    

    len = i;

    for (i = 0; i < len/2; i++)

    {

        char tmp;

        tmp = buf[i];

        buf[i] = buf[len-i-1];

        buf[len-i-1] = tmp;

    }

    

    return buf;    

}

typedef int * va_list;

#define va_start(ap, A)   (ap = (int *)&(A) + 1)

#define va_arg(ap, T)     (*(T *)ap++)

#define va_end(ap)        ((void)0)

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

{

    char c;    

    va_list ap;

        

    va_start(ap, format);

    

    while ((c = *format++) != '\0')

    {

        switch (c)

        {

            case '%':

                c = *format++;

                

                switch (c)

                {

                    char ch;

                    char * p;

                    int a;

                    char buf[100];

                                    

                    case 'c':

                        ch = va_arg(ap, int);

                        putchar(ch);

                        break;

                    case 's':

                        p = va_arg(ap, char *);

                        puts(p);

                        break;                    

                    case 'x':

                        a = va_arg(ap, int);

                        putint_hex(a);

                        break;        

                    case 'd':

                        a = va_arg(ap, int);

                        itoa(a, buf);

                        puts(buf);

                        break;    

                    

                    default:

                        break;

                }                

                break;        

        

            default:

                putchar(c);

                break;

        }

    }

    return 0;    

}

===================================================================

Makefile文件:

uart.bin:start.s main.c uart.c clock.c led.c lib.c

    arm-linux-gcc -nostdlib -c start.s -o start.o

    arm-linux-gcc -nostdlib -c main.c -o main.o

    arm-linux-gcc -nostdlib -c uart.c -o uart.o

    arm-linux-gcc -nostdlib -c lib.c -o lib.o

    arm-linux-gcc -nostdlib -c clock.c -o clock.o    

    arm-linux-gcc -nostdlib -c led.c -o led.o    

    arm-linux-ld -Ttext 0xD0020010 start.o main.o uart.o lib.o clock.o led.o -o uart_elf

    arm-linux-objcopy -O binary -S uart_elf uart.bin

clean:

    rm -rf *.o *.bin uart_elf *.dis

===================================================================

--printf的实现

问:什么是可选参数?

答:比如函数int printf(const char * format, ...),那么参数format后面的都是可选参数(注:不包含format),即可以传入可以不传入的参数。

问:C函数是怎么被组织进C程序的?

答:C语言的函数是从下(低地址)向上(高地址)压入堆栈的,如下图所示:

栈底 高地址 

        | .......      

        | 函数返回地址 

        | .......       

        | 函数最后一个参数 

        | ......                        

        | 函数第一个可变参数       <--va_start后ap指向  

        | 函数最后一个固定参数

| ...... 

        | 函数第一个固定参数  

        栈顶 低地址

问:实现可选参数,需要做些什么呢?

答:解析如下:

1.关键的宏:

typedef int * va_list;  //va_list等价于int *即整型指针,该变量类型应该根据具体的架构(ARM,X86)确定

#define va_start(ap, A)    (ap = (int *)&(A) + 1) //(int *)&得到A所在的地址,并强制类型转换为int *,然后这                                                                               //个地址加上A的大小,则使ap指向第一个可变参数!!

#define va_arg(ap, T)  (*(T *)ap++) //先对指针ap(即地址)进行强制类型转换,转换为该变量实际的类型,     

                                    //然后ap(即当前地址)自加1(即加一个类型的大小,指向下一个可变参                             

                                    //数的地址),这类应该注意的是,先使用后自加,然后取出该地址的值!!

#define va_end(ap)      ((void)0)

辅助理解(结合C函数是怎么被组织进C程序的):

      1).va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数,或者说,…之前的一个参数),函数参数列表中参数在内存中的存放顺序与函数声明时的顺序是一致的。如果有一va_test()函数的声明是void va_test(char a, char b, char c, …),则它的固定参数(和在内存中存放的顺序)依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c);

      2).va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中的下一个可选参数;

va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用va_start()、va_copy()恢复arg_ptr。每次调用va_start()或va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之间。

精简版prinf的例子:

typedef int * va_list;

#define va_start(ap, A)        (ap = (int *)&(A) + 1)

#define va_arg(ap, T)          (*(T *)ap++)

#define va_end(ap)             ((void)0)

void putchar_hex(char c) //用于显示一字节的十六进制数,也供函数putint_hex()调用

{

    char * hex = "0123456789ABCDEF";   //正确的定义和初始化字符串

    //char hex[] = "0123456789ABCDEF"; //错误的定义和初始化字符串

    

    putchar(hex[(c & 0xf0)>>4]); //显示高一位

    putchar(hex[(c & 0x0f)>>0]); //显示低一位

}

void putint_hex(int a)                  //用于显示四字节的十六进制数

{

    putchar_hex( (a>>24) & 0xFF ); //显示最高处的一字节的十六进制数

    putchar_hex( (a>>16) & 0xFF );

    putchar_hex( (a>>8) & 0xFF );

    putchar_hex( (a>>0) & 0xFF );

}

char * itoa(int a, char * buf) //显示十进制数

{

    int num = a;

    int i = 0;

    int len = 0;

    

    do 

    {

        buf[i++] = num % 10 + '0'; //将数字转换成它的ASCII码,存放在字符串数组中

        num /= 10;        

    } while (num); //直到十进制数变为0

    buf[i] = '\0';     //给字符串加一个结束标志位

    

    len = i;

    for (i = 0; i < len/2; i++) //该循环用于对数组buf中的元素进行倒序排序

    {

        char tmp;

        tmp = buf[i];

        buf[i] = buf[len-i-1];

        buf[len-i-1] = tmp;

    }

    

    return buf;    //返回数组的首地址

}

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

{

    char c;    

    va_list ap;           //定义一个int *型的指针ap    

    va_start(ap, format); //初始化ap,让它指向第一个可变参数的地址 

    

    while ((c = *format++) != '\0') //开始解析printf函数中的字符串(即第一个固定参数)

    {

        switch (c)  //在while中,依次读出字符串中的字符,在这里依依地进行判断和解析

        {

            case '%':                //如果字符是%,即格式申明符号,则解析该格式

                c = *format++; //让c等于%后的第一个字母

                switch (c)          //对上述字母进行解析

                {

                    char ch;

                    char * p;

                    int a;

                    char buf[100];

                                    

                    case 'c':                         //如果是%c,即输出字母

                        ch = va_arg(ap, int); //获取当前可变参数的值,并指向下一个可变参数

                        putchar(ch);              //调用底层(实际操作硬件的那层)来完成输出结果

                        break;

                    case 's':                          //如果是%s,即输出字符串

                        p = va_arg(ap, char *);

                        puts(p);

                        break;                    

                    case 'x':                         //如果是%x,即以十六进制输出

                        a = va_arg(ap, int);

                        putint_hex(a);

                        break;        

                    case 'd':                         //如果是%d,即以十进制输出

                        a = va_arg(ap, int);

                        itoa(a, buf);

                        puts(buf);

                        break;    

                    

                    default:

                        break;

                }                

                break;        

        

            default:

                putchar(c);              //输出字符串中为普通字符的字符

                break;

        }

    }

    return 0;    

注意:

在实际给出的代码里面是用wy_printf来代替printf的,目的只是为了让编译通过,当然还有其他的解决办法,详细讲解,请看韦东山的自己写bootloader的视频。


关键字:Tiny210  printf功能 引用地址:Tiny210裸机之实现printf功能

上一篇:Tiny210裸机简单命令的实现
下一篇:Tiny210裸机之UART串口操作

推荐阅读

2018可谓是素士高歌猛进的一年,素士声波电动牙刷青春版 X1、经典销冠白金/黑金版 X3、重磅新品粉金升级版 X3、负离子速干电吹风全线产品斩获德国 IF 设计大奖。2015年创立至今短短3年时间,素士凭借优秀的产品设计、技术研发、品控管理体系等优势强势跻身国内电动牙刷销量前四。既获得了行业认可,也收到了众多好评及回购支持,被誉为国产电动牙刷的...
10月8日,从国网基建部获悉,今年前三季度,国家电网有限公司110千伏及以上电网基建工程投产36134千米、21683万千伏安,完成年度计划的72.7%和77.4%;开工34856千米、21344万千伏安,完成年度任务的73.5%和69.9%,开工投产均超额完成里程碑计划。 年初以来,公司基建战线认真贯彻党中央、国务院决策部署,深入落实公司三届四次职代会暨2019年工作会议...
近日,广西桂林,1名毒贩和3名吸毒人员交易毒品时,被警用无人机严密监视,并将交易全过程在警方的指挥平台上进行了“直播”。视频截图警方通过高空远距离变焦拍摄进行取证,锁定嫌疑人轨迹,并通报地面小组,最终将涉案人员全部抓获。9月29日,广西桂林一名贩毒嫌疑人和三名吸毒人员进行交易,自以为神不知鬼不觉,没想到就在他们的头顶,一架警用无人机...
1.CPU工作模式(Mode)ARM CPU有七种模式,各种模式如下图所示。注意用户模式下不可进入其他模式,用户模式是在有操作系统的时候给应用程序使用的,写应用程序的人水平千差万别,不能保证写的程序是好是坏,所以让应用程序运行在用户模式,限制应用程序的权限,防止破坏整个系统,2.状态(State) ARM架构的CPU有ARM state和Thumb state,ARM State:用的是A...

史海拾趣

问答坊 | AI 解惑

中国半导体行业协会将申请成为世界半导体理事会成员

中国半导体行业协会将申请成为世界半导体理事会成员 美国半导体行业协会表示中国半导体行业协会的加入将增强这一世界性组织的影响力 2006-06-15   应世界半导体理事会(WSC)的邀请,中国半导体行业协会(CSIA)开始WSC的成员资格的申请工作 ...…

查看全部问答∨

BT Helper 1.1 汉化版

BTHelper可以帮助您优化BT客户端软件,提高下载速度。通过BTHelper优化的计算机将显著提高下载时的连接速度,加速查找更多的种子并立即连接到它们。BTHelper还可以为BitTorrent下载提供磁盘保护功能。 …

查看全部问答∨

笔记本计算机进入Vista过渡期,促2007年DRAM放量增长

根据DRAMeXchange的预估,笔记本计算机出货量在2007年应维持稳定增长,除取代台式电脑现象持续之外,微软(Microsoft)Vista操作系统的推出,以及笔记本计算机本身规格的提升,都将继续推动笔记本计算机的增长。DRAMeXchange预期,2007年全球笔记本计 ...…

查看全部问答∨

大功率白色发光二极管的特性研究

分析了大功率白色发光二极管的发光强度 即光强 、光通量和色坐标与测量位置的关系, 提出了解决的方法。同时, 对大功率白色发光二极管法向光强、光通量和峰值长随电流和时间的变化情况做了分析, 说明PN 结温度对于大功率发光二极管的发光具有较大的 ...…

查看全部问答∨

硬件工程师的基础知识

目的:基于实际经验与实际项目详细理解并掌握成为合格的硬件工程师的最基本知识…

查看全部问答∨

为什么vxworks启动后会打印出一些杂乱的信息呢?

各位高手,我把我的应用程序编译进内核后,启动vxworks后,经常会打印出一些杂乱的信息,甚至丢失了命令提示符,比如下面这个(我在RAM里建立了文件系统,并建立临时文件,最后销毁): ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] ]]]]]]]]]]]] ...…

查看全部问答∨

如何在WINCE下通过有线局域网访问网络

各位大虾;     有个问题请教大家, 我在ARM系统的开发板上要访问网络,网卡芯片是CS8900, 驱动也都有了, 在WINCE下我需要加哪些组件呢? 另外,如何配置呢/ 请知道的大虾指点一下, 不胜感激.…

查看全部问答∨

本人4年wince平台应用、驱动开发经验,求Wince平台兼职,13910531491

本人4年wince平台应用、驱动开发经验,求Wince平台兼职,13910531491…

查看全部问答∨

STM8S的C编译器太贵了!那位大侠处理一下啊!

IAR STM8S报价:2.3万人民币 COSMIC STM8S报价:2.3万人民币 Raisonance STM8S报价:990欧元(算成人民币大约:8600元不到) 三个用起来感觉Raisonance用起来好用些,价格也算是最便宜的!不过没有石皮 角刀牛。 比起AVR的CodeVisionAVR ...…

查看全部问答∨

ADC10实验例程(含C#上位机)

C#上位机学习资料 https://bbs.eeworld.com.cn/viewthread.php?tid=308129&page=1#pid1198878上周逛论坛看到上面的C#串口教程,觉得挺有趣的,跟着学了一下,结合LaunchPad写了一个简单的ADC10+串口上位机。第一次用C#,线程、运行机制呀什么的全 ...…

查看全部问答∨
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

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

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved