1.设置处理器模式
设置6ULL处于SVC模式下。设置下CPSR寄存器的bit4-0,也就是M[4:0]为10011=0x13.。读写状态寄存器需要用到MRS和MSR指令。MRS将CPSR寄存器数据读出到通用寄存器里面,MSR指令将通用寄存器的值写入到CPSR寄存器里面去。
2.设置SP指针
SP可以指向内部RAM,也可以指向DDR,我们将其指向DDR。SP设置到哪里?512MB的范围0x80000000---0x9FFFFFFF。栈大小,0x20000000=2MB。处理器栈增长方式,对于A7而言是向下增长的。
3.跳转到C语言
使用b指令,跳转到C语言函数,比如MAIN函数
1汇编部分实验程序编写
I.MX6U 的汇编部分代码和 STM32 的启动文件 startup_stm32f10x_hd.s 基本类似的,只是本
实验我们不考虑中断向量表,只考虑初始化 C 环境即可。在前面创建的 start.s 中输入如下代码:
2 C****语言部分实验程序编写
main.h
cpp#ifndef __MAIN_H #define __MAIN_H /* * CCM 相关寄存器地址 */ #define CCM_CCGR0 *((volatile unsigned int *)0X020C4068) #define CCM_CCGR1 *((volatile unsigned int *)0X020C406C) #define CCM_CCGR2 *((volatile unsigned int *)0X020C4070) #define CCM_CCGR3 *((volatile unsigned int *)0X020C4074) #define CCM_CCGR4 *((volatile unsigned int *)0X020C4078) #define CCM_CCGR5 *((volatile unsigned int *)0X020C407C) #define CCM_CCGR6 *((volatile unsigned int *)0X020C4080) /* * IOMUX 相关寄存器地址 */ #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068) #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4) /* * GPIO1 相关寄存器地址 */ #define GPIO1_DR *((volatile unsigned int *)0X0209C000) #define GPIO1_GDIR *((volatile unsigned int *)0X0209C004) #define GPIO1_PSR *((volatile unsigned int *)0X0209C008) #define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C) #define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010) #define GPIO1_IMR *((volatile unsigned int *)0X0209C014) #define GPIO1_ISR *((volatile unsigned int *)0X0209C018) #define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C) #endif
main.c
cpp1 #include "main.h" 2 3 /* 4 * @description : 使能 I.MX6U 所有外设时钟 5 * @param : 无 6 * @return : 无 7 */ 8 void clk_enable(void) 9 { 10 CCM_CCGR0 = 0xffffffff; 11 CCM_CCGR1 = 0xffffffff; 12 CCM_CCGR2 = 0xffffffff; 13 CCM_CCGR3 = 0xffffffff; 14 CCM_CCGR4 = 0xffffffff; 15 CCM_CCGR5 = 0xffffffff; 16 CCM_CCGR6 = 0xffffffff; 17 } 18 19 /* 20 * @description : 初始化 LED 对应的 GPIO 21 * @param : 无 22 * @return : 无 23 */ 24 void led_init(void) 25 { 26 /* 1、初始化 IO 复用, 复用为 GPIO1_IO03 */ 27 SW_MUX_GPIO1_IO03 = 0x5; 28 29 /* 2、配置 GPIO1_IO03 的 IO 属性 30 *bit 16:0 HYS 关闭 31 *bit [15:14]: 00 默认下拉 32 *bit [13]: 0 kepper 功能 33 *bit [12]: 1 pull/keeper 使能 34 *bit [11]: 0 关闭开路输出 35 *bit [7:6]: 10 速度 100Mhz 36 *bit [5:3]: 110 R0/6 驱动能力 37 *bit [0]: 0 低转换率 38 */ 39 SW_PAD_GPIO1_IO03 = 0X10B0; 40 41 /* 3、初始化 GPIO, GPIO1_IO03 设置为输出 */ 42 GPIO1_GDIR = 0X0000008; 43 44 /* 4、设置 GPIO1_IO03 输出低电平,打开 LED0 */ 45 GPIO1_DR = 0X0; 46 } 47 48 /* 49 * @description : 打开 LED 灯 50 * @param : 无 51 * @return : 无 52 */ 53 void led_on(void) 54 { 55 /* 56 * 将 GPIO1_DR 的 bit3 清零 57 */ 58 GPIO1_DR &= ~(1<<3); 59 } 60 61 /* 62 * @description : 关闭 LED 灯 63 * @param : 无 64 * @return : 无 65 */ 66 void led_off(void) 67 { 68 /* 69 * 将 GPIO1_DR 的 bit3 置 1 70 */ 71 GPIO1_DR |= (1<<3); 72 } 73 74 /* 75 * @description : 短时间延时函数 76 * @param - n : 要延时循环次数(空操作循环次数,模式延时) 77 * @return : 无 78 */ 79 void delay_short(volatile unsigned int n) 80 { 81 while(n--){} 82 } 83 84 /* 85 * @description : 延时函数,在 396Mhz 的主频下延时时间大约为 1ms 86 * @param - n : 要延时的 ms 数 87 * @return : 无 88 */ 89 void delay(volatile unsigned int n) 90 { 91 while(n--) 92 { 93 delay_short(0x7ff); 94 } 95 } 96 97 /* 98 * @description : main 函数 99 * @param : 无 100 * @return : 无 101 */ 102 int main(void) 103 { 104 clk_enable(); /* 使能所有的时钟 */ 105 led_init(); /* 初始化 led */ 106 107 while(1) /* 死循环 */ 108 { 109 led_off(); /* 关闭 LED */ 110 delay(500); /* 延时大约 500ms */ 111 112 led_on(); /* 打开 LED */ 113 delay(500); /* 延时大约 500ms */ 114 } 115 116 return 0; 117 }
clk_enable 函数是使能
CCGR0~CCGR6 所控制的所有外设时钟。 led_init 函数是初始化 LED 灯所使用的 IO ,包括设置
IO 的复用功能、 IO 的属性配置和 GPIO 功能,最终控制 GPIO 输出低电平来打开 LED 灯。
led_on 和 led_off 这两个函数看名字就知道,用来控制 LED 灯的亮灭的。 delay_short() 和 delay()
这两个函数是延时函数, delay_short() 函数是靠空循环来实现延时的, delay() 是对 delay_short()
的 简 单 封 装 ,在 I.MX6U 工作 在 396MHz(Boot ROM 设 置的 396MHz) 的 主 频 的 时候
delay_short(0x7ff)基本能够实现大约 1ms 的延时,所以 delay()函数我们可以用来完成 ms 延时。
main 函数就是我们的主函数了,在 main 函数中先调用函数 clk_enable() 和 led_init() 来完成时钟
使能和 LED 初始化,最终在 while(1) 循环中实现 LED 循环亮灭,亮灭时间大约是 500ms 。
编写 Makefile
第 1 行定义了一个变量 objs , objs 包含着要生成 ledc.bin 所需的材料: start.o 和 main.o ,也
就是当前工程下的 start.s 和 main.c 这两个文件编译后的 .o 文件。这里要注意 start.o 一定要放到 最前面!因为在后面链接的时候 start.o 要在最前面,因为 start.o 是最先要执行的文件!
第 3 行就是默认目标,目的是生成最终的可执行文件 ledc.bin , ledc.bin 依赖 start.o 和 main.o
如果当前工程没有 start.o 和 main.o 的时候就会找到相应的规则去生成 start.o 和 main.o 。比如
start.o 是 start.s 文件编译生成的,因此会执行第 8 行的规则。
第 4 行是使用 arm-linux-gnueabihf-ld 进行链接,链接起始地址是 0X87800000 ,但是这一行
用到了自动变量" $^ "," $^ "的意思是所有依赖文件的集合,在这里就是 objs 这个变量的值:
start.o 和 main.o 。链接的时候 start.o 要链接到最前面,因为第一行代码就是 start.o 里面的,因
此这一行就相当于:
第 5 行使用 arm-linux-gnueabihf-objcopy 来将 ledc.elf 文件转为 ledc.bin ,本行也用到了自动变量
" $@ "," @ "的意思是目标集合,在这里就是"ledc.bin",那么本行就相当于: ![](https://file.jishuzhan.net/article/1759521646500122625/ccbd4ac1c63c28084e27ec1a411992cb.webp) 第 6 行使用 arm-linux-gnueabihf-objdump 来反汇编,生成 ledc.dis 文件。 第 8\~15 行就是针对不同的文件类型将其编译成对应的 .o 文件,其实就是汇编 .s(.S) 和 .c 文 件,比如 start.s 就会使用第 8 行的规则来生成对应的 start.o 文件。第 9 行就是具体的命令,这 行也用到了自动变量"@ "和" $< ",其中" $< "的意思是依赖目标集合的第一个文件。比如 start.s 要编译成 start.o 的话第 8 行和第 9 行就相当于:
第 17 行就是工程清理规则,通过命令" make clean "就可以清理工程。
Makefile 文件就讲到这里,我们可以将整个工程拿到 Ubuntu 下去编译,编译完成以后可以使用 软件 imxdownload 将其下载到 SD 卡中,命令如下:
链接脚本
在上面的 Makefile 中我们链接代码的时候使用如下语句:
上面语句中我们是通过" -Ttext "来指定链接地址是 0X87800000 的,这样的话所有的文件
都会链接到以 0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区 域,或者叫做段里面,比如在 Linux 里面初始化函数就会放到 init 段里面。因此我们需要能够 自定义一些段,这些段的起始地址我们可以自由指定,同样的我们也可以指定一个文件或者函 数应该存放到哪个段里面去。要完成这个功能我们就需要使用到链接脚本,看名字就知道链接 脚本主要用于链接的,用于描述文件应该如何被链接在一起形成最终的可执行文件。其主要目 的是描述输入文件中的段如何被映射到输出文件中,并且控制输出文件中的内存排布。比如我 们编译生成的文件一般都包含 text 段、 data 段等等。
TMD,太难了,哪来的missing seperator艹艹
链接脚本的语法很简单,就是编写一系列的命令,这些命令组成了链接脚本,每个命令是
一个带有参数的关键字或者一个对符号的赋值,可以使用分号分隔命令。
最简单的链接脚本可以只包含一个命令" SECTIONS "
,
我们可以在这一个" SECTIONS "里面来描述输出文件的内存布局。
我们一般编译出来的代码 都包含在 text 、 data 、 bss 和 rodata 这四个段内,假设现在的代码要被链接到 0X10000000 这个 地址,数据要被链接到 0X30000000 这个地方,下面就是完成此功能的最简单的链接脚本:
第 1 行我们先写了一个关键字" SECTIONS ",后面跟了一个大括号,这个大括号和第 7 行
的大括号是一对,这是必须的。看起来就跟 C 语言里面的函数一样。
第 2 行对一个特殊符号" . "进行赋值," . "在链接脚本里面叫做定位计数器,默认的定位
计数器为 0 。我们要求代码链接到以 0X10000000 为起始地址的地方,因此这一行给" ."赋值
0X10000000 ,表示以 0X10000000 开始,后面的文件或者段都会以 0X10000000 为起始地址开 始链接。
第 3 行的" .text "是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链
接到" .text "这个段里面的所有文件," *(.text) "中的" * "是通配符,表示所有输入文件的 .text
段都放到" .text "中。
第 4 行,我们的要求是数据放到 0X30000000 开始的地方,所以我们需要重新设置定位计
数器" . ",将其改为 0X30000000 。如果不重新设置的话会怎么样?假设" .text "段大小为 0X10000 , 那么接下来的.data 段开始地址就是 0X10000000+0X10000=0X10010000 ,这明显不符合我们的 要求。所以我们必须调整定位计数器为 0X30000000 。
第 5 行跟第 3 行一样,定义了一个名为" .data "的段,然后所有文件的" .data "段都放到
这里面。但是这一行多了一个" ALIGN(4) ",这是什么意思呢?这是用来对" .data "这个段的起
始地址做字节对齐的, ALIGN(4) 表示 4 字节对齐。也就是说段" .data "的起始地址要能被 4 整
除,一般常见的都是 ALIGN(4) 或者 ALIGN(8) ,也就是 4 字节或者 8 字节对齐。
第 6 行定义了一个" .bss "段,所有文件中的" .bss "数据都会被放到这个里面," .bss "数
据就是那些定义了但是没有被初始化的变量。
上面就是链接脚本最基本的语法格式,我们接下来就按照这个基本的语法格式来编写我们本试 验的链接脚本,我们本试验的链接脚本要求如下:
- ①、链接起始地址为 0X87800000 。
②、 start.o 要被链接到最开始的地方,因为 start.o 里面包含这第一个要执行的命令。
根据要求,在 Makefile 同目录下新建一个名为" imx6ul.lds "的文件,然后在此文件里面输
入如下所示代码:
第 2 行设置定位计数器为
0X87800000 ,因为我们的链接地址就是 0X87800000 。第 5 行设置链接到开始位置的文件为 start.o ,
因为 start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。第 6 行是 main.o
这个文件,其实可以不用写出来,因为 main.o 的位置就无所谓了,可以由编译器自行决定链接 位置。
在第 11 、 13 行有" __bss_start "和" __bss_end "这两个东西?这个是什么呢?" __bss_start " 和"__bss_end "是符号,
第 11 、 13 这两行其实就是对这两个符号进行赋值,其值为定位符" . ",
这两个符号用来保存 .bss 段的起始地址和结束地址。前面说了 .bss 段是定义了但是没有被初始化的变量,我们需要手动对.bss 段的变量清零的,因此我们需要知道.bss 段的起始和结束地址,这样我们直接对这段内存赋 0 即可完成清零。通过第 11、13 行代码,.bss 段的起始地址和结束地址就保存在了"__bss_start"和"__bss_end"中,我们就可以直接在汇编或者 C 文件里面使用这两个符号。
修改 Makefile
我们已经编写好了链接脚本文件: imx6ul.lds ,我们肯定是要使用这个链接脚
本文件的,将 Makefile 中的如下一行代码: 改为:
其实就是将 -T 后面的 0X87800000 改为 imx6ul.lds ,表示使用 imx6ul.lds 这个链接脚本文 件。修改完成以后使用新的 Makefile 和链接脚本文件重新编译工程,编译成功以后就可以烧写 到 SD 卡中验证了。
下载验证
使用软件 imxdownload 将编译出来的 ledc.bin 烧写到 SD 卡中,命令如下:
烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板,如果代码运行正常的 话 LED0 就会以 500ms 的时间间隔亮灭。