04_I.MX6ULL搭建C语言环境

文章详细介绍了C语言在嵌入式系统中的运行环境构建,通常由汇编代码初始化,包括设置Cortex-A处理器的SVC模式、堆栈指针和进入C语言main函数的过程。此外,还展示了汇编源码如何切换处理器模式和设置栈指针,以及C语言源码中对硬件寄存器的访问。最后,提到了Makefile的使用和链接脚本的作用,用于生成可执行文件。
摘要由CSDN通过智能技术生成

目录

C语言运行环境构建

汇编文件内容

汇编源码

C语言源码

Makefil源码

链接脚本


C语言运行环境构建

        实际工作中是很少用到汇编去写嵌入式驱动的,毕竟汇编太难,而且写出来也不好理解,大部分情况下都是使用C语言去编写的。只是在开始部分用汇编来初始化一下C语言环境,比如初始化DDR、设置堆栈指针SP等等,当这些工作都做完以后就可以进入C语言环境,也就是运行C语言代码,一般都是进入main函数。其实我们用c语言编写的程序的时候都是要用汇编去把芯片c语言的环境构建起来。

汇编文件内容

在Cortex-A处理器运行模型中有九个模式如下

 

 每一种运行模式都有一组与之对应的寄存器组。每一种模式可见的寄存器包括15个通用寄存器(R0-R14)、一两个程序状态寄存器和一个程序计数器PC。在这些寄存器中,有些是所有模式所共用的同一个物理寄存器,有一些是各模式自己所独立拥有的,各个模式所拥有的寄存器如表

 浅色字体的是与User模式所共有的寄存器,蓝绿色背景的是各个模式所独有的寄存器。可以看出,在所有的模式中,低寄存器组(R0-R7)是共享同一组物理寄存器的,只是一些高寄存器组在不同的模式有自己独有的寄存器,比如FIQ模式下R8-R14是独立的物理寄存器。假如某个程序在FIQ模式下访问R13寄存器,那它实际访问的是寄存器R13_fiq,如果程序处于SVC模式下访问R13寄存器,那它实际访问的是寄存R13_svc。

汇编源码

.global _start /* 全局标号 */
_start: 
/* 进入 SVC 模式 */ 
mrs r0, cpsr 
bic r0, r0, #0x1f /*就是先把1f取反在与将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行定义了一个全局标号_start,可以理解为c语言的全局变量一样。

第2行就是标号_start开始的地方,相当于是一个start函数,这个start就是第一行代码。

第3~6行就是设置处理器进入SVC模式,这里设置处理器运行SVC模式下。处理器模式的

设置是通过修改CPSR(程序状态)寄存器来完成的,CPSR寄存器,其中M[4:0](CPSR的bit[4:0])就是设置处理器运行模式的,如果要将处理器设置为SVC模式,那么M[4:0]就要等于0x13。3~6行代码就是先使用指令MRS将CPSR寄存器的值读取到R0,然后修改R0中的值,设置R0的bit[4:0]为0x13,然后再使用指令MSR将修改后的R0重新写入到CPSR中。

第7行通过ldr指令设置SVC模式下的SP指针=0X80200000,因为I.MX6U的DDR3地址范围是0x80000000开始的就是DDR3起始地址DDR3。由于Cortex-A7的堆栈是向下增长的,所以将SP指针设置为0X80200000,因此SVC模式的栈0x80200000-0x80000000-0x200000-2MB

第8行就是跳转到main函数,main函数就是C语言代码了。至此汇编部分程序执行完成,就几行代码,用来设置处理器运行到SVC模式下、然后初始化SP指针、最终跳转到C文件的main函数中。

C语言源码

#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 
#include "main.h"

/*
 * @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);
	}
}
int main(void)
{
    /*时能所有外设时钟*/
    CCM_CCGR0 = 0xFFFFFFFF;
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;

    /*初始化LED*/
    
    /*复用为GPIO01__IO03*/
    SW_MUX_GPIO1_IO03 = 0x05;
    /*设置电气属性*/
    SW_PAD_GPIO1_IO03 = 0X10b0;
    /*设置GPIO为输出模式*/
    GPIO1_GDIR = 0x08;
    /*默认为低电平LED亮*/
    GPIO1_DR = 0x00;
   

    while (1)
    {
     /*设置LED闪烁*/     
        delay(100);
        led_off();
        delay(100);
        led_on();
    }

    return 0;
    
}

Makefil源码

objs := start.o main.o 

ledc.bin:$(objs)
	arm-linux-gnueabihf-ld -Timx6u.lds  -o ledc.elf $^
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis


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

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

clear:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

第1行定义了一个变量objs, objs 包含着要生成ledc.bin所需的材料:start.o和main.o,也就是当前工程下的start.s和main.c这两个文件编译后的.o文件。这里要注意start.o一定要放到最前面!因为在后面链接的时候start.o要在最前面,因为start.o是最先要执行的文件

2行就是默认目标,目的是生成最终的可执行文件ledc.bin, ledc.bin依赖start.o和main.o如果当前工程没有start.o和main.o的时候就会找到相应的规则去生成start.o和main.o。比如start.o是start.s文件编译生成的,因此会执行第8行或第10行的规则。

3行是使用arm-linux-gnueabihf-ld进行链接,链接起始地址是0x87800000,但是这一行用到了自动变量"$^”, "$^”的意思是所有依赖文件的集合,在这里就是objs这个变量的值:start.o和main.o。链接的时候start.o要链接到最前面,因为第一行代码就是start.o里面的,因此这一行就相当于:

arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf start.o main.o

4行使用arm-linux-gnueabihf-objcopy来将ledc.elf文件转为ledc.bin,本行也用到了自动变量"$@”, "$@”的意思是目标集合,在这里就是"ledc.bin",那么本行就相当于:

arm-linux-gnueabihf-objcopy -Ö binary -S ledc.elf ledc.bin

5行使用arm-linux-gnueabihf-objdump来反汇编,生成ledc.dis文件。

6~11行就是针对不同的文件类型将其编译成对应的.o文件,其实就是汇编.s(.S)和.c 文件,比如start.s就会使用第8行或第10行的规则来生成对应的start.o文件。第9行就是具体的命令,这行也用到了自动变量“$@”和“$<”,其中“$<”的意思是依赖目标集合的第一个文件。比如start.s 要编译成 start.o 的话第 8 行和第 9 行就相当于:

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

第12行就是清除所有.o文件和ledc.bin、ledc.elf、ledc.dis

链接脚本

在Makefil源码的第3行用了一个链接脚本来链接地址,其中imx6u.lds就是一个.lds的链接脚本文件。

arm-linux-gnueabihf-ld -Timx6u.lds  -o ledc.elf $^

SECTIONS{
    . = 0X87800000;
    .text :
    {
        start.o
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata)}
    .data ALIGN(4) : {*(.data)}
    __bss_start=.;
    .bss ALIGN(4) : {*(.bss) *(COMMON)}
    __bss_end=.;
}

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

第2行对一个特殊符号“.”进行赋值“.”在链接脚本里面叫做定位计数器,默认的定位计数器为0。设置定位计数器为0X87800000,因为我们的链接地址就是0X87800000开始就是用户编写的代码地址也就是这里的汇编文件。

第3行的“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链接到“.text”这个段里面的所有文件, "*(.text)"中的“*”是通配符,表示所有输入文件的.text段都放到".text”中,设置链接到开始位置的文件为start.o,因为start.o里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。

4行,只读数据放在紧接代码段(tex段)后面de地方,ALIGN(4)4字节对齐。

第5行放可读可写数据。

第6行记录__bss段存储开始地址_bss段就是存储了一些定义了但是没有被使用的变量。

第7行__bss存放紧跟在可读可写数据后面。

第8行记录__bss段结束地址。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值