(1) #include
(2) stdio:standard input output,标准输入输出
(3) 标准输入输出就是操作系统定义的默认的输入和输出通道。一般在 PC 机的情况下,标准输入指的是键盘,标准输出指的是屏幕。
(4) printf 函数和 scanf 函数可以和底层输入/输出函数绑定,然后这两个函数就可以和 stdio 绑定起来。也就是说我们直接调用 printf 函数输出,内容就会被从标准输出输出出去。
(5) 在我们这里,标准输出当然不是屏幕了,而是串口。标准输出也不是键盘,而是串口
。
(1) printf 函数工作时,内部实际调用了 2 个关键函数:一个是 vsprintf 函数(主要功能是格式化打印信息,最终得到纯字符串格式的打印信息等待输出),另一个就是真正的输出函数 putc(操控标准输出的硬件,将信息发送出去)
(1) 我们希望在我们的开发板上使用 printf 函数进行(串口)输出,使用 scanf 函数进行(串口)输入,就像在 PC 机上用键盘和屏幕进行输入输出一样。因此需要移植 printf 函数/ scanf 函数。
(2) 我们说的移植而不是编写,我们不希望自己完全重新编写,而是想尽量借用已有的代码(叫移植)。
(3) 一般移植 printf 函数可以有 3 个途径获取 printf 的实现源码:最原始最原本的来源就是 linux 内核中的 printk 。难度较大、关键是麻烦;稍微简单些的方法是从 uboot 中移植 printf ;更简单的方法就是直接使用别人移植好的。
(3) 我们课程中使用第三种方法,别人移植好的 printf 函数,来自于友善之臂的 Tiny210 的裸机教程中提供的。
lib/printf.c 文件的内容:
#include "vsprintf.h"
#include "string.h"
#include "printf.h"extern void putc(unsigned char c);
extern unsigned char getc(void);#define OUTBUFSIZE 1024
#define INBUFSIZE 1024// 自己定义了2个全局变量数组,分别作为发送/接收缓冲区。
// 将来发送时先将要发送的信息格式化送入发送缓冲区,然后putc函数直接从发送缓冲区取
// 数据发送出去。
static char g_pcOutBuf[OUTBUFSIZE];
static char g_pcInBuf[INBUFSIZE];// putc函数真正和输出设备绑定,这个函数需要我们自己去实现,这就是移植的关键
// printf("a = %d, b = %s.\n", a, p);
int printf(const char *fmt, ...)
{int i;int len;va_list args;va_start(args, fmt);len = vsprintf(g_pcOutBuf,fmt,args);va_end(args);for (i = 0; i < strlen(g_pcOutBuf); i++){putc(g_pcOutBuf[i]);}return len;
}int scanf(const char * fmt, ...)
{int i = 0;unsigned char c;va_list args;while(1){c = getc();putc(c);if((c == 0x0d) || (c == 0x0a)){g_pcInBuf[i] = '\0';break;}else{g_pcInBuf[i++] = c;}}va_start(args,fmt);i = vsscanf(g_pcInBuf,fmt,args);va_end(args);return i;
}
vsprintf 函数详解printf->vsprintf->vsnprintf->number
vsprintf 函数的作用是:按照我们的 printf 传进去的格式化标本,对变参进行处理,然后将之格式化后缓存在一个事先分配好的缓冲区中。
printf 后半段调用 putc 函数,将缓冲区中格式化好的字符串直接输出到标准输出。
顶层 makefile 文件:
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-arINCDIR := $(shell pwd)
# C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag
CFLAGS := -Wall -O2 -fno-builtin#导出这些变量到全局,其实就是给子文件夹下面的 Makefile 使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGSobjs := start.o led.o clock.o uart.o main.o
objs += lib/libc.auart.bin: $(objs)$(LD) -Tlink.lds -o uart.elf $^$(OBJCOPY) -O binary uart.elf uart.bin$(OBJDUMP) -D uart.elf > uart_elf.disgcc mkv210_image.c -o mkx210./mkx210 uart.bin 210.binlib/libc.a:cd lib; make; cd ..%.o : %.S$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c%.o : %.c$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -cclean:rm *.o *.elf *.bin *.dis mkx210 -fcd lib; make clean; cd ..
移植的库中的 makefile 文件:lib/Makefile:
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.olibc.a: $(objs)${AR} -r -o $@ $^%.o:%.c${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<%.o:%.S${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $
修改 uart.c 文件中的串口输出函数:
$ cat uart.c
#define GPA0CON 0xE0200000
#define UCON0 0xE2900004
#define ULCON0 0xE2900000
#define UMCON0 0xE290000C
#define UFCON0 0xE2900008
#define UBRDIV0 0xE2900028
#define UDIVSLOT0 0xE290002C
#define UTRSTAT0 0xE2900010
#define UTXH0 0xE2900020
#define URXH0 0xE2900024#define rGPA0CON (*(volatile unsigned int *)GPA0CON)
#define rUCON0 (*(volatile unsigned int *)UCON0)
#define rULCON0 (*(volatile unsigned int *)ULCON0)
#define rUMCON0 (*(volatile unsigned int *)UMCON0)
#define rUFCON0 (*(volatile unsigned int *)UFCON0)
#define rUBRDIV0 (*(volatile unsigned int *)UBRDIV0)
#define rUDIVSLOT0 (*(volatile unsigned int *)UDIVSLOT0)
#define rUTRSTAT0 (*(volatile unsigned int *)UTRSTAT0)
#define rUTXH0 (*(volatile unsigned int *)UTXH0)
#define rURXH0 (*(volatile unsigned int *)URXH0)/**************************************************************************************/
#define ULCON0_FUNC_NO_PARITY_MODE (0b000 << 3) // 无校验
#define ULCON0_FUNC_NUMBER_STOP_0_BIT (0b0 << 2) // Stop bit: 0
#define ULCON0_FUNC_WORLD_LENGTH_8_BIT (0b11 << 0) // 8 位数据位/**************************************************************************************/
#define UCON0_FUNC_CLOCK_SELECTION_PLCK (0b0 <<10) //选择PCLK
#define UCON0_FUNC_LOOPBACK_MODE_NORMAL (0b0 << 5) //正常模式
#define UCON0_FUNC_TX_MODE_POLLING (0b01 << 2) //轮询模式
#define UCON0_FUNC_RX_MODE_POLLING (0b01 << 0) //轮询模式/**************************************************************************************/
#define UFCON0_FUNC_FIFO_DISABLE (0b0 << 0) //禁止 FIFO 模式/**************************************************************************************/
#define UTRSTAT0_FUNC_TRANSMITTER_EMPTY (0b1 << 2) //发送缓冲区为空
#define BIT_LOCATION_UTRSTAT0_FUNC_TRANSMITTER_EMPTY (0b1 << 2) //发送缓冲器状态位
#define UTRSTAT0_FUNC_RECEIVE_BUFFER_DATA_READY (0b1 << 0) //接收缓冲区已接收到数据
#define BIT_LOCATION_UTRSTAT0_FUNC_RECEIVE_BUFFER_DATA_READY (0b1 << 0) //接收缓冲区状态位/**************************************************************************************/
#define BIT_WIDTH_GPA0 (4)
#define GPA0_0_FUNC_INPUT (0x0 << 0 * BIT_WIDTH_GPA0)
#define GPA0_0_FUNC_OUTPUT (0x1 << 0 * BIT_WIDTH_GPA0)
#define GPA0_0_FUNC_UART0RXD (0x2 << 0 * BIT_WIDTH_GPA0)
#define GPA0_0_FUNC_GPA0_INT0 (0Xf << 0 * BIT_WIDTH_GPA0)#define GPA0_1_FUNC_INPUT (0x0 << 1 * BIT_WIDTH_GPA0)
#define GPA0_1_FUNC_OUTPUT (0x1 << 1 * BIT_WIDTH_GPA0)
#define GPA0_1_FUNC_UART0TXD (0x2 << 1 * BIT_WIDTH_GPA0)
#define GPA0_1_FUNC_GPA0_INT1 (0Xf << 1 * BIT_WIDTH_GPA0)#define BIT_LOCATION_GPA0_CON0 (0xf << 0 * BIT_WIDTH_GPA0)
#define BIT_LOCATION_GPA0_CON1 (0xf << 1 * BIT_WIDTH_GPA0)/**************************************************************************************///串口初始化程序
void uart_init(void)
{// 初始化 TX RX 对应的 GPIO 引脚rGPA0CON &= ~(BIT_LOCATION_GPA0_CON0 | BIT_LOCATION_GPA0_CON1);rGPA0CON |= GPA0_0_FUNC_UART0RXD | GPA0_1_FUNC_UART0TXD;//几个关键寄存器的设置rULCON0 = ULCON0_FUNC_NO_PARITY_MODE | ULCON0_FUNC_NUMBER_STOP_0_BIT | ULCON0_FUNC_WORLD_LENGTH_8_BIT;rUCON0 = UCON0_FUNC_CLOCK_SELECTION_PLCK | UCON0_FUNC_LOOPBACK_MODE_NORMAL | UCON0_FUNC_TX_MODE_POLLING | UCON0_FUNC_RX_MODE_POLLING;rUMCON0 = 0;rUFCON0 = UFCON0_FUNC_FIFO_DISABLE;
#if 0//波特率设置 DIV_VAL = (PCLK / (bps X 16) ) - 1//PCLK_PSYS 用 66MHz 算 DIV_VAL = (66 X 10^6 / (115200 X 16)) - 1 = 34.807//小数是 0.8,0.8 x 16 = 12.8rUBRDIV0 = 34;//12 0xDDDD(1101_1101_1101_1101b)//13 0xDFDD(1101_1111_1101_1101b)rUDIVSLOT0 = 0xDDDD;
#endif//PCLK_PSYS 用 66.7MHz 算 DIV_VAL = (66.7 X 10^6 / (115200 X 16)) - 1 = 35.187//小数是 0.18,0.18 x 16 = 2.88rUBRDIV0 = 35;//2 0x0808(0000_1000_0000_1000b)//3 0x0888(0000_1000_1000_1000b)rUDIVSLOT0 = 0x0808;
}//串口接收程序,轮询方式,接收一个字节
void putc(char c)
{//串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去//因为串口控制器发送 1 个字节的速度远远低于 CPU 的速度,所以 CPU 发送1个字节前必须//确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)//如果缓冲区非空则位为0,此时应该循环,直到位为1while ((rUTRSTAT0 & BIT_LOCATION_UTRSTAT0_FUNC_TRANSMITTER_EMPTY) != UTRSTAT0_FUNC_TRANSMITTER_EMPTY) ;rUTXH0 = c;
}//串口接收程序,轮询方式,接收一个字节
char getc(void)
{while ((rUTRSTAT0 & BIT_LOCATION_UTRSTAT0_FUNC_RECEIVE_BUFFER_DATA_READY) != UTRSTAT0_FUNC_RECEIVE_BUFFER_DATA_READY) ;return rURXH0 & 0xff;
}
很遗憾,这次移植的 printf 函数的功能有问题,printf 函数无法输出内容。
在移植后的 uart stdio 项目中添加 link.lds 链接脚本,指定连接地址到0xd0020010。
$ cat Makefile
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-arINCDIR := $(shell pwd)
# C PreProcesser flag
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# C Compiler flag
CFLAGS := -Wall -O2 -fno-builtin# export these variables for lib/Makefile to use
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGSobjs := start.o led.o clock.o uart.o main.o
objs += lib/libc.auart.bin: $(objs)$(LD) -Tlink.lds -o uart.elf $^$(OBJCOPY) -O binary uart.elf uart.bin$(OBJDUMP) -D uart.elf > uart_elf.disgcc mkv210_image.c -o mkx210./mkx210 uart.bin 210.binlib/libc.a:cd lib; make; cd ..%.o : %.S$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c%.o : %.c$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -cclean:rm *.o *.elf *.bin *.dis mkx210 -fcd lib; make clean; cd ..$ cat link.lds
SECTIONS
{. = 0xd0020010;.text : {start.o* (.text)}.data : {* (.data)}bss_start = .;.bss : {* (.bss)}bss_end = .;
}
现象如下图,可以正常输出了:
源自朱有鹏老师.