计算机硬件基础
Linux层次结构

Linux子系统
- 进程管理:管理进程的创建、调度、销毁等
- 内存管理:管理内存的申请、释放、映射等
- 文件系统:管理和访问磁盘中的文件
- 设备管理:硬件设备及驱动的管理
- 网络协议:通过网络协议栈(TCP、IP…)进行通信
计算机的组成

输入设备
- 把其他信号转换成计算机能识别和处理的信号并送入计算机中
- 如键盘、鼠标、摄像头等
输出设备
- 运算结果以人或其他设备所能接受的形式送出计算机外
- 如显示器、音响、打印机等
存储器
- 存储器是用来存储程序和数据的部件,是实现"存储程序控制"的基础
- 如内存、硬盘等
运算器
- CPU中负责进行算数运算和逻辑运算的部件,其核心是算术逻辑单元ALU
控制器
- 控制器是CPU的指挥中心,其控制着整个CPU执行程序的逻辑过程
运算器和控制器共同组成了CPU
总线
总线是计算机中各个部件之间传送信息的公共通信干线, 在物理上就是一束导线按照其传递信息的类型可以分为数据总线、地址总线、控制总线
DMA总线
DMA(Direct Memory Access)即直接存储器访问,使用DMA总线可以不通过CPU直接在存储器之间进行数据传递
三级存储结构

Cache
- 速度最快、价格最贵、容量最小、断电数据丢失、cpu可直接访问
- 存储当前正在执行的程序中的活跃部分,以便快速地向CPU提供指令和数据
主存储器
- 速度、价格、容量介于Cache与辅存之间、断电数据丢失、cpu可直接访问
- 存储当前正在执行的程序和数据
辅助存储器
- 速度最慢、价格最低、容量最大、断电数据不丢失、cpu不可直接访问
- 存储暂时不运行的程序和数据,需要时再传送到主存
地址空间
一个处理器能够访问(读写)的存储空间是有限的,我们称这个空间为它的地址空间(寻址空间),一般来说N位地址总线的处理器的地址空间是2的N次方
CPU工作原理

一条指令的执行分为三个阶段
-
取址
- CPU将PC寄存器中的地址发送给内存,内存将其地址中对应的指令返回到CPU中的指令寄存器(IR)
-
译码
- 译码器对IR中的指令进行识别,将指令(机器码)解析成具体的运算
-
执行
- 控制器控制运算器中对应的运算单元进行运算,运算结果写入寄存器
ARM体系结构
处理器
RISC处理器
- 只保留常用的的简单指令,硬件结构简单,复杂操作一般通过简单指令的组合实现,一般指令长度固定,且多为单周期指令
- RISC处理器在功耗、体积、价格等方面有很大优势,所以在嵌入式移动终端领域应用极为广泛
CISC处理器
- 不仅包含了常用指令,还包含了很多不常用的特殊指令,硬件结构复杂,指令条数较多,一般指令长度和周期都不固定
- CISC处理器在性能上有很大优势,多用于PC及服务器等领域
SOC
即片上系统,将一个系统中所需要的全部部件集成在一个芯片中在体积、功耗、价格上有很大优势
指令集
指令
- 能够指示处理器执行某种运算的命令称为指令(如加、减、乘 …)
- 指令在内存中以机器码(二进制)的方式存在
- 每一条指令都对应一条汇编
- 程序是指令的有序集合
指令集
- 处理器能识别的指令的集合称为指令集
- 不同架构的处理器指令集不同
- 指令集是处理器对开发者提供的接口
ARM指令集
ARM指令集
- 所有指令(机器码)都占用32bit存储空间
- 代码灵活度高、简化了解码复杂度
- 执行ARM指令集时PC值每次自增4
Thumb指令集
- 所有指令(机器码)都占用16bit存储空间
- 代码密度高、节省存储空间
- 执行Thumb指令集时PC值每次自增2
编译原理
- 机器码(二进制)是处理器能直接识别的语言,不同的机器码代表不同的运算指令,处理器能够识别哪些机器码是由处理器的硬件设计所决定的,不同的处理器机器码不同,所以机器码不可移植
- 汇编语言是机器码的符号化,即汇编就是用一个符号来代替一条机器码,所以不同的处理器汇编也不一样,即汇编语言也不可移植
- C语言在编译时我们可以使用不同的编译器将C源码编译成不同架构处理器的汇编,所以C语言可以移植
ARM数据类型
ARM采用32位架构,基本数据类型有以下三种
- Byte 8bits
- Halfword 16bits
- Word 32bits
数据存储
- Word型数据在内存的起始地址必须是4的整数
- Halfword型数据在内存的起始地址必须是2的整数倍
即数据本身是多少位在内存存储时就应该多少位对齐
字节序
- 大端对齐
- 低地址存放高位,高地址存放低位
- 小端对齐
- 低地址存放低位,高地址存放高位
ARM一般使用小端对齐
ARM指令存储
- 处理器处于ARM状态时
- 所有指令在内存的起始地址必须是4的整数倍
- PC值由其[31:2]决定,[1:0]位未定义
- 处理器处于Thumb状态时
- 所有指令在内存的起始地址必须是2的整数倍
- PC值由其[31:1]决定,[0]位未定义
- 注:
即指令本身是多少位在内存存储时就应该多少位对齐
ARM工作模式
| 模式 | 释义 |
|---|---|
| User | 非特权模式,一般在执行上层的应用程序时ARM处于该模式 |
| FIQ | 当一个高优先级中断产生后ARM将进入这种模式 |
| IRQ | 当一个低优先级中断产生后ARM将进入这种模式 |
| SVC | 当复位或执行软中断指令后ARM将进入这种模式 |
| Abort | 当产生存取异常时ARM将进入这种模式 |
| Undef | 当执行未定义的指令时ARM将进入这种模式 |
| System | 使用和User模式相同寄存器集的特权模式 |
| Monitor | 为了安全而扩展出的用于执行安全监控代码的模式 |
- 不同模式拥有不同权限
- 不同模式执行不同代码
- 不同模式完成不同的功能
ARM工作模式分类
按照权限
User为非特权模式(权限较低),其余模式均为特权模式(权限较高)
按照状态
FIQ、IRQ、SVC、Abort、Undef属于异常模式,即当处理器遇到异常后会进入对应的模式
ARM寄存器组织
概念
寄存器是处理器内部的存储器,没有地址
ARM寄存器

ARM-9寄存器一共有40个
在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用
专用寄存器
- R15(PC,Program Counter)
- 程序计数器,用于存储当前取址指令的地址
- R14(LR,Link Register)
- 链接寄存器,一般有以下两种用途:
- 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址,程序需要返回时将LR的值复制到PC即可实现
- 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下.一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回
- 链接寄存器,一般有以下两种用途:
- R13(SP,Stack Pointer)
- 栈指针,用于存储当前模式下的栈顶地址
CPSR寄存器
CPSR(Current Program Status Register),当前程序状态寄存器

CPSR寄存器分为四个域
-
[31:24]为条件域用F表示 -
[23:16]为状态域用S表示 -
[15:8]为预留域用X表示 -
[8:0]为控制域用C表示 -
Bit[4:0] -
[10000]User [10001]FIQ [10010]IRQ [10011]SVC
-
[10111]Abort [11011]Undef [11111]System [10110]Monitor
-
Bit[5]- [0]ARM状态 [1]Thumb状态
-
Bit[6]- [0]开启FIQ [1]禁止FIQ
-
Bit[7]- [0]开启IRQ [1]禁止IRQ
-
Bit[28]- 当运算器中进行加法运算且产生符号位进位时该位自动置1,否则为0
- 当运算器中进行减法运算且产生符号位借位时该位自动置0,否则为1
-
Bit[29]- 当运算器中进行加法运算且产生进位时该位自动置1,否则为0
- 当运算器中进行减法运算且产生借位时该位自动置0,否则为1
-
Bit[30]- 当运算器中产生了0的结果该位自动置1,否则为0
-
Bit[31]- 当运算器中产生了负数的结果该位自动置1,否则为0
ARM异常处理
概念
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件异常事件处理完成之后再返回到被异常打断的点继续执行程序
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
ARM异常源
导致异常产生的事件称为异常源
| ARM异常源· | 释义 |
|---|---|
| FIQ | 快速中断请求引脚有效 |
| IRQ | 外部中断请求引脚有效 |
| Reset | 复位电平有效 |
| Software Interrupt | 执行swi指令 |
| Data Abort | 数据终止 |
| Prefetch Abort | 指令预取终止 |
| Undefined Instruction | 遇到不能处理的指令 |
ARM异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式
| 异常源 | FIQ | IRQ | Reset SWI | Data Abort Prefetch Abort | Undef Instruction |
|---|---|---|---|---|---|
| 异常模式 | FIQ | IRQ | SVC | Abort | Undef |
ARM异常响应
ARM产生异常后的动作(自动完成)
- 拷贝CPSR中的内容到对应异常模式下的SPSR_
- 修改CPSR的值
- 修改中断禁止位禁止相应的中断
- 修改模式位进入相应的异常模式
- 修改状态位进入ARM状态
- 保存返回地址到对应异常模式下的LR__
- 设置PC为相应的异常向量(异常向量表对应的地址)
异常向量表
-
异常向量表的本质是内存中的一段代码
-
表中为每个异常源分配了四个字节的存储空间
-
遇到异常后处理器自动将PC修改为对应的地址
-
因为异常向量表空间有限一般我们不会再这里
-
写异常处理程序,而是在对应的位置写一条跳
-
转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址

异常返回
ARM异常返回的动作(自己编写)
- 将SPSR_的值复制给CPSR使处理器恢复之前的状态
- 将LR__的值复制给PC使程序跳转回被打断的地址继续执行
IRQ异常举例

异常优先级

FIQ和IRQ的区别

ARM微架构
指令流水线
ARM指令流水线
- ARM7采用3级流水线
- ARM9采用5级流水线
- Cortex-A9采用8级流水线
注1:虽然流水线级数越来越多,但都是在三级流水线的基础上进行了细分
PC的作用(取指)
不管几级流水线,PC指向的永远是当前正在取指的指令,而当前正在执行的指令的地址为PC-8
指令流水线意义
指令流水线机制的引入确实能够大大的提升指令执行的速度但在实际执行程序的过程中很多情况下流
水线时是无法形成的比如芯片刚上电的前两个周期、执行跳转指令后的两个周期等所以指令流水线的
引入以及优化只能使平均指令周期不断的接近1而不可能真正的达到1,且流水线级数越多芯片设计的
复杂程度就越高,芯片的功耗就越高
多核处理器
即一个SOC中集成了多个CPU核
作用:不同的线程可以运行在不同的核心中做到真正的并发
资源:多核处理器共用外设与接口资源

ARM指令
汇编的本质
汇编:
- 每条汇编都会唯一对应一条机器码,且CPU能直接识别和执行即汇编中所有的指令都是CPU能够识别和执行的
- 汇编中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等都需要自己维护
C语言:
- 每条C语句都要被编译器编译成若干条汇编指令才能被CPU识别和执行即C语句中的指令CPU不一定能直接识别,需要编译器进行“翻译”
- C中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等 都是编译器来分配和维护
汇编和C语言的区别
-
汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,汇编语言可能有所差异,所以不易移植
-
C语言是一种结构化的高级语言。其优点是可读性好,移植容易,是普遍使用的一种计算机语言。缺点是占用资源较多,执行效率没有汇编高。
ARM指令
常用指令
数据转移指令
- MOV 数据转移指令
- MVN 数据转移取反指令
数据运算指令的格式
<操作码> <目标寄存器> <第一操作寄存器> <第二操作数>
- 操作码 表示执行那种操作
- 目标寄存器 用于存储运算的结果
- 第一操作寄存器 存储第一个参与运算的数据(
只能写寄存器) - 第二操作数 第二个参加运算的数据(可以是
寄存器也可以是立即数)
数据运算指令
- 加法:ADD R1,R2,R3 R1=R2+R3
- 减法:SUB R1,R2,R3 R1=R2-R3
- 逆向减法指令:RSB R1,R2,#3 R1=3-R2
- 乘法指令:MUL R1,R2,R3 R1=R2*R3 乘法不可以使用立即数 只能是两个寄存器相乘
- 除法:
ARM精简指令集不支持除法 - 按位与指令:AND R1,R2,R3 R1=R2&R3
- 按位或指令:ORR R1,R2,R3 R1=R2|R3
- 按位异或指令:EOR R1,R2,R3 R1=R2^R3
- 左移指令:LSL R1,R2,R3 R1=(R2<<R3)
- 右移指令:LSR R1,R2,R3 R1=(R2>>R3)
- 位清零指令:BIC R1,R2,#0XF 第二操作数中的哪一位为1,就将第一操作寄存器中的哪一位清零,然后将结果放入目标寄存器
- 数据运算指令的格式扩展
- MOV R1, R2, LSL #1 R1=(R2<<1)
- 数据运算指令对条件位(N、Z、C、V)的影响
- 默认情况下数据运算不会对条件位产生影响
在指令后加后缀”S"后可以影响SUBS R2,R1,#3
- 32位的cpu怎么计算64位的数据
- 将64位的数据拆为高32位和低32位
- ADDS R5,R1,R3
- ADC R6,R2,R4
带进位的加法运算指令 - 例如:
- MOV R1,#OXFFFFFFFF
- MOV R2,#0X00000001
- MOV R3,#OXOOOOOOO5
- MOV R4,#OXOOOOOOO2
- ADDS R5,R1,R3
- ADC R6,R2,R4
本质:R6 = R2+R4+'C'
- 例如:
- MOV R1,#OX00000001
- MOV R2,#0X00000002
- MOV R3,#OXOOOOOOO5
- MOV R4,#OXOOOOOOO1
- SUBS R5,R1,R3
- SBC R6,R2,R4
本质:R6 = R2-R4-'!C'
立即数
- #123 立即数(0-255)立即数就是包含在指令当中的数,属于指令的一部分
- 优点:
- 取指的时候就可以将其读取到cpu,不用单独去内存读取,速度快
- 缺点
- 不能时任意的32位的数字,有局限性
跳转指令
- B 标号
跳转到标号的地方但是不返回- 本质:将PC寄存器的值修改成标号下第一条指令的地址
- BL 标号 跳转到标号的地方并返回
- 本质:将PC寄存器的值修改成跳转标号下第一条指令的地址,同时将跳转指令的地址存储到LR
返回时,需要手动将LR的值赋给PC
- 修改PC值,地址需要自己计算
ARM指令的条件码

比较指令
CMP R1,R2
- 本质:就是一条减法指令,只是没有将运算的结果存入寄存器
比较跳转指令
BEQ FUNC
- 本质:if(Z==1){B FUNC}
ARM指令中大多数指令都可以带条件码后缀
访问读写内存Load/Srore指令
写内存
- STR R1,[R2]
- 将R1寄存器中的数据存储到R2指向的内存空间
- STRB R1,[R2]
- 一次写入一个字节的数据到R1
- 后缀B 一个字节
- 后缀H 半个字
- STR 默认一个字
读内存
- LDR R3,[R2]
- 将内存中R2的数据读到R3
LDR指令同样支持以上后缀
寻址方式
就是CPU去寻找一个操作数的方式
-
立即寻址
- ADD R1,R2,#1
-
寄存器寻址
-
ADD R1,R2,R3
-
寄存器移位寻址
- MOV R1,R2,LSL,#1
-
-
寄存器间接寻址
- STR R1,[R2]
-
基址加变址寻址
- STR R1,[R2,R3] 本质:将R1寄存器中的数据写入到R2+R3指向的内存空间
- STR R1,[R2,R3,LSL #1] 本质:将R1寄存器中的数据写入到R2+(R3<<1)指向的内存空间
-
基址加变址寻址的索引方式
- MOV R1,#0XFFFFFFFF
- MOV R2,#0X40000000
前索引STR R1,[R2,#8] 本质:将R1寄存器中的数据写入到R2+8指向的内存空间后索引STR R1,[R2],#8 本质:将R1寄存器中的数据写入到R2指向的内存空间,然后R2自增8自动索引STR R1,[R2,#8]! 本质:将R1寄存器中的数据写入到R2+8指向的内存空间,然后R2自增8
以上寻址和索引方式同样适用于LDR
多寄存器内存访问指令
- STM R11,{R1-R4} 本质:将R1-R4寄存器中的数据存储到内存以R11为起始的内存中
- LDM R11,{R6-R11} 本质:将内存中R11为起始地址的数据读取到R6-R11的内存中
当寄存器不连续时,使用逗号分隔不管寄存器列表中的顺序如何,存储时永远是低地址存储小编号的寄存器自动索引也适用于都寄存器内存访问指令- STM R11!,{R1-R4}
- SRMIA R11!,{R1-R4} 默认后缀为IA选项(包括R11的地址)
- SRMIB R11!,{R1-R4} 向自增4个字节,然后再存储(不包括R11的地址)
- SRMDA R11!,{R1-R4} 减少4字节,方向和SRMIA相反,向低地址递减(包括R11的地址)
- SRMDB R11!,{R1-R4} 减少4字节,方向和SRMIA相反,向低地址递减(不包括R11的地址)


栈的种类
栈的本质就是一段内存,程序运行时用于保存一些临时数据
如局部变量,函数的参数,返回值,以及跳转时需要保护的寄存器
栈的分类
- 增栈:压栈时栈指针越来越大,出栈时栈指针越来越小
- 减栈:压栈时栈指针越来越大,出栈时栈指针越来越小
- 满栈:栈指针指向最后一次压入到栈中的数据,压栈时需要先移动栈指针到相邻位置然后再压栈
- 空栈:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后需要将栈指针移动到相邻位置
栈分为空增(EA)、空减(ED)、满增(FA)、满减(FD)四种
ARM处理器一般使用满减栈
- STMFD R11!,{R1-R4} 入栈
- LDMFD R11!.{R6-R9} 出栈
例子
叶子函数:不需要记录LR
MOV SP, #0x40000020
MIAN:
MOV R1, #3
MOV R2, #5
@调用FUNC子程序
BL FUNC
ADD R3, R1, R2
B STOP
FUNC:
@压栈保护现场
STMFD PC!,{R1,R2}
MOV R1, #10
MOV R2, #20
SUB R3, R2, R1
@出栈恢复现场
LDMFD PC!.{R1,R2}
MOV PC, LR
STOP:
B STOP
非叶子函数:需要记录LR
MOV SP, #0x40000020
MIAN:
MOV R1, #3
MOV R2, #5
BL FUNC1
ADD R3, R1, R2
B STOP
FUNC1:
STMFD SP!, {R1,R2,LR}
MOV R1, #10
MOV R2, #20
BL FUNC2
SUB R3, R2, R1
LDMFD SP!, {R1,R2,LR}
MOV PC, LR
FUNC2:
STMFD SP!, {R1,R2}
MOV R1, #7
MOV R2, #8
MUL R3, R1, R2
LDMFD SP!, {R1,R2}
MOV PC, LR
专用指令
状态寄存器传送指令
读CPSR
- MSR R1,CPSR
写CPSE
- MSR CPSR,#0X10
软中断指令
SWI #1
- 软中断指令
@ 异常向量表
B MAIN
B .
B SWI_HANDLER
B .
B .
B .
B .
B .
@ 应用程序
MAIN:
MOV SP, #0x40000020
@ 初始化SVC模式下的栈指针
MSR CPSR, #0x10
@ 切换成USER模式,开启FIQ、IRQ
MOV R1, #1
MOV R2, #2
SWI #1
@ 触发软中断异常
ADD R3, R2, R1
B STOP
@ 异常处理程序
SWI_HANDLER:
SRMFD SP!,{R1,R2,LR}
@ 压栈保护现场
MOV R1, #10
MOV R2, #20
SUB R3, R2, R1
LDMFD SP!,{R1,R2,PC}^
出栈恢复现场
@ 将压入到栈中的LR(返回地址)出栈给PC,实现程序的返回
@ ‘^’表示出栈的同时将SPSR的值传递给CPSR,实现CPU状态的恢复
协处理器指令
操控制协处理器的指令
- 协处理器数据运算指令
- CDP
- 协处理器存储器访问指令
- STC
- 将协处理器中的数据存储到存储器
- LDC
- 将存储器中的数据读取到协处理器中
- 协处理器寄存器传送指令
- MRC
- 将协处理器中寄存器的数据传送到ARM处理器
- MCR
- 将ARM处理器中的寄存器数据传送到协处理器中的寄存器
伪指令
本身不是指令,编译器可以将其替换成若干条指令
-
NOP
- 空指令
-
LDR
- 可以将任意32位的数据放到寄存器中
- 可以是指令也可以是伪指令
- LDR R1,=0X12345678 @ 此时为伪指令
- LDR R1,[R2] @ 此时为指令
- LDR R1,=stop @ 此时为伪指令
- 将stop的地址写入R1寄存器
- LDR R1,stop @ 此时为伪指令
- 将stop地址中的内容写入寄存器
伪操作
不会生成代码,只是在编译阶段告诉编译器怎么编译
.global symbol
- 将symbol 声明为全局
.equ DATA,0XFF
MOV R1,#DATA
- 作用类似于#define
.macor FUNC
MOV R1,#1
MOV R2,#2
.endm
FUNC
- 调用FUNC函数时,相当于MOV R1,#1;MOV R2,#2两句
.if 1
MOV R1,#1
MOV R2,#2
.endif
- 条件成立MOV R1,#1;MOV R2,#2两句编译
- 否则不编译
.rept 3
MOV R1,#1
MOV R2,#2
.endr
- 将代码编译三遍
.weak symbol
B symbol
-
弱化一个符号,告诉编译器使用了symbol 但是symbol没有定义 不要报错
-
会把该语句编译成为一个空指令NOP
MOV R1,#1
.word 0xffffffff
MOV R2,#2
-
在当前地址申请一个字(4字节)的空间,并做初始化
-
在R1地址之后不放R2,放0xffffffff,空出一个字的内存,之后在放R2
MOV R1,#1
.byte 0xff
.align n
MOV R2,#2
- 在当前地址申请一个字节的空间,并做初始化
- .align 告诉编译器之后的代码一定要2的n次方对齐
MOV R1,#1
.space 12,0x12
MOV R2,#2
- 在当前地址申请任意个字节的空间,并做初始化
.arm
.thumb
- 告诉编译器后续的代码为arm代码
- 告诉编译器后续的代码为thumb代码
.end
- 汇编的结束
简述伪操作,伪指令和指令的区别
C和汇编的混合编程
C和汇编的混合编程的原则:
- 在那种语言环境下符合那种语言的语法规则
- 在汇编中将c语言中的函数当作标号来处理
- 在c语言中将汇编的标号当作函数来处理
汇编语言跳转C语言
MOV R1,#1;
MOV R2,#2
BL fun_c
MOV R3,#3
- fun_c c语言函数
C语言调用汇编语言
.global FUNC_ASM @将FUNC_ASM声明为全局
FUNC_ASM:
MOV R4,#4
MOV R5,#5
void func_c(void)
{
int a;
a++;
FUNC_ASM();
a--;
}
C内联汇编
void func_c(void)
{
int a;
a++;
asm
{
"MOV R6,#6\n"
"MOV R7,#7\n"
};
FUNC_ASM();
a--;
}
ATPCS协议
主要内容
介绍
ATPCS(ARM-Thumb Produce Call Standard)是ARM程序和Thumb程序中子程序调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。
栈的种类
-
使用满减栈
-
寄存器的使用
-
R15 PC程序计数器 只能用于存储程序的指针 不能做其他用途
-
R14 LR链接寄存器 只能用于存储返回地址,必能用作其他用途
-
R13 SP栈指针 只能用于存储栈指针,不能做其他用途
-
R0-R3 当函数的参数少于四个的时候,使用R0-R3传参,多出的部分用栈传递
- 函数的返回值使用R0寄存器传递
-
其余寄存器主要用于存储局部变量
-
FS4412开发环境搭建
安装交叉编译工具链
-
安装交叉编译工具链
-
在 ubuntu 下打开一个终端并进入到家目录
$ cd ~
- 在家目录下创建“Linux_4412/toolchain”目录用于存放交叉编译工具链
$ mkdir -p Linux_4412/toolchain
- 进入到该目录下
$ cd Linux_4412/toolchain/
- 将资料中“1.交叉编译工具链”下的 gcc-4.6.4.tar 拷贝到该目录下
- 解压“gcc-4.6.4.tar.xz”
$ tar xvf gcc-4.6.4.tar.xz
-
将交叉编译工具链添加到全局环境变量使其全局可用
-
回到家目录
$ cd ~
- 打开家目录下的配置文件“
- .bashrc”
$ vi .bashrc
-
在配置文件的最后一行添加如下内容,将交叉编译工具链的安装路径添加到了全局环境变量中,然后保存退出
-
重启配置文件使配置生效
$ source ~/.bashrc
- 执行以下命令
$ arm-none-linux-gnueabi-gcc -v
-
有以下信息打印则表示安装成功
-
注:
若使用的是 ubuntu 16.04 或更高的版本(64 位 ubuntu),需要安装 32 位库
$ sudo apt-get install lib32z1 lib32ncurses5
硬件控制原理

硬件控制原理:
-
CPU本身是不能直接控制硬件的,硬件一般是由其对应的控制器来控制,SOC中将各个硬件控制器的寄存器映射到了CPU地址空间中的一段范围,这样CPU就可以通过读写寄存器来间接控制硬件
-
注:这里的寄存器在S0C中但在CPU之外,有地址,访问方式与内存一样,常用于控制硬件
地址映射表:
- 在一个处理器中,一般会将Flash、RAM、寄存器等存储设备分别映射到寻址空间中的不同地址段,我们将这个映射关系成为这个处理器的地址映射表
GPIO
编程步骤
- 通过电路原理图分析LED的控制逻辑 - 高电平点亮、低电平熄灭
- 通过电路原理图查找LED与Exynos4412的连接关系 - GPX2_7
- 通过数据手册分析GPIO中哪些寄存器可以控制LED - GPX2CON、GPX2DAT
- 通过程序去操控对应的寄存器完成对LED的控制
编写汇编代码
.text
_start:
#打开led2
LED_CONFIG:
LDR R2, =0x11000c40
LDR R1, =0x10000000
STR R1, [R2]
LED_ON:
LDR R2, =0x11000c44
LDR R1, =0x00000080
STR R1, [R2]
STOP:
B STOP
.end
转bin格式
TARGET = led-asm
CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
all:
$(CC) -c $(TARGET).s -o $(TARGET).o
$(LD) $(TARGET).o -Ttext 0x40008000 -o $(TARGET).elf
$(OBJCOPY) -O binary -S $(TARGET).elf $(TARGET).bin
clean:
rm $(TARGET).o $(TARGET).elf $(TARGET).bin
led实验
led闪烁
.text
_start:
MAIN:
BL LED_CONFIG
LOOP:
BL LED_ON
BL DELAY
BL LED_OFF
BL DELAY
B LOOP
LED_CONFIG:
LDR R2, =0x11000c40
LDR R1, =0x10000000
STR R1, [R2]
MOV PC, LR
LED_ON:
LDR R2, =0x11000c44
LDR R1, =0x00000080
STR R1, [R2]
MOV PC, LR
LED_OFF:
LDR R2, =0x11000c44
LDR R1, =0x00000000
STR R1, [R2]
MOV PC, LR
DELAY:
LDR R1, =100000000
L:
SUB R1, R1, #1
CMP R1, #0
BNE L
MOV PC, LR
STOP:
B STOP
.end
C工程与寄存器封装
1.16进制地址
/*
* 一.汇编语言访问存储器
* 1.读存储器
* LDR R1,[R2]
* 2.写存储器
* STR R1,[R2]
*
* 二.C语言访问存储器
* 1.读存储器
* data = *ADDR
* 2.写存储器
* *ADDR = data
*/
void Delay(unsigned int Time)
{
while(Time --);
}
#if 0
int main()
{
/*通过设置GPX2CON寄存器来将GPX2_7引脚设置成输出功能*/
*(unsigned int *)0x11000c40 = 0x10000000;
while(1)
{
/*点亮LED2*/
*(unsigned int *)0x11000c44 = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
*(unsigned int *)0x11000c44 = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
2.define宏定义 寄存器地址
#if 0
#define GPX2CON (*(unsigned int *)0x11000c40)
#define GPX2DAT (*(unsigned int *)0x11000c44)
int main()
{
GPX2CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
3.结构体宏定义地址
#if 0
typedef struct
{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(gpx2 *)0x11000c40)
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
4.头文件定义
#if 0
#include "exynos_4412.h"
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
5.头文件定义 设置寄存器不干扰其他比特位
#include "exynos_4412.h"
int main()
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}
/*
* 1.unsigned int a; 将a的第3位置1,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****1***
* 00000000 00000000 00000000 00001000
*
* a = a | (1 << 3);
*
* 2.unsigned int a; 将a的第3位置0,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****0***
* 11111111 11111111 11111111 11110111
*
* a = a & (~(1 << 3));
*
* 3.unsigned int a; 将a的第[7:4]位置为0101,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** 0101****
*
* 1).先清零
* 11111111 11111111 11111111 00001111
* 00000000 00000000 00000000 11110000
* 00000000 00000000 00000000 00001111
*
* a = a & (~(0xF << 4));
*
* 2).再置位
* 00000000 00000000 00000000 01010000
* 00000000 00000000 00000000 00000101
*
* a = a | (0x5 << 4);
*
* => a = a & (~(0xF << 4)) | (0x5 << 4);
*/
UART
UART的寄存器
ULCONn
控制UART的帧格式
| 寄存器名字· | 位 | 描述 |
|---|---|---|
| Word Length | [0:1] | 发送数据的宽度 |
| Number of Stop Bit | [2] | 停止位的个数 0 = 每帧一个停止位 1 = 每帧两个停止位 |
| Parity | [5:3] | 奇偶校验方式 00= 5位 01 = 6位 10 = 7位 11 = 8位 |
| Infrared Mode | [6] | 是否使用红外模式 |
UCONn
设置UART的接受和发送模式
| 寄存器名字 | 位 | 描述 |
|---|---|---|
| Loop-back Mode | [5] | 将此位设置为1将触发UART进入回环模式。此模式仅用于测试目的。 |
| Transmit Mode | [3:2] | 确定哪个函数能够将Tx数据写入UART传输缓冲区。 00 =禁用 01=中断请求或轮询模式 10 = DMA模式 11 =保留 |
| Receive Mode | [1:0] | 确定哪个函数能够从UART接收缓冲区读取数据。 00 =禁用 01=中断请求或轮询模式 10 = DMA模式 11 =保留 |
UTRSTATn
设置UART的状态
| 寄存器名字 | 位 | 描述 |
|---|---|---|
| Transmit buffer empty | [1] | 当传输缓冲区为空时,该位自动设置为1。 0 =缓冲区不为空 1 =缓冲区为空(在非fifo模式下,它请求中断DMA)。 在FIFO模式下,当Tx FIFO触发电平设置为00(空)时,请求中断或DMA。当UART使用FIFO时,在UFSTAT中检查Tx FIFO Count位和Tx FIFO Full位,而不是这个位。 |
| Receive buffer data ready | [0] | 当接收缓冲区包含通过RXDn端口接收的有效数据时,它会自动将该位设置为1。 0 =缓冲区为空 1=缓冲区有一个接收到的数据 (在非fifo模式下,它请求中断或DMA)当UART使用FIFO时,在UFSTAT中检查Rx FIFO Count位和Rx FIFO Full位,而不是这个位。 |
UTXHn
发送数据 将想要写的数据写入该寄存器
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| UTXHn | [7:0] | 传送数据 |
URXHn
发送数据
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| URXHn | [7:0] | 接受数据 |
UBRDIVn
设置波特率
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| UBRDIVn | [15:0] | 设置波特率分割值 |
UFRACVALn
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| UFRACVALn | [3:0] | 确定波特率除数的小数部分。 |
UART的配置流程
1.将GPA1_0和GPA1_1分别设置成UART2的接收引脚和发送引脚 GPA1CON[7:0]
2.设置UART2的帧格式 ULCON2 8位数据位 1位停止位 无校验 正常模式
3.设置UART2的接受和发送模式为轮询模式 UCON2[3:0]
4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL
5.将要发送的数据写入发送寄存器 UTXH2
实验过程
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将要发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat = 0;
/*判断接收寄存器是否接收到了数据 UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1)
{
/*从接收寄存器中读取接收到的数据 URXH2*/
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void UART_Send_Str(char * pstr)
{
while(*pstr != '\0')
UART_Send_Byte(*pstr++);
}
void LED_Init()
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
}
void LED_open()
{
GPX2.DAT = GPX2.DAT | (1 << 7);
}
void LED_close()
{
GPX2.DAT = GPX2.DAT & (~(1 << 7));
}
int main()
{
char RecDat = 0;
UART_Init();
LED_Init();
while(1)
{
while( !(RecDat = UART_Rec_Byte()) );
if(RecDat == '2')
LED_open();
while( !(RecDat = UART_Rec_Byte()) );
if(RecDat == '2')
LED_close();
}
return 0;
}
WDT
| 寄存器 | 作用 |
|---|---|
| WTCON | 看门狗定时器控制寄存器 |
| WTDAT | 看门狗定时器数据寄存器 |
| WTCNT | 看门狗定时器计数寄存器 |
| WTCLRINT | 看门狗定时器中断清除寄存器 |
寄存器详解
WTCON
| 寄存器 | 位 | 描述 |
|---|---|---|
| Prescaler value | [15:8] | 预分频值 任意值(2^8-1) |
| RSVD | [7:6] | 让看门狗正常工作必须设置为00 |
| Clock select | [4:3] | 时钟选择 00 = 16 01 = 32 10 = 64 11= 128 |
| interrupt generation | [2] | 是否启动中断信号 0不启动 1启动 |
| RSVD | [1] | 让看门狗正常工作必须设置为0 |
| Reset enable/disable | [0] | 使能或禁用复位信号的WDT输出位。 0关闭看门狗定时器复位功能。 1=在Exynos 4412产生复位 |
WTCNT
| 寄存器 | 位 | 描述 |
|---|---|---|
| Conunt | [15:0] | 当前看门狗的计数值 |
WDT配置流程
- 设置一级分频
- 设置二级分频
- WTCNT递减频率 = PLCK(100000000)/(0xFF + 1)/128 = 3052
- 禁止WDT产生中断信号
- 使能WDT产生复位信号
- 设置计数器的初始值
- 使能WDT,计数器开始递减
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
int main()
{
/*设置一级分频*/
WDT.WTCON = WDT.WTCON | (0xFF << 8);
/*设置二级分频*/
/*WTCNT递减频率 = PLCK(100000000)/(0xFF + 1)/128 = 3052*/
WDT.WTCON = WDT.WTCON | (0x3 << 3);
/*禁止WDT产生中断信号*/
WDT.WTCON = WDT.WTCON & (~(1 << 2));
/*使能WDT产生复位信号*/
WDT.WTCON = WDT.WTCON | 1;
/*设置计数器的初始值*/
WDT.WTCNT = (3052 * 5);
/*使能WDT,计数器开始递减*/
WDT.WTCON = WDT.WTCON | (1 << 5);
while(1)
{
printf("WDT.WTCNT = %d\n",WDT.WTCNT);
/*喂狗*/
WDT.WTCNT = 3052;
Delay(100000);
}
return 0;
}
中断
中断
- 中断是一种硬件机制,在这种机制中,设备会通知CPU它需要引起注意。中断可以随时发生。因此,当CPU通过指示中断请求线收到中断信号时,CPU停止当前进程并通过将控制权传递给服务设备的中断处理程序来响应该中断。
轮询
-
轮询不是一种硬件机制,它是一种协议,CPU会稳定地检查该设备是否需要注意。无论设备告诉处理单元它希望进行硬件处理的位置如何,在轮询过程中,处理单元都会不断询问I/O设备是否希望进行CPU处理。CPU不断检查连接到其上的每个设备,以侦查是否有任何设备需要硬件注意。
-
每个设备都有一个指示命令就绪的位,指示该设备的状态,即该命令是否被硬件终止。如果命令位已准备就绪,则该命令已失效;否则,如果该位为零,则该命令无效。
#include "exynos_4412.h"
int main()
{
/*将GPX1_1设置成输入功能*/
GPX1.CON = GPX1.CON & (~(0xF << 4));
while(1)
{
/*判断GPX1_1引脚的状态,即判断按键是否按下*/
if(!(GPX1.DAT & (1 << 1)))
{
printf("Key2 Pressed\n");
/*等待松手*/
while(!(GPX1.DAT & (1 << 1)));
}
else
{
}
}
return 0;
}
中断和轮询之间的区别
- 1、在中断时,设备会通知CPU它需要引起注意。而在轮询中,CPU会稳定地检查设备是否需要注意。
- 2、中断不是协议,而是一种硬件机制。轮询不是硬件机制,而是协议。
- 3、在中断中,该设备由中断处理程序提供服务。轮询时,该设备由CPU维修。
- 4、断可以随时发生。轮询时,CPU会以固定或适当的间隔稳定地对设备进行投票。
- 5、在中断中,中断请求线用作指示设备需要维修的指示。在轮询时,命令就绪位用作指示,表明设备需要维修。
- 6、在中断中,一旦任何设备将其中断,处理器就会受到干扰。相反,在轮询中,处理器通过重复检查每个设备的命令就绪位来浪费无数的处理器周期。
中断控制器的作用
- 多个中断同时产生时可对这些中断挂起排队,然后按照优先级依次发送给CPU处理
- 可以为每一个中断分配一个优先级
- 一个中断正在处理时若又产生其它中断,可将新的中断挂起,待CPU空闲时再发送
- 可以为每一个中断选择一个CPU处理
- 可以为每一个中断选择一个中断类型(FIQ或IRQ)
- CPU接收到中断信号后并不能区分是哪个外设产生的,此时CPU可查询中断控制器
- 来获取当前的中断信号是由哪个硬件产生的,然后再进行对应的处理
- 可以打开或禁止每一个中断
中断寄存器详解
ICDDCR
| 寄存器 | 位 | 描述 |
|---|---|---|
| ICDDCR | [0] | 全同使能中断控制器使其能够接收外部设备产生的中断信号并转发给CPU接口 |
ICDISER_CPU
| 寄存器 | 位 | 描述 |
|---|---|---|
| ICDISER | [0:31] | 设置160个中断信号的打开和关闭 |
例如:中断ID为57的中断源,在ICDISER1 寄存器中的第25位管理
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
ICDIPTR_CPU
| 寄存器 | 位 | 描述 |
|---|---|---|
| ICDIPTR | [0:7] | 选择处理中断信号的cpu接口 |
例如:cpu0处理57号中断
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);//57号中断在ICDIPTR14寄存器中占8-15位
ICCICR_CPUn
| 寄存器 | 位 | 描述 |
|---|---|---|
| ICCICR | [0] | 中断控制器与cpu之间的接口控制寄存器 |
中断设置流程
-
外设层次– 让外部的硬件控制器能产生一个中断信号并发送给中断控制器-
将GPX1 1设置成中断功能
-
设置GPX1-1的中断触发方式为下降沿触发
-
使能GPX1-1的中断功能
-
-
中断控制器层次– 让中断控制器接收外设发来的中断信号并对其进行管理,然后再转发给一个合适的CPU去处理- 全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口
- 在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口
- 选择由CPU0来处理57号中断
- 使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0
-
中断异常处理

interface.c
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//IRQ异常处理
void do_irq(void)
{
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
int main()
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}
start.S
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
/*
* 从异常向量表再跳转到IRQ的异常处理程序
*/
b irq_handler
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
/*
* IRQ的异常处理程序
*/
irq_handler:
/*
* 因为产生IRQ异常后ARM自动保存到LR中的返回地址是被IRQ打断的指令
* 的下一条再下一条指令的地址,所以我们需要人为的去修正一下
*/
sub lr, lr, #4
/*
* 因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组
* 所以在处理异常之前需要先将之前寄存器中的值压栈保护
*/
stmfd sp!, {r0-r12,lr}
/*
* 跳转到do_irq处理异常
*/
bl do_irq
/*
* 异常返回
* 1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
* 2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前
* 3.将栈中保存的LR寄存器的值出栈给PC,使程序跳转回被异常打断的点继续执行
*/
ldmfd sp!,{r0-r12,pc}^
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
BL和IRQ中断的不同
- 当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到LR寄存器,然后再将LR寄存器中的值自减4, 所以LR寄存器中保存的就是BL指令下一条指令的地址
- 该时刻PC=N+8 LR=N+4

- IRQ中断
当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4,所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址 - 该时刻PC=N+12 LR=N+8

ADC
寄存器详解
ADCCON
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| RES | [16] | adc精度选择 0 10位 1 12位 |
| ECFLG | [15] | 转换完成标志 0 正在转换 1 转换完成 只读寄存器 无需设置 |
| PESCEN | [14] | ADC的分频器的开关 0 关闭 1打开 |
| PRSCVL | [13:6] | ADC的分频值 |
| STANDBY | [2] | 低功耗设置 0 正常模式 1 低功耗模式 |
| READ_START | [1] | adc转换开始读 将数据放入寄存器 等待cpu读取 若cpu不读则一直存在且不会进行下一次转换 |
| ENABLE_START | [0] | adc转换使能 0 无影响 1 开始一次转换 会被自动清为0 READ_START和ENABLE_START只可以打开一个 |
ADCDAT
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| DATA | [0:11] | 存放数据 adc最高精度为12位 只读寄存器 |
ADCMUX
| 寄存器名称 | 位 | 描述 |
|---|---|---|
| SEL_MUX | [3:0] | 选择adc转换通道 |
示例:
/*设置ADC精度为12bit*/
ADCCON = ADCCON | (1 << 16);
/*使能ADC分频器*/
ADCCON = ADCCON | (1 << 14);
/*设置ADC分频值 ADC时钟频率=PLCK/(19+1)=5MHZ ADC转换频率=5MHZ/5=1MHZ*/
ADCCON = ADCCON & (~(0xFF << 6)) | (19 << 6);
/*关闭待机模式,使能正常模式*/
ADCCON = ADCCON & (~(1 << 2));
/*关闭通过读使能AD转换*/
ADCCON = ADCCON & (~(1 << 1));
/*选择转换通道,3通道*/
ADCMUX = 3;
/*开始转换*/
ADCCON = ADCCON | 1;
/*等待转换完成*/
while(!(ADCCON & (1 << 15)));
/*读取转换结果*/
AdcValue = ADCDAT & 0xFFF;
/*将结果转换成实际的电压值mv*/
AdcValue = AdcValue * 0.44;
RTC
RTC简介
RTC(Real Time Clock)即实时时钟,它是一个可以为系统提供精确的时间基准的元器件,RTC一般采用精度较高的晶振作为时钟源,有些RTC为了在主电源掉电时还可以工作,需要外加电池供电
寄存器简介
| 名称 | 描述 |
|---|---|
BCDSEC | 秒 |
BCDMIN | 分 |
BCDHOUR | 时 |
BCDDAYWEEK | 星期 BCDDAYWEEK和 BCDDAY的地址在手册上是写反的 |
BCDDAY | 天 |
BCDMON | 月 |
BCDYEAR | 年 |
| ALMSEC | 告警闹钟)秒 |
| ALMMIN | 告警(闹钟)分 |
| ALMHOUR | 告警(闹钟)时 |
| ALMDAY | 告警(闹钟)天 |
| ALMMON | 告警(闹钟)月 |
| ALMYEAR | 告警(闹钟)年 |
| INTP | RTC中断寄存器 |
RTCCON | RTC控制寄存器 |
| TICCNT | RTC滴答定时器相关寄存器 |
| RTCALM | 告警(闹钟)控制寄存器 |
| CURTICCNT | RTC作为滴答定时器时的相关定时器 |
示例:
/**********************************************************************
* 函数功能:rtc 初始化
**********************************************************************/
void rtc_init()
{
/*使能RTC控制*/
RTCCON = RTCCON | 1;
/*校准时间信息*/
RTC.BCDYEAR = 0x023;
RTC.BCDMON = 0x08;
RTC.BCDDAY = 0x01;
RTC.BCDWEEK = 0x02;
RTC.BCDHOUR = 0x05;
RTC.BCDMIN = 0x50;
RTC.BCDSEC = 0x00;
/*禁止RTC控制*/
RTCCON = RTCCON & (~(1));
}
PWM
PWM
PWM(Pulse Width Modulation)即脉冲宽度调制,通过对脉冲的宽度进行调制,来获得所需要波形
PWM参数
- 周期

- 占空比

PWM寄存器简介
TCFG0 一级分频器
| 寄存器 | 位 | 描述 |
|---|---|---|
| Prescaler1 | [15:8] | 预分频器1 Timer2 3 4 |
| Prescaler0 | [7:0] | 预分频器0 Timer0 1 |
TCFG1 二级分频器
| 寄存器 | 位 | 描述 |
|---|---|---|
| Divider MUX4 | [19:16] | PWM4 的二级分频器 0000 = 1/1 0001 = 1/2 0010=1/4 0011 = 1/8 0100 = 1/16 |
| Divider MUX3 | [15:12] | PWM3 的二级分频器 |
| Divider MUX2 | [11:8] | PWM2 的二级分频器 |
| Divider MUX1 | [7:4] | PWM1 的二级分频器 |
| Divider MUX0 | [3:0] | PWM0 的二级分频器 |
TCON
| 寄存器 | 位 | 描述 |
|---|---|---|
| Timer 0 autoreload on/off | [3] | 自动重装载的功能 0 = 一次 1 = 间隔模式(自动加载) |
| Timer 0 output | [2] | 0 = 电平信号反转关闭 |
| Timer 0 manual update | [1] | 定时器0手动更新 1 = 更新 TCNTBO andTCMPBO的值 0 = 无影响 |
| Timer 0 start/stop | [0] | 0 =停止定时器0 1 =启动定时器0 |
TCNTB
| 寄存器 | 位 | 描述 |
|---|---|---|
| Timer 0 count buffer | [31:0] | 设置pwm周期 |
TCMPB
| 寄存器 | 位 | 描述 |
|---|---|---|
| Timer 0 count buffer | [31:0] | 设置pwm高电平所占的时间 应小于TCNTB中的值 |
TCNTO
| 寄存器 | 位 | 描述 |
|---|---|---|
| Timer 0 count observation | [31:0] | 可以查看当前计数器中的值 只读类型寄存器 |
配置流程
- 将GPD0_0引脚设置成PWM0的输出引脚
- 设置PWM0的一级分频 一级分频倍数设置为100倍
- 设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PLCK / (99 + 1) / 1 = 1M
- 设置PWM0为自动重装载,使其能够产生连续的脉冲信号
- 设置PWM0的频率为500HZ
- 设置PWM0的占空比为50%
- 将TCNTB0中的值手动装载到递减计数器
- 关闭手动更新
- 使能PWM0,递减计数器开始递减
示例:
/**********************************************************************
* 函数功能:pwm 初始化
**********************************************************************/
void pwm_init()
{
/*1.将GPD0_0引脚设置成PWM0的输出引脚,先清零后赋值*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2.设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
/*2.设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PCLK / (99 + 1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5.设置PWM0的频率为1000HZ*/
PWM.TCNTB0 = 1000;
/*6.设置PWM0的占空比为60%*/
PWM.TCMPB0 = 600;
/*7.将TCNTB0中的值手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8.关闭手动更新*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9.使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
}
I2C
简介
- 一种串行、半双工总线
- 有两根双向的信号线
- 数据线SDA用于收发数据
- 时钟线SCL用于通信双方时钟的同步
IIC总线通信过程
- 主机发送起始信号启用总线
- 主机发送一个字节数据指明从机地址和后续字节的传送方向
- 被寻址的从机发送应答信号回应主机
- 发送器发送一个字节数据
- 接收器发送应答信号回应发送器
- (循环步骤4、5)
- 通信完成后主机发送停止信号释放总线
IIC总线寻址方式
主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,'0’表示主机发送数据,'1’表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器
起始信号和停止信号
- SCL为高电平时,SDA由高变低表示起始信号
- SCL为高电平时,SDA由低变高表示停止信号
- 起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态停止信号产生后总线处于空闲状态

字节传送与应答
IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器即一帧共有9位

同步信号
IIC总线在进行数据传送时,时钟线SCL为低电平期间发送器向数据线上发送一位数据,在此期间数据线上的信号允许发生变化,时钟线SCL为高电平期间接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生变化,必须保持稳定

典型IIC时序
主机向从机发送数据

从机向主机发送数据

注:阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送;A表示应答, A非表示非应答,S表示起始信号,P表示终止信号
主机先向从机发送数据,然后从机再向主机发送数据

寄存器详解
I2CCON
| 寄存器名 | 位 | 描述 |
|---|---|---|
| Acknowledge generation | [7] | 应答信号的产生 0 不产生应答信号 1 产生应答信号 |
| Tx clock source selection | [6] | 时钟源的选择 1 512分频 0 16分频 |
| Tx/Rx Interrupt | [5] | i2c总线Tx/Rx中断启用/禁用位启用 0 禁止 1 允许 |
| Interrupt pending flag | [4] | 检测中断是否发生 建立在第5位置1的情况下 当产生中断时第四位的值自动置1 读出数据后清0 |
I2CSTAT
| 寄存器名 | 位 | 描述 |
|---|---|---|
| Mode selection | [7:6] | 模式选择 00=从接收模式 01=从发送模式 11=主发送模式 10主接收模式 |
| Busy signal status/START STOP condition | [5] | 产生起始和停止信号 |
| Serial output | [4] | 输出接受控制位 0 关闭 1 打开 |
I2CADD
| 寄存器名 | 位 | 描述 |
|---|---|---|
| Slave address | [7:0] | 从机地址设置寄存器 |
I2CDS
| 寄存器名 | 位 | 描述 |
|---|---|---|
| Data shift | [7:0] | 数据移位寄存器 发送需要发送的数据 |
示例:
/**********************************************************************
* 函数功能:I2C向特定地址写一个字节
* 输入参数:
* slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* data:写入的数据
**********************************************************************/
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第三个字节数据(即要写入到MPU6050内部指定的寄存器中的数据)写入发送寄存器*/
I2C5.I2CDS = data;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*发送停止信号 结束本次通信*/
I2C5.I2CSTAT = 0xD0;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时*/
mydelay_ms(10);
}
/**********************************************************************
* 函数功能:I2C从特定地址读取1个字节的数据
* 输入参数: slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* 返回参数: unsigned char: 读取的数值
**********************************************************************/
unsigned char iic_read(unsigned char slave_addr, unsigned char addr)
{
unsigned char data = 0;
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即要读取的MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*清除中断挂起标志位 重新开始一次通信 改变数据传送方向*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+读位1)*/
I2C5.I2CDS = slave_addr << 1 | 0x01;
/*设置IIC为主机接收模式 发送起始信号 使能IIC收发*/
I2C5.I2CSTAT = 0xb0;
/*等待从机接收到数据后应答*/
while(!(I2C5.I2CCON & (1<<4)));
/*禁止主机应答信号(即开启非应答 因为只接收一个字节) 清除中断标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<7))&(~(1<<4));
/*等待接收从机发来的数据*/
while(!(I2C5.I2CCON & (1<<4)));
/*将从机发来的数据读取*/
data = I2C5.I2CDS;
/*直接发起停止信号结束本次通信*/
I2C5.I2CSTAT = 0x90;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时等待停止信号稳定*/
mydelay_ms(10);
return data;
}
MPU6050
简介
MPU6050可以使用IIC总线和其他器件进行数据交互,我们可以使用IIC总线向MPU6050中的控制寄存器写入数据来设置MPU6050的工作参数也可以使用IIC总线从MPU6050中的数据寄存器读取数据来获取加速度、角速度等信息
写一个字节的数据

- 1.主机(Exynos4412)发送起始信号
- 2.主机发送从机地址(MPU6050的地址)及读写方向(写)
- 3.从机(MPU6050)发送应答信号
- 4.主机发送一个字节数据(要写的寄存器的地址)
- 5.从机发送应答信号
- 6.主机发送一个字节数据(要写到寄存器的数据)
- 7.从机发送应答信号8.主机发送停止信号
读一个字节的数据

- 1.主机(Exynos4412)发送起始信号
- 2.主机发送从机地址(MPU6050的地址)及读写方向(写)
- 3.从机(MPU6050)发送应答信号
- 4.主机发送一个字节数据(要写的寄存器的地址)
- 5.从机发送应答信号
- 6.主机(Exynos4412)发送起始信号
- 7.主机发送从机地址(MPU6050的地址)及读写方向(读)8.从机(MPU6050)发送应答信号
- 9.从机发送一个字节数据(要读的寄存器中的数据)
- 10.主机发送非应答信号(不再接收更多的数据)
- 11.主机发送停止信号
常用寄存器
/****************MPU6050内部常用寄存器地址****************/
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000°/s)
#define ACCEL_CONFIG 0x1C //加速计自检及测量范围及高通滤波频率,典型值:0x0(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define SlaveAddress 0x68 //MPU6050-I2C地址
示例
实时监测开发板的放置状态
#include "exynos_4412.h"
/****************MPU6050内部寄存器地址****************/
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x18(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define SlaveAddress 0x68 //MPU6050-I2C地址
/************************延时函数************************/
void mydelay_ms(int time)
{
int i,j;
while(time--)
{
for(i=0;i<5;i++)
for(j=0;j<514;j++);
}
}
/**********************************************************************
* 函数功能:I2C向特定地址写一个字节
* 输入参数:
* slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* data:写入的数据
**********************************************************************/
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第三个字节数据(即要写入到MPU6050内部指定的寄存器中的数据)写入发送寄存器*/
I2C5.I2CDS = data;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*发送停止信号 结束本次通信*/
I2C5.I2CSTAT = 0xD0;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时*/
mydelay_ms(10);
}
/**********************************************************************
* 函数功能:I2C从特定地址读取1个字节的数据
* 输入参数: slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* 返回参数: unsigned char: 读取的数值
**********************************************************************/
unsigned char iic_read(unsigned char slave_addr, unsigned char addr)
{
unsigned char data = 0;
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即要读取的MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*清除中断挂起标志位 重新开始一次通信 改变数据传送方向*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+读位1)*/
I2C5.I2CDS = slave_addr << 1 | 0x01;
/*设置IIC为主机接收模式 发送起始信号 使能IIC收发*/
I2C5.I2CSTAT = 0xb0;
/*等待从机接收到数据后应答*/
while(!(I2C5.I2CCON & (1<<4)));
/*禁止主机应答信号(即开启非应答 因为只接收一个字节) 清除中断标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<7))&(~(1<<4));
/*等待接收从机发来的数据*/
while(!(I2C5.I2CCON & (1<<4)));
/*将从机发来的数据读取*/
data = I2C5.I2CDS;
/*直接发起停止信号结束本次通信*/
I2C5.I2CSTAT = 0x90;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时等待停止信号稳定*/
mydelay_ms(10);
return data;
}
/**********************************************************************
* 函数功能:MPU6050初始化
**********************************************************************/
void MPU6050_Init ()
{
iic_write(SlaveAddress, PWR_MGMT_1, 0x00); //设置使用内部时钟8M
iic_write(SlaveAddress, SMPLRT_DIV, 0x07); //设置陀螺仪采样率
iic_write(SlaveAddress, CONFIG, 0x06); //设置数字低通滤波器
iic_write(SlaveAddress, GYRO_CONFIG, 0x18); //设置陀螺仪量程+-2000度/s
iic_write(SlaveAddress, ACCEL_CONFIG, 0x0); //设置加速度量程+-2g
}
/**********************************************************************
* 函数功能:gpio 初始化
**********************************************************************/
void gpio_init()
{
/*设置GPB_2引脚和GPB_3引脚功能为I2C传输引脚*/
GPB.CON = (GPB.CON & ~(0xF<<12)) | 0x3<<12; //设置GPB_3引脚功能为I2C_5_SCL
GPB.CON = (GPB.CON & ~(0xF<<8)) | 0x3<<8; //设置GPB_2引脚功能为I2C_5_SDA
}
/**********************************************************************
* 函数功能:rtc 初始化
**********************************************************************/
void rtc_init()
{
/*使能RTC控制*/
RTCCON = RTCCON | 1;
/*校准时间信息*/
RTC.BCDYEAR = 0x023;
RTC.BCDMON = 0x08;
RTC.BCDDAY = 0x01;
RTC.BCDWEEK = 0x02;
RTC.BCDHOUR = 0x05;
RTC.BCDMIN = 0x50;
RTC.BCDSEC = 0x00;
/*禁止RTC控制*/
RTCCON = RTCCON & (~(1));
}
/**********************************************************************
* 函数功能:pwm 初始化
**********************************************************************/
void pwm_init()
{
/*1.将GPD0_0引脚设置成PWM0的输出引脚,先清零后赋值*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2.设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
/*2.设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PCLK / (99 + 1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5.设置PWM0的频率为1000HZ*/
PWM.TCNTB0 = 1000;
/*6.设置PWM0的占空比为60%*/
PWM.TCMPB0 = 600;
/*7.将TCNTB0中的值手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8.关闭手动更新*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9.使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
}
/**********************************************************************
* 函数功能:key2按键 初始化
**********************************************************************/
void key_init()
{
/*将GPX1_1设置成输入功能*/
GPX1.CON = GPX1.CON & (~(0xF << 4));
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned char zvalue_h,zvalue_l; //存储z轴读取结果
unsigned char xvalue_h,xvalue_l; //存储x轴读取结果
unsigned char yvalue_h,yvalue_l; //存储y轴读取结果
short int zvalue;
short int xvalue;
short int yvalue;
unsigned int OldMin = 0, NewMin = 0, OldSec = 0, NewSec = 0;
key_init(); //初始化按键
pwm_init(); //初始化pwm
rtc_init(); //初始化rtc
gpio_init(); //初始化gpio
uart_init(); //初始化串口
MPU6050_Init(); //初始化MPU6050
while(1)
{
zvalue_h = iic_read(SlaveAddress, ACCEL_ZOUT_H); //获取MPU6050-Z轴加速度高字节
zvalue_l = iic_read(SlaveAddress, ACCEL_ZOUT_L); //获取MPU6050-Z轴加速度低字节
zvalue = (zvalue_h<<8)|zvalue_l; //获取MPU6050-Z轴加速度
xvalue_h = iic_read(SlaveAddress, ACCEL_XOUT_H); //获取MPU6050-X轴加速度高字节
xvalue_h = iic_read(SlaveAddress, ACCEL_XOUT_H); //获取MPU6050-X轴加速度高字节
xvalue = (xvalue_h<<8)|xvalue_l; //获取MPU6050-X轴加速度
yvalue_h = iic_read(SlaveAddress, ACCEL_YOUT_H); //获取MPU6050-Y轴加速度高字节
yvalue_h = iic_read(SlaveAddress, ACCEL_YOUT_H); //获取MPU6050-Y轴加速度高字节
yvalue = (yvalue_h<<8)|yvalue_l; //获取MPU6050-Y轴加速度
if(xvalue == 0 && yvalue == 0)//姿势判断为水平静止放置 1分钟1打印
{
NewMin = RTC.BCDMIN;
if(OldMin != NewMin)
{
printf(" Status: Normal\n");
printf("20%x-%x-%x %x %x:%x:%x\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
OldMin = NewMin;
}
}
else//姿势判断为发生倾斜 1秒1打印
{
NewSec = RTC.BCDSEC;
if(OldSec != NewSec)
{
printf(" Status: Warning\n");
printf("20%x-%x-%x %x %x:%x:%x",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
printf("xvalue = %d, yvalue = %d\n", xvalue, yvalue);
OldSec = NewSec;
}
//如果按下Key2,解除蜂鸣器的警报声
if (!(GPX1.DAT & (1 << 1)))
{
printf("Key2 Pressed\n");
PWM.TCON = PWM.TCON & (~(1));
/*等待松手*/
while(!(GPX1.DAT & (1 << 1)));
}
else
{
//让蜂鸣器产生“滴滴”的报警声
PWM.TCON = PWM.TCON | 1;
mydelay_ms(100);
PWM.TCON = PWM.TCON & (~(1));
mydelay_ms(100);
}
}
}
return 0;
}
```c
/
PWM.TCON = PWM.TCON | 1;
}
/**********************************************************************
* 函数功能:key2按键 初始化
**********************************************************************/
void key_init()
{
/*将GPX1_1设置成输入功能*/
GPX1.CON = GPX1.CON & (~(0xF << 4));
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned char zvalue_h,zvalue_l; //存储z轴读取结果
unsigned char xvalue_h,xvalue_l; //存储x轴读取结果
unsigned char yvalue_h,yvalue_l; //存储y轴读取结果
short int zvalue;
short int xvalue;
short int yvalue;
unsigned int OldMin = 0, NewMin = 0, OldSec = 0, NewSec = 0;
key_init(); //初始化按键
pwm_init(); //初始化pwm
rtc_init(); //初始化rtc
gpio_init(); //初始化gpio
uart_init(); //初始化串口
MPU6050_Init(); //初始化MPU6050
while(1)
{
zvalue_h = iic_read(SlaveAddress, ACCEL_ZOUT_H); //获取MPU6050-Z轴加速度高字节
zvalue_l = iic_read(SlaveAddress, ACCEL_ZOUT_L); //获取MPU6050-Z轴加速度低字节
zvalue = (zvalue_h<<8)|zvalue_l; //获取MPU6050-Z轴加速度
xvalue_h = iic_read(SlaveAddress, ACCEL_XOUT_H); //获取MPU6050-X轴加速度高字节
xvalue_h = iic_read(SlaveAddress, ACCEL_XOUT_H); //获取MPU6050-X轴加速度高字节
xvalue = (xvalue_h<<8)|xvalue_l; //获取MPU6050-X轴加速度
yvalue_h = iic_read(SlaveAddress, ACCEL_YOUT_H); //获取MPU6050-Y轴加速度高字节
yvalue_h = iic_read(SlaveAddress, ACCEL_YOUT_H); //获取MPU6050-Y轴加速度高字节
yvalue = (yvalue_h<<8)|yvalue_l; //获取MPU6050-Y轴加速度
if(xvalue == 0 && yvalue == 0)//姿势判断为水平静止放置 1分钟1打印
{
NewMin = RTC.BCDMIN;
if(OldMin != NewMin)
{
printf(" Status: Normal\n");
printf("20%x-%x-%x %x %x:%x:%x\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
OldMin = NewMin;
}
}
else//姿势判断为发生倾斜 1秒1打印
{
NewSec = RTC.BCDSEC;
if(OldSec != NewSec)
{
printf(" Status: Warning\n");
printf("20%x-%x-%x %x %x:%x:%x",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
printf("xvalue = %d, yvalue = %d\n", xvalue, yvalue);
OldSec = NewSec;
}
//如果按下Key2,解除蜂鸣器的警报声
if (!(GPX1.DAT & (1 << 1)))
{
printf("Key2 Pressed\n");
PWM.TCON = PWM.TCON & (~(1));
/*等待松手*/
while(!(GPX1.DAT & (1 << 1)));
}
else
{
//让蜂鸣器产生“滴滴”的报警声
PWM.TCON = PWM.TCON | 1;
mydelay_ms(100);
PWM.TCON = PWM.TCON & (~(1));
mydelay_ms(100);
}
}
}
return 0;
}
本文围绕ARM开发展开,先介绍计算机硬件基础,包括Linux层次结构、计算机组成等。接着深入讲解ARM体系结构、微架构和指令,如RISC与CISC处理器、ARM指令集等。还阐述C和汇编混合编程、ATPCS协议,最后详细说明FS4412开发环境搭建及GPIO、UART等硬件控制实验。

4820

被折叠的 条评论
为什么被折叠?



