一、uart stdio的移植1
1. 什么是 stdio
(1) #include <stdio.h>
(2) stdio:standard input output,标准输入输出
(3) 标准输入输出就是操作系统定义的默认的输入和输出通道。一般在 PC 机的情况下,标准输入指的是键盘,标准输出指的是屏幕。
(4) printf 函数和 scanf 函数可以和底层输入/输出函数绑定,然后这两个函数就可以和 stdio 绑定起来。也就是说我们直接调用 printf 函数输出,内容就会被从标准输出输出出去。
(5) 在我们这里,标准输出当然不是屏幕了,而是串口。标准输出也不是键盘,而是串口
。
2. printf 函数的工作原理
(1) printf 函数工作时,内部实际调用了 2 个关键函数:一个是 vsprintf 函数(主要功能是格式化打印信息,最终得到纯字符串格式的打印信息等待输出),另一个就是真正的输出函数 putc(操控标准输出的硬件,将信息发送出去)
3. 移植 printf 函数的三种思路
(1) 我们希望在我们的开发板上使用 printf 函数进行(串口)输出,使用 scanf 函数进行(串口)输入,就像在 PC 机上用键盘和屏幕进行输入输出一样。因此需要移植 printf 函数/ scanf 函数。
(2) 我们说的移植而不是编写,我们不希望自己完全重新编写,而是想尽量借用已有的代码(叫移植)。
(3) 一般移植 printf 函数可以有 3 个途径获取 printf 的实现源码:最原始最原本的来源就是 linux 内核中的 printk 。难度较大、关键是麻烦;稍微简单些的方法是从 uboot 中移植 printf ;更简单的方法就是直接使用别人移植好的。
(3) 我们课程中使用第三种方法,别人移植好的 printf 函数,来自于友善之臂的 Tiny210 的裸机教程中提供的。
4. 移植好的 printf 介绍
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 函数,将缓冲区中格式化好的字符串直接输出到标准输出。
二、uart stdio的移植2
1. 修改 Makefile 进行 printf 移植
顶层 makefile 文件:
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-ar
INCDIR := $(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 CFLAGS
objs := start.o led.o clock.o uart.o main.o
objs += lib/libc.a
uart.bin: $(objs)
$(LD) -Tlink.lds -o uart.elf $^
$(OBJCOPY) -O binary uart.elf uart.bin
$(OBJDUMP) -D uart.elf > uart_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 uart.bin 210.bin
lib/libc.a:
cd lib; make; cd ..
%.o : %.S
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
%.o : %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
cd lib; make clean; cd ..
移植的库中的 makefile 文件:lib/Makefile:
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o
libc.a: $(objs)
${AR} -r -o $@ $^
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f libc.a *.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.8
rUBRDIV0 = 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.88
rUBRDIV0 = 35;
//2 0x0808(0000_1000_0000_1000b)
//3 0x0888(0000_1000_1000_1000b)
rUDIVSLOT0 = 0x0808;
}
//串口接收程序,轮询方式,接收一个字节
void putc(char c)
{
//串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去
//因为串口控制器发送 1 个字节的速度远远低于 CPU 的速度,所以 CPU 发送1个字节前必须
//确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)
//如果缓冲区非空则位为0,此时应该循环,直到位为1
while ((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;
}
2、编译运行及测试
很遗憾,这次移植的 printf 函数的功能有问题,printf 函数无法输出内容。
3、解决 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-ar
INCDIR := $(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 CFLAGS
objs := start.o led.o clock.o uart.o main.o
objs += lib/libc.a
uart.bin: $(objs)
$(LD) -Tlink.lds -o uart.elf $^
$(OBJCOPY) -O binary uart.elf uart.bin
$(OBJDUMP) -D uart.elf > uart_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 uart.bin 210.bin
lib/libc.a:
cd lib; make; cd ..
%.o : %.S
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
%.o : %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
cd lib; make clean; cd ..
$ cat link.lds
SECTIONS
{
. = 0xd0020010;
.text : {
start.o
* (.text)
}
.data : {
* (.data)
}
bss_start = .;
.bss : {
* (.bss)
}
bss_end = .;
}
现象如下图,可以正常输出了:
源自朱有鹏老师.