stm32

一、GCC

1、gcc 命令

gcc [选项] [文件名字]

主要选项如下:

-c:只编译不链接为可执行文件,编译器将输入的.c 文件编译为.o 的目标文件。

-o:<输出文件名>用来指定编译结束以后的输出文件名,如果不使用这个选项的话 GCC 默 认编译出来的可执行文件名字为 a.out。

-g:添加调试信息,如果要使用调试工具(如 GDB)的话就必须加入此选项,此选项指示编 译的时候生成调试所需的符号信息。

-O:对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进 行优化,这样产生的可执行文件执行效率就高。

-O2:比-O 更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。

二、Cortex-A7 MPCore 架构

Cortex-A7 MPcore 处理器支持 1~4 核,通常是和 Cortex-A15 组成 big.LITTLE 架构的, Cortex-A15 作为大核负责高性能运算,比如玩游戏啥的,Cortex-A7 负责普通应用,因为 CortexA7 省电.

big.LITTLE 处理的设计旨在为适当的作业分配恰当的处理器。Cortex-A78 [1]核心是已开发的性能最高的 ARM 核心,而 Cortex-A55 [1] 核心是已开发的中端解决方案的最高效率动态的 ARM 核心。可以利用 Cortex-A78 [1]核心的性能来承担繁重的工作负载,而 Cortex-A55 [1] 可以最有效地处理智能手机的大部分工作负载。这些操作包括操作系统活动、用户界面和其他持续运行、始终连接的任务。

Cortex-A7MPCore 使用 ARMv7-A 架构:

①、SIMDv2 扩展整形和浮点向量操作

②、提供了与 ARM VFPv4 体系结构兼容的高性能的单双精度浮点指令,支持全功能的 IEEE754。

③、支持大物理扩展(LPAE),最高可以访问 40 位存储地址,也就是最高可以支持 1TB 的 内存。

④、支持硬件虚拟化。

⑥、支持 Generic Interrupt Controller(GIC)V2.0。

⑦、支持 NEON,可以加速多媒体和信号处理算法。

1、Cortex-A 处理器运行模型

以前的 ARM 处理器有 7 中运行模型:User、FIQ、IRQ、Supervisor(SVC)、Abort、Undef 和 System,其中 User 是非特权模式,其余 6 中都是特权模式。但新的 Cortex-A 架构加入了 TrustZone 安全扩展,所以就新加了一种运行模式:Monitor,新的处理器架构还支持虚拟化扩 展,因此又加入了另一个运行模式:Hyp,所以 Cortex-A7 处理器有 9 种处理模式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

除了 User(USR)用户模式以外,其它 8 种运行模式都是特权模式。这几个 运行模式可以通过软件进行任意切换,也可以通过中断或者异常来进行切换。大多数的程序都 运行在用户模式,用户模式下是不能访问系统所有资源的,有些资源是受限的,要想访问这些 受限的资源就必须进行模式切换。但是用户模式是不能直接进行切换的,用户模式下需要借助 异常来完成模式切换,当要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完 成处理器模式切换。

2、寄存器

ARM 架构提供了 16 个 32 位的通用寄存器(R0~R15)供软件使用,前 15 个(R0~R14)可以用 作通用的数据存储,R15 是程序计数器 PC,用来保存将要执行的指令。ARM 还提供了一个当 前程序状态寄存器 CPSR 和一个备份程序状态寄存器 SPSR,SPSR 寄存器就是 CPSR 寄存器的 备份。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Cortex-A7 有 9 种运行模式,每一种运行模式都有一组与之对应的寄存 器组。每一种模式可见的寄存器包括 15 个通用寄存器(R0~R14)、一两个程序状态寄存器和一个 程序计数器 PC。在这些寄存器中,有些是所有模式所共用的同一个物理寄存器,有一些是各模 式自己所独立拥有的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

,CortexA 内核寄存器组成如下:

①、34 个通用寄存器,包括 R15 程序计数器(PC),这些寄存器都是 32 位的。

②、8 个状态寄存器,包括 CPSR 和 SPSR。

③、Hyp 模式下独有一个 ELR_Hyp 寄存器。

2.1、通用寄存器

R0~R15 就是通用寄存器,通用寄存器可以分为以下三类:

①、未备份寄存器,即 R0~R7。 ②、备份寄存器,即 R8~R14。 ③、程序计数器 PC,即 R15。

2.1.1 未备份寄存器

未备份寄存器指的是 R0~R7 这 8 个寄存器,因为在所有的处理器模式下这 8 个寄存器都是 同一个物理寄存器,在不同的模式下,这 8 个寄存器中的数据就会被破坏。所以这 8 个寄存器 并没有被用作特殊用途。

2.1.2 备份寄存器

备份寄存器中的 R8~R12 这 5 个寄存器有两种物理寄存器,在快速中断模式下(FIQ)它们对 应着 Rx_irq(x=8~12)物理寄存器,其他模式下对应着 Rx(8~12)物理寄存器。FIQ 是快速中断模 式,看名字就是知道这个中断模式要求快速执行! FIQ 模式下中断处理程序可以使用 R8~R12 寄存器,因为 FIQ 模式下的 R8~R12 是独立的,因此中断处理程序可以不用执行保存和恢复中 断现场的指令,从而加速中断的执行过程。

备份寄存器 R13 一共有 8 个物理寄存器,其中一个是用户模式(User)和系统模式(Sys)共用 的,剩下的 7 个分别对应 7 种不同的模式。R13 也叫做 SP,用来做为栈指针。基本上每种模式 都有一个自己的 R13 物理寄存器,应用程序会初始化 R13,使其指向该模式专用的栈地址,这 就是常说的初始化 SP 指针。

备份寄存器 R14 一共有 7 个物理寄存器,其中一个是用户模式(User)、系统模式(Sys)和超 级监视模式(Hyp)所共有的,剩下的 6 个分别对应 6 种不同的模式。R14 也称为连接寄存器(LR), LR 寄存器在 ARM 中主要用作如下两种用途:

①、每种处理器模式使用 R14(LR)来存放当前子程序的返回地址,如果使用 BL 或者 BLX 来调用子函数的话,R14(LR)被设置成该子函数的返回地址,在子函数中,将 R14(LR)中的值赋 给 R15(PC)即可完成子函数返回,比如在子程序中可以使用如下代码:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

②、当异常发生以后,该异常模式对应的 R14 寄存器被设置成该异常模式将要返回的地址, R14 也可以当作普通寄存器使用。

2.1.3 程序计数器 R15

程序计数器 R15 也叫做 PC,R15 保存着当前执行的指令地址值加 8 个字节,这是因为 ARM 的流水线机制导致的。ARM 处理器 3 级流水线:取指->译码->执行,这三级流水线循环执行, 比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放 在 R15(PC)中。我们喜欢以当前正在执行的指令作为参考点,也就是以第一条指令为参考点, 那么 R15(PC)中存放的就是第三条指令,换句话说就是 R15(PC)总是指向当前正在执行的指令 地址再加上 2 条指令的地址。对于 32 位的 ARM 处理器,每条指令是 4 个字节,所以: R15 (PC)值 = 当前执行的程序位置 + 8 个字节。

2.2 程序状态寄存器

所有的处理器模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问。 CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志 等一些状态位以及一些控制位。所有的处理器模式都共用一个 CPSR 必然会导致冲突,为此, 除了 User 和 Sys 这两个模式以外,其他 7 个模式每个都配备了一个专用的物理状态寄存器,叫 做 SPSR(备份程序状态寄存器),当特定的异常中断发生时,SPSR 寄存器用来保存当前程序状 态寄存器(CPSR)的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。

因为 User 和 Sys 这两个模式不是异常模式,所以并没有配备 SPSR,因此不能在 User 和 Sys 模式下访问 SPSR,会导致不可预知的结果。由于 SPSR 是 CPSR 的备份,因此 SPSR 和 CPSR 的寄存器结构相同,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三、ARM 汇编基础

Cortex-A 芯片一上电 SP 指针还没初始化,C 环境还没准备 好,所以肯定不能运行 C 代码,必须先用汇编语言设置好 C 环境,比如初始化 DDR、设置 SP 指针等等,当汇编把 C 环境设置好了以后才可以运行 C 代码。所以 Cortex-A 一开始肯定是汇 编代码,其实 STM32 也一样的,一开始也是汇编,以 STM32F103 为例,启动文件 startup_stm32f10x_hd.s 就是汇编文件。

对于 Cortex-A 芯片来讲,大部分芯片在上电以后 C 语言环境还没准备好,所以第一行程序 肯定是汇编的,至于要写多少汇编程序,那就看你能在哪一步把 C 语言环境准备好。所谓的 C 语言环境就是保证 C 语言能够正常运行。C 语言中的函数调用涉及到出栈入栈,出栈入栈就要 对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由 SP 指针访问,SP 指 针指向栈顶。芯片一上电 SP 指针还没有初始化,所以 C 语言没法运行,对于有些芯片还需要 初始化 DDR,因为芯片本身没有 RAM,或者内部 RAM 不开放给用户使用,用户代码需要在 DDR 中运行,因此一开始要用汇编来初始化 DDR 控制器。

1、GNU汇编语法

我们要编写的是 ARM 汇编,编译使用的 GCC 交叉编译器,所以我们的汇编代码要符合 GNU 语法。

GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成, 每行一条语句,每条语句有三个可选部分,如下:

label:instruction @ comment

label 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到 指令的地址,标号也可以用来表示数据地址。注意 label 后面的“:”,任何以“:”结尾的标识 符都会被识别为一个标号。 instruction 即指令,也就是汇编指令或伪指令。 @符号,表示后面的是注释,就跟 C 语言里面的“/”和“/”一样,其实在 GNU 汇编文 件中我们也可以使用“/”和“/”来注释。 comment 就是注释内容。

比如如下代码:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面代码中“add:”就是标号,“MOVS R0,#0X12”就是指令,最后的“@设置 R0=0X12”就是 注释。

ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用 小写,但是不能大小写混用。

用户可以使用.section 伪操作来定义一个段。

.text 表示代码段。.data 初始化的数据段。.bss 未初始化的数据段。 .rodata 只读数据段。

可以自己使用.section 来定义一个段,每个段以段名开始,以下一段名或者文件结 尾结束

常见的伪操作有:

.byte 定义单字节数据,比如.byte 0x12。 .short 定义双字节数据,比如.short 0x1234。 .long 定义一个 4 字节数据,比如.long 0x12345678。 .equ 赋值语句,格式为:.equ 变量名,表达式,比如.equ num, 0x12,表示 num=0x12。 .align 数据字节对齐,比如:.align 4 表示 4 字节对齐。 .end 表示源文件结束。 .global 定义一个全局符号,格式为:.global symbol,比如:.global _start。

GNU 汇编同样也支持函数,函数格式如下:

函数名:

函数体 

​ 返回语句

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

“Undefined_Handler”就是函数名,“ldr r0, =Undefined_Handler”是函数体,“bx r0”是函数 返回语句,“bx”指令是返回指令,函数返回语句不是必须的。

2、Cortex-A7 常用汇编指令

2.1、 处理器内部数据传输指令

使用处理器做的最多事情就是在处理器内部来回的传递数据,常见的操作有:

①、将数据从一个寄存器传递到另外一个寄存器。

②、将数据从一个寄存器传递到特殊寄存器,如 CPSR 和 SPSR 寄存器。

③、将立即数传递到寄存器。 数据传输常用的指令有三个:MOV、MRS 和 MSR,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2 存储器访问指令

ARM 不能直接访问存储器,比如 RAM 中的数据,I.MX6UL 中的寄存器就是 RAM 类型 的,我们用汇编来配置 I.MX6UL 寄存器的时候需要借助存储器访问指令,一般先将要配置的值 写入到 Rx(x=0~12)寄存器中,然后借助存储器访问指令将 Rx 中的数据写入到 I.MX6UL 寄存器中。读取 I.MX6UL 寄存器也是一样的,只是过程相反。常用的存储器访问指令有两种:LDR 和 STR。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3、 压栈和出栈指令

通常会在 A 函数中调用 B 函数,当 B 函数执行完以后再回到 A 函数继续执行。要想 再跳回 A 函数以后代码能够接着正常运行,那就必须在跳到 B 函数之前将当前处理器状态保存 起来(就是保存 R0~R15 这些寄存器值),当 B 函数执行完成以后再用前面保存的寄存器值恢复 R0~R15 即可。保存 R0~R15 寄存器的操作就叫做现场保护,恢复 R0~R15 寄存器的操作就叫做 恢复现场。在进行现场保护的时候需要进行压栈(入栈)操作,恢复现场就要进行出栈操作。压栈 的指令为 PUSH,出栈的指令为 POP,PUSH 和 POP 是一种多存储和多加载指令,即可以一次 操作多个寄存器数据,他们利用当前的栈指针 SP 来生成地址。

假如我们现在要将 R0~R3 和 R12 这 5 个寄存器压栈,当前的 SP 指针指向 0X80000000,处理器的堆栈是向下增长的,使用的汇编代码如下:PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假如我们现在要再将 LR 进行压栈,汇编代码如下:PUSH {LR} @将 LR 进行压栈

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们要出栈的话 就是使用如下代码:

POP {LR} @先恢复 LR

POP {R0~R3,R12} @在恢复 R0~R3,R12

出栈的就是从栈顶,也就是 SP 当前执行的位置开始,地址依次减小来提取堆栈中的数据 到要恢复的寄存器列表中。

PUSH 和 POP 的另外一种写法是“STMFD SP!”和“LDMFD SP!”,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.4、跳转指令

有多种跳转操作,比如: ①、直接使用跳转指令 B、BL、BX 等。 ②、直接向 PC 寄存器里面写入数据。 上述两种方法都可以完成跳转操作,但是一般常用的还是 B、BL 或 BX,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.4.1 、B指令

B 指令会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指 令,ARM 处理器就会立即跳转到指定的目标地址。如果要调用的函数不会再返回到原来的执行 处,那就可以用 B 指令

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上述代码就是典型的在汇编中初始化 C 运行环境,然后跳转到 C 文件的 main 函数中运行,

2.4.2、BL指令

BL 指令相比 B 指令,在跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值,所以可以 通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行,这是子程序调用 一个基本但常用的手段。比如 Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编 来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存 在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到 irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用 B 指令了,因为 B 指令一旦跳转就再也不会回来了,这个时候要使用 BL 指令。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.5、算术运算指令

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.6、逻辑运算指令

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四、汇编LED灯实验

使用库函数来初始化 STM32 的一个 IO 为输出功能,代 码中重点要做的事情有以下几个:

①、使能指定 GPIO 的时钟。

②、初始化 GPIO,比如输出功能、上拉、速度等等。

③、STM32 有的 IO 可以作为其它外设引脚,也就是 IO 复用,如果要将 IO 作为其它外设 引脚使用的话就需要设置 IO 的复用功能。

④、最后设置 GPIO 输出高电平或者低电平。

1、I.MX6U IO 命名

I.MX6ULL 的 IO 分为两类:SNVS 域的和通用的,这两类 IO 本 质上都是一样的。

形如“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”的就是 GPIO 命名,命 名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是 GPIO 命名, 比如:GPIO1_IO01、UART1_TX_DATA、JTAG_MOD 等等。I.MX6ULL 的 GPIO 并不像 STM32 一样以 PA0~15 这样命名,他是根据某个 IO 所拥有的功能来命名的。

I.MX6U 的 GPIO 一共有 5 组:GPIO1、GPIO2、GPIO3、GPIO4 和 GPIO5, 其中 GPIO1 有 32 个 IO,GPIO2 有 22 个 IO,GPIO3 有 29 个 IO、GPIO4 有 29 个 IO,GPIO5 最少,只有 12 个 IO,这样一共有 124 个 GPIO。

2、I.MX6U IO 配置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

HYS(bit16):对应图 8.1.4.2 中 HYS,用来使能迟滞比较器,当 IO 作为输入功能的时候有 效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使 能此位。此位为 0 的时候禁止迟滞比较器,为 1 的时候使能迟滞比较器。

PUS(bit15:14):对应图 8.1.4.2 中的 PUS,用来设置上下拉电阻的,一共有四种选项可以选 择,如表 8.1.4.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PUE(bit13):图 8.1.4.2 没有给出来,当 IO 作为输入的时候,这个位用来设置 IO 使用上下 拉还是状态保持器。当为 0 的时候使用状态保持器,当为 1 的时候使用上下拉。状态保持器在 IO 作为输入的时候才有用,顾名思义,就是当外部电路断电以后此 IO 口可以保持住以前的状 态。

PKE(bit12):对应图 8.1.4.2 中的 PKE,此位用来使能或者禁止上下拉/状态保持器功能,为 0 时禁止上下拉/状态保持器,为 1 时使能上下拉和状态保持器。

ODE(bit11):对应图 8.1.4.2 中的 ODE,当 IO 作为输出的时候,此位用来禁止或者使能开 路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能。

SPEED(bit7:6):对应图 8.1.4.2 中的 SPEED,当 IO 用作输出的时候,此位用来设置 IO 速 度,设置如表 8.1.4.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DSE(bit5:3):对应图 8.1.4.2 中的 DSE,当 IO 用作输出的时候用来设置 IO 的驱动能力, 总共有 8 个可选选项,如表 8.1.4.3 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SRE(bit0):对应图 8.1.4.2 中的 SRE,设置压摆率,当此位为 0 的时候是低压摆率,当为 1 的时候是高压摆率。这里的压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时 间,时间越小波形就越陡,说明压摆率越高;反之,时间越多波形就越缓,压摆率就越低。如 果你的产品要过 EMC 的话那就可以使用小的压摆率,因为波形缓和,如果你当前所使用的 IO 做高速通信的话就可以使用高压摆率。

3、I.MX6U GPIO 配置

GPIO 是一个 IO 众多复用功能中的一种,比 如 GPIO1_IO00 这个 IO 可以复用为:I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID、 ENET1_REF_CLK 、 MQS_RIGHT 、 GPIO1_IO00 、 ENET1_1588_EVENT0_IN 、 SRC_SYSTEM_RESET 和 WDOG3_WDOG_B 这 9 个功能,GPIO1_IO00 是其中的一种,我们 想要把 GPIO1_IO00 用作哪个外设就复用为哪个外设功能即可。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

左上角部分的 GPIO 框图就是,当 IO 用作 GPIO 的时候需要设置的寄存器,一共有八个: DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR。

1、DR 寄存器,此寄存器是数据寄存器:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此寄存器是 32 位的,一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应 一个 GPIO。当 GPIO 被配置为输出功能以后,向指定的位写入数据那么相应的 IO 就会输出相 应的高低电平,比如要设置 GPIO1_IO00 输出高电平,那么就应该设置 GPIO1.DR=1。当 GPIO被配置为输入模式以后,此寄存器就保存着对应 IO 的电平值,每个位对对应一个 GPIO,例如, 当 GPIO1_IO00 这个引脚接地的话,那么 GPIO1.DR 的 bit0 就是 0。

2、GDIR 寄存器,这是方向寄存器:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GDIR 寄存器也是 32 位的,此寄存器用来设置某个 IO 的工作方向,是输入还是输出。同 样的,每个 IO 对应一个位,如果要设置 GPIO 为输入的话就设置相应的位为 0,如果要设置为 输出的话就设置为 1。比如要设置 GPIO1_IO00 为输入,那么 GPIO1.GDIR=0;

3、PSR 寄存器,这是 GPIO 状态寄存器:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状 态,也就是 GPIO 的高低电平值。功能和输入状态下的 DR 寄存器一样。

4、ICR1和ICR2这两个寄存器,都是中断控制寄存器,

ICR1用于配置低16个GPIO, ICR2 用于配置高 16 个 GPIO。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ICR1 用于 IO0~15 的配置, ICR2 用于 IO16~31 的配置。ICR1 寄存器中一个 GPIO 用两个 位,这两个位用来配置中断的触发方式,和 STM32 的中断很类似。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5、 IMR 寄存器,这是中断屏蔽寄存器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

IMR 寄存器也是一个 GPIO 对应一个位,IMR 寄存器用来控制 GPIO 的中断禁止和使能, 如果使能某个 GPIO 的中断,那么设置相应的位为 1 即可,反之,如果要禁止中断,那么就设 置相应的位为 0 即可。

6、寄存器 ISR,ISR 是中断状态寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ISR 寄存器也是 32 位寄存器,一个 GPIO 对应一个位,只要某个 GPIO 的中断发生,那么 ISR 中相应的位就会被置 1。所以,我们可以通过读取 ISR 寄存器来判断 GPIO 中断是否发生, 相当于 ISR 中的这些位就是中断标志位。当我们处理完中断以后,必须清除中断标志位,清除 方法就是向 ISR 中相应的位写 1,也就是写 1 清零。

7、EDGE_SEL 寄存器,这是边沿选择寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

EDGE_SEL 寄存器用来设置边沿中断,这个寄存器会覆盖 ICR1 和 ICR2 的设置,同样是一 个 GPIO 对应一个位。如果相应的位被置 1,那么就相当与设置了对应的 GPIO 是上升沿和下降 沿(双边沿)触发。

4、I.MX6U GPIO 时钟使能

STM32 的每个外设都有 一个外设时钟,GPIO 也不例外,要使用某个外设,必须要先使能对应的时钟。I.MX6U 其实也 一样的,每个外设的时钟都可以独立的使能或禁止,这样可以关闭掉不使用的外设时钟,起到 省电的目的。

CMM 有 CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CCM_CCGR0 是个 32 位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着 GPIO2 的外设时钟,两个位就有 4 种操作方式,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要将 I.MX6U 的 IO 作为 GPIO 使用,我们需要一下 几步:

①、使能 GPIO 对应的时钟。 ②、设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用 为 GPIO 功能。 ③、设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等。 ④、第②步已经将 IO 复用为了 GPIO 功能,所以需要配置 GPIO,设置输入/输出、是否使 用中断、默认输出电平等。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5、实验程序编写

1、使能 GPIO1 时钟

2、设置 GPIO1_IO03 的复用功能

3、配置 GPIO1_IO03

4、设置 GPIO

5、控制 GPIO 的输出电平

.global _start
_start:




ldr r0, =0X020C4070
ldr r1,=0xffffffff
str r1, [r0]

ldr r0,=0x020e0068;
ldr r1,=0x5
str r1,[r0]

ldr r0,=0x020e02f4
ldr r1,=0x10b0
str r1,[r0]

ldr r0, =0X0209C004
ldr r1, =0X0000008
str r1,[r0]

ldr r0, =0X0209C000
ldr r1, =0
str r1,[r0]

loop:
    b loop

5.1、arm-linux-gnueabihf-gcc 编译文件

因为本试验就一个 led.s 源文件,所以编译比较简 单。先将 led.s 编译为对应的.o 文件,在终端中输入如下命令:

arm-linux-gnueabihf-gcc -g -c led.s -o led.o

上述命令就是将 led.s 编译为 led.o,其中“-g”选项是产生调试信息,GDB 能够使用这些 调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文 件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o。执行上述命令以后就会编译生 成一个 led.o 文件,

led.o 文件并不是我们可以下载到开发板中运行的文件,一个工程中所有的 C 文件和汇编文件都会编译生成一个对应的.o 文件,我们需要将这.o 文件链接起来组合成可执行 文件。

5.2、arm-linux-gnueabihf-ld 链接文件

arm-linux-gnueabihf-ld 用来将众多的.o 文件链接到一个指定的链接位置。我们在学习 SMT32 的时候基本就没有听过“链接”这个词,我们一般用 MDK 编写好代码,然后点击“编 译”,MDK 或者 IAR 就会自动帮我们编译好整个工程,最后再点击“下载”就可以将代码下载 到开发板中。这是因为链接这个操作 MDK 或者 IAR 已经帮你做好了,后面我就以 MDK 为例 给大家讲解。

我们现在需要做的就是确定一下本试验最终的可执行文件其运行起始地址,也就是 链接地址。这里我们要区分“存储地址”和“运行地址”这两个概念,“存储地址”就是可执 行文件存储在哪里,可执行文件的存储地址可以随意选择。“运行地址”就是代码运行的时候 所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址 处,否则代码肯定运行出错。比如 I.MX6U 支持 SD 卡、EMMC、NAND 启动,因此代码可以 存储到 SD 卡、EMMC 或者 NAND 中,但是要运行的话就必须将代码从 SD 卡、EMMC 或者 NAND 中拷贝到其运行地址(链接地址)处,“存储地址”和“运行地址”可以一样,比如 STM32 的存储起始地址和运行起始地址都是 0X08000000。

本教程所有的裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将 可执行文件拷贝到链接地址处,这个链接地址可以在 I.MX6U 的内部 128KB RAM 中 (0X900000~0X91FFFF),也可以在外部的 DDR 中。本教程所有裸机例程的链接地址都在 DDR 中,链接起始地址为 0X87800000。I.MX6U-ALPHA 开发板的 DDR 容量有两种:512MB 和 256MB,起始地址都为 0X80000000,只不过 512MB 的终止地址为 0X9FFFFFFF,而 256MB 容 量的终止地址为 0X8FFFFFFF。之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其 链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址,不容易记混。

确定了链接地址以后就可以使用 arm-linux-gnueabihf-ld 来将前面编译出来的 led.o 文件链 接到 0X87800000 这个地址,使用如下命令:

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名 为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件

led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还 需要将 led.elf 文件转换为.bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具 了。

5.3、arm-linux-gnueabihf-objcopy 格式转换

arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为 led.bin 文件,命令如下: arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出, 选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试 信息。

5.4、arm-linux-gnueabihf-objdump 反汇编

大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码, 因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:

arm-linux-gnueabihf-objdump -D led.elf > led.dis

上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一 个名为 led.dis 文件。

5.5 创建 Makefile 文件

如果我们修改了 led.s 文件,那么就需要在重复一次上面的这些命令,太麻烦了,这个时候 我们就可以使用第三章讲解的 Makefile 文件了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6、代码烧写

STM32 等其他的单片机的时候,编译完代码以后可以直接通过 MDK 或者 IAR 下载到内部的 flash 中。但是 I.MX6U 虽然内部有 96K 的 ROM,但是这 96K 的 ROM 是 NXP 自己用的,不向用户开放。所以相当于说 I.MX6U 是没有内部 flash 的,但是我们的代码得有地 方存放啊,为此,I.MX6U 支持从外置的 NOR Flash、NAND Flash、SD/EMMC、SPI NOR Flash 和 QSPI Flash 这些存储介质中启动,所以我们可以将代码烧写到这些存储介质中中。在这些存 储介质中,除了 SD 卡以外,其他的一般都是焊接到了板子上的,我们没法直接烧写。但是 SD 卡是活动的,是可以从板子上插拔的,我们可以将 SD 卡插到电脑上,在电脑上使用软件将.bin 文件烧写到 SD 卡中,然后再插到板子上就可以了。其他的几种存储介质是我们量产的时候用 到的,量产的时候代码就不可能放到 SD 卡里面了,毕竟 SD 是活动的,不牢固,而其他的都是 焊接到板子上的,很牢固。

因此,我们在调试裸机和 Uboot 的时候是将代码下载到 SD 中,因为方便嘛,当调试完成 以后量产的时候要将裸机或者 Uboot 烧写到 SPI NOR Flash、EMMC、NAND 等这些存储介质 中的。

五、I.MX6U 启动方式详解

1、启动方式选择

BOOT 的处理过程是发生在 I.MX6U 芯片上电以后,芯片会根据 BOOT_MODE[1:0]的设置 来选择 BOOT 方式。BOOT_MODE[1:0]的值是可以改变的,有两种方式,一种是改写 eFUSE(熔 丝),一种是修改相应的 GPIO 高低电平。第一种修改 eFUSE 的方式只能修改一次,后面就不能 再修改了,所以我们不使用。我们使用的是通过修改 BOOT_MODE[1:0]对应的 GPIO 高低电平 来选择启动方式,所有的开发板都使用的这种方式,I.MX6U 有一个 BOOT_MODE1 引脚和 BOOT_MODE0 引脚,这两个引脚对应这 BOOT_MODE[1:0]。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中 BOOT_MODE1 和 BOOT_MODE0 在芯片内部是有 100KΩ下拉电阻的,所以默认是 0。BOOT_MODE1 和 BOOT_MODE0 这两个引脚我们也接到了底板的拨码开关上,这样我们 就可以通过拨码开关来控制 BOOT_MODE1 和 BOOT_MODE0 的高低电平。

BOOT_MODE1 和 BOOT_MODE0 在芯片内部是有 100KΩ下拉电阻的,所以默认是 0。BOOT_MODE1 和 BOOT_MODE0 这两个引脚我们也接到了底板的拨码开关上,这样我们 就可以通过拨码开关来控制 BOOT_MODE1 和 BOOT_MODE0 的高低电平。以 BOOT_MODE1 为例,当我们把 BOOT_CFG 的第一个开关拨到“ON”的时候,就相当于 BOOT_MODE1 引脚 通过 R88 这个 10K 电阻接到了 3.3V 电源,芯片内部的 BOOT_MODE1 又是 100K 下拉电阻接 地,因此此时 BOOT_MODE1 的电压就是 100/(10+100)*3.3V= 3V,这是个高电平,因此 BOOT_CFG 的中的 8 个开关拨到“ON”就是高电平,拨到“OFF”就是低电平。

I.MX6U 有四个 BOOT 模式,这四个 BOOT 模式由 BOOT_MODE[1:0]来控制,也就是 BOOT_MODE1 和 BOOT_MODE0 这两 IO。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.1 、串行下载

当 BOOT_MODE1 为 0,BOOT_MODE0 为 1 的时候此模式使能,串行下载的意思就是可 以通过 USB 或者 UART 将代码下载到板子上的外置存储设备中,我们可以使用 OTG1 这个 USB 口向开发板上的 SD/EMMC、NAND 等存储设备下载代码。我们需要将 BOOT_MODE1 拨到 “OFF”,将 BOOT_MODE0 拨到“ON”。这个下载是需要用到 NXP 提供的一个软件,一般用来最终量产的时候将代码烧写到外置存储设备中的,我们后面讲解如何使用。

1.2、 内部 BOOT 模式

当 BOOT_MODE1 为 1,BOOT_MODE0 为 0 的时候此模式使能,在此模式下,芯片会执 行内部的 boot ROM 代码,这段 boot ROM 代码会进行硬件初始化(一部分外设),然后从 boot 设 备(就是存放代码的设备、比如 SD/EMMC、NAND)中将代码拷贝出来复制到指定的 RAM 中, 一般是 DDR。

2、BOOT ROM 初始化内容

当我们设置 BOOT 模式为“内部 BOOT 模式”以后,I.MX6U 内部的 boot ROM 代码就会 执行,这个 boot ROM 代码都会做什么处理呢?首先肯定是初始化时钟,boot ROM 设置的系统 时钟如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在图中 BT_FREQ 模式为 0,可以看到,boot ROM 会将 I.MX6U 的内核时钟设置为 396MHz,也就是主频为 396Mhz。System PLL=528Mhz,USB PLL=480MHz,AHB=132MHz, IPG=66MHz。

内部 boot ROM 为了加快执行速度会打开 MMU 和 Cache,下载镜像的时候 L1 ICache 会打 开,验证镜像的时候 L1 DCache、L2 Cache 和 MMU 都会打开。一旦镜像验证完成,boot ROM 就会关闭 L1 DCache、L2 Cache 和 MMU。 中断向量偏移会被设置到 boot ROM 的起始位置,当 boot ROM 启动了用户代码以后就可 以重新设置中断向量偏移了。一般是重新设置到我们用户代码的开始地方。

3、启动设备

当 BOOT_MODE 设置为内部 BOOT 模式以后,可以从以下设备中启动:

①、接到 EIM 接口的 CS0 上的 16 位 NOR Flash。

②、接到 EIM 接口的 CS0 上的 OneNAND Flash。

③、接到 GPMI 接口上的 MLC/SLC NAND Flash,NAND Flash 页大小支持 2KByte、4KByte 和 8KByte,8 位宽。

④、Quad SPI Flash。

⑤、接到 USDHC 接口上的 SD/MMC/eSD/SDXC/eMMC 等设备。

⑥、SPI 接口的 EEPROM。

I.MX6U 同样提供了 eFUSE 和 GPIO 配置两种,eFUSE 就不讲 解了。我们重点看如何通过 GPIO 来选择启动设备,因为所有的 I.MX6U 开发板都是通过 GPIO 来配置启动设备的。正如启动模式由 BOOT_MODE[1:0]来选择一样,启动设备是通过BOOT_CFG1[7:0]、BOOT_CFG2[7:0]和 BOOT_CFG4[7:0]这 24 个配置 IO,这 24 个配置 IO 刚 好对应着 LCD 的 24 根数据线 LCD_DATA0~LCDDATA23,当启动完成以后这 24 个 IO 就可以 作为 LCD 的数据线使用。这 24 根线和 BOOT_MODE1、BOOT_MODE0 共同组成了 I.MX6U 的启动选择引脚。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在图中大部分的 IO 都接地了,只有几个 IO 接高,尤其是 BOOT_CFG4[7:0] 这 8 个 IO 都 10K 电阻下拉接地,所以我们压根就不需要去关注 BOOT_CFG4[7:0]。我们需要 重点关注的就只剩下了 BOOT_CFG2[7:0]和 BOOT_CFG1[7:0]这 16 个 IO。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4、镜像烧写

学习 STM32 的时候我们可以直接将编译生成的.bin 文件烧写到 STM32 内部 flash 里面,但 是 I.MX6U 不能直接烧写编译生成的.bin 文件,我们需要在.bin 文件前面添加一些头信息构成 满足 I.MX6U 需求的最终可烧写文件,I.MX6U 的最终可烧写文件组成如下:

①、Image vector table,简称 IVT,IVT 里面包含了一系列的地址信息,这些地址信息在 ROM 中按照固定的地址存放着。

②、Boot data,启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少等等。

③、Device configuration data,简称 DCD,设备配置信息,重点是 DDR3 的初始化配置

④、用户代码可执行文件,比如 led.bin。

最终烧写到 I.MX6U 中的程序其组成为:IVT+Boot data+DCD+.bin。

所以第八章 中的 imxdownload 所生成的 load.imx 就是在 led.bin 前面加上 IVT+Boot data+DCD。内部 Boot ROM 会将 load.imx 拷贝到 DDR 中,用户代码是要一定要从 0X87800000 这个地方开始的,因 为链接地址为 0X87800000,load.imx 在用户代码前面又有 3KByte 的 IVT+Boot Data+DCD 数 据,下面会讲为什么是 3KByte,因此 load.imx 在 DDR 中的起始地址就是 0X87800000- 3072=0X877FF400。

4.1 IVT 和 Boot Data 数据

load.imx 最前面的就是 IVT 和 Boot Data,IVT 包含了镜像程序的入口点、指向 DCD 的指 针和一些用作其它用途的指针。内部 Boot ROM 要求 IVT 应该放到指定的位置,不同的启动设 备位置不同,而 IVT 在整个 load.imx 的最前面,其实就相当于要求 load.imx 在烧写的时候应该 烧写到存储设备的指定位置去。整个位置都是相对于存储设备的起始地址的偏移,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以 SD/EMMC 为例,IVT 偏移为 1Kbyte,IVT+Boot data+DCD 的总大小为 4KByte1KByte=3KByte。假如 SD/EMMC 每个扇区为 512 字节,那么 load.imx 应该从第三个扇区开始 烧写,前两个扇区要留出来。load.imx 从第 3KByte 开始才是真正的.bin 文件。那么 IVT 里面究 竟存放着什么东西呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从图 可以看到,第一个存放的就是 header(头),header 格式如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

,Tag 为一个字节长度,固定为 0XD1,Length 是两个字节,保存着 IVT 长 度,为大端格式,也就是高字节保存在低内存中。最后的 Version 是一个字节,为 0X40 或者0X41。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9.4.1.4 是我们截取的 load.imx 的一部分内容,从地址 0X00000000~0X000025F,共 608 个字节的数据。我们将前 44 个字节的数据按照 4 个字节一组组合在一起就是:0X402000D1、 0X87800000、0X00000000、0X877FF42C、0X877FF420、0X877FF400、0X00000000、0X00000000、 0X877FF000、0X00200000、0X00000000。这 44 个字节的数据就是 IVT 和 Boot Data 数据,按 照图 9.4.1.2 和图 9.4.1.4 所示的 IVT 和 Boot Data 所示的格式对应起来如表 9.4.1.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2、DCD数据

I.MX6U 提出了一个 DCD(Device Config Data)的概念,和 IVT、Boot Data 一样,DCD 也是添加到 load.imx 里面的,紧跟在 IVT 和 Boot Data 后面,IVT 里面也指定了 DCD 的位置。DCD 其实就是 I.MX6U 寄存器地址和对应 的配置信息集合,Boot ROM 会使用这些寄存器地址和配置集合来初始化相应的寄存器,比如 开启某些外设的时钟、初始化 DDR 等等。DCD 区域不能超过 1768Byte。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DCD 的 header 和 IVT 的 header 类似

其中 Tag 是单字节,固定为 0XD2,Length 为两个字节,表示 DCD 区域的大小,包含 header, 同样是大端模式,Version 是单字节,固定为 0X40 或者 0X41。

图 9.4.2.1 中的 CMD 就是要初始化的寄存器地址和相应的寄存器值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Tag 为一个字节,固定为 0XCC。Length 是两个字节,包含写入的命令数据长 度,包含 header,同样是大端模式。Parameter 为一个字节,这个字节的每个位含义如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

bytes 表示是目标位置宽度,单位为 byte,可以选择 1、2、和 4 字节。flags 是命令控制标志位。

Address 和 Vlalue/Mask 就是要初始化的寄存器地址和相应的寄存器值,注 意采用的是大端模式!DCD 结构就分析到这里,在分析 IVT 的时候我们就已经说过了,DCD 数据是从图 9.4.1.4 的 0X2C 地址开始的。

六、C语言版LED灯实验

我们有两部分文件要做:

①、汇编文件 汇编文件只是用来完成 C 语言环境搭建。

②、C 语言文件 C 语言文件就是完成我们的业务层代码的,其实就是我们实际例程要完成的功能

.global _start
_start:
    mrs r0,cpsr
    bic r0,r0,#0x1f
    orr r0,r0,#0x13
    msr cpsr, r0
    ldr sp, =0X80200000
    b main

此汇编部分程序执行完成,就几行代码,用来设置处理器运行到 SVC 模式下、然后初始 化 SP 指针、最终跳转到 C 文件的 main 函数中。

DCD 数据包含了 DDR 配置 参数,I.MX6U 内部的 Boot ROM 会读取 DCD 数据中的 DDR 配置参数然后完成 DDR 初始化 的。

在 I.MX6U 工作 在 396MHz(Boot ROM 设 置的 396MHz)的 主 频 的 时候 delay_short(0x7ff)基本能够实现大约 1ms 的延时,所以 delay()函数我们可以用来完成 ms 延时。

1、 链接脚本

arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^

上面语句中我们是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件 都会链接到以 0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区 域,或者叫做段里面,比如在 Linux 里面初始化函数就会放到 init 段里面。因此我们需要能够 自定义一些段,这些段的起始地址我们可以自由指定,同样的我们也可以指定一个文件或者函 数应该存放到哪个段里面去。要完成这个功能我们就需要使用到链接脚本,看名字就知道链接 脚本主要用于链接的,用于描述文件应该如何被链接在一起形成最终的可执行文件。其主要目 的是描述输入文件中的段如何被映射到输出文件中,并且控制输出文件中的内存排布。比如我 们编译生成的文件一般都包含 text 段、data 段等等。

链接脚本的语法很简单,就是编写一系列的命令,这些命令组成了链接脚本,每个命令是 一个带有参数的关键字或者一个对符号的赋值,可以使用分号分隔命令。像文件名之类的字符 串可以直接键入,也可以使用通配符“*”。最简单的链接脚本可以只包含一个命令“SECTIONS”, 我们可以在这一个“SECTIONS”里面来描述输出文件的内存布局。我们一般编译出来的代码 都包含在 text、data、bss 和 rodata 这四个段内,假设现在的代码要被链接到 0X10000000 这个 地址,数据要被链接到 0X30000000 这个地方,下面就是完成此功能的最简单的链接脚本:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第 1 行我们先写了一个关键字“SECTIONS”,后面跟了一个大括号,这个大括号和第 7 行 的大括号是一对,这是必须的。看起来就跟 C 语言里面的函数一样。

第 2 行对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位 计数器为 0。我们要求代码链接到以 0X10000000 为起始地址的地方,因此这一行给“.”赋值0X10000000,表示以 0X10000000 开始,后面的文件或者段都会以 0X10000000 为起始地址开 始链接。

第 3 行的“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链 接到“.text”这个段里面的所有文件,“(.text)”中的“”是通配符,表示所有输入文件的.text 段都放到“.text”中。

第 4 行,我们的要求是数据放到 0X30000000 开始的地方,所以我们需要重新设置定位计 数器“.”,将其改为 0X30000000。如果不重新设置的话会怎么样?假设“.text”段大小为 0X10000, 那么接下来的.data 段开始地址就是 0X10000000+0X10000=0X10010000,这明显不符合我们的 要求。所以我们必须调整定位计数器为 0X30000000。

第 5 行跟第 3 行一样,定义了一个名为“.data”的段,然后所有文件的“.data”段都放到 这里面。但是这一行多了一个“ALIGN(4)”,这是什么意思呢?这是用来对“.data”这个段的起 始地址做字节对齐的,ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整 除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐。

第 6 行定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数 据就是那些定义了但是没有被初始化的变量。 上面就是链接脚本最基本的语法格式,我们接下

七、模仿 STM32 驱动开发格式实验

1、I.MX6U 寄存器定义

先将同属于一个外设的所有寄存器编写到一个结构体里面

使用“volatile”进行了修饰,目的是防止编 译器优化。

2、定义 IO 复用寄存器组的基地址

3、定义访问指针

八、官方 SDK 移植实验

1、I.MX6ULL 官方 SDK 包简介

IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);

IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0X10B0);

IOMUXC_SetPinMux 是 用 来 设 置 IO 复 用 功 能 的 , 最 终 肯 定 设 置 的 是 寄 存 器 “IOMUXC_SW_MUX_CTL_PAD_XX”。

函数 IOMUXC_SetPinConfig 设置的是 IO 的上下拉、 速度等的,也就是寄存器“IOMUXC_SW_PAD_CTL_PAD_XX

函数 IOMUXC_SetPinMux 在文件 fsl_iomuxc.h 中定义,函数源码如下:

static inline void IOMUXC_SetPinMux(uint32_t muxRegister,

                                    uint32_t muxMode,

                                    uint32_t inputRegister,

                                    uint32_t inputDaisy,

                                    uint32_t configRegister,

                                    uint32_t inputOnfield)

{

    *((volatile uint32_t *)muxRegister) =

        IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) | IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);



    if (inputRegister)

    {

        *((volatile uint32_t *)inputRegister) = IOMUXC_SELECT_INPUT_DAISY(inputDaisy);

    }

}

函数 IOMUXC_SetPinMux 有 6 个参数,这 6 个参数的函数如下:

muxRegister : IO 的 复 用 寄 存 器 地 址 , 比 如 GPIO1_IO03 的 IO 复 用 寄 存 器 SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。

muxMode: IO 复用值,也就是 ALT0~ALT8,对应数字 0~8,比如要将 GPIO1_IO03 设置 为 GPIO 功能的话此参数就要设置为 5

inputRegister:外设输入 IO 选择寄存器地址,有些 IO 在设置为其他的复用功能以后还需 要设置 IO 输入寄存器,比如 GPIO1_IO03 要复用为 UART1_RX 的话还需要设置寄存器 UART1_RX_DATA_SELECT_INPUT,此寄存器地址为 0X020E0624。

inputDaisy:寄存器 inputRegister 的值,比如 GPIO1_IO03 要作为 UART1_RX 引脚的话此 参数就是 1。

configRegister:未使用,函数 IOMUXC_SetPinConfig 会使用这个寄存器。

inputOnfield : IO 软 件 输 入 使 能 , 以 GPIO1_IO03 为 例 就 是 寄 存 器 SW_MUX_CTL_PAD_GPIO1_IO03 的 SION 位(bit4)。如果需要使能 GPIO1_IO03 的软件输入功 能的话此参数应该为 1,否则的话就为 0。

九、BSP 工程管理实验

十、 蜂鸣器实验

十一、按键输入实验

I.MX6U 的 IO 不仅能作 为输出,而且也可以作为输入。

1、按键输入简介

按键就两个状态:按下或弹起,将按键连接到一个 IO 上,通过读取这个 IO 的值就知道按 键是按下的还是弹起的。至于按键按下的时候是高电平还是低电平要根据实际电路来判断。当 GPIO 连接按键的时候就要做为输入 使用。

十二、主频和时钟配置实验

默认配置下 I.MX6U 工作频率为 396MHz。但是 I.MX6U 系列标准的工作频率为 528MHz,有些 型号甚至可以工作到 696MHz。

1 、I.MX6U 时钟系统详解

1.1、系统时钟来源

I.MX6U-ALPHA 开发板的系统时钟来源于两部分:32.768KHz 和 24MHz 的晶振,其中 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源,24MHz 晶振是 I.MX6U 内核 和其它外设的时钟源,也是我们重点要分析的。

1.2、7 路 PLL 时钟源

I.MX6U 的外设有很多,不同的外设时钟源不同,NXP 将这些外设的时钟源进行了分组, 一共有 7 组,这 7 组时钟源都是从 24MHz 晶振 PLL 而来的,因此也叫做 7 组 PLL。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、 ARM_PLL(PLL1),此路 PLL 是供 ARM 内核使用的,ARM 内核时钟就是由此 PLL 生成的,此 PLL 通过编程的方式最高可倍频到 1.3GHz。

②、528_PLL(PLL2),此路 PLL 也叫做 System_PLL,此路 PLL 是固定的 22 倍频,不可编程修改因此,此路 PLL 时钟=24MHz * 22 = 528MHz,这也是为什么此 PLL 叫做 528_PLL 的 原因。此 PLL 分出了 4 路 PFD,分别为:PLL2_PFD0~PLL2_PFD3,这 4 路 PFD 和 528_PLL 共同作为其它很多外设的根时钟源。通常 528_PLL 和这 4 路 PFD 是 I.MX6U 内部系统总线的 时钟源,比如内处理逻辑单元、DDR 接口、NAND/NOR 接口等等。

③、USB1_PLL(PLL3),此路 PLL 主要用于 USBPHY,此 PLL 也有四路 PFD,为: PLL3_PFD0~PLL3_PFD3,USB1_PLL 是固定的 20 倍频,因此 USB1_PLL=24MHz *20=480MHz。 USB1_PLL虽然主要用于USB1PHY,但是其和四路PFD同样也可以作为其他外设的根时钟源。

④、USB2_PLL(PLL7,没有写错!就是 PLL7,虽然序号标为 4,但是实际是 PLL7),看名 字就知道此路PLL是给USB2PHY使用的。同样的,此路PLL固定为20倍频,因此也是480MHz。

⑤、ENET_PLL(PLL6),此路 PLL 固定为 20+5/6 倍频,因此 ENET_PLL=24MHz * (20+5/6) = 500MHz。此路 PLL 用于生成网络所需的时钟,可以在此 PLL 的基础上生成 25/50/100/125MHz 的网络时钟。

⑥、VIDEO_PLL(PLL5),此路 PLL 用于显示相关的外设,比如 LCD,此路 PLL 的倍频可以 调整,PLL 的输出范围在 650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频, 可选 1/2/4/8/16 分频。

⑦、AUDIO_PLL(PLL4),此路 PLL 用于音频相关的外设,此路 PLL 的倍频可以调整,PLL 的输出范围同样也是 650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选 1/2/4 分频。

1.3 、时钟树简介

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一共有三部分:CLOCK_SWITCHER、CLOCK ROOT GENERATOR 和 SYSTEM CLOCKS。其中左边的 CLOCK_SWITCHER 就是我们上一小节讲解的那 7 路 PLL 和 8 路 PFD,右边的 SYSTEM CLOCKS 就是芯片外设,中间的 CLOCK ROOT GENERATOR 是最 复杂的!这一部分就像“月老”一样,给左边的CLOCK_SWITCHER和右边的SYSTEM CLOCKS 进行牵线搭桥。外设时钟源是有多路可以选择的,CLOCK ROOT GENERATOR 就负责从 7 路 PLL 和 8 路 PFD 中选择合适的时钟源给外设使用。具体操作肯定是设置相应的寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、此部分是时钟源选择器,ESAI 有 4 个可选的时钟源:PLL4、PLL5、PLL3_PFD2 和 pll3_sw_clk 。 具 体 选 择 哪 一 路 作 为 ESAI 的时钟源是由寄存器 CCM->CSCMR2 的 ESAI_CLK_SEL 位来决定的,用户可以自由配置,配置如图 16.1.3.3 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

②、此部分是 ESAI 时钟的前级分频,分频值由寄存器 CCM_CS1CDR 的 ESAI_CLK_PRED 来确定的,可设置 1~8 分频,假如现在 PLL4=650MHz,我们选择 PLL4 作为 ESAI 时钟,前级 分频选择 2 分频,那么此时的时钟就是 650/2=325MHz。

③、此部分又是一个分频器,对②中输出的时钟进一步分频,分频值由寄存器 CCM_CS1CDR 的 ESAI_CLK_PODF 来决定,可设置 1~8 分频。假如我们设置为 8 分频的话, 经过此分频器以后的时钟就是 325/8=40.625MHz。因此最终进入到 ESAI 外设的时钟就是 40.625MHz。

1.4、内核时钟设置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、内核时钟源来自于 PLL1,假如此时 PLL1 为 996MHz。

②、通过寄存器 CCM_CACRR 的 ARM_PODF 位对 PLL1 进行分频,可选择 1/2/4/8 分频, 假如我们选择 2 分频,那么经过分频以后的时钟频率是 996/2=498MHz。

③、大家不要被此处的 2 分频给骗了,此处没有进行 2 分频(我就被这个 2 分频骗了好久, 主频一直配置不正确!)。

④、经过第②步 2 分频以后的 498MHz 就是 ARM 的内核时钟,也就是 I.MX6U 的主频。

经过上面几步的分析可知,假如我们要设置内核主频为 528MHz,那么 PLL1 可以设置为 1056MHz,寄存器 CCM_CACRR 的 ARM_PODF 位设置为 2 分频即可。同理,如果要将主频设 置为 696MHz,那么 PLL1 就可以设置为 696MHz,CCM_CACRR 的 ARM_PODF 设置为 1 分 频即可。现在问题很清晰了,寄存器 CCM_CACRR 的 ARM_PODF 位很好设置,PLL1 的频率 可以通过寄存器 CCM_ANALOG_PLL_ARMn 来设置。接下来详细的看一下 CCM_CACRR 和 CCM_ANALOG_PLL_ARMn 这两个寄存器,CCM_CACRR 寄存器结构如图 16.1.4.2 所示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 CCM_CACRR 只有 ARM_PODF 位,可以设置为 0~7,分别对应 1~8 分频。如果要 设置为2分频的话CCM_CACRR就要设置为1。再来看一下寄存器CCM_ANALOG_PLL_ARMn,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ENABLE: 时钟输出使能位,此位设置为 1 使能 PLL1 输出,如果设置为 0 的话就关闭 PLL1 输出。

DIV_SELECT: 此位设置 PLL1 的输出频率,可设置范围为:54~108,PLL1 CLK = Fin * div_seclec/2.0,Fin=24MHz。如果 PLL1 要输出 1056MHz 的话,div_select 就要设置为 88。

在修改 PLL1 时钟频率的时候我们需要先将内核时钟源改为其他的时钟源,PLL1 可选择的 时钟源如图 16.1.4.4 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、pll1_sw_clk 也就是 PLL1 的最终输出频率。

②、此处是一个选择器,选择 pll1_sw_clk 的时钟源,由寄存器 CCM_CCSR 的 PLL1_SW_CLK_SEL 位决定 pll1_sw_clk 是选择 pll1_main_clk 还是 step_clk。正常情况下应该 选择 pll1_main_clk,但是如果要对 pll1_main_clk(PLL1)的频率进行调整的话,比如我们要设置 PLL1=1056MHz,此时就要先将 pll1_sw_clk 切换到 step_clk 上。等 pll1_main_clk 调整完成以后 再切换回来。

③、此处也是一个选择器,选择 step_clk 的时钟源,由寄存器 CCM_CCSR 的 STEP_SEL 位 来决定 step_clk 是选择 osc_clk 还是 secondary_clk。一般选择 osc_clk,也就是 24MHz 的晶振。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 CCM_CCSR 我们只用到了 STEP_SEL、PLL1_SW_CLK_SEL 这两个位,一个是用 来选择 step_clk 时钟源的,一个是用来选择 pll1_sw_clk 时钟源的。 到这里,修改 I.MX6U 主频的步骤就很清晰了,修改步骤如下:

①、 设置寄存器 CCSR 的 STEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振。

②、设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为 step_clk=24MHz,通过这一步我们就将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的 24M 晶振。

③、设置寄存器 CCM_ANALOG_PLL_ARMn,将 pll1_main_clk(PLL1)设置为 1056MHz。

④、设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,重新将 pll1_sw_clk 的时钟源切换回 pll1_main_clk,切换回来以后的 pll1_sw_clk 就等于 1056MHz。

⑤、最后设置寄存器 CCM_CACRR 的 ARM_PODF 为 2 分频,I.MX6U 的内核主频就为 1056/2=528MHz。

1.5、PFD时钟设置

PLL2、PLL3 和 PLL7 固定为 528MHz、480MHz 和 480MHz,PLL4~PLL6 都是针对特殊外设 的,用到的时候再设置。因此,接下来重点就是设置 PLL2 和 PLL3 的各自 4 路 PFD,NXP 推 荐的这 8 路 PFD 频率如表 16.1.5.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

先设置 PLL2 的 4 路 PFD 频率,用到寄存器是 CCM_ANALOG_PFD_528n,寄存器结构如图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 CCM_ANALOG_PFD_528n 其实分为四组,分别对应 PFD0~PFD3,每组 8 个 bit,

我们就以 PFD0 为例,看一下如何设置 PLL2_PFD0 的频率。PFD0 对应的寄存器位如下:

PFD0_FRAC: PLL2_PFD0 的分频数,PLL2_PFD0 的计算公式为 528x18/PFD0_FRAC,此 为可设置的范围为 12~35 。 如 果 PLL2_PFD0 的频率要设置为 352MHz 的 话 PFD0_FRAC=528x18/352=27。

PFD0_STABLE: 此位为只读位,可以通过读取此位判断 PLL2_PFD0 是否稳定。

PFD0_CLKGATE: PLL2_PFD0 输出使能位,为 1 的时候关闭 PLL2_PFD0 的输出,为 0 的 时候使能输出。

如果我们要设置 PLL2_PFD0 的频率为 352MHz 的话就需要设置 PFD0_FRAC 为 27, PFD0_CLKGATE 为 0 。 PLL2_PFD1~PLL2_PFD3 设置类似,频率计算公式都是 528*18/PFDX_FRAC(X=1~3) ,因此 PLL2_PFD1=594MHz 的话, PFD1_FRAC=16 ; PLL2_PFD2=400MHz 的话 PFD2_FRAC 不能整除,因此取最近的整数值,即 PFD2_FRAC=24, 这样 PLL2_PFD2 实际为 396MHz;PLL2_PFD3=297MHz 的话,PFD3_FRAC=32。

设 置 PLL3_PFD0~PLL3_PFD3 这 4 路 PFD 的 频 率 , 使 用 到 的 寄 存 器 是 CCM_ANALOG_PFD_480n,此寄存器结构如图 16.1.5.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 CCM_ANALOG_PFD_480n 和 CCM_ANALOG_PFD_528n 的结构是一模一样的,只是一个是 PLL2 的,一个是 PLL3 的。寄存器位的含义也是一样的,只 是 频 率 计 算 公 式 不 同 , 比 如 PLL3_PFDX=480*18/PFDX_FRAC(X=0~3) 。 如 果 PLL3_PFD0=720MHz 的话,PFD0_FRAC=12;如果 PLL3_PFD1=540MHz 的话,PFD1_FRAC=16; 如果 PLL3_PFD2=508.2MHz 的话,PFD2_FRAC=17;如果 PLL3_PFD3=454.7MHz 的话, PFD3_FRAC=19。

1.6、AHB、IPG 和 PERCLK 根时钟设置

7 路 PLL 和 8 路 PFD 设置完成以后最后还需要设置 AHB_CLK_ROOT 和 IPG_CLK_ROOT 的时钟,I.MX6U 外设根时钟可设置范围如图 16.1.6.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

AHB_CLK_ROOT 最高可以设置 132MHz, IPG_CLK_ROOT和PERCLK_CLK_ROOT最高可以设置66MHz。那我们就将AHB_CLK_ROOT、 IPG_CLK_ROOT 和 PERCLK_CLK_ROOT 分 别 设 置 为 132MHz 、 66MHz 、 66MHz 。 AHB_CLK_ROOT 和 IPG_CLK_ROOT 的设计如图 16.1.6.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、此选择器用来选择 pre_periph_clk 的时钟源,可以选择 PLL2、PLL2_PFD2、PLL2_PFD0 和 PLL2_PFD2/2。寄存器 CCM_CBCMR 的 PRE_PERIPH_CLK_SEL 位决定选择哪一个,默认 选择 PLL2_PFD2,因此 pre_periph_clk=PLL2_PFD2=396MHz。

②、此选择器用来选择 periph_clk 的时钟源,由寄存器 CCM_CBCDR 的 PERIPH_CLK_SEL 位与 PLL_bypass_en2 组成的或来选择。当 CCM_CBCDR 的 PERIPH_CLK_SEL 位为 0 的时候 periph_clk=pr_periph_clk=396MHz。

③、通过 CBCDR 的 AHB_PODF 位来设置 AHB_CLK_ROOT 的分频值,可以设置 1~8 分 频,如果想要 AHB_CLK_ROOT=132MHz 的话就应该设置为 3 分频:396/3=132MHz。图 16.1.6.2 中虽然写的是默认 4 分频,但是 I.MX6U 的内部 boot rom 将其改为了 3 分频!

④、通过 CBCDR 的 IPG_PODF 位来设置 IPG_CLK_ROOT 的分频值,可以设置 1~4 分频, IPG_CLK_ROOT 时钟源是 AHB_CLK_ROOT,要想 IPG_CLK_ROOT=66MHz 的话就应该设置 2 分频:132/2=66MHz。

PERCLK_CLK_ROOT 时钟频率,其时钟结构图如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从 图 16.1.6.3 可 以 看 出 , PERCLK_CLK_ROOT 来 源 有 两 种 : OSC(24MHz) 和 IPG_CLK_ROOT,由寄存器 CCM_CSCMR1 的 PERCLK_CLK_SEL 位来决定,如果为 0 的话 PERCLK_CLK_ROOT 的时钟源就是 IPG_CLK_ROOT=66MHz 。可以通过寄存器 CCM_CSCMR1 的 PERCLK_PODF 位来设置分频,如果要设置 PERCLK_CLK_ROOT 为 66MHz 的话就要设置为 1 分频。

CCM_CBCDR、CCM_CBCMR 和 CCM_CSCMR1,我 们依次来看一下这些寄存器,CCM_CBCDR 寄存器结构如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PERIPH_CLK2_PODF:periph2 时钟分频,可设置 0~7,分别对应 1~8 分频。

PERIPH2_CLK_SEL:选择 peripheral2 的主时钟,如果为 0 的话选择 PLL2,如果为 1 的 话选择 periph2_clk2_clk。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握 手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。

PERIPH_CLK_SEL:peripheral 主时钟选择,如果为 0 的话选择 PLL2,如果为 1 的话选 择 periph_clk2_clock。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完 成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。

AXI_PODF:axi 时钟分频,可设置 0~7,分别对应 1~8 分频。

AHB_PODF:ahb 时钟分频,可设置 0~7,分别对应 1~8 分频。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中 指定位表示。 IPG_PODF:ipg 时钟分频,可设置 0~3,分别对应 1~4 分频。

AXI_ALT_CLK_SEL:axi_alt 时钟选择,为 0 的话选择 PLL2_PFD2,如果为 1 的话选择 PLL3_PFD1。

AXI_CLK_SEL:axi 时钟源选择,为 0 的话选择 periph_clk,为 1 的话选择 axi_alt 时钟。

FABRIC_MMDC_PODF:fabric/mmdc 时钟分频设置,可设置 0~7,分别对应 1~8 分频。

PERIPH2_CLK2_PODF:periph2_clk2 的时钟分频,可设置 0~7,分别对应 1~8 分频。

寄存器 CCM_CBCMR:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LCDIF1_PODF:lcdif1 的时钟分频,可设置 0~7,分别对应 1~8 分频。

PRE_PERIPH2_CLK_SEL:pre_periph2 时钟源选择,00 选择 PLL2,01 选择 PLL2_PFD2, 10 选择 PLL2_PFD0,11 选择 PLL4。

PERIPH2_CLK2_SEL:periph2_clk2 时钟源选择为 0 的时候选择 pll3_sw_clk,为 1 的时候 选择 OSC。 PRE_PERIPH_CLK_SEL:pre_periph 时钟源选择,00 选择 PLL2,01 选择 PLL2_PFD2,10 选 择 PLL2_PFD0,11 选择 PLL2_PFD2/2。

PERIPH_CLK2_SEL:peripheral_clk2 时钟源选择,00 选择 pll3_sw_clk,01 选择 osc_clk, 10 选择 pll2_bypass_clk。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此寄存器主要用于外设时钟源的选择,比如 QSPI1、ACLK、GPMI、BCH 等外设,我们重 点看一下下面两个位: PERCLK_CK_SEL:perclk 时钟源选择,为 0 的话选择 ipg clk,为 1 的话选择 osc clk。

PERCLK_PODF:perclk 的时钟分频,可设置 0~7,分别对应 1~8 分频。

在修改如下时钟选择器或者分频器的时候会引起与 MMDC 的握手发生: ①、mmdc_podf ②、periph_clk_sel ③、periph2_clk_sel ④、arm_podf ⑤、ahb_podf 发生握手信号以后需要等待握手完成,寄存器 CCM_CDHIPR 中保存着握手信号是否完成, 如果相应的位为 1 的话就表示握手没有完成,如果为 0 的话就表示握手完成,很简单,这里就 不详细的列举寄存器 CCM_CDHIPR 中的各个位了。 另外在修改 arm_podf 和 ahb_podf 的时候需要先关闭其时钟输出,等修改完成以后再打开, 否则的话可能会出现在修改完成以后没有时钟输出的问题。

十三、GPIO 中断实验

中断系统是一个处理器重要的组成部分,中断系统极大的提高了 CPU 的执行效率。

1、STM32 中断系统回顾

STM32 的中断系统主要有以下几个关键点:

①、中断向量表。 ②、NVIC(内嵌向量中断控制器)。 ③、中断使能。 ④、中断服务函数

1.1、中断向量表

中断向量表是一个表,这个表里面存放的是中断向量。中断服务程序的入口地址或存放中 断服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。 这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以 后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。中断向量表在整个程 序的最前面。

我们说 ARM 处理器都是从地址 0X00000000 开始运行的,但是我们学习 STM32 的时候 代码是下载到 0X8000000 开始的存储区域中。因此中断向量表是存放到 0X8000000 地址处 的,而不是 0X00000000,这样不是就出错了吗?为了解决这个问题,Cortex-M 架构引入了一 个新的概念——中断向量表偏移,通过中断向量表偏移就可以将中断向量表存放到任意地址 处,中断向量表偏移配置在函数 SystemInit 中完成,通过向 SCB_VTOR 寄存器写入新的中断 向量表首地址即可

I.MX6U 所使用的 Cortex-A7 内核也有中断向量表和中断向量 表偏移,而且其含义和 STM32 是一模一样的!只是用到的寄存器不同而已,概念完全相同!

1.2、NVIC(内嵌向量中断控制器)

中断系统得有个管理机构,对于 STM32 这种 Cortex-M 内核的单片机来说这个管理机构叫 做 NVIC,全称叫做 Nested Vectored Interrupt Controller。既 然 Cortex-M 内核有个中断系统的管理机构—NVIC,那么 I.MX6U 所使用的 Cortex-A7 内核是 不是也有个中断系统管理机构?答案是肯定的,不过 Cortex-A 内核的中断管理机构不叫做 NVIC,而是叫做 GIC,全称是 general interrupt controller。

1.3、中断使能

要使用某个外设的中断,肯定要先使能这个外设的中断,以 STM32F103 的 PE2 这个 IO 为 例,假如我们要使用 PE2 的输入中断肯定要使用如下代码来使能对应的中断:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.4、中断服务函数

我们使用中断的目的就是为了使用中断服务函数,当中断发生以后中断服务函数就会被调 用,我们要处理的工作就可以放到中断服务函数中去完成。同样以 STM32F103 的 PE2 为例, 其中断服务函数如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当 PE2 引脚的中断触发以后就会调用其对应的中断处理函数 EXTI2_IRQHandler,我们可 以在函数 EXTI2_IRQHandler 中添加中断处理代码。同理,I.MX6U 也有中断服务函数,当某个 外设中断发生以后就会调用其对应的中断服务函数。

2、Cortex-A7 中断系统简介

跟 STM32 一样,Cortex-A7 也有中断向量表,中断向量表也是在代码的最前面。CortexA7 内核有 8 个异常中断。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

中断向量表里面都是中断服务函数的入口地址,因此一款芯片有什么中断都是可以从中断 向量表看出来的。

对于 Cortex-M 内 核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。对于 CotexA 内核来说并没有这么做,在表 17.1.2.1 中有个 IRQ 中断, Cortex-A 内核 CPU 的所有外部中 断都属于这个 IRQ 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服 务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出 相应的处理。这些外部中断和 IRQ 中断的关系如图 17.1.2.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

左侧的 Software0_IRQn~PMU_IRQ2_IRQ 这些都是 I.MX6U 的中断,他 们都属于 IRQ 中断。当图 17.1.2.1 左侧这些中断中任意一个发生的时候 IRQ 中断都会被触发, 所以我们需要在 IRQ 中断服务函数中判断究竟是左侧的哪个中断发生了,然后再做出具体的处 理。

①、复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面 做一些初始化工作,比如初始化 SP 指针、DDR 等等。

②、未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。

③、软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI 指令来引起软中断,通过软中断来陷入到内核空间。

④、指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。

⑤、数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。

⑥、IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此 中断的发生。

⑦、FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。

3、GIC 控制器简介

3.1、GIC 控制器总览

GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器,类似 Cortex-M 内核中的 NVIC。目前 GIC 有 4 个版本:V1~V4,V1 是最老的版本,已经被废弃了。V2~V4 目前正在大 量的使用。GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、Cortex-A9、Cortex-A15 等, V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。I.MX6U 是 Cortex-A 内核 的,因此我们主要讲解 GIC V2。GIC V2 最多支持 8 个核。ARM 会根据 GIC 版本的不同研发 出不同的 IP 核,那些半导体厂商直接购买对应的 IP 核即可,比如 ARM 针对 GIC V2 就开发出 了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是 ARM 内核只提供了四个信号给 GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和 IRQ,他们之间的 关系如图 17.1.3.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

VFIQ:虚拟快速 FIQ。 VIRQ:虚拟外部 IRQ。 FIQ:快速中断 IRQ。 IRQ:外部中断 IRQ。

VFIQ 和 VIRQ 是针对虚拟化的,我们不讨论虚拟化,剩下的就是 FIQ 和 IRQ 了,我们前 面都讲了很多次了。本教程我们只使用 IRQ,所以相当于 GIC 最终向 ARM 内核就上报一个 IRQ 信号。那么 GIC 是如何完成这个工作的呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

左侧部分就是中断源,中间部分就是 GIC 控制器,最右侧就是中断控制器向 处理器内核发送中断信息。我们重点要看的肯定是中间的 GIC 部分,GIC 将众多的中断源分为 分为三类:

①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最 常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口 中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。

②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定 有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。

③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器 GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

3.2、中断 ID

中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是 中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包 含了 PPI、SPI 和 SGI,那么这三类中断是如何分配这 1020 个中断 ID 的呢?这 1020 个 ID 分 配如下:

ID0~ID15:这 16 个 ID 分配给 SGI。

ID16~ID31:这 16 个 ID 分配给 PPI。

ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断 ,至于 具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。

3.3、GIC 逻辑分块

GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。

Distributor(分发器端):此逻辑块负责处理各个中断事件的分发问 题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制 每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要 工作如下:

①、全局中断使能控制。 ②、控制每一个中断的使能或者关闭。 ③、设置每个中断的优先级。 ④、设置每个中断的目标处理器列表。 ⑤、设置每个外部中断的触发模式:电平触发或边沿触发。 ⑥、设置每个中断属于组 0 还是组 1。

CPU Interface(CPU 接口端):CPU 接口端听名字就知道是和 CPU Core 相连接的,因此在 图 17.1.3.2 中每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface。CPU 接口端 就是分发器和 CPU Core 之间的桥梁,CPU 接口端主要工作如下:

①、使能或者关闭发送到 CPU Core 的中断请求信号。

②、应答中断。

③、通知中断处理完成。

④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。

⑤、定义抢占策略。

⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。

3.4、CP15 协处理器

CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有 16 个 32 位寄存器。CP15 协处理器的访问通过如下另个指令完成:

MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。

MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。 MRC 就是读 CP15 寄存器,MCR 就是写 CP15 寄存器,MCR 指令格式如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

cond:指令执行的条件码,如果忽略的话就表示无条件执行。

opc1:协处理器要执行的操作码。

Rt:ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。

CRn:CP15 协处理器的目标寄存器。

CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将 CRm 设置为 C0,否则结果不可预测。

opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。

MRC 的指令格式和 MCR 一样,只不过在 MRC 指令中 Rt 就是目标寄存器,也就是从 CP15 指定寄存器读出来的数据会保存在 Rt 中。而 CRn 就是源寄存器,也就是要读取的写处 理器寄存器。

假如我们要将 CP15 中 C0 寄存器的值读取到 R0 寄存器中,那么就可以使用如下命令:

MRC p15, 0, r0, c0, c0, 0

CP15 协处理器有 16 个 32 位寄存器,c0~c15,本章来看一下 c0、c1、c12 和 c15 这四个寄 存器

3.4.1 c0 寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当 MRC/MCR 指令中的 CRn=c0,opc1=0,CRm=c0,opc2=0 的时候就表示 此时的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器,这个也是 c0 的基本作用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

bit31:24:厂商编号,0X41,ARM。 bit23:20:内核架构的主版本号,ARM 内核版本一般使用 rnpn 来表示,比如 r0p1,其中 r0 后面的 0 就是内核架构主版本号。 bit19:16:架构代码,0XF,ARMv7 架构。 bit15:4:内核版本号,0XC07,Cortex-A7 MPCore 内核。 bit3:0:内核架构的次版本号,rnpn 中的 pn,比如 r0p1 中 p1 后面的 1 就是次版本号。

3.4.2、 c1 寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当 MRC/MCR 指令中的 CRn=c1,opc1=0,CRm=c0,opc2=0 的时候就表示 此时的 c1 就是 SCTLR 寄存器,也就是系统控制寄存器,这个是 c1 的基本作用。SCTLR 寄存 器主要是完成控制功能的,比如使能或者禁止 MMU、I/D Cache 等,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

bit13:V , 中断向量表基地址选择位,为 0 的话中断向量表基地址为 0X00000000,软件可 以使用 VBAR 来重映射此基地址,也就是中断向量表重定位。为 1 的话中断向量表基地址为 0XFFFF0000,此基地址不能被重映射。 bit12:I,I Cache 使能位,为 0 的话关闭 I Cache,为 1 的话使能 I Cache。 bit11:Z,分支预测使能位,如果开启 MMU 的话,此位也会使能。 bit10:SW,SWP 和 SWPB 使能位,当为 0 的话关闭 SWP 和 SWPB 指令,当为 1 的时候 就使能 SWP 和 SWPB 指令。 bit9:3:未使用,保留。 bit2:C,D Cache 和缓存一致性使能位,为 0 的时候禁止 D Cache 和缓存一致性,为 1 时 使能。 bit1:A,内存对齐检查使能位,为 0 的时候关闭内存对齐检查,为 1 的时候使能内存对齐 检查。 bit0:M,MMU 使能位,为 0 的时候禁止 MMU,为 1 的时候使能 MMU。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.4.3、c12 寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当 MRC/MCR 指令中的 CRn=c12,opc1=0,CRm=c0,opc2=0 的时候就表 示此时 c12 为 VBAR 寄存器,也就是向量表基地址寄存器。设置中断向量表偏移的时候就需要 将新的中断向量表基地址写入 VBAR 中,比如在前面的例程中,代码链接的起始地址为 0X87800000,而中断向量表肯定要放到最前面,也就是 0X87800000 这个地址处。所以就需要 设置 VBAR 为 0X87800000,设置命令如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.4.4、c15 寄存器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

MRC p15, 4, r1, c15, c0, 0 ; 获取 GIC 基础地址,基地址保存在 r1 中。

获取到 GIC 基地址以后就可以设置 GIC 相关寄存器了,比如我们可以读取当前中断 ID, 当前中断 ID 保存在 GICC_IAR 中,寄存器 GICC_IAR 属于 CPU 接口端寄存器,寄存器地址 相对于 CPU 接口端起始地址的偏移为 0XC,

MRC p15, 4, r1, c15, c0, 0 ;获取 GIC 基地址

ADD r1, r1, #0X2000 ;GIC 基地址加 0X2000 得到 CPU 接口端寄存器起始地址

LDR r0, [r1, #0XC] ;读取 CPU 接口端起始地址+0XC 处的寄存器值,也就是寄存器 ;GIC_IAR 的值

下,通过 c0 寄存器可以获取到处理器内 核信息;通过 c1 寄存器可以使能或禁止 MMU、I/D Cache 等;通过 c12 寄存器可以设置中断 向量偏移;通过 c15 寄存器可以获取 GIC 基地址。

3.5、 中断使能

中断使能包括两部分,一个是 IRQ 或者 FIQ 总中断使能,另一个就是 ID0~ID1019 这 1020 个中断源的使能。

3.5.1、IRQ 和 FIQ 总中断使能

IRQ 和 FIQ 分别是外部中断和快速中断的总开关,就类似家里买的进户总电闸,然后 ID0~ID1019 这 1020 个中断源就类似家里面的各个电器开关。要想开电视,那肯定要保证进户 总电闸是打开的,因此要想使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断(本教程不使用 FIQ)。

寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使 能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.5.2 ID0~ID1019 中断使能和禁止

GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁 止,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。一个 bit 控制一个中断 ID 的使能,那么 就需要 512/32=16 个 GICD_ISENABLER 寄存器来完成中断的使能。同理,也需要 16 个 GICD_ICENABLER 寄存器来完成中断的禁止。其中 GICD_ISENABLER0 的 bit[15:0]对应 ID15~0 的 SGI 中断,GICD_ISENABLER0 的 bit[31:16]对应 ID31~16 的 PPI 中断。剩下的 GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。

3.6、中断优先级设置

3.6.1、优先级数配置

Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,两者同样是可以配置 的。GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高!Cortex-A7 选择了 32 个 优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先 级,寄存器结构如图 17.1.6.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级,其他优先级数设 置如表 17.1.6.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

I.MX6U 是 Cortex-A7 内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000。

3.6.2、抢占优先级和子优先级位数设置

抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的,GICC_BPR 寄存器结构如图 17.1.6.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如 I.MX6U 的优先级 位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。

3.6.3、优先级设置

如果优先级个数为 32 的话,使用寄 存器 D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。比如要设置 ID40 中断的优先级为 5,示例代码如下:

GICD_IPRIORITYR[40] = 5 << 3;

有关优先级设置的内容就讲解到这里,优先级设置主要有三部分:

①、设置寄存器 GICC_PMR,配置优先级个数,比如 I.MX6U 支持 32 级优先级。

②、设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占 优先级。

③、设置指定中断 ID 的优先级,也就是设置外设优先级。

十三、EPIT定时器实验

定时器是最常用的外设,常常需要使用定时器来完成精准的定时功能,I.MX6U 提供了多 种硬件定时器,有些定时器功能非常强大。

1、EPIT 定时器简介

EPIT 的全称是:Enhanced Periodic Interrupt Timer,直译过来就是增强的周期中断定时器, 它主要是完成周期性中断定时的。学过 STM32 的话应该知道,STM32 里面的定时器还有很多 其它的功能,比如输入捕获、PWM 输出等等。但是 I.MX6U 的 EPIT 定时器只是完成周期性中 断定时的,仅此一项功能!至于输入捕获、PWM 输出等这些功能,I.MX6U 由其它的外设来完 成。

EPIT 是一个 32 位定时器,在处理器几乎不用介入的情况下提供精准的定时中断,软件使 能以后 EPIT 就会开始运行,EPIT 定时器有如下特点:

①、时钟源可选的 32 位向下计数器。 ②、12 位的分频值。 ③、当计数值和比较值相等的时候产生中断

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、这是个多路选择器,用来选择 EPIT 定时器的时钟源,EPIT 共有 3 个时钟源可选择, ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq。

②、这是一个 12 位的分频器,负责对时钟源进行分频,12 位对应的值是 0~4095,对应着 1~4096 分频。

③、经过分频的时钟进入到 EPIT 内部,在 EPIT 内部有三个重要的寄存器:计数寄存器 (EPIT_CNR)、加载寄存器(EPIT_LR)和比较寄存器(EPIT_CMPR),这三个寄存器都是 32 位的。 EPIT 是一个向下计数器,也就是说给它一个初值,它就会从这个给定的初值开始递减,直到减 为 0,计数寄存器里面保存的就是当前的计数值。如果 EPIT 工作在 set-and-forget 模式下,当计数寄存器里面的值减少到 0,EPIT 就会重新从加载寄存器读取数值到计数寄存器里面,重新开 始向下计数。比较寄存器里面保存的数值用于和计数寄存器里面的计数值比较,如果相等的话 就会产生一个比较事件。

④、比较器。 ⑤、EPIT 可以设置引脚输出,如果设置了的话就会通过指定的引脚输出信号。

⑥、产生比较中断,也就是定时中断。

EPIT 定时器有两种工作模式:set-and-forget 和 free-running,这两个工作模式的区别如下:

set-and-forget 模式:EPITx_CR(x=1,2)寄存器的 RLD 位置 1 的时候 EPIT 工作在此模式 下,在此模式下 EPIT 的计数器从加载寄存器 EPITx_LR 中获取初始值,不能直接向计数器寄存 器写入数据。不管什么时候,只要计数器计数到 0,那么就会从加载寄存器 EPITx_LR 中重新 加载数据到计数器中,周而复始。

free-running 模式:EPITx_CR 寄存器的 RLD 位清零的时候 EPIT 工作在此模式下,当计数 器计数到0以后会重新从0XFFFFFFFF开始计数,并不是从加载寄存器EPITx_LR中获取数据。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CLKSRC(bit25:24):EPIT 时钟源选择位,为 0 的时候关闭时钟源,1 的时候选择选择 Peripheral 时钟(ipg_clk),为 2 的时候选择 High-frequency 参考时钟(ipg_clk_highfreq),为 3 的时 候选择 Low-frequency 参考时钟(ipg_clk_32k)。

PRESCALAR(bit15:4):EPIT 时钟源分频值,可设置范围 0~4095,分别对应 1~4096 分频。

RLD(bit3):EPIT 工作模式,为 0 的时候工作在 free-running 模式,为 1 的时候工作在 set-and-forget 模式。本章例程设置为 1,也就是工作在 set-and-forget 模式。

OCIEN(bit2):比较中断使能位,为 0 的时候关闭比较中断,为 1 的时候使能比较中断,本 章试验要使能比较中断。

ENMOD(bit1):设置计数器初始值,为 0 时计数器初始值等于上次关闭 EPIT 定时器以后 计数器里面的值,为 1 的时候来源于加载寄存器。

EN(bit0):EPIT 使能位,为 0 的时候关闭 EPIT,为 1 的时候使能 EPIT。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 EPITx_SR 只有一个位有效,那就是 OCIF(bit0),这个位是比较中断标志位,为 0 的 时候表示没有比较事件发生,为 1 的时候表示有比较事件发生。当比较中断发生以后需要手动 清除此位,此位是写 1 清零的。

寄存器 EPITx_LR、EPITx_CMPR 和 EPITx_CNR 分别为加载寄存器、比较寄存器和计数寄 存器,这三个寄存器都是用来存放数据的,很简单。

EPIT 的配置步骤如下:

1、设置 EPIT1 的时钟源

设置寄存器 EPIT1_CR 寄存器的 CLKSRC(bit25:24)位,选择 EPIT1 的时钟源。

2、设置分频值

设置寄存器 EPIT1_CR 寄存器的 PRESCALAR(bit15:4)位,设置分频值。

3、设置工作模式

设置寄存器 EPIT1_CR 的 RLD(bit3)位,设置 EPTI1 的工作模式。

4、设置计数器的初始值来源

设置寄存器 EPIT1_CR 的 ENMOD(bit1)位,设置计数器的初始值来源。

5、使能比较中断

我们要使用到比较中断,因此需要设置寄存器 EPIT1_CR 的 OCIEN(bit2)位,使能比较中 断。

6、设置加载值和比较值

设置寄存器 EPIT1_LR 中的加载值和寄存器 EPIT1_CMPR 中的比较值,通过这两个寄存器 就可以决定定时器的中断周期。 7、EPIT1 中断设置和中断服务函数编写

使能 GIC 中对应的 EPIT1 中断,注册中断服务函数,如果需要的话还可以设置中断优先 级。最后编写中断服务函数。

8、使能 EPIT1 定时器

配置好 EPIT1 以后就可以使能 EPIT1 了,通过寄存器 EPIT1_CR 的 EN(bit0)位来设置。 通过以上几步我们就配置好 EPIT 了,通过 EPIT 的比较中断来实现 LED0 的翻转。

十四、定时器按键消抖实验

用到按键就要处理因为机械结构带来的按键 抖动问题,也就是按键消抖。前面的实验中都是直接使用了延时函数来实现消抖,因为简单, 但是直接用延时函数来实现消抖会浪费 CPU 性能,因为在延时函数里面 CPU 什么都做不了。 如果按键使用中断的话更不能在中断里面使用延时函数,因为中断服务函数要快进快出!

1、定时器按键消抖简介

是在按键按下以后延时一段时间再 去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。 但是这有一个缺点,就是延时函数会浪费 CPU 性能,因为延时函数就是空跑。如果按键是用中 断方式实现的,那就更不能在中断服务函数里面使用延时函数,因为中断服务函数最基本的要 求就是快进快出!上一章我们学习了 EPIT 定时器,定时器设置好定时时间,然后 CPU 就可以 做其他事情去了,定时时间到了以后就会触发中断,然后在中断中做相应的处理即可。因此, 我们可以借助定时器来实现消抖,按键采用中断驱动方式,当按键按下以后触发按键中断,在 按键中断中开启一个定时器,定时周期为 10ms,当定时时间到了以后就会触发定时器中断,最 后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的 按键。

如何使用 EPIT1 来配合按键 KEY 来实现具体的消抖,步骤如下:

1、配置按键 IO 中断

配置按键所使用的 IO,因为要使用到中断驱动按键,所以要配置 IO 的中断模式。

2、初始化消抖用的定时器

上面已经讲的很清楚了,消抖要用定时器来完成,所以需要初始化一个定时器,这里使用 上一章讲解的 EPIT1 定时器,也算是对 EPIT1 定时器的一次巩固。定时器的定时周期为 10ms, 也可根据实际情况调整定时周期。

3、编写中断处理函数

需要编写两个中断处理函数:按键对应的 GPIO 中断处理函数和 EPIT1 定时器的中断处理 函数。在按键的中断处理函数中主要用于开启 EPIT1 定时器,EPIT1 的中断处理函数才是重点, 按键要做的具体任务都是在定时器 EPIT1 的中断处理函数中完成的,比如控制蜂鸣器打开或关 闭。

十五、高精度延时实验

延时函数是很常用的 API 函数,在前面的实验中我们使用循环来实现延时函数,但是使用 循环来实现的延时函数不准确,误差会很大。虽然使用到延时函数的地方精度要求都不会很严 格(要求严格的话就使用硬件定时器了),但是延时函数肯定是越精确越好,这样延时函数就可 以使用在某些对时序要求严格的场合。

1、高精度延时简介

1.1、GPT 定时器简介

在使用 STM32 的时候可以使用 SYSTICK 来实现高精度延 时。I.MX6U 没有 SYSTICK 定时器,但是 I.MX6U 有其他定时器啊,比如第十八章讲解的 EPIT 定时器。本章我们使用 I.MX6U 的 GPT 定时器来实现高精度延时,顺便学习一下 GPT 定时器, GPT 定时器全称为 General Purpose Timer。

GPT 定时器是一个 32 位向上定时器(也就是从 0X00000000 开始向上递增计数),GPT 定时 器也可以跟一个值进行比较,当计数器值和这个值相等的话就发生比较事件,产生比较中断。 GPT 定时器有一个 12 位的分频器,可以对 GPT 定时器的时钟源进行分频,GPT 定时器特性如 下:

①、一个可选时钟源的 32 位向上计数器。 ②、两个输入捕获通道,可以设置触发方式。 ③、三个输出比较通道,可以设置输出模式。 ④、可以生成捕获中断、比较中断和溢出中断。 ⑤、计数器可以运行在重新启动(restart)或(自由运行)free-run 模式。 GPT 定时器的可选时钟源如图 20.1.1.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一共有五个时钟源,分别为:ipg_clk_24M、GPT_CLK(外部时钟)、 ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq。本例程选择 ipg_clk 为 GPT 的时钟源,ipg_clk=66MHz。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、此部分为 GPT 定时器的时钟源,前面已经说过了,本章例程选择 ipg_clk 作为 GPT 定 时器时钟源。

②、此部分为 12 位分频器,对时钟源进行分频处理,可设置 0~4095,分别对应 1~4096 分 频。

③、经过分频的时钟源进入到 GPT 定时器内部 32 位计数器。

④和⑤、这两部分是 GPT 的两路输入捕获通道,本章不讲解 GPT 定时器的输入捕获。

⑥、此部分为输出比较寄存器,一共有三路输出比较,因此有三个输出比较寄存器,输出 比较寄存器是 32 位的。

⑦、此部分位输出比较中断,三路输出比较中断,当计数器里面的值和输出比较寄存器里 面的比较值相等就会触发输出比较中断。

GPT 定时器有两种工作模式:重新启动(restart)模式和自由运行(free-run)模式,这两个工作 模式的区别如下:

重新启动(restart)模式:当 GPTx_CR(x=1,2)寄存器的 FRR 位清零的时候 GPT 工作在此 模式。在此模式下,当计数值和比较寄存器中的值相等的话计数值就会清零,然后重新从 0X00000000 开始向上计数,只有比较通道 1 才有此模式!向比较通道 1 的比较寄存器写入任何 数据都会复位 GPT 计数器。对于其他两路比较通道(通道 2 和 3),当发生比较事件以后不会 复位计数器。

自由运行(free-run)模式:当 GPTx_CR(x=1,2)寄存器的 FRR 位置 1 时候 GPT 工作在此模 式下,此模式适用于所有三个比较通道,当比较事件发生以后并不会复位计数器,而是继续计 数,直到计数值为 0XFFFFFFFF,然后重新回滚到 0X00000000。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SWR(bit15):复位 GPT 定时器,向此位写 1 就可以复位 GPT 定时器,当 GPT 复位完成以 后此为会自动清零。

FRR(bit9):运行模式选择,当此位为 0 的时候比较通道 1 工作在重新启动(restart)模式。当 此位为 1 的时候所有的三个比较通道均工作在自由运行模式(free-run)。

CLKSRC(bit8:6):GPT 定时器时钟源选择位,为 0 的时候关闭时钟源;为 1 的时候选择 ipg_clk 作为时钟源;为 2 的时候选择 ipg_clk_highfreq 为时钟源;为 3 的时候选择外部时钟为 时钟源;为 4 的时候选择 ipg_clk_32k 为时钟源;为 5 的时候选择 ip_clk_24M 为时钟源。本章 例程选择 ipg_clk 作为 GPT 定时器的时钟源,因此此位设置位 1(0b001)。

ENMOD(bit1):GPT 使能模式,此位为 0 的时候如果关闭 GPT 定时器,计数器寄存器保 存定时器关闭时候的计数值。此位为 1 的时候如果关闭 GPT 定时器,计数器寄存器就会清零。

EN(bit):GPT 使能位,为 1 的时候使能 GPT 定时器,为 0 的时候关闭 GPT 定时器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

寄存器 GPTx_PR 我们用到的重要位就一个:PRESCALER(bit11:0),这就是 12 位分频值, 可设置 0~4095,分别对应 1~4096 分频。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ROV(bit5):回滚标志位,当计数值从 0XFFFFFFFF 回滚到 0X00000000 的时候此位置 1。

IF2~IF1(bit4:3):输入捕获标志位,当输入捕获事件发生以后此位置 1,一共有两路输入捕 获通道。如果使用输入捕获中断的话需要在中断处理函数中清除此位。

OF3~OF1(bit2:0):输出比较中断标志位,当输出比较事件发生以后此位置 1,一共有三路 输出比较通道。如果使用输出比较中断的话需要在中断处理函数中清除此位。

GPT 定时器的计数寄存器 GPTx_CNT,这个寄存器保存着 GPT 定时器的当前 计数值。最后看一下 GPT 定时器的输出比较寄存器 GPTx_OCR,每个输出比较通道对应一个 输出比较寄存器,因此一个 GPT 定时器有三个 OCR 寄存器,它们的作都是相同的。以输出比 较通道 1 为例,其输出比较寄存器为 GPTx_OCR1,这是一个 32 位寄存器,用于存放 32 位的 比较值。当计数器值和寄存器 GPTx_OCR1 中的值相等就会产生比较事件,如果使能了比较中 断的话就会触发相应的中断。

1.2、定时器实现高精度延时原理

如果设置 GPT 定时器的时钟源为 ipg_clk=66MHz,设置 66 分频,那么进入 GPT 定时器的最终时钟频率就是 66/66=1MHz,周期为 1us。GPT 的计数器每计一个数就表示“过去” 了 1us。如果计 10 个数就表示“过去”了 10us。通过读取寄存器 GPTx_CNT 中的值就知道计 了个数,比如现在要延时 100us,那么进入延时函数以后纪录下寄存器 GPTx_CNT 中的值为 200, 当 GPTx_CNT 中的值为 300 的时候就表示 100us 过去了,也就是延时结束。GPTx_CNT 是个 32 位寄存器,如果时钟为 1MHz 的话,GPTx_CNT 最多可以实现 0XFFFFFFFFus=4294967295us ≈4294s≈72min。也就是说 72 分钟以后 GPTx_CNT 寄存器就会回滚到 0X00000000,也就是溢 出,所以需要在延时函数中要处理溢出的情况。关于定时器实现高精度延时的原理就讲解到这 里,原理还是很简单的,高精度延时的实现步骤如下:

1、设置 GPT1 定时器

首先设置 GPT1_CR 寄存器的 SWR(bit15)位来复位寄存器 GPT1。复位完成以后设置寄存 器 GPT1_CR 寄存器的 CLKSRC(bit8:6)位,选择 GPT1 的时钟源为 ipg_clk。设置定时器 GPT1 的工作模式,

2、设置 GPT1 的分频值

设置寄存器 GPT1_PR 寄存器的 PRESCALAR(bit111:0)位,设置分频值。

3、设置 GPT1 的比较值

如果要使用 GPT1 的输出比较中断,那么 GPT1 的输出比较寄存器 GPT1_OCR1 的值可以 根据所需的中断时间来设置。本章例程不使用比较输出中断,所以将 GPT1_OCR1 设置为最大 值,即:0XFFFFFFFF。

4、使能 GPT1 定时器

设置好 GPT1 定时器以后就可以使能了,设置 GPT1_CR 的 EN(bit0)位为 1 来使能 GPT1 定 时器。

5、编写延时函数

GPT1定时器已经开始运行了,可以根据前面介绍的高精度延时函数原理来编写延时函数, 针对 us 和 ms 延时分别编写两个延时函数。

十六、UART 串口通信实验

不管是单片机开发还是嵌入式 Linux 开发,串口都是最常用到的外设。可以通过串口将开 发板与电脑相连,然后在电脑上通过串口调试助手来调试程序。

在嵌入式 Linux 中一般使用串口作为控制台,所以掌握串口是必备的技能。

1、I.MX6U 串口简介

1.1、UART 通信格式

串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传 输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信 距离远,但是速度相对会低,串口是一种很常用的工业接口。I.MX6U 自带的 UART 外设就是 串口的一种,UART 全称是 UniversalAsynchronous Receiver/Trasmitter,也就是异步串行收发器。 既然有异步串行收发器,那肯定也有同步串行收发器,学过 STM32 的同学应该知道,STM32 除了有 UART 外 ,还有 另 外一 个 叫 做 USART 的 东 西。 USART 的全称是 Universal Synchronous/Asynchronous Receiver/Transmitter,也就是同步/异步串行收发器。相比 UART 多了 一个同步的功能,在硬件上体现出来的就是多了一条时钟线。一般 USART 是可以作为 UART 使用的,也就是不使用其同步的功能。

UART 作为串口的一种,其工作原理也是将数据一位一位的进行传输,发送和接收各用一 条线,因此通过 UART 接口与外界相连最少只需要三条线:TXD(发送)、RXD(接收)和 GND(地 线)。图 21.1.1.1 就是 UART 的通信格式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

空闲位:数据线在空闲状态的时候为逻辑“1”状态,也就是高电平,表示没有数据线空闲, 没有数据传输。

起始位:当要传输数据的时候先传输一个逻辑“0”,也就是将数据线拉低,表示开始数据 传输。

数据位:数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节 传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输。

奇偶校验位:这是对数据中“1”的位数进行奇偶校验用的,可以不使用奇偶校验功能。

停止位:数据传输完成标志位,停止位的位数可以选择 1 位、1.5 位或 2 位高电平,一般都 选择 1 位停止位。

波特率:波特率就是 UART 数据传输的速率,也就是每秒传输的数据位数,一般选择 9600、 19200、115200 等。

1.2、UART 电平标准

UART 一般的接口电平有 TTL 和 RS-232,一般开发板上都有 TXD 和 RXD 这样的引脚, 这些引脚低电平表示逻辑 0,高电平表示逻辑 1,这个就是 TTL 电平。RS-232 采用差分线,-3~- 15V 表示逻辑 1,+3~+15V 表示逻辑 0。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TTL 接口部分有 VCC、GND、RXD、TXD、 RTS 和 CTS。RTS 和 CTS 基本用不到,使用的时候通过杜邦线和其他模块的 TTL 接口相连即 可。

RS-232 电平需要 DB9 接口,I.MX6U-ALPHA 开发板上的 COM3(UART3)口就是 RS-232 接 口的,如图 21.1.1.3 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于现在的电脑都没有 DB9 接口了,取而代之的是 USB 接口,所以就催生出了很多 USB 转串口 TTL 芯片,比如 CH340、PL2303 等。通过这些芯片就可以实现串口 TTL 转 USB。I.MX6UALPHA开发板就使用CH340 芯片来完成UART1和电脑之间的连接,只需要一条USB 线即可。

2、I.MX6U UART 简介

I.MX6U 一共 有 8 个 UART,其主要特性如下:

①、兼容 TIA/EIA-232F 标准,速度最高可到 5Mbit/S。

②、支持串行 IR 接口,兼容 IrDA,最高可到 115.2Kbit/s。

③、支持 9 位或者多节点模式(RS-485)。

④、1 或 2 位停止位。

⑤、可编程的奇偶校验(奇校验和偶校验)。

⑥、自动波特率检测(最高支持 115.2Kbit/S)。

UART 的时钟源是由寄存器 CCM_CSCDR1 的 UART_CLK_SEL(bit)位来选择的,当为 0 的 时候 UART 的时钟源为 pll3_80m(80MHz),如果为 1 的时候 UART 的时钟源为 osc_clk(24M), 一般选择 pll3_80m 作为 UART 的时钟源。寄存器 CCM_CSCDR1 的 UART_CLK_PODF(bit5:0) 位是 UART 的时钟分频值,可设置 0~63,分别对应 1~64 分频,一般设置为 1 分频,因此最终 进入 UART 的时钟为 80MHz。

UART 的控制寄存器 1,即 UARTx_UCR1(x=1~8),此寄存器的结构如图 21.1.2.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ADBR(bit14):自动波特率检测使能位,为 0 的时候关闭自动波特率检测,为 1 的时候使 能自动波特率检测。 UARTEN(bit0):UART 使能位,为 0 的时候关闭 UART,为 1 的时候使能 UART。

UART 的控制寄存器 2,即:UARTx_UCR2,此寄存器结构如图 21.1.2.2 所 示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

IRTS(bit14):为 0 的时候使用 RTS 引脚功能,为 1 的时候忽略 RTS 引脚。

PREN(bit8):奇偶校验使能位,为 0 的时候关闭奇偶校验,为 1 的时候使能奇偶校验。

PROE(bit7):奇偶校验模式选择位,开启奇偶校验以后此位如果为 0 的话就使用偶校 验,此位为 1 的话就使能奇校验。

STOP(bit6):停止位数量,为 0 的话 1 位停止位,为 1 的话 2 位停止位。

WS(bit5):数据位长度,为 0 的时候选择 7 位数据位,为 1 的时候选择 8 位数据位。

TXEN(bit2):发送使能位,为 0 的时候关闭 UART 的发送功能,为 1 的时候打开 UART 的发送功能。

RXEN(bit1):接收使能位,为 0 的时候关闭 UART 的接收功能,为 1 的时候打开 UART 的接收功能。

SRST(bit0):软件复位,为 0 的是时候软件复位 UART,为 1 的时候表示复位完成。复位 完成以后此位会自动置 1,表示复位完成。此位只能写 0,写 1 会被忽略掉。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本章实验就用到了寄存器 UARTx_UCR3 中的位 RXDMUXSEL(bit2),这个位应该始终为 1,

下寄存器 UARTx_USR2,这个是 UART 的状态寄存器 2,此寄存器结构如图 21.1.2.4 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TXDC(bit3):发送完成标志位,为 1 的时候表明发送缓冲(TxFIFO)和移位寄存器为空,也 就是发送完成,向 TxFIFO 写入数据此位就会自动清零。

RDR(bit0):数据接收标志位,为 1 的时候表明至少接收到一个数据,从寄存器 UARTx_URXD 读取数据接收到的数据以后此位会自动清零。

寄 存 器 UARTx_UFCR 、 UARTx_UBIR 和 UARTx_UBMR , 寄 存 器 UARTx_UFCR 中我们要用到的是位 RFDIV(bit9:7),用来设置参考时钟分频。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过这三个寄存器可以设置 UART 的波特率,波特率的计算公式如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过 UARTx_UFCR 的 RFDIV 位、UARTx_UBMR 和 UARTx_UBIR 这三者的配合即可得 到我们想要的波特率。比如现在要设置 UART 波特率为 115200,那么可以设置 RFDIV 为 5(0b101),也就是 1 分频,因此 Ref Freq=80MHz。设置 UBIR=71,UBMR=3124,根据上面的 公式可以得到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后来看一下寄存器 UARTx_URXD 和 UARTx_UTXD,这两个寄存器分别为 UART 的接 收和发送数据寄存器,这两个寄存器的低八位为接收到的和要发送的数据。读取寄存器UARTx_URXD 即可获取到接收到的数据,如果要通过 UART 发送数据,直接将数据写入到寄 存器 UARTx_UTXD 即可。

UART1 的配置步骤如下:

1、设置 UART1 的时钟源

设置 UART 的时钟源为 pll3_80m,设置寄存器 CCM_CSCDR1 的 UART_CLK_SEL 位为 0 即可。

2、初始化 UART1

初始化 UART1 所使用 IO,设置 UART1 的寄存器 UART1_UCR1~UART1_UCR3,设置内 容包括波特率,奇偶校验、停止位、数据位等等。

4、使能 UART1

UART1 初始化完成以后就可以使能 UART1 了,设置寄存器 UART1_UCR1 的位 UARTEN 为 1。

5、编写 UART1 数据收发函数

编写两个函数用于 UART1 的数据收发操作。

十七、串口格式化函数移植实验

1、串口格式化函数简介

格式化函数说的是 printf、sprintf 和 scanf 这样的函数,分为格式化输入和格式化输出两类 函数。学习 C 语言的时候常常通过 printf 函数在屏幕上显示字符串,通过 scanf 函数从键盘获取 输入。这样就有了输入和输出了,实现了最基本的人机交互。学习 STM32 的时候会将 printf 映 射到串口上,这样即使没有屏幕,也可以通过串口来和开发板进行交互。在 I.MX6U-ALPHA 开 发板上也可以使用此方法,将 printf 和 scanf 映射到串口上,这样就可以使用 SecureCRT 作为开 发板的终端,完成与开发板的交互。也可以使用 printf 和 sprintf 来实现各种各样的格式化字符 串,方便我们后续的开发。

十八、DDR3 实验

一般 Cortex-A 芯 片自带的 RAM 很小,比如 I.MX6U 只有 128KB 的 OCRAM。如果要运行 Linux 的话完全不够 用的,所以必须要外接一片 RAM 芯片,I.MX6U 支持 LPDDR2、LPDDR3/DDR3,I.MX6U-ALPHA 开发板上选择的是 DDR3

1、DDR3 内存简介

1.1、何为 RAM 和 ROM?

RAM:随机存储器,可以随时进行读写操作,速度很快,掉电以后数据会丢失。比如内存 条、SRAM、SDRAM、DDR 等都是 RAM。RAM 一般用来保存程序数据、中间结果,比如我 们在程序中定义了一个变量 a,然后对这个 a 进行读写操作。

ROM:只读存储器,笔者认为目前“只读存储器”这个定义不准确。比如我们买手机,通 常会告诉你这个手机是 4+64 或 6+128 配置,说的就是 RAM 为 4GB 或 6GB,ROM 为 64G 或 128GB。但是这个 ROM 是 Flash,比如 EMMC 或 UFS 存储器,因为历史原因,很多人还是将 Flash 叫做 ROM。但是 EMMC 和 UFS,甚至是 NAND Flash,这些都是可以进行写操作的!只 是写起来比较麻烦,要先进行擦除,然后再发送要写的地址或扇区,最后才是要写入的数据,相比于 RAM,向 ROM 或者 Flash 写入数据要复杂很多,因此意味着速度就会变慢(相比 RAM),但是 ROM 和 Flash 可以将容量做的很大,而且掉电以后数据不会丢失,适合用来存储资料,比如音 乐、图片、视频等信息。

RAM 速度快,可以直接和 CPU 进行通信,但是掉电以后数据会丢失,容量不 容易做大(和同价格的 Flash 相比)。ROM(目前来说,更适合叫做 Flash)速度虽然慢,但是容量 大、适合存储数据。对于正点原子的 I.MX6U-ALPHA 开发板而言,256MB/512MB 的 DDR3 就 是 RAM,而 512MB NANF Flash 或 8GB EMMC 就是 ROM。

1.2、SRAM 简介

SRAM 的全称叫做 Static Random-Access Memory,也就是静态随机存储器,这里的“静态” 说的就是只要 SRAM 上电,那么 SRAM 里面的数据就会一直保存着,直到 SRAM 掉电。对于 RAM 而言需要可以随机的读取任意一个地址空间内的数据,因此采用了地址线和数据线分离的方式,这里就以 STM32F103/F407 开发板常用的 IS62WV51216 这颗 SRAM 芯片为例简单的 讲解一下 SRAM,这是一颗 16 位宽(数据位为 16 位)、1MB 大小的 SRAM,芯片框图如图 23.1.2.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、地址线

这部分是地址线,一共 A0~A18,也就是 19 根地址线,因此可访问的地址大小就是 2^19=524288=512KB。不是说 IS62WV51216 是个 1MB 的 SRAM 吗?为什么地址空间只有 512KB?前面我们说了 IS62WV51216 是 16 位宽的,也就是一次访问 2 个字节,因此需要对 512KB 进行乘 2 处理,得到 512KB*2=1MB。位宽的话一般有 8 位/16 位/32 位,根据实际需求 选择即可,一般都是根据处理器的 SRAM 控制器位宽来选择 SRAM 位宽。

②、数据线

这部分是 SRAM 的数据线,根据 SRAM 位宽的不同,数据线的数量要不同,8 位宽就有 8 根数据线,16 位宽就有 16 根数据线,32 位宽就有 32 根数据线。IS62WV51216 是一个 16 位宽 的 SRAM,因此就有 16 根数据线,一次访问可以访问 16bit 的数据,也就是 2 个字节。因此就 有高字节和低字节数据之分,其中 IO0~IO7 是低字节数据,IO8~IO15 是高字节数据。

③、控制线

SRAM 要工作还需要一堆的控制线,CS2 和 CS1 是片选信号,低电平有效,在一个系统中 可能会有多片 SRAM(目的是为了扩展 SRAM 大小或位宽),这个时候就需要 CS 信号来选择当 前使用哪片 SRAM。另外,有的 SRAM 内部其实是由两片 SRAM 拼接起来的,因此就会提供 两个片选信号。

OE 是输出使能信号,低电平有效,也就是主控从 SRAM 读取数据。

WE 是写使能信号,低电平有效,也就是主控向 SRAM 写数据。

UB 和 LB 信号,前面我们已经说了,IS62WV51216 是个 16 位宽的 SRAM,分为高字节和 低字节,那么如何来控制读取高字节数据还是低字节数据呢?这个就是 UB 和 LB 这两个控制 线的作用,这两根控制线都是低电平有效。UB 为低电平的话表示访问高字节,LB 为低电平的 话表示访问低字节。

SDRAM 比 SRAM 容量大,但是价格更低。 SRAM 突出的特点就是无需刷新(SDRAM 需要刷新,后面会讲解),读写速度快!所以 SRAM 通常作为 SOC 的内部 RAM 使用或 Cache 使用,比如 STM32 内存的 RAM 或 I.MX6U 内部的 OCRAM 都是 SRAM。

1.3、SDRAM 简介

SDRAM 全称是 Synchronous Dynamic Random Access Memory,翻译过来就是同步动态随机存储器,“同步”的意思是 SDRAM 工作 需要时钟线,“动态”的意思是 SDRAM 中的数据需要不断的刷新来保证数据不会丢失,“随机” 的意思就是可以读写任意地址的数据。

与 SRAM 相比,SDRAM 集成度高、功耗低、成本低、适合做大容量存储,但是需要定时 刷新来保证数据不会丢失。因此 SDRAM 适合用来做内存条,SRAM 适合做高速缓存或 MCU 内部的 RAM。SDRAM 目前已经发展到了第四代,分别为:SDRAM、DDR SDRAM、DDR2 SDRAM、DDR3 SDRAM、DDR4 SDRAM。

就以 STM32 开发板最常用的华邦 W9825G6KH 为例,W9825G6KH 是一款 16 位宽(数据位为 16 位)、32MB 的 SDRAM、速度一 般为 133MHz、166MHz 或 200MHz。W9825G6KH 框图如图 23.1.3.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、控制线

CLK:时钟线,SDRAM 是同步动态随机存储器,“同步”的意思就是时钟,因此需要一根 额外的时钟线,这是和 SRAM 最大的不同,SRAM 没有时钟线。

CKE:时钟使能信号线,SRAM 没有 CKE 信号。

CS:片选信号,这个和 SRAM 一样,都有片选信号。

RAS:行选通信号,低电平有效,SDRAM 和 SRAM 的寻址方式不同,SDRAM 按照行、 列来确定某个具体的存储区域。因此就有行地址和列地址之分,行地址和列地址共同复用同一 组地址线,要访问某一个地址区域,必须要发送行地址和列地址,指定要访问哪一行?哪一列? RAS 是行选通信号,表示要发送行地址,行地址和列地址访问方式如图 23.1.3.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CAS:列选通信号,和 RAS 类似,低电平有效,选中以后就可以发送列地址了。

WE:写使能信号,低电平有效。

②、A10 地址线

A10 是地址线,那么这里为什么要单独将 A10 地址线给提出来呢?因为 A10 地址线还有另 外一个作用,A10 还控制着 Auto-precharge,也就是预充电。这里又提到了预充电的概念,SDRAM 芯片内部会分为多个 BANK,关于 BANK 我们稍后会讲解。SDRAM 在读写完成以后,如果要 对同一个 BANK 中的另一行进行寻址操作就必须将原来有效的行关闭,然后发送新的行/列地 址,关闭现在工作的行,准备打开新行的操作就叫做预充电。一般 SDSRAM 都支持自动预充电 的功能。

③、地址线

对于 W9825G6KH 来说一共有 A0~A12,共 13 根地址线,但是我们前面说了 SDRAM 寻址 是按照行地址和列地址来访问的,因此这 A0~A12 包含了行地址和列地址。不同的 SDRAM 芯 片,根据其位宽、容量等的不同,行列地址数是不同的,这个在 SDRAM 的数据手册里面会也 清楚的。比如 W9825G6KH 的 A0~A8 是列地址,一共 9 位列地址,A0~A12 是行地址,一共 13 位,因此可寻址范围为:29*213=4194304B=4MB,W9825G6KH 为 16 位宽(2 个字节),因此 还需要对 4MB 进行乘 2 处理,得到 4*2=8MB,但是 W9825G6KH 是一个 32MB 的 SDRAM 啊, 为什么算出来只有 8MB,仅仅为实际容量的 1/4。不要急,这个就是我们接下来要讲的 BANK, 8MB 只是一个 BANK 的容量,W9825G6KH 一共有 4 个 BANK。

④、BANK 选择线

S0 和 BS1 是 BANK 选择信号线,在一片 SDRAM 中因为技术、成本等原因,不可能做 一个全容量的 BANK。而且,因为 SDRAM 的工作原理,单一的 BANK 会带来严重的寻址冲 突,减低内存访问效率。为此,人们在一片 SDRAM 中分割出多块 BANK,一般都是 2 的 n 次 方,比如 2,4,8 等。图 23.1.1.2 中的⑤就是 W9825G6KH 的 4 个 BANK 示意图,每个 SDRAM 数据手册里面都会写清楚自己是几 BANK。前面我们已经计算出来了一个 BANK 的大小为 8MB, 那么四个 BANK 的总容量就是 8MB*4=32MB。

既然有4个BANK,那么在访问的时候就需要告诉SDRAM,我们现在需要访问哪个BANK, BS0 和 BS1 就是为此而生的,4 个 BANK 刚好 2 根线,如果是 8 个 BANK 的话就需要三根线, 也就是 BS0~BS2。BS0、BS1 这两个线也是 SRAM 所没有的。

⑤、BANK 区域

关于 BANK 的概念前面已经讲过了,这部分就是 W9825G6KH 的 4 个 BANK 区域。这个 概念也是 SRAM 所没有的。

⑥、数据线

W9825G6KH 是 16 位宽的 SDRAM,因此有 16 根数据线,DQ0~DQ15,不同的位宽其数 据线数量不同,这个和 SRAM 是一样的。

⑦、高低字节选择

W9825G6KH 是一个 16 位的 SDRAM,因此就分为低字节数据和高字节数据,LDQM 和 UDQM 就是低字节和高字节选择信号,这个也和 SRAM 一样。

1.4、DDR 简介

DDR 内存是 SDRAM 的升级版本,SDRAM 分为 SDR SDRAM、 DDR SDRAM、DDR2 SDRAM、DDR3 SDRAM、DDR4 SDRAM。可以看出 DDR 本质上还是 SDRAM,只是随着技术的不断发展,DDR 也在不断的更新换代。先来看一下 DDR,也就是 DDR1,人们对于速度的追求是永无止境的,当发现 SDRAM 的速度不够快的时候人们就在思 考如何提高 SDRAM 的速度,DDR SDRAM 由此诞生。

DDR 全称是 Double Data Rate SDRAM,也就是双倍速率 SDRAM,看名字就知道 DDR 的 速率(数据传输速率)比 SDRAM 高 1 倍!这 1 倍的速度不是简简单单的将 CLK 提高 1 倍, SDRAM 在一个 CLK 周期传输一次数据,DDR 在一个 CLK 周期传输两次数据,也就是在上升 沿和下降沿各传输一次数据,这个概念叫做预取(prefetch),相当于 DDR 的预取为 2bit,因此 DDR 的速度直接加倍!比如 SDRAM 速度一般是 133~200MHz,对应的传输速度就是 133~200MT/s,在描述 DDR 速度的时候一般都使用 MT/s,也就是每秒多少兆次数据传输。 133MT/S 就是每秒 133M 次数据传输,MT/s 描述的是单位时间内传输速率。同样 133~200MHz 的频率,DDR 的传输速度就变为了 266~400MT/S,所以大家常说的 DDR266、DDR400 就是这 么来的。

DDR2 在 DDR 基础上进一步增加预取(prefetch),增加到了 4bit,相当于比 DDR 多读取一 倍的数据,因此 DDR2 的数据传输速率就是 533~800MT/s,这个也就是大家常说的 DDR2 533、 DDR2 800。当然了,DDR2 还有其他速度,这里只是说最常见的几种。

DDR3 在 DDR2 的基础上将预取(prefetch)提高到 8bit,因此又获得了比 DDR2 高一倍的传 输速率,因此在总线时钟同样为 266~400MHz 的情况下,DDR3 的传输速率就是 1066~1600MT/S。 I.MX6U 的 MMDC 外设用于连接 DDR,支持 LPDDR2、DDR3、DDR3L,最高支持 16 位数据 位宽。总线速度为 400MHz(实际是 396MHz),数据传输速率最大为 800MT/S。

LPDDR3、DDR3 和 DDR3L 的区别,这三个都是 DDR3,但是区别主要在于工作电压,LPDDR3 叫做低功耗 DDR3,工作电压为 1.2V。DDR3 叫做标压 DDR3,工作电压为 1.5V,一般台式内 存条都是 DDR3。DDR3L 是低压 DDR3,工作电压为 1.35V,一般手机、嵌入式、笔记本等都 使用 DDR3L。

NT5CC256M16EP-EK 是一款容量为 4Gb,也就是 512MB 大小、 16 位宽、1.35V、传输速率为 1866MT/S 的 DDR3L 芯片。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、控制线

ODT:片上终端使能,ODT 使能和禁止片内终端电阻。

ZQ:输出驱动校准的外部参考引脚,此引脚应该外接一个 240 欧的电阻到 VSSQ 上,一般 就是直接接地了。

RESET:复位引脚,低电平有效。

CKE:时钟使能引脚。 A12:A12 是地址引脚,但是有也有另外一个功能,因此也叫做 BC 引脚,A12 会在 READ和 WRITE 命令期间被采样,以决定 burst chop 是否会被执行。

CK 和 CK#:时钟信号,DDR3 的时钟线是差分时钟线,所有的控制和地址信号都会在 CK 对的上升沿和 CK#的下降沿交叉处被采集。

CS#:片选信号,低电平有效。

RAS#、CAS#和 WE#:行选通信号、列选通信号和写使能信号。

②、地址线

A[14:0]为地址线,A0~A14,一共 15 根地址线,根据 NT5CC256M16ER-EK 的数据手册可 知,列地址为 A0~A9,共 10 根,行地址为 A0~A14,共 15 根,因此一个 BANK 的大小就是 210*2152=32MB2=64MB,根据图 23.1.4.2 可知一共有 8 个 BANK,因此 DDR3L 的容量就 是 64*8=512MB。

③、BANK 选择线

一片 DDR3 有 8 个 BANK,因此需要 3 个线才能实现 8 个 BANK 的选择,BA0~BA2 就是 用于完成 BANK 选择的。

④、BANK 区域

DDR3 一般都是 8 个 BANK 区域。

⑤、数据线

因为是 16 位宽的,因此有 16 根数据线,分别为 DQ0~DQ15。

⑥、数据选通引脚

DQS 和 DQS#是数据选通引脚,为差分信号,读的时候是输出,写的时候是输入。LDQS(有 的叫做 DQSL)和 LDQS#(有的叫做 DQSL#)对应低字节,也就是 DQ0~7,UDQS(有的叫做 DQSU) 和 UDQS#(有的叫做 DQSU#),对应高字节,也就是 DQ8~15。

⑦、数据输入屏蔽引脚

DM 是写数据输入屏蔽引脚。

2、DDR3 关键时间参数

2.1、传输速率

比如 1066MT/S、1600MT/S、1866MT/S 等,这个是首要考虑的,因为这个决定了 DDR3 内 存的最高传输速率。

2.2、tRCD 参数

tRCD 全称是 RAS-to-CAS Delay,也就是行寻址到列寻址之间的延迟。DDR 的寻址流程是 先指定 BANK 地址,然后再指定行地址,最后指定列地址确定最终要寻址的单元。BANK 地址 和行地址是同时发出的,这个命令叫做“行激活”(Row Active)。行激活以后就发送列地址和具 体的操作命令(读还是写),这两个是同时发出的,因此一般也用“读/写命令”表示列寻址。在 行有效(行激活)到读写命令发出的这段时间间隔叫做 tRCD,如图 23.2.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

tRCD 为 13.91ns,这个我们在初始化 DDR3 的时候需要配置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

NT5CC256M16ER-EK 这个 DDR3 的 CL-TRCD-TRP 时间参数为“13- 13-13”。因此 tRCD=13,这里的 13 不是 ns 数,而是 CLK 时间数,表示 13 个 CLK 周期。

2.3、CL 参数

当列地址发出以后就会触发数据传输,但是数据从存储单元到内存芯片 IO 接口上还需要一段 时间,这段时间就是非常著名的 CL(CAS Latency),也就是列地址选通潜伏期,如图 23.2.4 所 示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一般 tRCD 和 CL 大小一样。

2.4、AL 参数

在 DDR 的发展中,提出了一个前置 CAS 的概念,目的是为了解决 DDR 中的指令冲突, 它允许 CAS 信号紧随着 RAS 发送,相当于将 DDR 中的 CAS 前置了。但是读/写操作并没有因 此提前,依旧要保证足够的延迟/潜伏期,为此引入了 AL(Additive Latency),单位也是时钟周期 数。AL+CL 组成了 RL(Read Latency),从 DDR2 开始还引入了写潜伏期 WL(Write Latency), WL 表示写命令发出以后到第一笔数据写入的潜伏期。引入 AL 以后的读时序如图 23.2.5 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.5、tRC 参数

tRC 是两个 ACTIVE 命令,或者 ACTIVE 命令到 REFRESH 命令之间的周期,DDR3L 数 据手册会给出这个值,比如 NT5CC256M16EP-EK 的 tRC 值为 47.91ns,

2.6、tRAS 参数

tRAS 是 ACTIVE 命令到 PRECHARGE 命令之间的最小时间

3 、I.MX6U MMDC 控制器简介

3.1、 MMDC 控制器

MMDC 就是 I.MX6U 的内存控制器,MMDC 是一个多模的 DDR 控制器,可以连接 16 位宽的 DDR3/DDR3L、16 位 宽的 LPDDR2,MMDC 是一个可配置、高性能的 DDR 控制器。MMDC 外设包含一个内核 (MMDC_CORE)和 PHY(MMDC_PHY),内核和 PHY 的功能如下:

MMDC 内核:内核负责通过 AXI 接口与系统进行通信、DDR 命令生成、DDR 命令优化、 读/写数据路径。

MMDC PHY:PHY 负责时序调整和校准,使用特殊的校准机制以保障数据能够在 400MHz 被准确捕获。

MMDC 的主要特性如下:

①、支持 DDR3/DDR3Lx16、支持 LPDDR2x16,不支持 LPDDR1MDDR 和 DDR2。

②、支持单片 256Mbit~8Gbit 容量的 DDR,列地址范围:8-12 位,行地址范围 11-16bit。2 个片选信号。

③、对于 DDR3,最大支持 8bit 的突发访问。

④、对于 LPDDR2 最大支持 4bit 的突发访问。

⑤、MMDC 最大频率为 400MHz,因此对应的数据速率为 800MT/S。

⑥、支持各种校准程序,可以自动或手动运行。支持 ZQ 校准外部 DDR 设备,ZQ 校准 DDR I/O 引脚、校准 DDR 驱动能力。

3.2 、MMDC 控制器信号引脚

DDR 对于硬件要求非常严格,因此 DDR 的引脚都是独立的,一般没有复用功能,只做为 DDR 引脚使用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于图 23.3.2.1 中的引脚是 DDR 专属的,因此就不存在所谓的 DDR 引脚复用配置,只需 要设置 DDR 引脚的电气属性即可,注意,DDR 引脚的电气属性寄存器和普通的外设引脚电气 属性寄存器不同!

3.3、 MMDC 控制器时钟源

前面说了很多次,I.MX6U 的 DDR 或者 MDDC 的时钟频率为 400MHz,那么这 400MHz 时 钟源怎么来的呢?这个就要查阅 I.MX6ULL 参考手册的《Chapter 18 Clock Controller Module(CCM)》章节。MMDC 时钟源如图 23.3.3.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①、pre_periph2 时钟选择器,也就是 periph2_clkd 的前级选择器,由 CBCMR 寄存器的 PRE_PERIPH2_CLK_SEL 位(bit22:21)来控制,一共有四种可选方案,如表 23.3.3.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

I.MX6U 内部 boot rom 就是设置 PLL2_PFD2 作为 MMDC 的最终时 钟源,这就是 I.MX6U 的 DDR 频率为 400MHz 的原因。

②、periph2_clk 时钟选择器,由 CBCDR 寄存器的 PERIPH2_CLK_SEL 位(bit26)来控制, 当为 0 的时候选择 pll2_main_clk 作为 periph2_clk 的时钟源,当为 1 的时候选择 periph2_clk2_clk 作为 periph2_clk 的时钟源。这里肯定要将 PERIPH2_CLK_SEL 设置为 0,也就是选择 pll2_main_clk 作为 periph2_clk 的时钟源,因此 periph2_clk=PLL2_PFD0=396MHz。

③、最后就是分频器,由 CBCDR 寄存器的 FABRIC_MMDC_PODF 位(bit5:3)设置分频值, 可设置 0~7,分别对应 1~8 分频,要配置 MMDC 的时钟源为 396MHz,那么此处就要设置为 1 分频,因此 FABRIC_MMDC_PODF=0。

十九、RGBLED显示实验

LCD 液晶屏是常用到的外设,通过 LCD 可以显示绚丽的图形、界面等,提高人机交互的 效率。I.MX6U 提供了一个 eLCDIF 接口用于连接 RGB 接口的液晶屏。

1、 LCD 和 eLCDIF 简介

1.1 、LCD 简介

LCD 全称是 Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、 电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器了。

LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体 管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方 向,从而达到控制每个像素点偏振光出射与否而达到显示目的。

1.1.1、分辨率

LCD 显示器都是由一个一个的像素点组成,像素点就类似一个灯(在 OLED 显示 器中,像素点就是一个小灯),这个小灯是 RGB 灯,也就是由 R(红色)、G(绿色)和 B(蓝色)这三 种颜色组成的,而 RGB 就是光的三原色。1080P 的意思就是一个 LCD 屏幕上的像素数量是 1920*1080 个,也就是这个屏幕一列 1080 个像素点,一共 1920 列,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

X 轴就是 LCD 显示器的横轴,Y 轴就是显 示器的竖轴。图中的小方块就是像素点,一共有 19201080=2073600 个像素点。左上角的 A 点 是第一个像素点,右下角的 C 点就是最后一个像素点。2K 就是 25601440 个像素点,4K 是 3840*2160 个像素点。很明显,在 LCD 尺寸不变的情况下,分辨率越高越清晰。同样的,分辨 率不变的情况下,LCD 尺寸越小越清晰。比如我们常用的 24 寸显示器基本都是 1080P 的,而 我们现在使用的 5 寸的手机基本也是 1080P 的,但是手机显示细腻程度就要比 24 寸的显示器 要好很多!

由此可见,LCD 显示器的分辨率是一个很重要的参数,但是并不是分辨率越高的 LCD 就 越好。衡量一款 LCD 的好坏,分辨率只是其中的一个参数,还有色彩还原程度、色彩偏离、亮 度、可视角度、屏幕刷新率等其他参数。

1.1.2、像素格式

一般一个 R、 G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点 3 个字节,这种像素格式称为 RGB888。如果再加入 8bit 的 Alpha(透明)通道的话一个像素点就是 32bit,也就是 4 个字节,这种像素格式称为 ARGB8888。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个像素点是 4 个字节,其中 bit31~bit24 是 Alpha 通道,bit23~bit16 是 RED 通道,bit15~bit14 是 GREEN 通道,bit7~bit0 是 BLUE 通道。所以红色对应的值就是 0X00FF0000,蓝色对应的值就是 0X000000FF,绿色对应的值为 0X0000FF00。通过调节 R、G、 B的比例可以产生其它的颜色,比如0X00FFFF00就是黄色,0X00000000就是黑色,0X00FFFFFF 就是白色。大家可以打开电脑的“画图”工具,在里面使用调色板即可获取到想要的颜色对应 的数值。

1.1.3、LCD屏幕接口

LCD 屏幕或者说显示器有很多种接口,比如在显示器上常见的 VGA、HDMI、DP 等等, 但是I.MX6U-ALPHA开发板不支持这些接口。I.MX6U-ALPHA支持RGB接口的LCD,RGBLCD 接口的信号线如表 24.1.1.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

R[7:0]、G[7:0]和B[7:0]这24根是数据线,DE、VSYNC、 HSYNC 和 PCLK 这四根是控制信号线。RGB LCD 一般有两种驱动模式:DE 模式和 HV 模式, 这两个模式的区别是 DE 模式需要用到 DE 信号线,而 HV 模式不需要用到 DE 信号线,在 DE 模式下是可以不需要 HSYNC 信号线的,即使不接 HSYNC 信号线 LCD 也可以正常工作。

ATK-7016 的屏幕接口原理图如图 24.1.1.4 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图中 J1 就是对外接口,是一个 40PIN 的 FPC 座(0.5mm 间距),通过 FPC 线,可以连接 到 I.MX6U-ALPHA 开发板上面,从而实现和 I.MX6U 的连接。该接口十分完善,采用 RGB888 格式,并支持 DE&HV 模式,还支持触摸屏和背光控制。右侧的几个电阻,并不是都焊接的, 而是可以用户自己选择。默认情况,R1 和 R6 焊接,设置 LCD_LR 和 LCD_UD,控制 LCD 的 扫描方向,是从左到右,从上到下(横屏看)。而 LCD_R7/G7/B7 则用来设置 LCD 的 ID,由于 RGBLCD 没有读写寄存器,也就没有所谓的 ID,这里我们通过在模块上面,控制 R7/G7/B7 的 上/下拉,来自定义 LCD 模块的 ID,帮助 MCU 判断当前 LCD 面板的分辨率和相关参数,以提 高程序兼容性。这几个位的设置关系如表 24.1.1.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ATK-7016 模块,就设置 M2:M0=010 即可。这样,我们在程序里面,读取 LCD_R7/G7/B7, 得到 M0:M2 的值,从而判断 RGBLCD 模块的型号,并执行不同的配置,即可实现不同 LCD 模 块的兼容。

1.1.4、LCD 时间参数

如果将 LCD 显示一帧图像的过程想象成绘画,那么在显示的过程中就是用一根“笔”在不 同的像素点画上不同的颜色。这根笔按照从左至右、从上到下的顺序扫描每个像素点,并且在 像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。假如一个 LCD 的分 辨率为 1024*600,那么其扫描如图 24.1.1.5 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LCD 是怎么扫描显示一帧图像的。一帧图像也是由一行一行 组成的。HSYNC 是水平同步信号,也叫做行同步信号,当产生此信号的话就表示开始显示新的 一行了,所以此信号都是在图 24.1.1.5 的最左边。当 VSYNC 信号是垂直同步信号,也叫做帧 同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在图 24.1.1.5 的左 上角。

到有一圈“黑边”,真正有效的显示区域是中间的白色部分。那这一圈 “黑边”是什么东西呢?这就要从显示器的“祖先”CRT 显示器开始说起了,CRT 显示器就是 以前很常见的那种大屁股显示器,CRT 显示器屁股后面是个电子枪,这个电子枪就是我们上面说的“画笔”,电子枪打出的电子撞 击到屏幕上的荧光物质使其发光。只要控制电子枪从左到右扫完一行(也就是扫描一行),然后 从上到下扫描完所有行,这样一帧图像就显示出来了。也就是说,显示一帧图像电子枪是按照 ‘Z’形在运动,当扫描速度很快的时候看起来就是一幅完成的画面了。

当显示完一行以后会发出 HSYNC 信号,此时电子枪就会关闭,然后迅速的移动到屏幕的 左边,当 HSYNC 信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在 HSYNC 信号结束到电子枪重新打开之间会插入一段延时,这段延时就图 24.1.1.5 中的 HBP。当显示完 一行以后就会关闭电子枪等待 HSYNC 信号产生,关闭电子枪到 HSYNC 信号产生之间会插入 一段延时,这段延时就是图 24.1.1.5 中的 HFP 信号。同理,当显示完一帧图像以后电子枪也会关闭,然后等到 VSYNC 信号产生,期间也会加入一段延时,这段延时就是图 24.1.1.5 中的 VFP。 VSYNC 信号产生,电子枪移动到左上角,当 VSYNC 信号结束以后电子枪重新打开,中间也会 加入一段延时,这段延时就是图 24.1.1.5 中的 VBP。

HBP、HFP、VBP 和 VFP 就是导致图 24.1.1.5 中黑边的原因,但是这是 CRT 显示器存在黑 边的原因,现在是 LCD 显示器,不需要电子枪了,那么为何还会有黑边呢?这是因为 RGB LCD 屏幕内部是有一个 IC 的,发送一行或者一帧数据给 IC,IC 是需要反应时间的。通过这段反应 时间可以让 IC 识别到一行数据扫描完了,要换行了,或者一帧图像扫描完了,要开始下一帧图 像显示了。因此,在 LCD 屏幕中继续存在 HBP、HFP、VPB 和 VFP 这四个参数的主要目的是 为了锁定有效的像素数据。

1.1.5、RGB LCD 屏幕时序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,查阅所使用的 LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。

HSPW:有些地方也叫做 thp,是 HSYNC 信号宽度,也就是 HSYNC 信号持续时间。HSYNC 信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK。

HBP:有些地方叫做 thb,前面已经讲过了,术语叫做行同步信号后肩,单位是 CLK

HOZVAL:有些地方叫做 thd,显示一行数据所需的时间,假如屏幕分辨率为 1024*600, 那么 HOZVAL 就是 1024,单位为 CLK。

HFP:有些地方叫做 thf,前面已经讲过了,术语叫做行同步信号前肩,单位是 CLK。

当 HSYNC 信号发出以后,需要等待 HSPW+HBP 个 CLK 时间才会接收到真正有效的像素 数据。当显示完一行数据以后需要等待 HFP 个 CLK 时间才能发出下一个 HSYNC 信号,所以 显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的 LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。

VSPW:有些地方也叫做 tvp,是 VSYNC 信号宽度,也就是 VSYNC 信号持续时间,单位 为 1 行的时间。

VBP:有些地方叫做 tvb,前面已经讲过了,术语叫做帧同步信号后肩,单位为 1 行的时 间。

LINE:有些地方叫做 tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为 1024*600, 那么 LINE 就是 600 行的时间。

VFP:有些地方叫做 tvf,前面已经讲过了,术语叫做帧同步信号前肩,单位为 1 行的时间。

显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP 个行时间,最终的计算公式:

T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)

因此我们在配置一款 RGB LCD 的时候需要知道这几个参数:HOZVAL(屏幕有效宽度)、 LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP 和 VFP。ALIENTEK 三款 RGB LCD 屏幕的参数如表 24.1.1.3 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.1.6、像素时钟
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
欢迎下载研华科技主题白皮书: 【深度剖析】研华多核异构ARM核心板之机器视觉应用案例 [摘要] TI Sitara系列AM5718/5728是采用ARM+DSP多核异构架构,可以实现图像采集、算法处理、显示、控制等功能,具有实时控制、低功耗、多标准工业控制网络互联、工业人机界面的优化、2D/3D图形处理、1080 HD的高清视频应用、工业控制设备的小型化等特点。广泛应用在机器视觉、工业通讯、汽车多媒体、医疗影像、工厂自动化、工业物联网等领域。 https://www.eefocus.com/resource/advantech/index.p... OpenMV Cam概述: OpenMV项目旨在通过开发开源的低成本机器视觉摄像机,为业余爱好者和制造商提供机器视觉。OpenMV项目于2015年成功通过Kickstarter资助。第一代OpenMV摄像机基于STM的STM32F ARM Cortex-M MCU和Omnivision OV7725传感器。OpenMV摄像机可以在Python3中进行编程,并附有大量的图像处理功能,如面部检测和跟踪,关键点描述符,彩色斑点跟踪,QR和条形码支持,AprilTags,GIF和MJPEG记录等等。 OpenMV摄像机板内置RGB和红外LED,用于编程和视频流的USB FS,uSD插座和I / O头,可以分解PWM,UART,SPI和I2C。此外,OpenMV还支持使用诸如WiFi,BLE,Thermal(FIR)和LCD屏蔽等I / O头的扩展模块(屏蔽)。 OpenMV配备了专门用于支持OpenMV摄像机的跨平台IDE(基于QT创建者)。IDE允许查看帧缓冲区,访问传感器控制,上传脚本并通过串行通过USB(或WiFi / BLE(如果可用))在相机上运行它们。 STM32 机器人视觉摄像机OpenMV Cam实物截图: STM32 机器人视觉摄像机OpenMV Cam特性: 所有I / O引脚输出3.3V并具有5V容限。 在标准的M12镜头座上配有一个2.8mm镜头,便于其他镜头交换。 全速USB(12Mbs)接口到您的计算机。您的OpenMV摄像机在插入时将显示为虚拟COM端口和USB闪存驱动器。 一个能够100Mb读/写的微型SD卡插槽,允许您的OpenMV摄像头记录视频并轻松拉取机器视觉资产从微型SD卡。 SPI总线可以运行高达45Mbs,使您可以轻松地将系统中的图像数据传输到LCD屏蔽,WiFi屏蔽或另一个微控制器。 I2C总线,CAN总线和异步串行总线(TX / RX),用于与其他微控制器和传感器接口。 12位ADC和12位DAC。 两个用于伺服控制的I / O引脚。 所有I / O引脚上的中断和PWM(板上有9个I / O引脚)。 另外还有一个RGB LED和两个高功率的850nm红外LED。 由MacroFab在美国制造的OpenMV LLC OpenMV Cam M4 与 M7区别: STM32 机器人视觉摄像机OpenMV Cam应用: 标记跟踪 您可以使用您的OpenMV Cam来检测组的颜色,而不是独立的颜色。这允许您创建可以放置在对象上的色彩制作者(2个或更多颜色标签),允许您的OpenMV Cam了解标签对象的内容。视频演示这里。 人脸检测 您可以使用OpenMV Cam(或任何通用对象)检测Faces。您的OpenMV Cam可以处理Haar Cascades进行通用对象检测,并配有内置的Frontal Face Cascade和Eye Haar Cascade来检测脸部和眼睛。 眼睛追踪 您可以使用眼动跟踪与您的OpenMV摄像头来检测某人的注视。然后,您可以使用它来控制机器人。眼睛跟踪检测瞳孔的位置,同时检测图像中是否有眼睛。 光流 您可以使用光流来检测您的OpenMV摄像机正在查看的翻译。例如,您可以使用四通孔上的光流来确定其在空气中的稳定性。 QR码检测/解码 您可以使用OpenMV Cam在其视野中读取QR码。通过QR码检测/解码,您可以使智能机器人能够读取环境中的标签。您可以在此处查看我们的视频。 边缘/线路检测 您可以通过Canny Edge Detector算法或简单的高通滤波进行边缘检测,然后进行阈值处理。在您拥有二进制图像后,您可以使用霍夫检测器查找图像中的所有行。通过边缘/线检测,您可以使用OpenMV Cam来轻松检测对象的方向。 模板匹配 您可以使用与OpenMV Cam模板匹配来检测翻译的预先保存的图像何时被视图。例如,可以使用模板匹配来查找PCB上的基准或读取显示器上的已知数字。 图像捕获 您可以使用OpenMV摄像头捕获多达320x240 RGB565(或640x480灰度)BMP / JPG / PPM / PGM图像。您可
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值