前言:
本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
正文:
本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第19 讲” 的读书笔记。第19讲主要是介绍I.MX6U处理器的UART串口并实现串口UART的字符格式化打印实验。本节将参考正点原子的视频教程第18讲和配套的正点原子开发指南文档进行学习。
0. 概述
上一章实验我们实现了 UART1 基本的数据收发功能,虽然可以用来调试程序,但是功能太单一了,只能输出字符。如果需要输出数字的时候就需要我们自己先将数字转换为字符,非常的不方便。学习 STM32 串口的时候我们都会将 printf 函数映射到串口上,这样就可以使用printf 函数来完成格式化输出了,使用非常方便。本章我们就来学习如何将 printf 这样的格式化函数移植到 I.MX6U-ALPHA 开发板上。
1. 串口格式化函数简介
格式化函数说的是 printf, sprintf 和 scanf 这样的函数,分为格式化输出和格式化输入两类函数。学习C语言的时候常常会通过 printf 函数在屏幕上显示字符串,通过 scanf 函数从键盘获取输入。这样就用了输入和输出了,实现了最基本的人机交互。学习STM32的时候会将 printf 映射到串口上,这样即使没有屏幕,也可以通过串口和开发板进行交互。在 I.MX6U-ALPHA 开发板上也可以使用此方法,将 printf 和 scanf 映射到串口上,这样就可以使用 SecureCRT 作为开发板的终端,完成与开发板的交互。也可以使用 printf 和 sprintf 来实现各种各样的格式化字符串,方便我们后续的开发。串口驱动我们上一章已经编写完成了,而且实现了最基本的字节收发,本章我们就通过移植网上别人已经做好的文件来实现格式化函数。
2. 硬件原理分析
本章所需的硬件和上一章相同
3. 实验程序编写
本章实验所需要移植的源码已经放到了开发板光盘中,路径为: 1、例程源码->5、模块驱动源码->2、格式化函数源码->stdio,文件夹 stdio 里面的文件就是我们要移植的源码文件。本章实验在上一章例程的基础上完成,将 stdio 文件夹复制到实验工程根目录中,如图 22.3.1 所示:
stdio 里面有两个文件夹: include 和 lib,这两个文件夹里面的内容如图 22.3.2 所示:
图 22.3.2 就是 stdio 里面的所有文件, stdio 里面的文件其实是从 uboot 里面移植过来的。后面学习 uboot 以后大家有兴趣的话可以自行从 uboot 源码里面“扣”出相应的文件,完成格式化函数的移植。这里要注意一点, stdio 中并没有实现完全版的格式化函数,比如 printf 函数并不支持浮点数,但是基本够我们使用了。
将正点原子提供的示例源码中的"stdio"文件夹放到本实验的 "14_printf" 目录中,然后修改Makefile 将移植的"stdio"目录放到编译源文件里。
3.1 Makefile编译错误1
执行 "make" 命令进行编译,此时编译器报如下错误提示:
在正点原子的视频教程和指导文档里说明了解决此编译错误的方法是修改Makefile,在Makefile编译命令里加上如下编译选项 "-Wa,-mimplicit-it=thumb"【ARM 嵌入式 编译系列 2.3 -- GCC 编译参数学习 -Wa,-mimplicit-it=thumb 使用介绍】-CSDN博客文章浏览阅读750次,点赞12次,收藏8次。在使用编译 ARM 架构代码时,你可能会碰到一些控制汇编器行为的编译器选项。-wa是一个 GCC 编译器选项,用于向汇编器传递参数。-wa。_-wa,-mimplicit-it=thumbhttps://blog.csdn.net/sinat_32960911/article/details/135645189
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -fno-builtin -Wa,-mimplicit-it=thumb $(INCLUDE) -O2 -c -o $@ $<
3.2 Makefile编译错误2
执行 "make" 命令进行编译,此时编译器报如下错误提示:
"__aeabi_uidiv" 用来实现ARM无符号数除法,编译链接器报错的是因为 ARM 是RISC精简指令集没有算术除法指令,所以在ARM裸机开发(也包括Uboot)里面的除法运算需要使用到 GCC 编译器提供的 libgcc.a 库里提供的ARM汇编除法函数实现。一般从uboot或者Kernel内核里能够找到ARM汇编除法函数的实现 "__aeabi_uidiv" 。
LIBPATH := -lgcc -L /home/dimon/I.MX6U/tool/gcc-linaro-4.9.4-2017.01-i686_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4/
#LIBGCCA := /home/dimon/I.MX6U/tool/gcc-linaro-4.9.4-2017.01-i686_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4/libgcc.a
$(TARGET).bin : $(OBJS)
echo $(LD) $(TARGET).elf
# $(LD) -Timx6u.lds $(LIBPATH) -o $(TARGET).elf $^ $(LIBGCCA)
$(LD) -Timx6u.lds -o $(TARGET).elf $^ $(LIBGCCA) $(LIBPATH)
$(OBJCOPY) -O binary -S $(TARGET).elf $(TARGET).bin
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
# $(OBJDUMP) -d -s -m arm $(TARGET).elf > $(TARGET).dis
注意这里经过我的测试发现 "$(LIBPATH)" 需要写在链接命令的最后,刚开始我将"$(LIBPATH)" 写在了链接命令的中间部分链接的时候编译器一直报错,即使我们修改Makefiel指定了链接路径。
div64.c:(.text+0x22): undefined reference to `__aeabi_uidiv'
make: *** [printf.bin] Error 1
这里"$(LIBPATH)"在链接命令里先后顺序影响到最终链接结果是否成功的原因是和GCC LD 连接器的文件链接顺序机制有关系。在《程序员的自我修养-链接装载与库》中好像介绍过这一部分,印象中好像是先在链接.o文件的时候发现找不到某个符号,然后去搜索 "-Lxx -lxx" 指令的库文件中去搜索未定义的符号,如果先在链接命令里指定了"-Lxx -lxx"此时没有未定义符号的话就忽略掉了,记得号向是这样解释的。
好了,这里注意 "$(LIBPATH)" 需要写在链接命令的最后。
4. prinf格式化源码编写
移植好以后就要测试相应的函数工作是否正常,我们使用 scanf 函数等待键盘输入两个整数,然后将两个整数进行相加并使用 printf 函数输出结果。在 main.c 里面输入如下内容。
#include "cc.h"
#include "bsp_clk.h"
#include "bsp_led.h"
#include "bsp_delay.h"
#include "bsp_beep.h"
#include "bsp_gpio.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exti.h"
#include "bsp_epittimer.h"
#include "bsp_keyfilter.h"
#include "bsp_delay.h"
#include "bsp_uart.h"
#include "stdio.h"
char *banner = "========================================================\r\n"
"正点原子I.MX6ULL ALPHA/Mini开发板Linux驱动之ARM逻辑开发\r\n" \
"--Date: 2024/05/21\r\n" \
"--Author: ChenHaoxu, Dimon.chen, 11813202388@qq.com\r\n" \
"========================================================\r\n";
int main(void)
{
static uint8_t led_state = OFF;
// static uint8_t beep_state = OFF;
int_init(); /* 中断初始化 */
imx6u_clkinit(); /* 时钟主频初始化,PLL1, PLL2, PLL3 */
clk_init(); /* 使能所有外设时钟 */
led_init(); /* led gpio 初始化 */
beep_init(); /* beep gpio 初始化 */
//key_init(); /* key gpio 初始化 */
exti_init(); /* gpio外设中断初始化 */
//epittimer_init(0, 0, 33000000); /* EPIT分频frac=0 1分频,EPIT1时钟源66MHz,EPIT1->LR加载值计数器=33MHz,定时周期为500ms */
//epittimer_init(1, 0, 66000000/20); /* EPIT分频frac=0 1分频,EPIT1时钟源66MHz,EPIT1->LR加载值计数器=33MHz,定时周期为1000/20=50ms */
keyfilter_init();
delay_init();
uart_init(); /* UART初始化 */
led_switch(LED_0, ON);
beep_switch(ON);
delay(200);
beep_switch(OFF);
delay(200);
beep_switch(ON);
delay(200);
beep_switch(OFF);
printf("%s", banner);
unsigned int a = 10;
while(1){
led_state = !led_state;
led_switch(LED_0, led_state);
printf("Hello World\r\n");
printf("a=%d hex=%x\r\n", a, a);
scanf("%u", &a);
}
return 0;
}
5. 编译烧写SD卡验证实验结果
译修改主频后源码烧录SD卡验证本节的 I.MX6U UART串口实验。预期烧录SD卡后正点原子I.MX6ULL ALPHA/Mini 开发板后,UART串口可以在串口工具,如SecureCRT或者Xshell上打印字符串输出。
我本地验证的结果是基于GPT定时器的高精度延时实验结果正常,UART串口可以在串口工具XShell上打印字符串。
6. 总结和实验遇到的问题记录
本节移植了正点原子示例源码中的 printf 格式化打印函数,之后就可以在串口中实现字符串格式化打印来调试I.MX6U ARM 裸机开发板了,这样会方便很多。本节实验里遇到的问题主要是移植 printf 函数时的编译报错问题,需要修改Makefile解决,makefile修改方法在正点原子的教程里和网上博客里有多用说明,本文也记录了解决问题的详细步骤。
7. 结束
本文至此结束。