ARM体系结构及接口技术

本文围绕ARM开发展开,先介绍计算机硬件基础,包括Linux层次结构、计算机组成等。接着深入讲解ARM体系结构、微架构和指令,如RISC与CISC处理器、ARM指令集等。还阐述C和汇编混合编程、ATPCS协议,最后详细说明FS4412开发环境搭建及GPIO、UART等硬件控制实验。

计算机硬件基础

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遇到异常后会切换成对应的异常模式

异常源FIQIRQReset SWIData Abort Prefetch AbortUndef Instruction
异常模式FIQIRQSVCAbortUndef

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告警(闹钟)年
INTPRTC中断寄存器
RTCCONRTC控制寄存器
TICCNTRTC滴答定时器相关寄存器
RTCALM告警(闹钟)控制寄存器
CURTICCNTRTC作为滴答定时器时的相关定时器

示例:

/**********************************************************************
 * 函数功能: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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值