目录
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段结束地址。