i.MX6ULL终结者程序编写

本实验对应的例程在光盘资料的:i.MX6UL终结者光盘资料\04_裸机例程源码\2_led_C program目录下,我们在Ubuntu系统建立“1_Led_C program”文件夹,然后在“Led_C program”文件夹下建立文件:start.S、main.c、main.h。其中start.S是汇编文件,main.c和main.h是C语言文件。

我们在前面新建的“strart.S”文件中输入下面的代码:

1	.global _start  		/* 全局标号 */

2	/*
3	 * 描述:	_start函数,程序从此函数开始执行,此函数主要功能是设置C
4	 *		 运行环境。
5	 */
6	_start:

7		/* 进入SVC模式 */
8		mrs r0, cpsr
9		bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
10		orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/
11		msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/

12		ldr sp, =0X80200000	/* 设置栈指针			 */
13		b main				/* 跳转到main函数 		 */

第1行定义了一个全局标号_start
第6行是程序的入口、
第8行到第11行是设置处理器进入SVC模式
第12行通过ldr指令设置SVC模式下的sp指针(0x80200000),i.MX6 ULL终结者开发板上的内存地址范围是0X800000000XA0000000(512MB)或0X800000000X90000000(256MB),所以不论是512MB版本还是256MB版本的,其内存起始地址都是0X80000000,由于i.MX6ULL的堆栈是向下增长的,所以SP指针设置成0X80200000(0X80200000-0X80000000,是2MB的栈空间,足够我们使用了)。
第13行是跳转到main函数(C语言的入口函数)。

至此汇编部分的程序我们就完成了,主要用来设置处理器在SVC模式下运行,然后初始化SP指针,最后跳转到C程序的main入口函数。如果大家有接触过三星的S3C2440,S3c6410或者S5PV210的处理器,我们在使用内存之前必须先初始化CPU的内存控制器,所以在他们的汇编文件中一定有内存控制器的初始化代码(比如Uboot的汇编中)。大家可能会发现我们上面编写的start.S文件中并没有发现初始化内存控制器的代码,但是却将SVC模式下的SP指针设置到了内存的地址范围里面,这样不是有问题吗?大家还记得在第六章我们在编译生成“led.bin”文件以后,通过create_imx工具在“led.bin”文件添加了一些数据包头吗,也就是DCD数据,在第六章我们已经讲过DCD数据里面包含了内存控制器的参数配置了,i.MX6ULL内存固化的Boot ROM程序会读取DCD数据中的内存控制器参数,bin完成内存控制器的初始化配置。

接下来我们开始实现C语言部分,首先我们打开前面建立的“main.h”文件,然后输入下面的代码:

1	#ifndef __MAIN_H
2	#define __MAIN_H

3	/* 
4	 * CCM相关寄存器地址 
5	 */
6	#define CCM_CCGR0                       *((volatile unsigned int *)0X020C4068)
7	#define CCM_CCGR1                       *((volatile unsigned int *)0X020C406C)
8	#define CCM_CCGR2                       *((volatile unsigned int *)0X020C4070)
9	#define CCM_CCGR3                       *((volatile unsigned int *)0X020C4074)
10	#define CCM_CCGR4                       *((volatile unsigned int *)0X020C4078)
11	#define CCM_CCGR5                       *((volatile unsigned int *)0X020C407C)
12	#define CCM_CCGR6                       *((volatile unsigned int *)0X020C4080)

13	/* 
14	 * IOMUX相关寄存器地址 
15	 */
16	#define SW_MUX_GPIO1_IO03       *((volatile unsigned int *)0X020E0068)
17	#define SW_PAD_GPIO1_IO03       *((volatile unsigned int *)0X020E02F4)

18	/* 
19	 * GPIO1相关寄存器地址 
20	 */
21	#define GPIO1_DR                        *((volatile unsigned int *)0X0209C000)
22	#define GPIO1_GDIR                      *((volatile unsigned int *)0X0209C004)
23	#define GPIO1_PSR                       *((volatile unsigned int *)0X0209C008)
24	#define GPIO1_ICR1                      *((volatile unsigned int *)0X0209C00C)
25	#define GPIO1_ICR2                      *((volatile unsigned int *)0X0209C010)
26	#define GPIO1_IMR                       *((volatile unsigned int *)0X0209C014)
27	#define GPIO1_ISR                       *((volatile unsigned int *)0X0209C018)
28	#define GPIO1_EDGE_SEL          		  *((volatile unsigned int *)0X0209C01C)

29	#endif

在main.h中,我们通过宏的方式定义了要使用到的所有寄存器名称,后面的十六进制数就是寄存器对应的地址,比如GPIO1_DR寄存器的地址是0X0209C000。然后保存并退出“main.h”。

接下来我们打开“main.c”文件,在里面输入下面的代码:

1	#include "main.h"

2	/*
3	 * @description	: 使能I.MX6U所有外设时钟
4	 * @param 		: 无
5	 * @return 		: 无
6	 */
7	void clk_enable(void)
8	{
    
    
9		CCM_CCGR0 = 0xffffffff;
10		CCM_CCGR1 = 0xffffffff;
11		CCM_CCGR2 = 0xffffffff;
12		CCM_CCGR3 = 0xffffffff;
13		CCM_CCGR4 = 0xffffffff;
14		CCM_CCGR5 = 0xffffffff;
15		CCM_CCGR6 = 0xffffffff;
16	}

17	/*
18	 * @description	: 初始化LED对应的GPIO
19	 * @param 		: 无
20	 * @return 		: 无
21	 */
22	void led_init(void)
23	{
    
    
24		/* 1、初始化IO复用 */
25	SW_MUX_GPIO1_IO03 = 0x5;	/* 复用为GPIO1_IO03 */

26		/* 2、、配置GPIO1_IO03的IO属性	
27		 *bit 16:0 HYS关闭
28		 *bit [15:14]: 00 默认下拉
29	     *bit [13]: 0 kepper功能
30  	   *bit [12]: 1 pull/keeper使能
31	     *bit [11]: 0 关闭开路输出
32	     *bit [7:6]: 10 速度100Mhz
33	     *bit [5:3]: 110 R0/6驱动能力
34	     *bit [0]: 0 低转换率
35	     */
36		SW_PAD_GPIO1_IO03 = 0X10B0;		

37		/* 3、初始化GPIO */
38		GPIO1_GDIR = 0X0000008;	/* GPIO1_IO03设置为输出 */

39		/* 4、设置GPIO1_IO03输出低电平,打开LED0 */
40		GPIO1_DR = 0X0;
41	}

42	/*
43	 * @description	: 打开LED灯
44	 * @param 		: 无
45	 * @return 		: 无
46	 */
47	void led_on(void)
48	{
    
    
49		/* 
50		 * 将GPIO1_DR的bit3清零	 
51		 */
52		GPIO1_DR &= ~(1<<3); 
53	}

54	/*
55	 * @description	: 关闭LED灯
56	 * @param 		: 无
57	 * @return 		: 无
58	 */
59	void led_off(void)
60	{
    
    
61	/*    
62		 * 将GPIO1_DR的bit3置1
63		 */
64		GPIO1_DR |= (1<<3);
65	}

66	/*
67	 * @description	: 短时间延时函数
68	 * @param - n	: 要延时循环次数(空操作循环次数,模式延时)
69	 * @return 		: 无
70	 */
71	void delay(volatile unsigned int n)
72	{
    
    
73		while(n--){
    
    }
74	}

75	/*
76	 * @description	: 延时函数,在396Mhz的主频下
77	 * 			  	  延时时间大约为1ms
78	 * @param - n	: 要延时的ms数
79	 * @return 		: 无
80	 */
81	void mdelay(volatile unsigned int n)
82	{
    
    
83		while(n--)
84		{
    
    
85			delay(0x7ff);
86		}
87	}

88	/*
89	 * @description	: mian函数
90	 * @param 	    : 无
91	 * @return 		: 无
92	 */
93	int main(void)
94	{
    
    
95		clk_enable();		/* 使能所有的时钟		 	*/
96		led_init();			/* 初始化led 				*/

97		while(1)			/* 死循环 					*/
98		{
    
    	
99			led_off();		/* 关闭LED   				*/
100			mdelay(300);	/* 延时大约500ms 		*/

101			led_on();		/* 打开LED		 			*/
102			mdelay(300);	/* 延时大约500ms 			*/
103		}

104		return 0;
105	}

main.c文件里面一共有7个函数,下面一个个的看下每个函数的具体功能:

  1. void clk_enable(void)函数使能CCM_CCGR0-CCM_CCGR6的时钟(使能所有外设时钟)。

  2. void led_init(void)函数初始化LED对应的IO(复用成GPIO,默认下拉,输出低电平)。

  3. void led_on(void)函数点亮LED(对应GPIO输出低电平)。

  4. void led_off(void)函数关闭LED(对应GPIO输出高电平)。

  5. void delay_short(volatile unsigned int n)延时函数。

  6. void mdelay(volatile unsigned int n)毫秒级延时函数。

  7. int main(void),C程序主函数。先调用clk_enable()函数完成外设时钟的使能,然后调用led_init()函数完成LED对应的IO的初始化,最后进入while(1)死循环,实现LED每隔300毫秒亮灭的状态切换。

然后我们新建Makfile文件,在里面输入下面的内容:

1	objs := start.o main.o

2	led.bin:$(objs)
3		arm-linux-gnueabihf-ld -Ttext 0X87800000 -o led.elf $^
4		arm-linux-gnueabihf-objcopy -O binary -S led.elf $@

5	%.o:%.s
6		arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

7	%.o:%.S
8		arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

9	%.o:%.c
10		arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

11	clean:
12	rm -rf *.o led.bin led.elf

在本Makefile文件中我们使用到了变量和自动变量,关于Makefile的变量和自动变量可以参考《跟我一起写Makefile.pdf》文档(光盘资料的:i.MX6UL终结者光盘资料\10_其它参考资料 目录下)
第1行我们定义了变量objs,该变量包含要生成的led.bin文件所需要的start.o和main.o,也就是我们工程的start.S和main.c编译生成的“.o”文件。需要我们注意的是start.o一定要放到最前面,因为后面链接的时候start.o要在最前面,这样才能保证start.o在链接完成以后,运行的时候最先执行。
第2行就是默认最终生成的可执行文件“led.bin”,“led.bin”文件依赖于“start.o”和“main.o”两个文件,如果当前工程目录下没有这两个文件,Make命令就会在Makefile中找到相应的规则生成“start.o”和“main.o”。比如“start.s”文件根据第5行的规则生成,“start.S”根据规则中的第7行生成,“main.o”根据规则中的第9行生成。
第3行使用arm-linux-gnueabihf-ld进行连接,连接的起始地址是0x87800000,这一行用到了自动变量“$^”,它是指所有依赖文件的集合,也就是变量“objs”,即“start.o”和“main.o”,连接的时候汇编的start.o要放在最前面,因为程序首先从汇编的“_start”标号处开始执行,因此这一行展开就是:
arm-linux-gnueabihf-ld -Ttext 0X87800000 -o led.elf start.o main.o
第4行使用arm-linux-gnueabihf-objcopy -O binary -S ledc.elf @ 把 “ l e d . e l f ” 文 件 转 换 成 “ l e d . b i n ” 文 件 , 这 里 也 用 到 了 自 动 变 量 “ @把“led.elf”文件转换成“led.bin”文件,这里也用到了自动变量“ @led.elfled.bin@”,它的意思是目标集合,也就是“led.elf”,那么本行展开相当于:
arm-linux-gnueabihf-objcopy -O binary -S led.elf led.bin

第5行到第10行是针对不同后缀名的文件,将其编译成对应的“.o”文件。比如start.s就会使用第5行的规则生成对应的“start.o”文件,第6行就是具体执行的命令,这里用到了自动变量“ @ ” 和 “ @”和“ @<”,“$<”的意思是依赖目标集合的第一个文件,比如“start.s”编译成“start.o”,第5行和第6行的代码相当于:

start.o:start.s                                                                           
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o start.o start.s                               

第11行是清理规则,通过“make clean”可以删除编译生成的中间文件以及目标文件。
Makefile文件我们就分析到这里。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46635880/article/details/108578059