利用imx6ull板学习裸机arm板开发(6.22-6.24)

发布于:2025-06-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、代码编写编译环境:代码编写:在windows下载一个vs code编写源码,通过FiliZilla软件上传到ubantu,编译:在linux的ubantu终端编译。具体步骤:

        1、在https://www.filezilla.cn/download下载FileZilla,FileZilla作为客户端, Ubuntu作为服务端。此外还需要在Ubuntu下安装SSH服务器: sudo apt-get install vsftpd ,并配置文件: sudo vi /etc/vsftpd.conf 。使能本地功能和写入功能:

之后就可以在FileZilla下连接服务器了。点击文件->站点->新建站点之后填入Ubuntu的用户名和密码即可互传文件。
        2、vs code直接应用商店下载,这是一个免费软件。

        3、其次要安装的就是交叉编译工具链,这是一个非常重要的工具。我们知道我们编写的程序都是需要经过编译才能够生成可执行程序的,之前我们一直使用一个叫做gcc的编译器编译程序,之后才能运行。接下来我们要做的事情也是类似的,问题在于我们编写程序和编译程序的地方是在PC即电脑上,可程序却需要在板子上运行,准确的说是需要在i.mx6ull芯片上运行,因此我们的面临的局面就是用一台x86架构的计算编译出一个arm架构运行的程序,那么使用原来的gcc肯定是不行了,而是需要使用一种特殊的gcc,这个特殊的gcc就称为交叉编译工具链。交叉编译器中“交叉”的意思就是在一个架构上编译另外一个架构的代码,相当于两种架构“交叉”起来了。交叉编译器有很多种,我们使用 Linaro 出品的交叉编译器, Linaro 是一间非营利性质的开放源代码软件工程公司, Linaro 开发了很多软件,最著名的就是 Linaro GCC 编译工具链(编译器),关于 Linaro 详细的介绍可以到 Linaro 官网查阅。 Linaro GCC 编译器下载地址如下: https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linuxgnueabihf/

网站上提供的交叉编译器有很多种,我们只需要关注这两种: gcc-linaro-4.9.4-2017.01-i686_armlinux-gnueabihf.tar.tar.xz 和 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz,第一个是针对 32 位系统的,第二个是针对 64 位系统的。由于我们使用的Ubuntu是64位的,因此我们选择第二种。

先将gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz拷贝到Ubuntu中,之后在Ubuntu下创建文件夹sudo mkdir /usr/local/arm,将压缩包拷贝到该文件夹中: sudo cp gcc-linaro-4.9.4- 2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f ,解压缩sudo tar -vxf gcc-linaro- 4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 。等待解压完成,解压完成以后会生成一个名为“gcclinaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf”的文件夹,这个文件夹里面就是我们的交叉编译工具链

为了使用方便,我们还需要做一些环境变量方面的设置: vi ~/.bashrc ,在文件末尾加上export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin ,如果文件中包括其他交叉工具链的内容,就将之前的内容删除掉。然后重新启动Ubuntu。

最后还需安装: sudo apt-get install lsb-core lib32stdc++6

在控制台下输入arm-linux-gnueabihf-gcc –v能够看到编译器版本信息的话就证明交叉编译工具链安装成功了。

二、程序实验

1、前瞻

        1、相同引脚的多种不同功能,被称之为IO功能复用。
        2、imx6ull的IO复用控制寄存器(IOMUX Controller):可以设置引脚的具体功能,如GPIO、UART等。
        3、时钟使能:只有开启对应模块时钟才能使用该模块,IMX6ULL每个外设的时钟都可以独立的使能或禁止,这样可以关闭掉不使用的外设时钟,起到省电的目的。
        4、引脚设置寄存器(PAD Settings Registers):设置该引脚工作时的电器特性,如上下拉电阻配置、io速度配置、压摆率配置等。

完成上述两个寄存器的设置,就可以让引脚按照指定的方式、指定的电器特性来工作了。

2、led点灯

        1、GPIO:led点灯需要用引脚GPIO功能,IO在该功能下需要设置的8个寄存器:DR、 GDIR、 PSR、 ICR1、 ICR2、 EDGE_SEL、 IMR 和ISR。imx6ull一共由GPIO1~GPIO5 五组 GPIO,每组 GPIO 都有这 8 个32位寄存器。我们来看一下这 8 个寄存器都是什么含义:

        DR是数据寄存器,每一位对应一个引脚,例如 GPIO1.DR=0x0001,就是把第一组GPIO的引脚1设为高电平;

        GDIR 是方向寄存器,设置IO 的输入/输出。比如要设置 GPIO1_IO00 为输入,那么 GPIO1.GDIR=0;

        PSR 是状态寄存器。也就是 GPIO 的高低电平值。功能和输入状态下并启用输入路径时的 DR 寄存器一样。

        ICR1和 ICR2是中断控制寄存器。 ICR1 用于 IO0~15 的配置, ICR2 用于 IO16~31 的配置。 ICR1 寄存器中一个 GPIO 用两个位,这两个位用来配置中断的触发方式。

        IMR 是中断屏蔽寄存器。用来控制 GPIO 的中断禁止和使能,如果使能某个 GPIO 的中断,那么设置相应的位为 1 即可。

        ISR 是中断状态寄存器。只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置 1。当我们处理完中断以后,必须清除中断标志位,清除方法:写 1 清零。

        EDGE_SEL边沿选择寄存器。这个寄存器会覆盖 ICR1 和 ICR2 的设置如果相应的位被置 1,那么对应的 GPIO 是上升沿和下降沿(双边沿)触发。

2、Clock Controller Module (CCM)时钟控制。CMM 有CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,这7个寄存器控制着 I.MX6U 的所有外设时钟开关。以 CCM_CCGR0 为例来看一下如何禁止或使能一个外设的时钟:
        CCM_CCGR0 是个 32 位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着GPIO2 的外设时钟,两个位就有 4 种操作方式,它们分别是:

3、总结IO 作为 GPIO 使用几步:

        1、使能 GPIO 对应的时钟;

        2、设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能;

        3、设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等;

        4、第2步已经将 IO 复用为了 GPIO 功能,所以需要配置 GPIO,设置输入/输出、是否使用中断、默认输出电平等。

4、通过原理图看出,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮, 当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通, 因此 LED0 也就不会点亮。

5、汇编语言编程实现led电灯:

        1、为了方便,直接打开所有外设时钟:读到r0的是ccm_ccgr内存地址。

(复位后默认svc模式,特权模式)

        2、配置io复用寄存器、引脚配置寄存器、引脚设为输出、led对应引脚输出低电平点亮。

        3、在ubantu上用交叉编译器编译文件生成arm上可以运行的可执行程序:

        arm-linux-gnueabihf-gcc -g -c led.s -o led.o   编译生成led.o文件。

        arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf   链接生成led.elf文件。

        arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin   生成led.bin文件,即最终文件,可在arm板运行。

        可选(arm-linux-gnueabihf-objdump -D led.elf > led.dis   反汇编指令,可以得到汇编源码查看)

每次都要输入四次指令来编译链接格式转换,为了简化过程,来写一个简单的makefile:

之后编译只需要输入指令make即可。

        4、怎么把.bin文件下到IMX6ULL开发板?51单片机、stm32等都可以直接通过 Keil下载到内部的 flash中。但是 I.MX6U 虽然内部有 96K 的 ROM, 但是这 96K 的 ROM 是 NXP自己用的, 不向用户开放。 所以相当于说 I.MX6U 是没有内部 flash 的, 但是我们的代码得有地方存放啊, 为此, I.MX6U 支持从外置的NOR Flash、 NAND Flash、 SD/EMMC、 SPI NOR Flash和 QSPI Flash 这些存储介质中启动, 所以我们可以将代码烧写到这些存储介质中中。 在这些存储介质中, 除了 SD 卡以外, 其他的一般都是焊接到了板子上的, 我们没法直接烧写。 但是 SD卡是活动的, 是可以从板子上插拔的, 我们可以将 SD 卡插到电脑上,在电脑上使用软件将.bin文件烧写到 SD 卡中, 然后再插到板子上就可以了。 其他的几种存储介质是我们量产的时候用到的, 量产的时候代码就不可能放到 SD 卡里面了, 毕竟 SD 是活动的, 不牢固, 而其他的都是焊接到板子上的, 很牢固。

        如何将我们前面编译出来的 led.bin 烧写到 SD 卡中呢? 肯定有人会认为直接复制led.bin 到 SD 卡中不就行了, 错! 编译出来的可执行文件是怎么存放到 SD 中的, 文件存放的位置NXP 是有详细规定的。
        我 们 使 用 一 个 NXP 提 供 软 件 来 将 编 译 出 来 的 .bin 文 件 烧 写 到 SD 卡 中 , 这 个 软 件 叫 做“imxdownload”, imxdownlaod 只能在 Ubuntu 下使用, 使用方法如下:
        首先将工具拷贝到工程目录下并添加运行权限。 之后准备一张新的 SD(TF)卡, 确保 SD 卡里面没有有用的数据, 因为我们在烧写代码的时候可能会格式化 SD 卡。 Ubuntu 下所有的设备文件都在目录“/dev”里面, 所以插上 SD 卡以后也会出现在“/dev”里面, 其中存储设备都是以“/dev/sd”开头的。 我们要先看一下不插 SD 卡的时候电脑都有哪些存储设备, 以防插入 SD 卡以后分不清谁是谁。 输入如下所示命令: ls /dev/sd* 。 例如我这里看到的结果是/dev/sda /dev/sda1 /dev/sda2 /dev/sda3 四个文件, 分别代表Ubuntu下的磁盘和三个分区。 使用读卡器将 SD 卡插到电脑, 一定要确保 SD 卡是挂载到了 Ubuntu 系统中, 而不是 Windows下。 再次使用上述命令, 我这里看到的是/dev/sda /dev/sda1 /dev/sda2 /dev/sda3 /dev/sdb /dev/sdb1 多出的两个文件就是sd卡及其分区了。 我们要做的就是把led.bin文件烧写到sd卡里去。 使用命令./imxdownload led.bin /dev/sdb 。 注意千万不能写成./imxdownload led.bin /dev/sda, sda是系统磁盘, 会造成Ubuntu损坏的!

烧写的最后一行会显示烧写大小、 用时和速度, 比如 led.bin 烧写到 SD 卡中的大小是 3.2KB, 用时 0.0160821s, 烧写速度是 201KB/s。 注意这个烧写速度, 如果这个烧写速度在几百 KB/s 以下那么就是正常烧写。 如果这个烧写速度大于几十 MB/s、 甚至几百 MB/s 那么肯定是烧写失败了。 解决方法就是重新插拔 SD 卡, 一般出现这种情况, 重新插拔 SD 卡基本没啥用, 只有重启Ubuntu, 至于原因, 我也不清楚。

烧写完成以后会在当前工程目录下生成一个 load.imx 的文件, 这个文件就是软件 imxdownload根据 NXP 官方启动方式介绍的内容, 在 led.bin 文件前面添加了一些数据头以后生成的。 最终烧写到 SD卡里面的其实就是这个 load.imx 文件, 而非led.bin。

        5、代码已经烧写到了 SD 卡中了, 接下来就是将 SD 卡插到开发板的 SD 卡槽中, 然后设置拨码开关为 SD 卡启动, 拨码开关设置如图。

6、C语言实现led点灯:

在汇编 LED 灯实验中, 我们讲解了如何使用汇编来编写 LED 灯驱动, 实际工作中是很少用到汇编去写嵌入式驱动的, 毕竟汇编太难, 而且写出来也不好理解, 大部分情况下都是使用 C 语言去编写的。 只是在开始部分用汇编来初始化一下 C 语言环境, 比如初始化 DDR、 设置栈指针 SP 等等, 当这些工作都做完以后就可以进入 C 语言环境, 也就是运行 C 语言代码, 一般都是进入 main 函数。 所以我们有两部分文件要做: 1、 汇编文件, 用于搭建c语言运行的必要环境; 2、 C 语言文件, C 语言文件就是完成我们的业务层代码的, 其实就是我们实际例程要完成的功能。

        1、初始化操作需要在管理模式下进行)(特权模式下才可以通过芯片手册的外设地址访问对应外设)

        第4~7行, 设置arm内核工作模式为svc模式。第9行, 通过 ldr 指令设置 SVC 模式下的 SP 指针=0X80200000, 因为 I.MX6U-ALPHA 开发 板 上 的DDR3地址范围是 0X80000000~0XA0000000(512MB。由于 Cortex-A7 的堆栈是向下增长的, 所以将 SP 指针设 置 为 0X80200000 , 因 此 SVC 模 式 的 栈 大 小0X80200000-0X80000000=0X200000=2MB , 2MB的栈空间已经很大了, 如果做裸机开发的话绰绰有余。之后程序就跳转到c语言环境下运行main函数。

切换状态也可以用指令cps:

cps 0x13即可切换到svc模式

        2、在main.c中首先定义我们要使用的几个寄存器。 原理就是将手册中的绝对地址强转成指针, 并通过该指针访问该指针指向的内容。 在强转过程中我们使用了volatile关键字, 这个关键字的原意是易变的。 在c语言中的作用是禁止编译器所作的优化操作。 譬如int i = 100; int *p = &I, *q = *I;这两行代码定义的两个指针都指向变量i, 如果在程序中*p = *q;编译器将会优化这行代码。 因为既然p、 q都指向i, 那么这个赋值显然是没有必要的。 但是在驱动开发中, 寄存器中的值是可能随时发生变化的, 优化的结果就是无法反映寄存器中的真实值。 因此需要禁止这类优化。

        3、之后就像操作单片机那样, 编写两个函数, 一个用于初始化CCM, 为了简化, 这里把所有外设时钟都打开了;另外一个就是初始化Led对应的引脚。

        4、最后再编写几个函数用于操作led, 并在主函数中调用。

5、程序写好之后我们的工程中有两个文件, start.s和main.c。多文件编译我们需要修改之前的Makefile

这段Makefile的内容主要是给出几个规则,分别是创建.o的2个规则,创建.bin的规则。编译后烧写程序并运行可以看到led在不断闪烁。

补充1:makefile常用知识点:

1. 基本语法结构

切记command前不是空格,是tab

%只能第一行用,%.o:%.c     ,   *.c  只能在后面的命令里用,都表示所有该类型文件。

注释:#。。。

补充2:GNU工具链常用工具介绍:

gcc:编译器。 将高级语言代码(C语言等)转化为汇编代码,再将汇编代码转化为目标文件(.o)。

ld:链接器。   将多个目标文件链接为可执行文件(.elf),进行地址分配和重定位,处理库文件链接。解析处理符号引用。

objcopy:目标文件格式转化器。   在不同格式的目标文件直接进行转换,将elf格式转换为纯二进制格式(.bin),从目标文件中提取特定的段,修改目标文件的内容。

objdump:反汇编器。    将机器码转回汇编代码(.dis),查看目标文件详细信息,分析程序的执行指令。

        *file  文件名   查看文件信息。

 7、用SDK开发裸机驱动

        按照面的方法,每次访问外设都要自己设置一个指针指向它,十分麻烦,那么就需要芯片厂家提供的sdk。SDK(软件开发工具包)包含以下内容:外设驱动库(如上述的硬件操作函数)。中间件(如RTOS、文件系统、网络协议栈)。开发工具(如编译器配置、调试脚本、IDE插件)。示例代码(针对具体开发板的Demo)。文档(API手册、硬件参考指南)。我们要用到的就是外设驱动库,里面已经有所有外设地址的声明,如下所示五个头文件。

引脚复用在头文件中被封装成了函数,引脚配置类似:

利用sdk操作led,启动文件还是前面写的,只需要把这五个库放进工程,然后main.c调用即可:

为什么函数传参只有两个,因为第一个参数实际上是宏定义,文本替换后有5个参数,如上所示。

也可以把led相关操作放在一个.c文件,主函数调用。makefile的obj只需要加一个.o即可。

8、链接脚本

imx6ull的内存空间为:

        其中:0x8000 0000到0xffff ffff共2gb是ddram,但是只有0x8000 0000到0xa000 0000(左闭右开区间)共512mb是可访问的,用来存放代码等,0xa0000000上面的无法访问,0x8000 0000以下的数据空间是用来存放各种外设寄存器的。

之前ld链接是把代码存到以 0X87800000 为起始地址的区域,这段区域可以理解为text段。但是除了代码段,还要很多其他的段,如图所示,这是elf文件里的段,但我们需要了解的段只有五个:

        rodata段:只读数据段。const修饰的全局变量、全局只读数组、常量:整数、浮点数

字符串等。

        data段:已初始化的全局变量和静态变量。

        COMMON段:未初始化的全局变量、未初始化的静态局部变量

        bss段:用于存放未初始化的静态全局变量,和初始化为0的全局变量

        text段:存放代码

局部变量不在这些地方,而是存放在栈区,链接脚本不用管,在启动代码对栈指针初始化过了。

        

        2、举例说明一下,假设我们要把的代码要被链接到0X10000000 这个地址,数据要被链接到 0X30000000 这个地址。

第 1 行我们先写了一个关键字“SECTIONS”,后面跟了一个大括号,这个大括号和第 9 行的大括号是一对,这是必须的。看起来就跟 C 语言里面的函数一样。

第 3 行对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位计数器为 0。我们要求代码链接到以0X10000000 为起始地址的地方,因此这一行给“.”赋值0X10000000,表示以 0X10000000 开始,后面的文件或者段都会以 0X10000000 为起始地址开始链接。

第 4行的“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链接到“.text”这个段里面的所有文件,“*(.text)”中的“*”是通配符,表示所有输入文件的.text段都放到“.text”中。

第 6 行,我们的要求是数据放到 0X30000000 开始的地方,所以我们需要重新设置定位计数器“.”,将其改为 0X30000000。如果不重新设置的话会怎么样?假设“.text”段大小为 0X10000,那么接下来的.data 段开始地址就是0X10000000+0X10000=0X10010000,这明显不符合我们的要求。所以我们必须调整定位计数器为 0X30000000。

第 7 行跟第 3 行一样,定义了一个名为“.data”的段,然后所有文件的“.data”段都放到这里面。但是这一行多了一个“ALIGN(4)”,这是什么意思呢?这是用来对“.data”这个段的起始地址做字节对齐的, ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐。

第 8 行定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数据未初始化的全局静态变量和初始化为0的全局变量。

3、编写链接脚本:

按照之前所述的规则,可以很容易写出我们需要的链接脚本。第 3 行设置定位计数器为0X87800000,因为我们的链接地址就是 0X87800000。第 6 行设置链接到开始位置的文件为start.o,因为 start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。第 7 行是 main.o这个文件,其实可以不用写出来,因为 main.o 的位置就无所谓了,可以由编译器自行决定链接位置。在第 14、 16 行有“__bss_start”和“__bss_end”这两个东西?这个是什么呢?我们知道bss段中保存的是未初始化的静态全局变量和初始化未0的全局变量,而未初始化的全局变量变量保存在COMMON段。上述无论何种段在主函数运行前都必须清零(c语言规定的)。因此我们必须保证在进入到main函数之前把他们都清零才行。这个操作本身并不复杂,类似于将一个数值所有元素都赋值未0,但是这个这个数组的起始位置和结束位置随着程序的不同位置也会发生变化的。为了能够获得这个数组的起始和结束位置,我们设置两个标识符__bss_start和__bss_end,这两个标识符是可以在start.S中访问的,那么此后我们就可以在汇编代码中对bss段(COMMON段)精选清除操作了。

在rodata后面有个*,因为只读数据一般会有后缀表示几字节对其,但有些数据没有后缀,不加*就会只链接有后缀的数据,加上才会全部链接。

4、全局变量清零:

5、最后在Makefile中将链接指令改为armlinux-gnueabihf-ld -Timx6ul.lds $^ - oled.elf 。这里的imx6ull.lds就是我们上面编写的链接脚本文件。

9、beep蜂鸣器

        跟led类似,只需要改引脚复用跟引脚配置即可。

makefile加一个beep.o

10、板机支持包bsp思想

        main.c   start.s经常变,我们把它放在project文件夹

        led.c  led.h    beep.c    beep.h写完就固定了,我们把它放在bsp文件夹

        外设库文件由芯片厂家提供,我们把它放在imx6ull文件夹

        然后剩下makefile     imx6ull.lds和imxdownload就放外面即可

        为了保存编译生成的.o创一个空的文件夹obj。

                                                 

修改makefile:

改变脚本:只需要改start.o的位置。

11、key按键

        前面两个实验都使用的是GPIO 输出控制功能, IMX6ULL 的 IO 不仅能作为输出,而且也可以作为输入。开发板上有一个按键,按键连接了一个 IO,将这个 IO 配置为输入功能,读取这个 IO 的值即可获取按键的状态(按下或松开)。按键就两个状态:按下或弹起,将按键连接到一个 IO 上,通过读取这个 IO 的值就知道按键是按下的还是弹起的。 KEY0 没有按下的时候 UART1_CTS 应该是高电平,当 KEY0按下以后 UART1_CTS 就是低电平。按照之前我们学习的知识,很容易就能够写出这段代码来。

其他地方跟前面led、beep差不多,只是GDIR改为输入,然后通过dr中的数据判断高低电平来判断是否按下。

12、gpio封装

可以看到前三个程序都有对GPIO功能下的寄存器进行访问所以来为gpio封装一个.c。

1、首先创建一个名为gpio.h的头文件,并在头文件中声明一个枚举和一个结构体。枚举中的两个常量分别表示引脚的方向:输入或者输出;结构体两个成员分别表示 引脚的方向和作为输出时的默认电平状态。然后写gpio.c。

GPIO_Type来自sdk中的头文件,直接用。

如何使用封装好的GPIO呢,在用led.c演示:

2、GPIO封装内容补充:

仍旧在gpio.c里面写:

在led.c使用“


网站公告

今日签到

点亮在社区的每一天
去签到