嵌入式Linux裸机开发笔记2(IMX6ULL)—汇编LED实验(4)

引言

        前面3讲笔记已经为我们准备好了进行实验的工具方法,接下来我们开始从分析led灯的原理图开始,具体实操代码。

2.2 硬件原理分析

        I.MX6U ALPHA 开发板上有一个 LED 灯,原理图如下图 所示:

        可以看出,LED0 接到了 GPIO_3 上, GPIO_3 就是 GPIO1_IO03 ,当 GPIO1_IO03输出低电平(0) 的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平 (1) 的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0  就亮,输出 1 就灭。

2.3 实验程序编写

        按照 2.1 小节中讲的,我们需要对 GPIO1_IO03 做如下设置:
1 、使能 GPIO1 时钟
       GPIO1 的时钟由 CCM_CCGR1 bit27 bit26 这两个位控制,将这两个位都设置位 11 即可。本教程所有例程已经将 I.MX6U 的所有外设时钟都已经打开了,因此这一步可以不用做。
2 、设置 GPIO1_IO03 的复用功能
     找到 GPIO1_IO03 的复用寄存器“ IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 ”的地址为0X020E0068,然后设置此寄存器,将 GPIO1_IO03 这个 IO 复用为 GPIO 功能,也就是 ALT5
3 、配置 GPIO1_IO03
     找到 GPIO1_IO03 的配置寄存器“ IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 ”的地址为0X020E02F4,根据实际使用情况,配置此寄存器。
4 、设置 GPIO
        本实验中 GPIO1_IO03 是作为输出功能的,因此 GPIO1_GDIR bit3 要设置为 1 ,表示输
出。
5 、控制 GPIO 的输出电平
        经过前面几步,GPIO1_IO03 已经配置好了,只需要向 GPIO1_DR 寄存器的 bit3 写入 0 即可控制 GPIO1_IO03 输出低电平,打开 LED ,向 bit3 写入 1 可控制 GPIO1_IO03 输出高电平,关闭 LED
        注意所有的裸机实验默认都在 Ubuntu 下完成,使用 VSCode 编辑器!如果没有安装Ubuntu可以参考:
        既然是实验,肯定要自己动手创建工程,新建一个名为“1_leds ”的文件夹,然后在“ 1_leds ” 这个目录下新建一个名为“led.s ”的汇编文件和一个名为“ .vscode ”的目录,创建好以后“ 1_leds ” 文件夹如下图 所示:

        上图中.vscode 文件夹里面存放 VSCode 的工程文件,led.s 就是我们新建的汇编文件, 我们稍后会在 led.s 这个文件中编写汇编程序。使用 VSCode 打开 1_leds 这个文件夹,打开以后如下图所示:

led.s 中输入如下代码:

/**************************************************************
文件名: led.s
作者 : 嘴硬的xiaolaji
描述 : 裸机实验1 汇编点灯
    使用汇编来点亮开发板上的LED灯,学习和掌握如何用汇编语言来
    完成对I.MX6U处理器的GPIO初始化和控制。
日志 : 2023/11/29 嘴硬的xiaolaji创建
**************************************************************/

1
2 . global _start /* 全局标号 */
3
4 /*
5 * 描述: _start 函数,程序从此函数开始执行此函数完成时钟使能、
6 * GPIO 初始化、最终控制 GPIO 输出低电平来点亮 LED 灯。
7 */
8 _start :
9 /* 例程代码 */
10 /* 1 、使能所有时钟 */
11 ldr r0 , = 0X020C4068 /* 寄存器 CCGR0 */
12 ldr r1 , = 0XFFFFFFFF
13 str r1 , [ r0 ]
14
15 ldr r0 , = 0X020C406C /* 寄存器 CCGR1 */
16 str r1 , [ r0 ]
17
18 ldr r0 , = 0X020C4070 /* 寄存器 CCGR2 */
19 str r1 , [ r0 ]
20
21 ldr r0 , = 0X020C4074 /* 寄存器 CCGR3 */
22 str r1 , [ r0 ]
23
24 ldr r0 , = 0X020C4078 /* 寄存器 CCGR4 */
25 str r1 , [ r0 ]
26
27 ldr r0 , = 0X020C407C /* 寄存器 CCGR5 */
28 str r1 , [ r0 ]
29
30 ldr r0 , = 0X020C4080 /* 寄存器 CCGR6 */
31 str r1 , [ r0 ]
32
33
34 /* 2 、设置 GPIO1_IO03 复用为 GPIO1_IO03 */
35 ldr r0 , = 0X020E0068 /* 将寄存器 SW_MUX_GPIO1_IO03_BASE 加载到 r0 */
36 ldr r1 , = 0X5 /* 设置寄存器 SW_MUX_GPIO1_IO03_BASE MUX_MODE 5 */
37 str r1 ,[ r0 ]
38
39 /* 3 、配置 GPIO1_IO03 IO 属性
40 *bit 16:0 HYS 关闭
41 *bit [15:14]: 00 默认下拉
42 *bit [13]: 0 kepper 功能
43 *bit [12]: 1 pull/keeper 使能
44 *bit [11]: 0 关闭开路输出
45 *bit [7:6]: 10 速度 100Mhz
46 *bit [5:3]: 110 R0/6 驱动能力
47 *bit [0]: 0 低转换率
48 */
49 ldr r0 , = 0X020E02F4 /* 寄存器 SW_PAD_GPIO1_IO03_BASE */
50 ldr r1 , = 0X10B0
51 str r1 ,[ r0 ]
52
53 /* 4 、设置 GPIO1_IO03 为输出 */
54 ldr r0 , = 0X0209C004 /* 寄存器 GPIO1_GDIR */
55 ldr r1 , = 0X0000008
56 str r1 ,[ r0 ]
57
58 /* 5 、打开 LED0
59 * 设置 GPIO1_IO03 输出低电平
60 */
61 ldr r0 , = 0X0209C000 /* 寄存器 GPIO1_DR */
62 ldr r1 , = 0
63 str r1 ,[ r0 ]
64
65 /*
66 * 描述: loop 死循环
67 */
68 loop :
69         b loop
        第 2 行定义了一个全局标号 _start ,代码就是从 _start 这个标号开始顺序往下执行的。
      第 11 行使用 ldr 指令向寄存器 r0 写入 0X020C4068 ,也就是 r0=0X020C4068 ,这个是CCM_CCGR0 寄存器的地址。
        第 12 行使用 ldr 指令向寄存器 r1 写入 0XFFFFFFFF ,也就是 r1=0XFFFFFFFF 。因为我们要开启所有的外设时钟,因此 CCM_CCGR0~CCM_CCGR6 所有寄存器的 32 位都要置 1 ,也就是写入 0XFFFFFFFF
        第 13 行使用 str r1 中的值写入到 r0 所保存的地址中去,也就是给 0X020C4068 这个地址写入 0XFFFFFFFF ,相当于 CCM_CCGR0=0XFFFFFFFF ,就是打开 CCM_CCGR0 寄存器所
控制的所有外设时钟。
        第 15~31 行都是向 CCM_CCGRX(X=1~6) 寄存器写入 0XFFFFFFFF 。这样我就通过汇编代码使能了 I.MX6U 的所有外设时钟。
        第35~37 行是设置 GPIO1_IO03 的复用功能, GPIO1_IO03 的复用寄存器地址为 0X020E0068, 寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 MUX_MODE 设置为 5 就是将GPIO1_IO03 设置为 GPIO
        第 49~51 行 是 设 置 GPIO1_IO03的 配 置 寄 存 器 , 也 就 是 寄 存 器
IOMUX_SW_PAD_CTL_PAD_GPIO1_IO03 的值,此寄存器地址为 0X020E02F4 ,代码里面已经
给出了这个寄存器详细的位设置。
        第 54~63 行是设置 GPIO 功能,经过上面几步操作, GPIO1_IO03 这个 IO 已经被配置为了GPIO 功能,所以还需要设置跟 GPIO 有关的寄存器。第 54~56 行是设置 GPIO1->GDIR 寄存器,将GPIO1_IO03 设置为输出模式,也就是寄存器的 GPIO1_GDIR bit3 1
        第 61~63 行设置 GPIO1->DR 寄存器,也就是设置 GPIO1_IO03 的输出,我们要点亮开发板上的 LED0 ,那么 GPIO1_IO03 就必须输出低电平,所以这里设置 GPIO1_DR 寄存器为 0
68~69 行是死循环,通过 b 指令, CPU 重复不断的跳到 loop 函数执行,进入一个死循环。

2.4 编译下载验证

2.4.1 编译代码

因为我们现在是直接在 Ubuntu 下使用 VSCode 编译的代码,在编译之前我们先了解几个编译工具。
1 arm-linux-gnueabihf-gcc 编译文件
我们是要编译出在 ARM 开发板上运行的可执行文件,所以要使用 交叉编译器
arm-linux-gnueabihf-gcc 来编译。因为本试验就一个 led.s 源文件,所以编译比较简单。先将 led.s 编译为对应的 .o 文件,在终端中输入如下命令:
        arm-linux-gnueabihf-gcc -g -c led.s -o led.o
上述命令就是将 led.s 编译为 led.o ,其中“-g”选项是产生调试信息, GDB 能够使用这些调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o 。执行上述命令以后就会编译生成一个 led.o 文件,如下图 所示:
        图中 led.o 文件并不是我们可以下载到开发板中运行的文件,一个工程中所有的 C文件和汇编文件都会编译生成一个对应的.o 文件,我们需要将这 .o 文件链接起来组合成可执行文件。
2 arm-linux-gnueabihf-ld 链接文件
         arm-linux-gnueabihf-ld 用来将众多的.o 文件链接到一个指定的链接位置。
        我们现在需要做的就是确定一下本试验最终的可执行文件其运行起始地址,也就是链接地址。这里我们要区分 “存储地址”和“运行地址” 这两个概念, “存储地址”就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。“运行地址”就是代码运行的时候所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。 比如 I.MX6U 支持 SD 卡、 EMMC NAND 启动,因此代码可以存储到 SD 卡、 EMMC 或者 NAND 中,但是要运行的话就必须将代码从 SD 卡、 EMMC 或者NAND 中拷贝到其运行地址 ( 链接地址 ) 处,“存储地址”和“运行地址”可以一样,比如STM32 的存储起始地址和运行起始地址都是 0X08000000 。本教程所有的裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,这个链接地址可以在 I.MX6U 的内部 128KB RAM 中(0X900000~0X91FFFF),也可以在外部的 DDR 中。本教程所有裸机例程的链接地址都在 DDR 中,链接起始地址为 0X87800000 I.MX6U-ALPHA 开发板的 DDR 容量有两种: 512MB 和256MB,起始地址都为 0X80000000 ,只不过 512MB 的终止地址为 0X9FFFFFFF ,而 256MB 容量的终止地址为 0X8FFFFFFF 。之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000 ,这样我们统一使用 0X87800000 这个链接地址,不容易记混。
        确定了链接地址以后使用如下命令:
        arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
        上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名
led.elf 。上述命令执行完以后就会在工程目录下多一个 led.elf 文件,如下图 所示:
        led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的 .bin 文件,因此还
需要将 led.elf 文件转换为 .bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具
了。
3 arm-linux-gnueabihf-objcopy 格式转换
        arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为
led.bin 文件,命令如下:
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
        上述命令中,“-O”选项指定以什么格式输出,后面的“binary ”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。上述命令执行完成以后,工程目录如下图所示:
        至此我们终于等到了想要的东西—led.bin 文件。
4 arm-linux-gnueabihf-objdump 反汇编
        大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
        arm-linux-gnueabihf-objdump -D led.elf > led.dis
        上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件如下图所示:
        
        打开led.dis,从上图可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号 _start ,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以 0X87800000 为起始地址的区域。
        总结一下我们为了编译 ARM 开发板上运行的 led.o 这个文件使用了如下命令:
        arm-linux-gnueabihf-gcc -g -c led.s -o led.o
        arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
        arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
        arm-linux-gnueabihf-objdump -D led.elf > led.dis
        如果我们修改了 led.s 文件,那么就需要在重复一次上面的这些命令,太麻烦了,这个时候
我们就可以使用  Makefile 文件了。

8.4.2 创建 Makefile 文件

是用“ touch ”命令在工程根目录下创建一个名为“ Makefile ”的文件,如下图 所示:

        创建好 Makefile 文件以后就需要根据 Makefile 语法编写 Makefile 文件了, Makefile 基本语 法我们已经在第三章讲解了,在 Makefile 中输入如下内容:
示例代码:
led.bin : led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean :
rm -rf *.o led.bin led.elf led.dis
        创建好 Makefile 以后我们就只需要执行一次“ make ”命令即可完成编译,过程下图所示:

        至此,有关代码编译、arm-linux-gnueabihf 交叉编译器的使用就到这里了。接下来讲解如何将 led.bin 烧写到 SD 卡中。

2.4.3 代码烧写

        我们在调试裸机和 Uboot 的时候是将代码下载到 SD 中,因为方便嘛,当调试完成以后量产的时候要将裸机或者 Uboot 烧写到 SPI NOR Flash EMMC NAND 等这些存储介质中的。我们必须按照 NXP 的规定来将代码烧写到 SD 卡中,否则代码是绝对运行不起来的。
        正点原子专门编写了一个软件来将编译出来的.bin 文件烧写到 SD 卡中,这个软件叫做 “imxdownload“,本人也是借吃到了正点原子的开源福利,白嫖了这个软件,同时本人很多学习内容也是跟着正点原子的教程进行学习。 注意 imxdownlaod 只能在 Ubuntu 下使用。有需要的小伙伴可以去正点原子官方开源网站去进行下载。如果找不到,可以联系我,我发给你。
        使用步骤如下:
1 、将 imxdownload 拷贝到工程根目录下
        我们要将 imxdownload 拷贝到工程根目录下,也就是和 led.bin 处于同一个文件夹下,要不 然烧写会失败的,拷贝完成以后如下图 所示:

2 、给予 imxdownload 可执行权限
        我们直接将软件 imxdownload Windows 下复制到 Ubuntu 中以后, imxdownload 默认是
没有可执行权限的。我们需要给予 imxdownload 可执行权限,使用命令“ chmod ”,命令如下:

        通过对比 可以看到,当给予 imxdownload 可执行权限以后其名字变成了绿色的,如果没有可执行权限的话其名字颜色是白色的。所以在 Ubuntu 中我们可以初步的从文件名字的颜色判断其是否具有可执行权限。
3 、确定要烧写的 SD 卡。
        准备一张新的 SD(TF) 卡,确保 SD 卡里面没有数据,因为我们在烧写代码的时候可能会格式化 SD 卡!!!
        Ubuntu 下所有的设备文件都在目录“ /dev ”里面,所以插上 SD 卡以后也会出现在“ /dev ”里面,其中存储设备都是以“/dev/sd ”开头的。我们要先看一下不插 SD 卡的时候电脑都有哪些存储设备,以防插入 SD 卡以后分不清谁是谁。
        如果你的电脑没有找到 SD 卡的话,尝试重启一下 Ubuntu 操作!        
4 、向 SD 卡烧写 bin 文件
        使用 imxdownload SD 卡烧写 led.bin 文件,命令格式如下:
        ./imxdownload <.bin file> <SD Card>
        其中.bin 就是要烧写的 .bin 文件, SD Card 就是你要烧写的 SD 卡,比如我的电脑使用如下 命令烧写 led.bin /dev/sdd 中:
        ./imxdownload led.bin /dev/sdd
        烧写的最后一行会显示烧写大小、用时和速度,比如 led.bin 烧写到 SD 卡中的大小是 3.2KB ,用时 0.0160821s ,烧写速度是 201KB/s 。注意这个烧写速度,如果这个烧写速度在几百 KB/s 以下那么就是正常烧写。 如果这个烧写速度大于几十 MB/s 、甚至几百 MB/s 那么肯定是烧写失败了! 烧写完成以后会在当前工程目录下生成一个 load.imx 的文件,load.imx 这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容,在 led.bin 文件前面添加了一些数据头以后生成的。最终烧写到 SD 卡里面的就是这个 load.imx 文件,而非led.bin。至于具体添加了些什么内容,会在之后的笔记中讲解。

        至此没有什么意外的话,烧写完成后,我们就可以是将 SD 卡插到开发板的 SD 卡槽中进行验证了。而第一个实验汇编led的实验至此也就基本结束了。

          *************************************************************************************************

        本次笔记学习了很多正点原子的教程,按照他的教程,自己进行了一遍具体的代码实操,本笔记仅作为自己的回顾。最后由于本人还没买他的板子(穷)哈哈哈,所以就没给大家放验证的图片。不过整体过程下来没出什么奇怪的问题,自己的收获也很多,包括一些编写代码的规范,也看了正点原子的视频,视频里左神也说了更多的细节,我只记录了自己的过程、收获和容易出问题的地方。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值