14. 串口控制台建立
串口控制台建立这一节的主要有三个内容:
1.控制台框架搭建
1.1控制台的分类介绍:
1.1.1菜单型控制台:就是选中设置好的数字或者字母选项后执行相应功能的控制台:
例如刚进入uboot之后的界面,就是菜单型控制台:
等待我们输入命令,来执行相应的操作。例如上面,如果此时我们输入1,就是进行Format the nand Flash的操作:
1.1.2解析型控制台:在上面的菜单型控制台里,选择5:Exit to command line:后会出现:
就进入了解析型控制台:
我们输入help后,控制台会去解析这一命令是不是该控制台所支持的,如果是,它会去调用相应的help函数来运行。会列出这个uboot所在的解析型控制台所支持的命令:
由于解析型控制台实现起来会很复杂,今天就选择相对简单的菜单型控制台,来实现一个相对简单的自己的uboot控制台。
菜单型控制台:
可以看到一个菜单型控制台,第一件事就是先把菜单打印出来,而且会循环打印,首先想到的就是使用printf来打印菜单信息。代码:
上面就是uboot里的提示信息,根据用户的选项来执行对应的功能函数,通过switch来选择实现。上面就是菜单型框架。
在上面的菜单型控制台的实现里,最主要的两个函数是scanf和printf。
2. scanf和printf的实现
首先先实现printf函数,在实现一个函数的时候,第一考虑的是它的功能,printf的功能是打印信息的,printf在PC机里打印信息是打印到显示器里的。然而在开发板里,printf的信息是打印到串口终端的。接着就是要了解该函数的参数。要了解它的参数需要在命令行执行:man 3 printf:
可以看到第一个就是我们的printf函数,只是它的参数是采用的变参的方式,变参用三个点表示,就是该函数的参数是不一定的。可以为零,可以为一个,可以为很多个。后面变参的形式由前面的const char *format来决定的。就是如果const char *format指定的是一个整形参数,那么。。。就得是整形参数,如果const char *format是字符型参数,那么。。。就得是字符型参数。
实现思路:将传进来的参数转化为字符串型的buffer,然后字符串型的buffer通过一个while循环,利用上一节实现的void putc(unsigned char ch)函数输出到串口终端。
实现思路的难点是怎么把传进来的参数转化为字符串型的buffer。
1)在上一节的uboot工程里创建一个printf.c:
2)接着把printf.c加入到Makefile里面去:
3)printf.c的实现:(1)将变参转化为字符串。(2)打印字符串到串口。
对于(2)打印字符串到串口。起始实现起来很方便,因为前面已经实现了putc()函数:
有难度的是(1)将变参转化为字符串。这里需要用到C语言中已经实现好的一些宏。关于变参处理的一些宏(函数)。来帮我们完成。(1)将变参转化为字符串。实现步骤:
上面就实现了将变参转化为字符串。但是问题又来了。上面的三个宏(函数)又得我们实现,而且实现起来是非常复杂的,而且是很标准的。这里就可以采用移植的方法。
我们去实现这种嵌入式的软件,并不是说,所有的代码都需要自己去编写。比如后面还有实现tftp,tftp还需要ip协议栈,对于ip协议栈,我们是无法实现的。所以就是在实现嵌入式的软件的时候,很大时候需要做的是移植。
我们实现移植的地方有两个,一个是Linux内核里,或是C库。这里谈到移植,对于现阶段的我们来说,难度太大了。所以现在我们只要知道,这三个函数的功能是什么,和会使用这些从Linux内核提取好的一些文件,集成到自己的uboot工程里面使用就可以了。
下面是两个提取好的文件目录:
把这两个文件夹拷贝到原先的工程目录下:
接下来对工程文件夹下的Makefile进行如下修改:
修改前:
修改:
-
把printf.c放到lib文件夹下,在lib文件夹里一起编译得lib.o,则需要包含lib/lib.o。所以把printf.o换成lib/lib.o.
-
然后,gboot.bin是用过gboot.elf得到的。
-
gboot.elf是通过链接上面的*.o文件得到的。
-
为了得到lib/lib.o则需要进入到lib文件夹进行编译,make的结果是获得all:
all是在lib文件夹下make后得到的文件:
-
还要进入lib文件夹进行clean:
最后修改的文件为:
lib下的Makefile:
顶层的Makefile为:
修改好了之后进行编译make,会报下面的错:
上面的ctype.c文件是在lib文件夹下的,这里报错说明已经把lib文件夹下的文件已经用进了gboot.bin工程,只是报错了。错误信息是在ctype.c有很多未定义的符号。接下来打开ctype.c,看看是怎么回事:
上面看到ctyp.c里使用了这些符号,make为什么会报这些符号没定义呢?可以看到它包含了ctype.h头文件,打开:
可以看到include/ctype.h里已经定义了这些符号了。可以看到ctype.h已经定义了这些符号。问题出在在编译的时候,arm-linux-gcc这编译器默认寻找头文件的路径,并没有找到我们现在工程里include/ctype.h头文件。这就需要我们在编译的时候,指定编译器arm-linux-gcc在编译的时候除了要去默认路径需找头文件,还要去我们指定的路径寻找头文件。自己指定头文件路径是通过-I参数去指定寻找头文件路径。
打开lib目录下的Makefile:
这里,我们需要在arm-linux-gcc命名后面加-I来指定还要需找头文件的路径。但是可以看到,已经有CFLAGS变量了,它就是来保存一些编译的参数的。CFLAGS是在工程文件夹下的Makefile定义的:
打开顶层的Makefile加入CFLAGS的定义:
该CFLAGS变量的值是一个路径,通过shell命令pwd获得当前的相对路径,包含该路径下的include文件夹。为了底层的目录能够用到该文件,需要将变量export:
修改了之后,make,先前的问题解决了:
可以看到前面的问题解决了,但是又出现了新的问题:printf.c里的第3行的va_list没有定义。打开printf.c:
上面这个类型va_list是在lib/vsprintf.h文件里定义的:
所以要解决这个错误,只需要将lib/vsprintf.h包含到printf.c即可解决:
加入之后编译make,会有以下错误:
第一个警告,uart.c:29: warning: conflicting types for built-in function 'putc'然后打开uart.c的29行,发现根本没有什么内敛函数。起始这是因为在Makefile里少了一个参数:-fno-builtin
再把该变量CFLAGS加到该Makefile里编译c文件的地方:
重新编译:
会发现警告没了。又有错误。就是在编译lib/lib.o的时候发现未定义的vaprintf。哦哦,这是我们的函数写错了,应该是vsprintf。修改之后:
修改后再make:还是原来的错误:
按照道理修改了之后应该是不会再出现这样的错误的。再说现在工程里已经没有vaprintf的影子。怎么还是这个错误。无论怎能make clea后make,还是那个错误。搞到无奈,求救百度,百度无解。最后make clean之后进入lib文件夹,发现lib文件夹里生成的东西没有被清除。就在lib文件夹下进行make clean后退到原来工程目录,make clean,奇迹出现了:
但是还是有一个错误,这是我们使用了为定义和实现的scanf函数导致的:
这是我们先把它注释掉:
修改之后,重新编译成功了:
生成了gboot.bin:
烧写到开发板,发现串口终端的内容输出了:
上面就实现了printf函数了,接下来就是实现scanf函数了。在Redhat 6.4执行命令:man 3 scanf:
可以看到返回值是int,第一个参数是const char *format,第二个参数还是变参。该函数实现的过程跟printf是相反的。Scanf函数是:1)先获取输入的字符串。2)获得字符串进行格式转换,传递给系统。实现代码:
编译成功:
到这里,printf和scanf都实现好了,编译并烧写到开发板:
可以看到我们要的输入效果出现了。我们输入1,没有反应,输入4,会输出Error:wrong
Selection。在后面的操作里将实现switch选择所对应的处理方法。到这里控制台的基本搭建完成了。
3.程序结构的优化
可以看到,现在目录下的文件已经很乱了,随着工程的深入,文件会越来越多,为了管理的方便,这里,我们把有关设备驱动的C文件放到dev文件夹里:
这样,我们的工程文件的布局就合理多了。当然,dev文件夹下也需要Makefile文件,从lib文件夹下拷贝过去再进行修改:
最后dev下的Makefile的内容为:
这样就修改好了dev目录下的Makefile,注意,我们顶层的Makefile也需要改:
做了上面的相应修改后,make:
到这里,我们的基本工程框架已经实现了,结构也比较合理了。为以后跟多功能的加入搭建好了平台。