一,启动文件
cpp
.global _start
_start:
@设置处理器进入SVC模式
mrs r0, cpsr @读取cpsr到r0
bic r0, r0, #0x1f @清除cpsr的bit4-0
orr r0, r0, #0x13 @使用svc模式
msr cpsr, r0 @将r0写入到cpsr
ldr sp, =0x80200000 @设置sp指针起始地址,此处已初始化ddr,若开发板未初始化ddr,则要手动初始化
b main @跳转到C语言main函数
汇编位运算
设置CPSR寄存器,看M[4:0] 设置为1001100XB ,时6ULL处于SVC模式,读写状态寄存器需要用MRS(特殊寄存器读写到普通寄存器中)MSR(普通寄存器读写到特殊寄存器中)指令
设置SP来控制出栈和入栈,可以指向内部RAM,也可以DDR,这里指向DDR,设置栈大小为0x200000=2MB,栈是向上增长。
二,main文件
cpp
main.c
#include "main.h"
/* 使能外设时钟 */
void clk_enable(void)
{
CCM_CCGR1 = 0XFFFFFFFF;
CCM_CCGR2 = 0XFFFFFFFF;
CCM_CCGR3 = 0XFFFFFFFF;
CCM_CCGR4 = 0XFFFFFFFF;
CCM_CCGR5 = 0XFFFFFFFF;
CCM_CCGR6 = 0XFFFFFFFF;
}
/* 初始化LED */
void led_init(void)
{
SW_MUX_GPIO1_IO03 = 0X5; /* 复用为GPIO1-IO03 */
SW_PAD_GPIO1_IO03 = 0X10B0; /* 设置GPIO01_IO03电气属性*/
/* GPIO初始化 */
GPIO1_GDIR = 0X8; /* 设置未输出 */
GPIO1_DR = 0X0; /* 打开LED灯 */
}
/* 短延时一个循环1ms */
void delay_short(volatile unsigned int n)
{
while(n--){}
}
/* 延时 */
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7ff);
}
}
/* 打开led灯 */
void led_on(void)
{
GPIO1_DR &= ~(1<<3); /* bit3清零 */
}
/* 关闭led灯 */
void led_off(void)
{
GPIO1_DR |= (1<<3); /* bit3置1 */
}
int main(void)
{
/* 初始化时钟 */
clk_enable();
/* 初始化LED */
led_init();
/* 设置LED闪烁 */
while(1){
led_on();
delay(500);
led_off();
delay(500);
};
return 0;
}
main.h
#ifndef _MAIN_H
#define _MAIN_H
/*定义要使用的寄存器,根据操作手册看寄存器地址进行配置*/
#define CCM_CCGR0 *((volatile unsigned int *)0x020C4068) //volatile是防止被更改,此处对宏读写操作就是直接读写该地址寄存器
#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
三,链接脚本文件
cpp
SECTIONS{
. = 0x87800000; /* 链接起始地址 */
.text : /* 程序段存放所有程序 */
{
start.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)} /* rodata段存放所有rodata */
.data ALIGN(4) : {*(.data*)} /* data段存放所有data */
_bss_start=.; /* .bss的起始地址 */
.bss ALIGN(4) : {*(.bss) *(COMMON)} /* 存放.bss数据,定义但未初始化的变量 */
_bss_end=.; /* .bss的终止地址 */
}
/* 这段代码是一个连接脚本文件的片段,将文件分为不同的段,用于描述将各个输入目标文件(object file)的不同部分如.text、.rodata、.data和.bss等合并到最终的可执行文件中的内存布局。 */
四,makefile文件
cpp
objs = main.0 start.o
ledc.bin : $(objs)
arm-linux-gnueabihf-ld -Timx6ull.lds $^ -o ledc.elf
#arm-linux-gnueabihf-ld -Ttext 0X87800000 $^ -o ledc.elf
#这条命令的意思是:使用 arm-linux-gnueabihf-ld 链接器将所有依赖的目标文件($^ 展开后的目标文件列表)链接成一个名为 ledc.elf 的 ELF 文件,并且指定程序的文本段从内存地址 0x87800000 开始。
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 -O2 -o $@ $<
#Wall表示显示编译时所有警告 -nostdlib表示不链接系统标准启动文件和库文件 -O2表示优化等级
%.o : %.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
#-c:这个选项告诉编译器只执行编译步骤,不进行链接。编译器将源代码编译成目标文件(通常是 .o 文件)。
#-o $@:-o 是一个链接器选项,用于指定输出文件的名称。$@ 是一个 Makefile 特殊变量,代表当前规则的 target 名称。在这个上下文中,它将被替换为当前规则的目标文件名,例如 start.o。
#$<:这是另一个 Makefile 特殊变量,代表当前规则依赖的第一个文件。在这个上下文中,它通常指向源代码文件,例如 start.s。
#这串指令的意思是:使用 arm-linux-gnueabihf-gcc 编译器,打开所有警告,不链接标准库,只编译不链接,输出文件名为当前规则的目标文件名,源代码文件为当前规则的第一个依赖文件。这通常用于编译单个源文件到目标文件的过程。