ARM uart stdio 的移植

news2025/1/11 10:52:37

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

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

在这里插入图片描述


源自朱有鹏老师.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/114224.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言——操作符详解(下)

C语言——操作符详解&#xff08;下&#xff09;一、赋值操作符二、复合赋值符三、单目操作符单目操作符介绍四、 关系操作符五、逻辑操作符六、条件操作符七、逗号表达式八、下标引用、函数调用和结构成员8.1 [ ] 下标引用操作符8.2 ( ) 函数调用操作符8.3访问一个结构的成员一…

【Linux】Linux指令串讲

大家好&#xff0c;今天要开启一个新的专题&#xff1a;Linux 今天的内容是指令还有一些基本的Linux知识补充 由于Linux的知识很难明确写出分类&#xff0c;所以目录就不会做的特别详细完全 喜欢的小伙伴点赞收藏一下不迷路哦 目录 1.目录 2.文件 3.路径 1.目录 1.创建目录…

初识Docker:(3)Docker架构

初识Docker&#xff1a;&#xff08;3&#xff09;Docker架构镜像和容器Docker和DockerHubDocker架构总结镜像和容器 镜像&#xff08;Image&#xff09;&#xff1a;Docker将应用程序及其所需的以来、函数库、环境、配置等文件打包在一起&#xff0c;成为镜像。 容器&#x…

力扣27.移除元素(双指针算法)

题目描述&#xff1a; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考…

编译openssl支持libcurl的https访问

如果编译时不带openssl库那么无法访问https的网页&#xff0c;从网页端什么也获取不到。 在调用post请求访问翔云OCR人脸识别时无法访问&#xff0c;而使用ssl&#xff0c;需要先在系统中安装OpenSSL。 如下图安装说明&#xff1a; 所以将原先安装的libcurl库删掉&#xff0…

MySQL事务的隔离级别

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;MySQL事务的隔离级别 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪…

【C与数据结构】——寒假提高每日练习Day2

一共16日的练习&#xff0c;分为选择题与编程题&#xff0c;涵盖了C语言所学以及数据结构的重点&#xff0c;以及一些秋招、春招面试的高频考点&#xff0c;难度会随着天数而上升。1-8day为C语言&#xff0c;9-16day为数据结构。 &#xff08;建议在电脑客户端进行&#xff0c…

docker中的数据卷

目录 1. 为什么使用数据卷 2. 数据卷基本操作 2.1 创建数据卷 2.2 查看数据卷 2.3 查看数据卷详细信息 2.4 数据卷删除 3. 数据卷的使用 3.1 先创建数据卷再挂载 3.2 直接挂载宿主机目录 3.3 只读数据卷 4. 数据卷容器 4.1 新建数据卷容器 4.2 新建一个容器来使用数…

Nacos学习笔记 (8)服务发现基础应用

1. 什么是服务发现 在微服务架构中&#xff0c;整个系统会按职责能力划分为多个服务&#xff0c;通过服务之间协作来实现业务目标。这样在我们的代码中免不了要进行服务间的远程调用&#xff0c;服务的消费方要调用服务的生产方&#xff0c;为了完成一次请求&#xff0c;消费方…

求空间曲线的切线,法平面

求空间曲线的切线&#xff0c;法平面&#xff1a;归结为求空间曲线的切向量 进而用点向式直线方程表示出切线&#xff0c;点法式方程表示出法平面 情况一&#xff1a;空间曲线以参数式给出&#xff0c;求切向量时直接求导即可&#xff0c;如下题 情况二&#xff1a;空间曲线…

ROS2 基础概念 话题

ROS2 基础概念 话题1. Topics2. rqt_graph3. 话题4. 话题类型5. 话题发布6. 话题频率1. Topics 话题是节点交换消息的总线 节点可以向任意数量的话题发布数据&#xff0c;并同时订阅任意数量的话题 2. rqt_graph 将使用rqt_graph来可视化不断变化的节点和话题&#xff0c;以…

云服务器ECS入门及云上网站部署

云服务器ECS入门及云上网站部署 一、什么是云服务器ECS 云服务器ECS (Elastic Compute Service) 是阿里云提供的性能卓越、稳定可靠、弹性扩展的laaS(Infrastructure as a Service) 级别云计算服务 云服务器ECS免去了您采购IT硬件的前期准备&#xff0c;让您像使用水、电、天…

JavaScript操作BOM对象

BOM&#xff1a;浏览器对象模型 window代表浏览器窗口 >window.alert(1) undefined >window.innerHeight //浏览器内部高度 242 >window.innerWidth 1229 >window.outerHeight //浏览器外部高度 824 >window.outerWidth 1536 Navigator&#xff0c;封装了浏…

【Linux】第六部分 远程登录

【Linux】第六部分 远程登录 文章目录【Linux】第六部分 远程登录6. 远程登录6.1 配置hosts映射文件6.2 xshell和xftp的使用总结6. 远程登录 6.1 配置hosts映射文件 为什么要配置呢?利于后续我们进行连接方便,就比如:我们没有办法记住很多手机号,但是我们可以记住人名,我们打…

【20221225】【剑指1】链表

1、从尾到头打印链表 可以用rbegin&#xff0c;rend&#xff1b;也可以用reverse翻转数组。 2、如果是翻转链表的话&#xff08;双指针法&#xff0c;用虚拟头节点依次翻转&#xff09;&#xff1a;https://blog.csdn.net/HYAIWYH/article/details/127118468?ops_request_mi…

java ssm 摄影约拍系统的设计

目录 第一章 绪论 5 1.1 研究背景 5 1.2系统研究现状 5 1.3 系统实现的功能 6 1.4系统实现的特点 6 1.5 本文的组织结构 6 第二章开发技术与环境配置 7 2.1 Java语言简介 7 2.2JSP技术 8 2.3 MySQL环境配置 8 2.4 MyEclipse环境配置 9 2.5 mysql数据库介绍 9 2.6 B/S架构 9 第三…

6、集合介绍

文章目录6、集合6.1 介绍6.2 常用接口和类6.3 ArrayList6.3.1 介绍6.3.2 基本操作6.3.3 常用方法6.4 LinkedList6.4.1 基本操作6.4.2 常用方法6.5 泛型6.5.1 介绍6.5.2 基本使用6.6 比较器6.7 ArrayList和LinkedList的比较6.8 HashSet6.8.1 介绍6.8.2 常用方法6.8.3 重复数据6.…

Attetion is all you need论文阅读笔记

Attetion is all you need 参考&#xff1a;沐神&#xff08; 沐神_论文精讲_Attention is all you need&#xff09; 1、Abstract 主流的序列转录模型&#xff08;给一个序列生成另一个序列&#xff0c;比如机器翻译&#xff0c;给一句英文&#xff0c;生成一句中文&#x…

数据可视化大屏应急管理综合指挥调度系统完整案例详解(PHP-API、Echarts、百度地图)

文章目录项目说明一、项目说明单位信息数据库字段&#xff1a;资源数据库字段项目需求二、项目开发1.项目分析2.引入库3.项目开发&#xff08;1&#xff09;地图容器构建&#xff08;2&#xff09;筛选和返回按钮事件&#xff08;3&#xff09;企业筛选功能&#xff08;4&#…

【java】stream流

文章目录体验stream流stream流的生成方式Stream流的常见中间操作方法Stream流的常见中间操作方法Stream流的常见终结操作方法Stream流的练习Stream流的收集操作体验stream流 package heima.stream流;import java.util.ArrayList;public class P1 {public static void main(Stri…