ARM uart stdio 的移植
创始人
2024-05-01 02:22:20
0

一、uart stdio的移植1

1. 什么是 stdio

(1) #include
(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-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;
}

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

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

在这里插入图片描述


源自朱有鹏老师.

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...