ARM uart stdio 的移植

一、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  = .;
}

现象如下图,可以正常输出了:

在这里插入图片描述


源自朱有鹏老师.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值