I.MX6U C语言运行环境构建及驱动开发格式

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

cpp 复制代码
1 #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 的时间间隔亮灭。
相关推荐
hopetomorrow3 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull13 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i22 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落25 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜34 分钟前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming198738 分钟前
STL关联式容器之set
开发语言·c++
带多刺的玫瑰42 分钟前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
GIS 数据栈1 小时前
每日一书 《基于ArcGIS的Python编程秘笈》
开发语言·python·arcgis
Mr.131 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++