ARM裸机开发——I.MX6U_汇编LED灯驱动

1.I.MX6U外设时钟:

I.MX6U的外设时钟配置都通过CCGR0~CCGR6共七个寄存器控制。

汇编指令基础:

理解Cortex-A的寄存器:

理解寄存器(R0~R15...)、主存(ram...)、缓存(L1、L2...)、辅存(rom...):

1.寄存器(Register)​
​定位:位于CPU内部,直接参与运算,是存储层级的最高层。

​特点:速度最快(0延迟)、容量极小(通常为16-64字节)、易失性。

​是否属于内存:​否,寄存器是CPU的组成部分,独立于内存。

​2. 缓存(Cache)​

​层级细分:缓存是主存的高速缓冲,两者属于不同层级。缓存存储主存中的高频数据副本,但不属于主存本

​L1缓存:集成在CPU核内,分为指令缓存(I-Cache)和数据缓存(D-Cache) ,速度最快(2-4周期),容量32KB-1MB。
​L2缓存:容量更大(256KB-8MB),速度次之(10-20周期),可能共享多核访问。
​L3缓存​(部分架构):共享于多核之间,容量更大但速度较慢。
​定位:介于CPU与主存之间,解决速度差异问题,​不属于内存,是独立的层级。

​功能:缓存高频访问的主存数据,命中率可达90%以上。

​3. 主存(内存/RAM)​
​定义:直接与CPU交互的易失性存储器,即常说的"内存条",由DRAM(动态RAM)构成。

​特点:速度较缓存慢(50-100周期)、容量GB级、断电数据丢失。

​是否包含缓存:​否,缓存是独立层级,但主存数据可通过缓存加速访问。

​4. 外存(辅存)​
​组成:ROM(如BIOS芯片)、硬盘、SSD、光盘等非易失性存储设备。

​特点:速度慢(毫秒级)、容量TB级、长期保存数据。

​ROM是否属于内存:​否,ROM属于外存,用于固化程序(如系统引导代码)。
EEPROM属于外存,是ROM的一种可电擦写变体。
Flash属于外存,是一种非易失性存储器,特性与硬盘类似。

关于各层级存储的访问方式:


1. 指令直接操作
​定义:CPU直接通过指令对寄存器进行读写,无需通过总线或地址映射。
​应用场景: 寄存器操作(如MOV R0, R1)。
​技术原理: 寄存器是CPU内部的存储单元,指令直接指定寄存器地址。
​示例:

bash 复制代码
ADD R0, R1, R2   ; 将R1和R2的值相加,结果存入R0

**​特点:**速度最快(0延迟),但容量极小(仅几十字节)。

​2. 地址映射(全相联/组相联)​
​定义:将主存地址映射到缓存中的位置,解决缓存与主存间的数据定位问题。

​应用场景: 缓存(L1/L2/L3)的数据存取。
​技术原理:

​全相联映射:主存块可映射到缓存任意位置,灵活性高但硬件成本大。

​组相联映射:主存块映射到缓存特定组内,平衡速度与冲突率(如4路组相联)。

​示例:

若主存地址0x1234的数据被缓存到L1的组2中,下次访问时直接读取组2内容。

​特点:通过局部性原理减少主存访问次数,提升效率。

​3. 总线传输+地址计算
​定义:CPU通过地址总线发送物理地址,数据总线传输内容,结合地址计算逻辑访问主存。
​应用场景: 主存(DRAM)读写操作。
​技术原理:
​地址总线: 发送目标地址(如0x5678)。
​数据总线: 传输实际数据(如32位整数)。
​地址计算: 段基址+偏移量(实模式)或虚拟地址转物理地址(保护模式)。

​示例:

bash 复制代码
MOV EAX, [0x2000] ; 读取主存地址0x2000的数据到EAX寄存器

​特点: 速度较慢(50-100周期),但容量大(GB级)。

​4. I/O接口或内存映射
​定义:通过I/O端口或内存映射地址直接操作外设寄存器。
​应用场景: 外设控制(如UART、GPIO)。

​技术原理:
​I/O接口: 专用指令(如IN/OUT)访问外设端口(如x86架构)。

​内存映射I/O(MMIO)​:将外设寄存器映射到内存地址空间(如ARM架构)。

​示例:

bash 复制代码
// 内存映射方式操作UART发送数据
#define UART_TX (*(volatile uint32_t*)0x4000C000)
UART_TX = 'A'; // 向地址0x4000C000写入字符'A'

​特点: 绕过缓存,直接控制硬件,需手动维护一致性。

从程序员的角度怎么访问上述四个存储区:

一、寄存器访问
主要方法:
​汇编指令直接操作
通过汇编指令(如MOV AX, BX)直接读写寄存器。

​C语言中的指针或结构体映射
​指针强制类型转换:通过地址直接访问寄存器,例如:

bash 复制代码
#define GPIO_MODER (*(volatile uint32_t *)0x40020000)

​结构体映射:将寄存器组定义为结构体,通过指针访问,常用于硬件驱动开发。
​隐式访问
程序计数器(PC)、状态寄存器(FLAGS)等由CPU自动管理,无需显式操作。

​二、主存(内存)访问
主存通过总线与CPU交互,分为栈区、堆区、静态区等。
主要方法:
​指针操作
直接通过地址访问内存单元,例如:

bash 复制代码
int *ptr = (int *)0x20000000; *ptr = 10;

动态内存分配(malloc/free)管理堆区。
​虚拟内存管理

操作系统通过页表机制将虚拟地址转换为物理地址,支持内存分页和交换。

​总线传输与寻址模式

CPU通过地址总线、数据总线和控制总线访问内存,支持直接寻址、间接寻址等模式。
​三、缓存(Cache)访问
缓存由硬件自动管理,程序员通常无法直接操作,但可通过优化提升命中率。
优化策略:


​四、外存(硬盘/SSD等)访问
外存通过文件系统或硬件接口访问,速度较慢但容量大。

主要方法:

​文件系统API

使用标准库函数(如fopen、fread)或系统调用(如Linux的open、read)。

​内存映射文件(mmap)​

将文件映射到内存地址空间,通过指针直接读写,减少用户态与内核态切换。

​I/O端口与硬件接口

通过内存映射I/O(MMIO)访问外设寄存器(如UART控制器)。
直接操作物理地址(如Linux的/dev/mem设备)。

​数据库与持久化

使用SQLite、Redis等数据库管理外存数据,结合缓存提升效率

L1和L2缓存:

寄存器(R0~R15)

​(1)通用寄存器(R0~R14)​:

用于算术逻辑运算(如ADD R0, R1, R2)。

存储函数参数和返回值(ARM调用约定中,R0~R3用于传参)。

​(2)程序计数器(R15/PC)​:

存储下一条待执行指令的地址,控制程序流(如跳转指令B LABEL会修改PC)。

​(3)状态寄存器(CPSR/SPSR)​:

存储条件标志(如进位、溢出)、中断屏蔽位和处理器模式(用户模式、IRQ模式等)。

不同的工作模式对应有不同的寄存器。

CPSR寄存器:

注意: ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用

小写,但是不能大小写混用。

(1)汇编的数据传输:

一、时钟:

IM6ULL时钟树介绍:

无论哪个设备需要工作,时钟信号是必要的,没有信号外设就不能按照时序进行工作,那么什么时候读取信号,什么时候存信号都不知道,这个学了数电会明白很多。

那么我们来研究一下IM6U的时钟管理:
1.首先由OSC(振荡器)产生基础时钟,学数电就知道这种是一类材料通电压后会产生不同频率的振荡信号,然后由振荡电路选择其中需要的信号再进行放大,用不到的信号就去除。

2.CCM(时钟控制模块)接收OSC发出的基础时钟信号,然后进行分频/倍频之后再输出。其中PLL指锁相环,主要功能是​​实现输入信号与输出信号在频率和相位上的精确同步​​,PLL通过反馈机制实现动态频率校准,而普通振荡器(如晶振)仅提供固定频率输出。

3.LPCG(时钟控制模块)拿到CCM处理过的时钟信号后将它们分别发送给不同的模块。

4.经过时钟树流程后的时钟信号是工作状态可输入给外设的时钟信号,而一开始的OSC出来的基础时钟信号还流向了IOMUX的原因可能是用于待机模式的工作需要。

时钟控制:

寄存器:

下面时钟控制寄存器中我们可以依次查看:


那么应该先选择时钟源,不过我看了一下,CCSR已经默认好了,特殊情况才需要更改为什么步进控制,CCR也是默认好了在需要切换到省电模式的时候需要设置,分频也默认了,所以我们只需要通过CCGR控制各个模块的时钟就好了。

CCGR模块时钟:

下图是CCGR0的寄存器,解读为,上面的这个CGR value的值有四个模式,因为CCGR是一个32位的寄存器,同时每两位对应一个外设,也就是说CCGR0 的 两个位bit0 和bit1 可以代表一个外设GPIO0,然后给这两位赋值为00 01 10 11,这四个值就对应将GPIO0设置为CGR value 的四个模式中的一个。

原子的教程要把所有的CCGR0 到 CCGR6的每一位都赋值为1,我不太理解,我的理解是只打开需要的,并且把其他外设的都关闭。
要用到的是GPIO1_IO03
在CCGR1 的 CG13中,我们将它赋值为01(除停止模式(STOP)外,所有模式下时钟开启。)
然后将其他的设置为0

不知道为什么,CCGR4的时钟被关闭就不能点亮led灯,所以全部时钟都使能先。

程序:

bash 复制代码
.global _start


_start:

MOV R0, =0xFFFFFFFF
MOV R1, =0X00000000
MOV R3, =0X020C406C

@使能时钟
STR R1,[R3]


LDR R3, =0X020C4068
STR R1,[R3]

LDR R3, =0X020C4070
STR R1,[R3]

LDR R3, =0X020C4074
STR R1, [R3]

LDR R3, =0X020C4078
STR R1, [R3]

LDR R3, =0X020C407C
STR R1, [R3]

LDR R3, =0X020C4080
STR R1, [R3]

二、IO引脚:

首先我们这章要使用IO口驱动LED灯点亮,那么就需要先了解板子的IO口配置,将IO口配置成GPIO功能,所以进入手册后先找到GPIO部分。

右图是GPIO配置方案:

其中右边的三个方块分别表示有九个相关配置的寄存器:
GPIO方块------表示IO口配置为GPIO功能时的相关配置寄存器

​IOMUXC(I/O Multiplexer Controller)------IO口多功能复用控制

复制代码
​​SW_MUX_CTL_PAD_​​*:配置引脚功能复用模式(如选择GPIO或UART模式)。
​​SW_PAD_CTL_PAD_​​*:设置引脚电气特性(如上拉/下拉电阻、驱动能力、压摆率等)。

注意:*号表示这是一个系列,意思就是这个系列下每一个功能都有对应的寄存器对相关

相关寄存器:

接下来我们根据手册的配置方案图对相关寄存器进行认识,然后再配置:

SW_MUX_CTL_PAD_(多路复用控制):

首先,因为IO口可以用作很多功能(I2C,Uart,GPIO...)所以先将IO口配置为GPIO模式,那么就先来了解SW_MUX_CTL_PAD_(多路复用控制)的内容:

定位到第32章,用翻译软件翻译一下它的子目录就,第一个是GDP(通用目的寄存器)和DMA相关,第二个是SNVS(掉电不易失...),第三个是我们想看的IO多路复用控制。

点开后,发现,有很多寄存器,这些寄存器的名字表示它们默认的功能,需要复用为其他功能时就需要对MUX_MODE空间进行操作。

SW_PAD_CTL_PAD_(引脚的电气属性和输入/输出特性):


压摆率:反映了器件在大信号输入时的动态响应能力,直接影响输出信号的上升/下降时间和高频信号的保真度。

000000001110

注意:这两个寄存器配置的是GPIO1_03这个引脚的属性,就是一个实际的引脚的属性。这是在IO的章节中找到的,是用于配置某个IO引脚的,将这个引脚配置成了GPIO1_03并设置了这个引脚的电气属性,这是配置了一个IO引脚,接下来要去配置的是GPIO的功能。

程序:

bash 复制代码
@配置复用寄存器到GPIO功能
MOV R3, =0x020E0068
MOV R4, =0x5
STR R4, [R3]
@配置GPIO01_03的电气属性
MOV R4, =0x38
MOV R3, =0x020E02F4
STR R4, [R3]

三、GPIO功能:

切换到GPIO的章节:

因此配置好方向寄存器再设置数据寄存器为0即可点亮LED灯。

每一个寄存器都是32位的,每个GPIO组有32个引脚,所以将GPIO1_03设置为输出即将GPIR寄存器的第3位设置为1。

程序

bash 复制代码
.global _start


_start:

LDR R1, =0xFFFFFFFF
LDR R0, =0X00000000
LDR R3, =0X020C406C

@使能时钟
STR R1,[R3]


LDR R3, =0X020C4068
STR R1,[R3]

LDR R3, =0X020C4070
STR R1,[R3]

LDR R3, =0X020C4074
STR R1, [R3]

LDR R3, =0X020C4078
STR R1, [R3]

LDR R3, =0X020C407C
STR R1, [R3]

LDR R3, =0X020C4080
STR R1, [R3]

@配置复用寄存器到GPIO功能
LDR R3, =0x020E0068
LDR R4, =0x5
STR R4, [R3]
@配置GPIO01_03的电气属性
@LDR R4, =0X10B0
LDR R4, =0x38
LDR R3, =0x020E02F4
STR R4, [R3]

@配置GPIO功能的属性:
LDR R3, =0x0209C004
LDR R4, =0x00000008
STR R4, [R3]

LDR R3, =0x0209C000
STR R0, [R3]

编译:

编译流程:

(1)编译成目标文件:

bash 复制代码
arm-linux-gnueabihf-gcc -g -c led.s -o led.o

错误:

第一次错误:
bash 复制代码
wenyecheng@wenyecheng-virtual-machine:~/Linux/arm/LED/start$ arm-linux-gnueabihf-gcc -g -c led.s -o led.o
led.s: Assembler messages:
led.s:6: 错误: immediate expression requires a # prefix -- `mov R0,=0xFFFFFFFF'
led.s:7: 错误: immediate expression requires a # prefix -- `mov R1,=0X00000000'
led.s:8: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C406C'
led.s:14: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C4068'
led.s:17: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C4070'
led.s:20: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C4074'
led.s:23: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C4078'
led.s:26: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C407C'
led.s:29: 错误: immediate expression requires a # prefix -- `mov R3,=0X020C4080'
led.s:33: 错误: immediate expression requires a # prefix -- `mov R3,=0x020E0068'
led.s:34: 错误: immediate expression requires a # prefix -- `mov R4,=0x5'
led.s:37: 错误: immediate expression requires a # prefix -- `mov R4,=0x28'
led.s:38: 错误: immediate expression requires a # prefix -- `mov R3,=0x020E02F4'
led.s:42: 错误: immediate expression requires a # prefix -- `mov R3,=0x0209C004'
led.s:43: 错误: immediate expression requires a # prefix -- `mov R4,=0x00000008'
led.s:46: 错误: immediate expression requires a # prefix -- `mov R3,=0x0209C000'

原因:

第二次错误:我以为把=改为#就可以了,才注意到,MOV的参数只能是合法立即数就是小的,地址这种不行。
bash 复制代码
led.s:8: 错误: invalid constant (20c406c) after fixup
led.s:14: 错误: invalid constant (20c4068) after fixup
led.s:17: 错误: invalid constant (20c4070) after fixup
led.s:20: 错误: invalid constant (20c4074) after fixup
led.s:23: 错误: invalid constant (20c4078) after fixup
led.s:26: 错误: invalid constant (20c407c) after fixup
led.s:29: 错误: invalid constant (20c4080) after fixup
led.s:33: 错误: invalid constant (20e0068) after fixup
led.s:38: 错误: invalid constant (20e02f4) after fixup
led.s:42: 错误: invalid constant (209c004) after fixup
led.s:46: 错误: invalid constant (209c000) after fixup

解决方法:

按esc切换到命令模式:
输入 :%s/old/new (将所有old字符改为new)
输入:%s/MOV/LDR

然后就没报错了

(2)将.o文件链接到一个位置:

详细需要自己去了解,链接这个工作:

bash 复制代码
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

链接过程:

(1)重定位:

重定位是指将程序中的​​逻辑地址空间​​(编译时生成的相对地址)转换为​​物理地址空间​​(内存实际地址)的过程。这一过程使得程序能够在不同内存位置加载和执行,无需修改源代码。

(2)运行地址和存储地址:


(3)为什么是链接.o(目标文件):

(3)格式转换:

将elf文件转换为最终烧写的bin文件

bash 复制代码
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

烧写bin文件到SD卡:

没有SD设备:

Linux的所有设备都对应在 /dev目录下:
输入:

bash 复制代码
ls /dev/sd*

就会返回连接的SD卡设备
如果没有返回,可以检查:
1.在状态栏------>虚拟机------>可移动设备,然后找到sd卡进行连接
2.在底部确定有对应SD卡的图标并亮绿色


3.在状态栏------>虚拟机------>设置------>USB控制器(将USB兼容性勾选到3.1或更高)

烧写:

命令:

bash 复制代码
./imxdownload <.bin file> <SD Card> //这里的sd路径一点要写对
./imxdownload ./bin/led.bin /dev/sdb

烧写失败原因:

如果你的烧写速度不是kb,一般就是错的。
1.sd卡路径没写对
2.文件路径没写对

Makefile 文件:

创建一个名为makefile的文件,记住要在工程根目录下

bash 复制代码
touch makefile

将下面的内容复制进去,关于makefile的语法可以自己了解。

bash 复制代码
TAG = ./bin/led.bin
ASM = ./src/led.s
OBJ = ./build/led.o
ELF = ./build/led.elf
BIN = ./bin/led.bin
ARM_GCC = arm-linux-gnueabihf-gcc
ARM_LD = arm-linux-gnueabihf-ld 
ARM_ELF2BIN = arm-linux-gnueabihf-objcopy
COM_PAR = -g -c
LD_PAR =  -Ttext 0X87800000
TRA_PAR = -O binary -S -g
SD = /dev/sdb

$(TAG):$(ASM)
	$(ARM_GCC) $(COM_PAR) $(ASM) -o $(OBJ)
	$(ARM_LD) $(LD_PAR) $(OBJ) -o $(ELF)
	$(ARM_ELF2BIN) $(TRA_PAR) $(ELF) $(BIN)
load:
	./imxdownload $(TAG) $(SD)
clean:
	rm ./bin/*

编写完成后:终端输入,记住要在工程根目录下

编译

bash 复制代码
make

下载:

bash 复制代码
make load

删除文件就把load改为clean

相关推荐
mucheni1 小时前
迅为RK3562开发板ARM四核A53核心板多种系统适配全开源
arm开发
阿让啊1 小时前
单片机获取真实时间的实现方法
c语言·开发语言·arm开发·stm32·单片机·嵌入式硬件
深圳信迈科技DSP+ARM+FPGA6 小时前
基于ARM+FPGA+DSP的储能协调控制器解决方案,支持国产化
arm开发·fpga开发·信号处理
承接电子控制相关项目8 小时前
STM32F103 单片机(基于 ARM Cortex-M3 内核)的启动过程涉及硬件初始化、固件配置和程序执行流程。
arm开发·stm32·单片机·cortex-m3内核启动
技术领导力11 小时前
事关数据安全,ARM被爆不可修复漏洞
arm开发
完成大叔1 天前
嵌入式:ARM公司发展史与核心技术演进
arm开发·嵌入式硬件
zhmc1 天前
Keil A51汇编伪指令
汇编
木木不迷茫(˵¯͒¯͒˵)1 天前
Keil MDK‑5 中使用 GNU ARM GCC 的 -Wno-* 选项屏蔽编译警告
arm开发·gnu·keil
迷路的小灰仔2 天前
ESP32 搭建IDF+Vscode环境(详细教程)
c语言·arm开发·单片机·mcu·物联网·visual studio·iot