赞
踩
version : v1.0 「2023.4.27」
author: Y.Z.T.
简介: 随记, 关于I.MX6ULL 系统SOC 的部分寄存器
⭐️ 目录
简单了解原理
ARM
汇编的伪操作差异GUN ARM
伪操作指令补充数据传输指令
在
GUN ARM
汇编语言中 , 标号_start
是汇编程序的入口 , 如果希望这个标号被其他文件引用 , 只需要在定义的地方使用.globa
伪操作声明即可 (.globa
表示定义一个全局标号 ).globa -start ...
- 1
- 2
- 3
.section
伪操作
可以通过
.section
伪操作自定义一个段.section <section name> {,"<flags>"} .section .mysection "awx" @注释 : 定义一个可写、可执行的段 .align 2
- 1
- 2
- 3
- 4
- 注意在使用伪操作
.section
定义一个段时,每个段以段名开始,以 下一个段名或文件结尾 作为结束标记。- 在定义段名时,注意不要和系统预留的段名冲突,(如
.text
、.data
、.bss
、.rodata
)
系统预留的段名
在定义数据的过程中需要注意 :
- 二进制 数据通常以
0B
或0b
开头- 八进制 数据以
0
开头- 十六进制 数据以
0x
开头- 十进制 数据则以
非0
数字开头- 负数 前面加
-
- 取补 用
~
- 不相等 用
〈〉
- 其他运算符号如
+、-、*、%、<、<<、\>、>>、|、&、^、!、==、>=、&&
与C语言语法相似- 字符串常量 要用双引号
""
括起来- 使用
.ascii
定义字符串时要自行在结尾加\0
.string
伪操作可以定义多个字符串.asciz
伪操作可以定义一个以NULL
字符结尾的字符串- 用
.rep
t 伪操作可以重复定义数据- 在
GNU ARM
汇编程序中经常使用小圆点.
表示 当前指令的地址.ascii "hello\0" .string "hello", "world!" .asciz "hello" .rept 3 .byte 0x10 .endr
- 1
- 2
- 3
- 4
使用 伪操作 定义浮点数
@标签: 命令 f: .float 3.14 .equ f,3.1415
- 1
- 2
- 3
- 4
- 上面通过使用
.float
伪操作定义一个浮点数f
,并初始化为3.14
- 并通过
.equ
伪操作 将浮点数重新赋值成3.1415
.equ
伪操作除了给数据赋值,还可以把常量定义在代码段中,然后在代码中直接引用。类似C语言中的#define
宏定义.section .data .equ DELAY,100 ... .section .text ... MOV R0,$DELAY ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
stmdb
和ldmia
- stmdb和ldmia指令一般配对使用
- stmdb用于将寄存器压栈
- ldmia用于将寄存器弹出栈
- 它们的作用是保存使用到的寄存器
ARM指令的多数据传输(STM、LDM)中,提到:多寄存器的Load和Store指令分为2组:
- 一组用于数据的存储与读取,对应于IA、IB、DA、DB,
- 一组用于堆栈操作,对应于FD、ED、FA、EA,
STMIB
(地址先增而后完成操作)、STMFA
(满递增堆栈);LDMIB
、LDMED
;STMIA
(完成操作而后地址递增)、STMEA
(空递增堆栈);LDMIA
、LDMFD
;STMDB
(地址先减而后完成操作)、STMFD
(满递减堆栈);LDMDB
、LDMEA
;STMDA
(完成操作而后地址递减)、STMED
(空递减堆栈);LDMDA
、LDMFA
。IA
模式表示:每次传送后地址+4;(After Increase)
DB
模式表示:每次传送前地址-4;(Before Decrease)
在《imx6ull 中文参考手册》 199页
可以看见 :
- GPIO1 有 32 个 IO
- GPIO2 有 22 个 IO
- GPIO3 有 29 个 IO
- GPIO4 有 29 个 IO
- GPIO5 有 12 个 IO
SW_MUX_CTL_PAD_*
: 配置IO口的复用功能
以
GPIO1_IO1
举例:
- 可以看到
GPIO1_IO01
可以复用为8种不同功能的IO
SW_PAD_CTL_PAD_*
: 配置IO口的电气属性
还是以
GPIO1_IO00
举例:
- 用于配置
IO
的 上下拉电阻、IO速度、IO驱动能力、压摆率等电气属性- 一般主要设置压摆率 (SRE)、驱动能力 (DSE)、速度 (SPEED )、上下拉 ( PUS)、 开漏 (ODE)
GPIO1_IO00
输出高电平,那么就应该设置 GPIO1.DR=1
)GPIO1_IO00
这个引脚接地的话,那么 GPIO1.DR
的 bit0
就是 0)GPIO1_IO00
为输入,那么 GPIO1.GDIR=0
)GPIO
的状态DR
寄存器一样。 )ICR1 和 ICR2寄存器 : 中断控制寄存器
寄存器中的每两位对应一个IO , ICR1
用于配置低16个GPIO (IO0 ~ IO15) , ICR2 用于配置高 16 个 GPIO ( IO16~ IO31 )
这两个位用来配置中断的触发方式
举例: 设置
GPIO1_IO15
为上升沿触发中断 (GPIO1.ICR1=2<<30
)
GPIO1_IO00
的中断,那么就可以设置 GPIO1.MIR=1
)GPIO
中断是否发生ICR1
和 ICR2
的设置GPIO1.EDGE_SEL=1
,那么就表示 GPIO1_IO01
是双边沿触中断 )
CCM_CCGRx
寄存器 用于使能各个外设模块的时钟 , 为每2位对应一个外设时钟CGR值如下所示:
地址 : 0x 20C_4068H
使能方式 :
以GPIO2的时钟为例 ( CCM_CCGR0 = 3<<30 )
地址 :
0x20C_406C
H
地址 :
0x 20C_4070
H
地址
0x20C_4074
H
地址 :
0x20C_4078
H
地址 :
0x20C_407C
H
地址 :
0x20C_4080
H
一般步骤:
GPIO
对应的时钟 (CCM_CCGRx
寄存器)IOMUXC_SW_MUX_CTL_PAD_XX_XX
,设置 IO 的复用功能IOMUXC_SW_PAD_CTL_PAD_XX_XX
,设置 IO 的上下拉、速度等电气属性DR
、GDIR
、ICR
等寄存器)将裸机的汇编点灯程序编译成可执行文件
.bin
, 需要在.bin
文件前面添加 头部信息包括 镜像向量表 (
IVT
) 、Boot启动数据 (Boot data
)、 配置信息 (DCD
)等 , 后面才是用户可执行代码 (Application
)
如下所示:
不同引导设备的 偏移量与 加载区域大小 ( 即 头部信息)
- 则
IVT + Boot Data + DCD
的大小为 加载区域大小 减去 偏移量 (4K Byte - 1K Byte = 3K Byte
)
在
IVT
和Boot Data
中存放着各个 部分的 链接地址如 :
entry
存放的是镜像第一行指令所在的位置 , 即 用户执行代码 的入口 , 可以看到箭头指向了Application
self
存放的是IVT
复制到DDR
中以后的首地址 , 可以看到箭头目的地址指向IVT
的开头
在裸机点灯的程序中, 链接的地址是 0x87800000
即 用户执行代码 (
Application
) 的 起始地址entry = 0x87800000
镜像向量表 (
IVT
) 的起始地址self = 0x877FF400
[0x87800000 - 3k byte = 0x877FF400
]
Boot
启动数据 (Boot Data
) 的起始地址boot data = 0x877FF420
- [ 此处是
IVT
的地址 加上IVT
的长度0x877FF400 + 32 Byte = 0x877FF420
]
配置信息 (
DCD
) 的起始地址dcd = 0x877FF42C
- [ 此处是
Boot Data
的地址 加上Boot data
的长度0x877FF420 + 12 Byte = 0x877FF42C
]
整个头部信息的 起始地址
start = 0X877FF000
- [ 此处是 IVT 的起始地址 减去 1K Byte 的偏移量
0x877FF400 - 1K Byte = 0X877FF000
]
镜像向量表 ( IVT
) 存放了 8条 信息 , 每条信息大小都是 32位
IVT 存放的内容:
header 存放格式:
Tag
为一个字节长度,值固定为 0XD1
Length
为大端模式的两字节字段 , 存放着 IVT
的长度信息 , IVT 长度固定为32字节 ( 0x0020
) , 则Length = 0x2000
Version
为单字节字段 , 值为 0x40
或 0x41
Boot Data
存放了 3条 信息 , 每条信息大小都是 32位
start
存放的是整个头部信息的存放地址 , 包含偏移量length
存放的是整个镜像 文件的大小plugin
插件
DCD 存放 程序镜像中包含的各种配置信息 ( 即
IMX6ULL
寄存器地址 和对应配置信息的集合 )因为复位芯片后 ,
I.MX6U
片内的所有寄存器都会复位为默认值,但是这些默认值往往不是我们想要的值 , 通过在DCD
中添加这部分寄存器的配置信息 ( 如系统时钟 ) ,Boot ROM
会使用这些寄存器地址和配置集合来初始化相应的寄存器DCD 区域不能超过 1768Byte
DCD格式 :
Header格式
Tag
是单字节,固定为 0XD2
Length
为大端模式的两字节字段 , 存放着 DCD
的长度信息Version
为单字节 , 固定为0x41
CMD 格式:
CMD 包含 写数据命令、检查数据命令、NOP命令、解锁命令
其中写数据命令格式如下:
- Tag 是单字节,固定为
0xCC
Length
为大端模式的两字节字段 , 存放着*** 写入数据的长度信息***CMD
是命令 , 包含header
Address
是地址 , 存放着写入数据的目标地址 ( 即寄存器地址 )Value / Mask
值或掩码 , 存放着前一个 地址 (Address
) 的数据值 ( 即寄存器值)
Parameter
参数 的格式如下:
Parameter
为单字节数据bytes
表示是目标位置宽度,单位为byte
,可以选择 1、2、和 4 字节flags
是命令控制标志位
例:
// 定义全局入口符号
.global _start
_start:
mrs r0, cpsr // 将CPSr寄存器的值读到 r0 中
bic r0, r0, #0x1f // 0001 1111 将低5位清零
orr r0, r0, #0x13 // 0001 0011 将 [M0 ~ M4]设置为SVC模式
msr cpsr, r0 // 将r0 的值重新写回cpsr寄存器中
ldr sp, =0x80200000 // Cortex -A 是FD满递减栈 , DDR的起始地址是0x80000000 , 栈空间 2MB
b main // 跳转main函数
main.c && led.c
/******************************** main.c ******************************/ #include "main.h" #include "led.h" /* * @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) { led_init(); while (1) { set_led_off(); delay(500); set_led_on(); delay(500); } return 0; } /******************************** led.c ******************************/ #include "led.h" #include "main.h" void led_init (void) { /* 使能时钟 */ CCM->CCGR1 = (3 << 26) | (3 << 18) ; //使能GPIO1的时钟 和 SIM时钟 , 因为是烧录到SD卡的 /* IO复用为GPIO */ SW_MUX_GPIO1_IO03 = 0x05; /* 电气属性设置 */ SW_PAD_GPIO1_IO03 = 0x00001058; /* GPIO 设置为输出 */ GPIO1->GDIR = 1 << 3; /* GPIO初始化为低电平 */ GPIO1->DR = 0; } void set_led_on(void) { GPIO1->DR &= ~(1 << 3); } void set_led_off(void) { GPIO1->DR |= (1 << 3); }
main.h $$ led.h
/******************************** led.h ******************************/ #ifndef __LED_H #define __LED_H void led_init (void); void set_led_on(void); void set_led_off(void); #endif /******************************** main.h ******************************/ #ifndef __MAIN_H #define __MAIN_H /* * 外设寄存器组的基地址 */ #define CCM_BASE (0X020C4000) #define GPIO1_BASE (0x0209C000) /* * IOMUX 相关寄存器地址 */ #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068) #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4) /* * CCM寄存器结构体定义 */ typedef struct { volatile unsigned int CCR; volatile unsigned int CCDR; volatile unsigned int CSR; volatile unsigned int CCSR; volatile unsigned int CACRR; volatile unsigned int CBCDR; volatile unsigned int CBCMR; volatile unsigned int CSCMR1; volatile unsigned int CSCMR2; volatile unsigned int CSCDR1; volatile unsigned int CS1CDR; volatile unsigned int CS2CDR; volatile unsigned int CDCDR; volatile unsigned int CHSCCDR; volatile unsigned int CSCDR2; volatile unsigned int CSCDR3; volatile unsigned int RESERVED_1[2]; volatile unsigned int CDHIPR; volatile unsigned int RESERVED_2[2]; volatile unsigned int CLPCR; volatile unsigned int CISR; volatile unsigned int CIMR; volatile unsigned int CCOSR; volatile unsigned int CGPR; volatile unsigned int CCGR0; volatile unsigned int CCGR1; volatile unsigned int CCGR2; volatile unsigned int CCGR3; volatile unsigned int CCGR4; volatile unsigned int CCGR5; volatile unsigned int CCGR6; volatile unsigned int RESERVED_3[1]; volatile unsigned int CMEOR; } CCM_Type; /* * GPIO寄存器结构体 */ typedef struct { volatile unsigned int DR; volatile unsigned int GDIR; volatile unsigned int PSR; volatile unsigned int ICR1; volatile unsigned int ICR2; volatile unsigned int IMR; volatile unsigned int ISR; volatile unsigned int EDGE_SEL; }GPIO_Type; /* * 外设指针 */ #define CCM ((CCM_Type *)CCM_BASE) #define GPIO1 ((GPIO_Type *)GPIO1_BASE) #endif
CC =arm-linux-gnueabihf link :=-ld -Timx6ul.lds -o # 使用链接脚本 elf_bin :=-objcopy -O binary -S obj :=$(subst .S,.o,$(wildcard *.S)) $(subst .c,.o,$(wildcard *.c)) #将c文件和s文件替换成 o 文件 ledc.bin: $(obj) $(CC)$(link) ledc.elf $^ # 链接 $(CC)$(elf_bin) ledc.elf $@ # 格式转换 %.o:%.c $(CC)-gcc -Wall -nostdlib -c -o $@ $< # -Wall表示显示编译的时候的所有警告 %.o:%.S $(CC)-gcc -Wall -nostdlib -c -o $@ $< # -nostdilib表示不链接系统标准启动文件和库文件 .PHONY: clean clean: rm *.o *.bin *.elf
SECTIONS{
. = 0X87800000;
.text :
{
start.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
- 使用关键字
SECTIONS
, 描述输出文件的内存布局.
是 定位计数器 , 其值表示以其为地址开始 , 这里是以0x87800000
为起始地址.text
.rodata
.data
. bss
分别是代码段、只读数据段、数据段、bss段- 这几个段名冒号后面的
ALIGN(4)
表示以四字节对齐 , 大括号里面表示输入文件 ( 即将什么文件 放入哪一段)__bss_start
和__bss_end
相当于两个变量 , 其值均为定位符.
, 分别报存了bss
段 的起始地址 和 结束地址 , 方便对bss
段进行清零
时钟树由三部分组成:时钟切换控制器,根时钟产生器,系统时钟
时钟切换控制器: 用来将外部晶振进行倍频,以实现稳定且高频的时钟信号。
根时钟产生器: 在时钟切换控制器配置完成之后的输出时钟就成了根时钟产生器的时钟源,在根时钟产生器中,经过寄存器配置之后,就成为了外设时钟的时钟源
一个外设的时钟信号的产生途径:晶振向芯片输入时钟信号,信号进入时钟切换控制器,经过用户配置,产生
PLL
时钟信号,该信号进入根时钟产生步骤,经过分频或倍频,最终成为系统某个外设的时钟信号。
主时钟生成
ARM_PLL(PLL1)
,此路PLL
是供ARM
内核使用的,ARM
内核时钟就是由此PLL
生成的,此PLL
通过编程的方式最高可倍频到1.3GHz
528_PLL(PLL2)
,此路PLL
也叫做System_PLL
,此路PLL
是固定的 22 倍频 , 即528MHZ
- 此PLL 有四路PFD :
PLL2_PFD0
、PLL2_PFD1
、PLL2_PFD2
、PLL2_PFD3
, 这些PFD
以PLL
为基础,经过不同的分频系数产生不同频率的时钟- 官方推荐配置参数
USB1_PLL(PLL3)
,此路PLL
主要用于USB1PHY
, 此路PLL
是固定的20倍频 , 即480MHZ
- 此PLL 也有四路PFD :
PLL3__PFD0
、PLL3__PFD1
、PLL3__PFD2
、PLL3__PFD3
- 官方推荐配置参数
USB2_PLL(PLL7)
主要用于USB2PHY
,此路PLL
固定为20倍频,因此也是480MHz
ENET_PLL(PLL6)
, 此路PLL
固定为20+5/6
倍频 , 即480MHz
VIDEO_PLL(PLL5)
用于显示相关的外设 ,PLL
的输出范围在650MHz~1300MHz
, 可选1/2/4/8/16
分频
AUDIO_PLL(PLL4)
用于音频相关的外设 ,PLL
的输出范围在650MHz~1300MHz
, 可选1/2/4
分频
时钟树
- 橙色梯形的是 选择器 ,可以选择不同的
PLL
时钟作为 时钟源- 矩形就是 分频器(2位,3位,6位)
- 黄色高亮的就是用来配置选择器的各种寄存器
- 红色字是用来配置分频系数的 , 就是配置 寄存器的某个几位<>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。