×
单片机 > 单片机程序设计 > 详情

Tiny210裸机之实现printf功能

发布时间:2020-06-06 发布时间:
|

注意:本节串口的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();   // 初始化UART0 

    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语言的函数是从下(低地址)向上(高地址)压入堆栈的,如下图所示:

栈底 高地址 

        | .......      

        | 函数返回地址 

        | .......       

        | 函数最后一个参数 

        | ......                        

        | 函数第一个可变参数       

        | 函数最后一个固定参数

| ...... 

        | 函数第一个固定参数  

        栈顶 低地址

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

答:解析如下:

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的视频。





『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
C51 特殊功能寄存器SFR的名称和地址