Linux学习笔记---裸机点灯

        实验板使用的是 Cortex-A7 架构,完成LED闪烁功能,初步体验Linux开发。

一、创建启动文件

        单片机开发时,每个工程会有一个.s启动文件,Linux裸机开发同样需要,.s文件使用汇报语言,一般用于设置堆栈、工作模式、程序跳转等工作。

1.1 、Cortex-A7 处理器模式

 Cortex-A7 处理器有 9 种处理模式

Linux学习笔记---裸机点灯_第1张图片

 程序状态寄存器(CPSR):

其中M[4:0]用于选择处理器模式:Linux学习笔记---裸机点灯_第2张图片

 1.2、设置SP指针

        sp指针可以指向内部RAM,也可以指向DDR。这里我们指向512MB的DDR存储器,地址范围是0x80000000~0x9FFFFFFF。

        A7处理器栈的增长方式是向下增长,若设置栈大小为0x200000=2MB,其实地址为0x80000000,那么sp指针应设置为0x80200000。

1.3 、程序跳转

        启动文件负责跳转到C语言的main函数。

1.4、启动文件



.global _start  		/* 全局标号 */

/* * 描述:	_start函数,程序从此函数开始执行,此函数主要功能是设置C运行环境。 */
_start:
	/* 进入SVC模式 */
	mrs r0, cpsr	    /* 将CPSR的值读到R0寄存器中 	*/
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/

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

  1. 上面代码中.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局变量
  2.  MRS 指令用于将特殊寄存器(如 CPSR SPSR)中的数据传递给通用寄存器,要读取特殊
    寄存器的数据只能使用 MRS 指令!
  3. MSR 指令和 MRS 刚好相反,MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就
    是写特殊寄存器,写特殊寄存器只能使用 MSR。
  4. LDR 主要用于从存储加载数据到寄存器 Rx 中
  5. B 指令会将 PC 寄存器的值设置为跳转目标地址

二、创建C语言main文件

创建C文件


#include "main.h"

/* * @description	: 使能I.MX6U所有外设时钟
 * @param 		: 无
 * @return 		: 无 */
void clk_enable(void)
{
	CCM_CCGR0 = 0xffffffff;
	CCM_CCGR1 = 0xffffffff;
	CCM_CCGR2 = 0xffffffff;
	CCM_CCGR3 = 0xffffffff;
	CCM_CCGR4 = 0xffffffff;
	CCM_CCGR5 = 0xffffffff;
	CCM_CCGR6 = 0xffffffff;
}

/* * @description	: 初始化LED对应的GPIO
 * @param 		: 无
 * @return 		: 无 */
void led_init(void)
{
	/* 1、初始化IO复用 */
	SW_MUX_GPIO1_IO03 = 0x5;	/* 复用为GPIO1_IO03 */

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

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

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

/* * @description	: 打开LED灯
 * @param 		: 无
 * @return 		: 无 */
void led_on(void)
{
	/* 
	 * 将GPIO1_DR的bit3清零	 
	 */
	GPIO1_DR &= ~(1<<3); 
}

/* * @description	: 关闭LED灯
 * @param 		: 无
 * @return 		: 无 */
void led_off(void)
{
	/*    
	 * 将GPIO1_DR的bit3置1
	 */
	GPIO1_DR |= (1<<3);
}

/* * @description	: 短时间延时函数
 * @param - n	: 要延时循环次数(空操作循环次数,模式延时)
 * @return 		: 无 */
void delay_short(volatile unsigned int n)
{
	while(n--){}
}

/* * @description	: 延时函数,在396Mhz的主频下
 * 			  	  延时时间大约为1ms
 * @param - n	: 要延时的ms数
 * @return 		: 无 */
void delay(volatile unsigned int n)
{
	while(n--)
	{
		delay_short(0x7ff);
	}
}


/* * @description	: mian函数
 * @param 	    : 无
 * @return 		: 无 */
int main(void)
{
	clk_enable();		/* 使能所有的时钟		 	*/
	led_init();			/* 初始化led 			*/

	while(1)			/* 死循环 				*/
	{	
		led_off();		/* 关闭LED   			*/
		delay(500);		/* 延时大约500ms 		*/

		led_on();		/* 打开LED		 	*/
		delay(500);		/* 延时大约500ms 		*/
	}

	return 0;
}

 创建头文件:

#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

 三、编译、下载

3.1、编译的步骤

  1. 使用命令arm-linux-gnueabihf-gcc -g -c led.s -o led.o,将.s文件编译成.o文件
  2. 使用命令arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf 连接.O文件到指定地址,生成elf文件
  3. 使用命令arm-linux-gnueabihf-objcopy -O binary -g -S led.elf led.bin 将elf文件转换成bin文件
  4. 使用命令arm-linux-gnueabihf-objdump -D led.elf >led.dis,将elf文件反汇编成dis文件

Linux学习笔记---裸机点灯_第3张图片

3.2、make工具、makefile文件 

Make工具:

         利用make可以自动完成编译工作

  1. 如果修改了某个文件,则值需要重新编译这个文件
  2. 如果某个头文件被修改,则重新编译所有包含该头文件的源文件

        利用这种自动编译可以简化开发工作,避免不必要的重新编译。Make工具通过一个makefile文件来完成并自动维护编译工作,makefile文件描述了整个工程的编译、连接规则。

四、MakeFile详解

4.1、 Makefile  规则格式

目标…... : 依赖文件集合……
命令 1
命令 2
…
第1条规则:
1 main: main.o input.o calcu.o
2     gcc -o main main.o input.o calcu.o

第2条规则:
3 main.o: main.c
4     gcc -c main.c

第3条规则:
5 input.o: input.c
6     gcc -c input.c

第4条规则:
7 calcu.o: calcu.c
8     gcc -c calcu.c


第5条规则:
10 clean:
11     rm *.o
12     rm main
 
  1. 第1条规则依赖于文件main.o、input.o 和 calcu.o这个三个.o 文件,这三个.o 文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行
  2. 第2条规则生成main.o,依赖于编译main.c。
  3. 第3条规则生成input.o,依赖于编译input.c。
  4. 第4条规则生成calcu.o,依赖于编译calcu.c。
  5. 第4条规则是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o 文件以及 main,因此clean 的功能就是完成工程的清理,“make clean”的执行过程如

4.2、 Makefile  的变量

跟 C 语言一样 Makefile 也支持变量的,先看一下前面的例子

1 #Makefile 变量的使用
2 objects = main.o input.o calcu.o
3 main: $(objects)
4     gcc -o main $(objects)
 
  1. 第 2 行定义了一个变量 objects,并且给这个变量进行了赋值,其值为字符串“main.o input.o calcu.o”
  2. 第3和4行使用到了变量objects,Makefile 中变量的引用方法是“$(变量名)”,

4.2.1、赋值符“= ”

        使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值。

name = zxl
curname = $(name)
name = zhongnanyin
 print:
    @echo curname: $(curname)
  1. 第 1 行定义了一个变量 name,变量值为“zxl” 
  2. 第 2 行也定义了一个变量curname,curname的变量值引用了变量name,按照我们C写语言的经验此时curname的值就是“zxl”
  3. 第 3 行将变量 name 的值改为了“zhongnanyin”

则输出结果是:zhongnanyin

借助另外一个变量,可以将变量的真实,值推到后面去定义。也就是变量的真实值取决于它所引用的变量的最后一次有效值。

4.2.2、赋值符“:= ”

name = zxl
curname := $(name)
name = zhongnanyin
 print:
    @echo curname: $(curname)

输出结果是:zxl

赋值符“:=”不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别。

4.2.3、赋值符“?= ”

        curname ?= zhongnanyin。

        上述代码的意思就是,如果变量 curname 前面没有被赋值,那么此变量就是“zhongnanyin”,
如果前面已经赋过值了,那么就使用前面赋的值。

4.2.4、变量追加“+= ”

        Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进
去,此时就要使用到符号“+=”。

objects = main.o inpiut.o
objects += calcu.o

        一开始变量 objects 的值为“main.o input.o”,后面我们给他追加了一个“calcu.o”,因此变
量 objects 变成了“main.o input.o calcu.o”,这个就是变量的追加。

4.3、Makefile模式规则

        4.1写的代码中第2、3、4条规则是将对应的.c 源文件编译为.o 文件,如果工程中 C 文件很多的话显然不能这么做。

        因此就要使用Makefile 中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。

        “%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符,a.%.c 就表示以 a.开头,以.c 结束的所有文件

objects = main.o input.o calcu.o
main: $(objects)
    gcc -o main $(objects)
%.o : %.c
     #命令
clean:
rm *.o
rm main

        命令怎么写呢?

4.4 、Makefile  自动化变量

        所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中。

        

自动化变量 描述
$@ 规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模式中定义的目标集合
$< 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%”)定义的,那么“$<”就是符合模式的一系列的文件集合
$^ 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,“$^”会去除重复的依赖文件,值保留一份
 
$+  和“$^”类似,但是当依赖文件存在重复的话不会去除重复的依赖文件。
$* 这个变量表示目标模式中"%"及其之前的部分,如果目标是 test/a.test.c,目标模
式为 a.%.c,那么“$*”就是 test/a.test。
表 3.4.4.1 自动化变量
$% 当目标是函数库的时候表示规则中的目标成员名,如果目标不是函数库文件,那么其值为空
$< 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%”)定义的,那么“$<”就是符合模式的一系列的文件集合。        
$? 所有比目标新的依赖目标集合,以空格分开。

 使用变量、模式规则、自动化变量优化4.1小结中的代码:

objects = main.o input.o calcu.o
main: $(objects)
    gcc -o main $(objects)

%.o : %.c
    gcc -c $<

clean:
rm *.o
rm main

4.5、 Makefile  伪目标

        伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。



五、创建工程的Makefile文件

使用上面介绍的makefile知识,创建本次工程的makefile文件:

objs := start.o main.o

ledc.bin:$(objs)    /*最终目标编译出ledc.bin文件,需要依赖与start.o  mian.o两个文件**/
	arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
    /*连接.O文件到指定地址,生成elf文件*/

	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
    /*将elf文件转换成bin文件*/

	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
    /*将elf文件反汇编成dis文件*/

	
%.o:%.s /*.o文件  依赖于.c文件*/
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<     /*将.s文件编译成.o文件*/
	/*-O2 是优化等级*/
%.o:%.S
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<    /*将.S文件编译成.o文件*/
	
%.o:%.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<    /*将.c文件编译成.o文件*/
	
clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis
	
	

六、链接脚本文件

        上面makefile文件中的Timx6ul.lds是一个链接脚本文件,他描述了要链接的文件、链接顺序、链接首地址。

        链接脚本文件每个命令是一个带有参数的关键字或者一个对符号的赋值,可以使用分号分隔命令。如下:

SECTIONS{                   /*关键词SECTIONS和一对大括号  表示连接脚本文件 */

. = 0X10000000;            
 /* “.”在链接脚本里面叫做定位计数器,默认值为0,对其赋值就是修改起始地址*/

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

. = 0X10000000;      /*定义数据段 起始地址*/
.data ALIGN(4) : { *(.data) }
.bss ALIGN(4) : { *(.bss) }           /** ALIGN(4)表示 4 字节对齐*/
}

下面创建本次工程使用的链接脚本文件“imx6ul.lds”

SECTIONS{
    . = 0X87800000;   /**设置定位计数器为0X87800000*/
    .text :
        {
            start.o   /*开始位置的文件为start.o,一定要链接到最开始的地方*/
            main.o
            *(.text)  /*剩余的所有text段,程序编译出来的都属于text段*/
        }
    .rodata ALIGN(4) : {*(.rodata*)}
    .data ALIGN(4)   : { *(.data) }
    __bss_start = .;  /*设置起始地址*/
    .bss ALIGN(4) : { *(.bss) *(COMMON) }   /*.bss 表示定义 但没有被初始化的变量*/
    __bss_end = .;                          /*编译后“.” 的值会变,这里获取结束地址*/
}

七、编译下载到SD卡

Linux学习笔记---裸机点灯_第4张图片

插入SD卡到ubuntu虚拟机:

Linux学习笔记---裸机点灯_第5张图片

Linux学习笔记---裸机点灯_第6张图片

你可能感兴趣的:(Linux,linux)