异常向量表:
.global _start ; 声明_start为全局符号,作为程序的入口点,链接器会从这里开始执行
; 异常向量表(Exception Vector Table)
; ARM处理器规定在地址0x00000000开始的位置必须存放异常向量表
; 当发生复位或各类异常时,CPU会自动跳转到对应位置执行
_start:
ldr pc, =_reset_handler ; 0x00: 复位异常向量,程序启动或复位时执行
ldr pc, =_undefine_handler ; 0x04: 未定义指令异常向量
ldr pc, =_svc_handler ; 0x08: 管理模式(SVC)异常向量
ldr pc, =_prefetch_abort_handler ; 0x0C: 预取中止异常向量(取指令出错)
ldr pc, =_data_abort_handler ; 0x10: 数据中止异常向量(访问数据出错)
ldr pc, =_reserved_handler ; 0x14: 保留向量(未使用)
ldr pc, =_irq_handler ; 0x18: 中断请求(IRQ)异常向量
ldr pc, =_fiq_handler ; 0x1C: 快速中断请求(FIQ)异常向量
; 异常处理函数(当前均为简单的死循环,仅作占位使用)
_undefine_handler:
ldr pc, =_undefine_handler ; 未定义指令异常:无限循环(原地跳转)
_svc_handler:
ldr pc, =_svc_handler ; 管理模式异常:无限循环
_prefetch_abort_handler:
ldr pc, =_prefetch_abort_handler ; 预取中止异常:无限循环
_data_abort_handler:
ldr pc, =_data_abort_handler ; 数据中止异常:无限循环
_reserved_handler:
ldr pc, =_reserved_handler ; 保留向量:无限循环
_irq_handler:
ldr pc, =_irq_handler ; IRQ中断:无限循环
_fiq_handler:
ldr pc, =_fiq_handler ; FIQ中断:无限循环
; 复位处理函数(系统上电或复位后实际执行的第一个函数)
_reset_handler:
; 配置IRQ模式的堆栈
mrs r0, cpsr ; 将当前程序状态寄存器(CPSR)的值读取到r0
bic r0, r0, #0x1F ; 清除CPSR的低5位(模式控制位)
orr r0, r0, #0x12 ; 设置为IRQ模式(0x12表示IRQ模式,禁止IRQ中断)
msr cpsr, r0 ; 将修改后的值写回CPSR,完成模式切换
ldr sp, =0x86000000 ; 设置IRQ模式下的栈指针(SP)到0x86000000地址
; 配置系统模式的堆栈
mrs r0, cpsr ; 再次读取CPSR寄存器
bic r0, r0, #0x1F ; 清除模式控制位
orr r0, r0, #0x1F ; 设置为系统模式(0x1F表示系统模式,特权模式)
msr cpsr, r0 ; 切换到系统模式
ldr sp, =0x84000000 ; 设置系统模式下的栈指针(SP)到0x84000000地址
; 初始化硬件并点亮LED
bl _enable_clock ; 跳转到时钟使能函数(bl指令会保存返回地址到lr寄存器)
bl _init_led ; 跳转到LED初始化函数
bl _led_on ; 跳转到点亮LED函数
b finished ; 跳转到主循环(程序主体逻辑结束)
; 点亮LED函数(控制LED输出低电平点亮)
_led_on:
ldr r0, =0x0209C000 ; 加载GPIO1_DR寄存器地址到r0(数据输出寄存器)
ldr r1, [r0] ; 读取当前GPIO1_DR寄存器的值到r1
bic r1, r1, #(1 << 3) ; 清除r1的第3位(bit3),对应LED引脚输出低电平
str r1, [r0] ; 将修改后的值写回GPIO1_DR寄存器,LED点亮
bx lr ; 返回到调用处(利用lr寄存器中的返回地址)
; LED初始化函数(配置GPIO引脚为输出模式)
_init_led:
; 配置引脚复用功能
ldr r0, =0x020E0068 ; 加载IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器地址
mov r1, #0x05 ; 设置值为0x05,将引脚复用为GPIO功能
str r1, [r0] ; 写入配置值到复用控制寄存器
; 配置引脚电气属性
ldr r0, =0x020E02F4 ; 加载IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03寄存器地址
ldr r1, =0x10B0 ; 加载电气属性配置值(驱动强度、速度等)
str r1, [r0] ; 写入电气属性配置
; 配置GPIO为输出模式
ldr r0, =0x0209C004 ; 加载GPIO1_GDIR寄存器地址(方向控制寄存器)
ldr r1, [r0] ; 读取当前方向寄存器值
orr r1, r1, #(1 << 3) ; 设置第3位(bit3)为1,配置为输出模式
str r1, [r0] ; 写入方向配置值
bx lr ; 返回到调用处
; 时钟使能函数(开启外设时钟,硬件才能工作)
_enable_clock:
ldr r0, =0x020C4068 ; 加载CCM_CCGR0寄存器地址(时钟门控寄存器0)
mov r1, #0xFFFFFFFF ; 设置所有位为1,使能对应外设时钟
str r1, [r0] ; 写入CCM_CCGR0寄存器
ldr r0, =0x020C406C ; 加载CCM_CCGR1寄存器地址
str r1, [r0] ; 使能对应外设时钟
ldr r0, =0x020C4070 ; 加载CCM_CCGR2寄存器地址
str r1, [r0] ; 使能对应外设时钟
ldr r0, =0x020C4074 ; 加载CCM_CCGR3寄存器地址
str r1, [r0] ; 使能对应外设时钟
ldr r0, =0x020C4078 ; 加载CCM_CCGR4寄存器地址
str r1, [r0] ; 使能对应外设时钟
ldr r0, =0x020C407C ; 加载CCM_CCGR5寄存器地址
str r1, [r0] ; 使能对应外设时钟
ldr r0, =0x020C4080 ; 加载CCM_CCGR6寄存器地址
str r1, [r0] ; 使能对应外设时钟
ldr r0, =0x020C4088 ; 加载CCM_CCGR7寄存器地址
str r1, [r0] ; 使能对应外设时钟
bx lr ; 返回到调用处
; 程序主循环(完成初始化后进入无限循环)
finished:
b finished ; 无限循环(原地跳转,防止程序跑飞)
arm.s文件编译烧录:
# 查看当前目录文件,显示有imxdownload工具和start.S汇编源文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls
imxdownload start.S
# 使用交叉编译器arm-linux-gnueabihf-gcc编译汇编文件
# -g: 生成调试信息
# -c: 只编译不链接,生成目标文件
# start.S: 输入的汇编源文件
# -o start.o: 输出目标文件为start.o
linux@ubuntu:~/IMX6ULL/LED_ASM$ arm-linux-gnueabihf-gcc -g -c start.S -o start.o
# 再次查看目录,确认生成了start.o目标文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls
imxdownload start.o start.S
# 使用交叉链接器arm-linux-gnueabihf-ld进行链接
# -Ttext 0X87800000: 指定代码段起始地址为0X87800000(IMX6ULL的运行地址)
# start.o: 输入的目标文件
# -o start.elf: 输出可执行文件为start.elf
linux@ubuntu:~/IMX6ULL/LED_ASM$ arm-linux-gnueabihf-ld -Ttext 0X87800000 start.o -o start.elf
# 查看目录,确认生成了start.elf可执行文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls
imxdownload start.elf start.o start.S
# 使用objcopy工具将ELF文件转换为二进制文件(适合烧录)
# -O binary: 指定输出格式为二进制
# -S: 不复制重定位信息和符号信息
# -g: 不复制调试信息
# start.elf: 输入的ELF文件
# start.bin: 输出的二进制文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
# 查看目录,确认生成了start.bin二进制文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls
imxdownload start.bin start.elf start.o start.S
# 使用objdump工具反汇编ELF文件,生成汇编代码文本(用于调试分析)
# -D: 对所有段进行反汇编
# start.elf: 输入的ELF文件
# > start.dis: 将反汇编结果重定向到start.dis文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ arm-linux-gnueabihf-objdump -D start.elf > start.dis
# 查看目录,确认生成了start.dis反汇编文件
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls
imxdownload start.bin start.dis start.elf start.o start.S
# 给imxdownload工具添加可执行权限(IMX6ULL专用下载工具)
linux@ubuntu:~/IMX6ULL/LED_ASM$ chmod +x imxdownload
# 确认imxdownload已具有可执行权限(文件名颜色通常会变为绿色)
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls
imxdownload start.bin start.dis start.elf start.o start.S
# 查看系统中的磁盘设备,确认SD卡对应的设备文件(这里/dev/sdb是目标SD卡)
linux@ubuntu:~/IMX6ULL/LED_ASM$ ls /dev/sd*
/dev/sda /dev/sda1 /dev/sda2 /dev/sda3 /dev/sdb /dev/sdb1
# 使用imxdownload工具将二进制文件下载到SD卡
# start.bin: 要下载的二进制程序
# /dev/sdb: 目标SD卡设备(注意不要选错设备,否则会损坏数据)
linux@ubuntu:~/IMX6ULL/LED_ASM$ ./imxdownload start.bin /dev/sdb
I.MX6ULL bin download software # 工具提示信息:IMX6ULL专用下载软件
Edit by:zuozhongkai # 作者信息
Date:2019/6/10 # 日期
Version:V1.1 # 版本
log:V1.0 initial version,just support 512MB DDR3 # 版本日志
V1.1 and support 256MB DDR3
file start.bin size = 340Bytes # 显示要下载的文件大小
Board DDR SIZE: 512MB # 检测到开发板DDR容量为512MB
Delete Old load.imx # 删除旧的load.imx文件(中间过渡文件)
Create New load.imx # 创建新的load.imx文件
Download load.imx to /dev/sdb ...... # 开始下载到SD卡
[sudo] password for linux: # 提示输入sudo密码(操作磁盘设备需要管理员权限)
6+1 records in # 输入的记录数(dd命令输出)
6+1 records out # 输出的记录数
3412 bytes (3.4 kB, 3.3 KiB) copied, 0.139808 s, 24.4 kB/s # 传输统计信息
arm-linux-gnueabihf-gcc -g -c start.S -o start.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 start.o -o start.elf
arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
arm-linux-gnueabihf-objdump -D start.elf > start.dis
chmod +x imxdownload
ls /dev/sd*
./imxdownload start.bin /dev/sdb