1.编程基础
1.C语言基础
2.C高级及Linux
3.数据结构
2.应用开发
1.IO
2.进程
3.网络编程
3.底层开发
1.ARM
2.系统移植
3.驱动开发
操作系统的作用
向下管理硬件、向上提供接口(API)
应用开发
即使用系统提供的接口(API),做上层应用程序的开发
底层开发
即做操作系统本身的开发
Linux子系统
1.进程管理:管理进程的创建、调度、销毁等
2.内存管理:管理内存的申请、释放、映射等
3.文件系统:管理和访问磁盘中的文件
4.设备管理:硬件设备及驱动的管理
5.网络协议:通过网络协议栈(TCP、IP…)进行通信
逻辑1和0
在计算机中数据的存储、运算、传输都是以高低电平的方式
所以数字电路中用高、低电平来表示逻辑1和0
输入设备
把其他信号转换成计算机能识别和处理的信号并送入计算机中
如键盘、鼠标、摄像头等
输出设备
把运算结果以人或其他设备所能接受的形式送出计算机外
如显示器、音响、打印机等
存储器
存储器是用来存储程序和数据的部件,是实现"存储程序控制"的基础
如内存、硬盘等
运算器
CPU1中负责进行算数运算和逻辑运算的部件,其核心是算术逻辑单元ALU
控制器
控制器是CPU的指挥中心,其控制着整个CPU执行程序的逻辑过程
注:运算器和控制器共同组成了CPU
总线
总线是计算机中各个部件之间传送信息的公共通信干线, 在物理上就是一束导线按照其传递信息的类型可以分为数据总线、地址总线、控制总线
DMA总线
DMA(Direct Memory Access)即直接存储器访问,使用DMA总线可以不通过CPU直接在存储器之间进行数据传递
使用三级存储结构是为了兼顾速度、容量、价格
Cache
速度最快、价格最贵、容量最小、断电数据丢失、cpu可直接访问
存储当前正在执行的程序中的活跃部分,以便快速地向CPU提供指令( 能够指示处理器执行某种运算的命令)和数据
主存储器
速度、价格、容量介于Cache与辅存之间、断电数据丢失、cpu可直接访问
存储当前正在执行的程序和数据
辅助存储器
速度最慢、价格最低、容量最大、断电数据不丢失、cpu不可直接访问
存储暂时不运行的程序和数据,需要时再传送到主存
一个处理器能够访问(读写)的存储空间是有限的,我们称这个空间为它的地址空间(寻址空间),一般来说N位地址总线的处理器的地址空间是2的N次方
一条指令的执行分为三个阶段
1.取址:
CPU将PC寄存器中的地址发送给内存,内存将其地址中对应的指令返回
到CPU中的指令寄存器(IR)
2.译码:
译码器对IR中的指令进行识别,将指令(机器码)解析成具体的运算
3.执行:
控制器控制运算器中对应的运算单元进行运算,运算结果写入寄存器
每执行一条指令后PC的值会自动增加指向下一条指令,指向取址的指令
ARM的含义
ARM(Advanced RISC Machines)有三种含义
一个公司的名称、一类处理器的通称、一种技术
ARM公司
> 成立于1990年11月,前身为Acorn计算机公司
> 主要设计ARM系列RISC处理器内核
> 授权ARM内核给生产和销售半导体的合作伙伴,ARM公司并不生产芯片
> 提供基于ARM架构的开发设计技术软件工具、评估板、调试工具、应用软件
总线架构、外围设备单元等
早先经典处理器
包括ARM7、ARM9、ARM11家族
Cortex-A系列
针对开放式操作系统的高性能处理器
应用于智能手机、数字电视、智能本等高端运用
Cortex-R系列
针对实时系统、满足实时性的控制需求
应于汽车制动系统、动力系统等
Cortex-M系列
为单片机驱动的系统提供了低成本优化方案
应用于传统的微控制器市场、智能传感器、汽车周边等
RISC处理器(精简指令集计算机(Reduced Instruction set Computer))
只保留常用的的简单指令,硬件结构简单,复杂操作一般通过简单指令的组合实现,一般指令长度固定,且多为单周期指令
RISC处理器在功耗、体积、价格等方面有很大优势,所以在嵌入式移动终端领域应用极为广泛
CISC处理器(复杂指令集电脑(complex instruction set computer))
不仅包含了常用指令,还包含了很多不常用的特殊指令,硬件结构复杂,指令条数较多,一般指令长度和周期都不固定
CISC处理器在性能上有很大优势,多用于PC及服务器等领域
SOC(System on Chip)
即片上系统,将一个系统中所需要的全部部件集成在一个芯片中,在体积、功耗、价格上有很大优势
现在嵌入式也好,手机也好用的芯片基本都是SOC级别的芯片
拆开电脑看,有CPU,有硬盘有内存, 都是独立的,但是拆开手机看,这些都集成到一个芯片内部了.
指令
能够指示处理器执行某种运算的命令称为指令(如加、减、乘 …)
指令在内存中以机器码(二进制)的方式存在
每一条指令都对应一条汇编
程序是指令的有序集合
指令集
处理器能识别的指令的集合称为指令集
不同架构的处理器指令集不同
指令集是处理器对开发者提供的接口
大多数ARM处理器都支持两种指令集:
ARM指令集
所有指令(机器码)都占用32bit存储空间
代码灵活度高、简化了解码复杂度
执行ARM指令集时PC值每次自增4
Thumb指令集(美/θʌm/)
所有指令(机器码)都占用16bit存储空间
代码密度高、节省存储空间
执行Thumb指令集时PC值每次自增2
机器码(二进制)是处理器能直接识别的语言,不同的机器码代表不同的运算指令,处理器能够识别哪些机器码是由处理器的硬件设计所决定的,不同的处理器机器码不同,所以机器码不可移植
汇编语言是机器码的符号化,即汇编就是用一个符号来代替一条机器码,所以不同的处理器汇编也不一样,即汇编语言也不可移植
C语言在编译时我们可以使用不同的编译器将C源码编译成不同架构处理器的汇编,所以C语言可以移植
ARM采用32位架构,基本数据类型有以下三种
Byte 8bits
Halfword 16bits
Word 32bits
数据存储
Word型数据在内存的起始地址必须是4的整数倍
Halfword型数据在内存的起始地址必须是2的整数倍
注:即数据本身是多少位在内存存储时就应该多少位对齐
大端对齐
低地址存放高位,高地址存放低位
a = 0x12345678;
小端对齐
低地址存放低位,高地址存放高位
a = 0x12345678;
注:ARM一般使用小端对齐
处理器处于ARM状态时
所有指令在内存的起始地址必须是4的整数倍
PC值由其[31:2]决定,[1:0]位未定义(4的整数倍地址,最低两位肯定是0,当赋值PC 的值的最低两位会被强制置零。 )
处理器处于Thumb状态时
所有指令在内存的起始地址必须是2的整数倍
PC值由其[31:1]决定,[0]位未定义
不同模式拥有不同权限
不同模式执行不同代码
不同模式完成不同的功能
那么多模式是为了适应搭载其上的诸多操作系统
按照权限
User为非特权模式(权限较低),其余模式均为特权模式(权限较高)
按照状态
FIQ、IRQ、SVC、Abort、Undef属于异常模式,即当处理器遇到异常后
会进入对应的模式
概念
寄存器是处理器内部的存储器,没有地址
作用
一般用于暂时存放参与运算的数据和运算结果
8模式ARM有40个寄存器,
1)未分组寄存器:R0-R7,共`8`个;
2)分组寄存器R8-R15。
1.其中FIQ模式下有单独的一组R8-R12共`5`个;另外6种模式共用一组R8-R12,共`5`个;
2.栈指针寄存器R13,用户模式和系统模式共用`1` 个,另外6种模式各自独有1个,共`6`个;
3.链接寄存器R14,用户模式和系统模式共用`1`个,另外6种模式各自独有1个,共`6`个;
4.程序计数器PC即R15寄存器,共`1`个;
3)状态寄存器CPSR`1`个,和6个备份状态寄存器SPSR,共`6`个
注
在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用
7模式ARM37 个,少了监控者模式下独有的,一个R13,一个R14,一个备份状态寄存器SPSR。
R15(PC,Program Counter)
程序计数器,用于存储当前取址指令的地址
R14(LR,Link Register)
链接寄存器,一般有以下两种用途:
> 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址
程序需要返回时将LR的值复制到PC即可实现
> 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下
一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回
R13(SP,Stack Pointer)
栈指针,用于存储当前模式下的栈顶地址
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] V
> 当运算器中进行加法运算且产生符号位进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生符号位借位时该位自动置0,否则为1
Bit[29] C
> 当运算器中进行加法运算且产生进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生借位时该位自动置0,否则为1
Bit[30] Z
当运算器中产生了0的结果该位自动置1,否则为0
Bit[31] N
当运算器中产生了负数的结果该位自动置1,否则为0
概念
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生,这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件,异常事件处理完成之后再返回到被异常打断的点继续执行程序
异常由内部或外部源产生并引起处理器处理一个事件。在处理异常之前,处理器状态必须保留,一遍在异常处理程序完成后,原来的程序能够重新执行。同一时刻可能出现多个异常。
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
概念
导致异常产生的事件称为异常源
ARM异常源
FIQ 快速中断请求引脚有效
IRQ 外部中断请求引脚有效
Reset 复位电平有效
Software Interrupt 执行swi指令
Data Abort 数据终止
Prefetch Abort 指令预取终止
Undefined Instruction 遇到不能处理的指令
异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切
换成对应的异常模式
ARM产生异常后的动作(自动完成)
1.拷贝CPSR中的内容到对应异常模式下的SPSR_
2.修改CPSR的值
2.1.修改中断禁止位禁止相应的中断
2.2.修改模式位进入相应的异常模式
2.3.修改状态位进入ARM状态
3.保存返回地址到对应异常模式下的LR_
4.设置PC为相应的异常向量(异常向量表对应的地址)
CPSR(Current Program Status Register),当前程序状态寄存器
注
在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用
异常向量表
> 异常向量表的本质是内存中的一段代码
> 表中为每个异常源分配了四个字节的存储空间
> 遇到异常后处理器自动将PC修改为对应的地址
> 因为异常向量表空间有限一般我们不会在这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址
但可以通过配置协处理器来修改其地址
ARM异常返回的动作(自己编写)
1.将SPSR_的值复制给CPSR 使处理器恢复产生异常之前的状态
2.将LR_的值复制给PC 使程序跳转回被打断的地址继续执行
注:整个过程CPSR保存的永远是当前程序运行状态SPSR只是异常时对原来的CPSR进行备份
异常优先级
ARM处理器中有7种类型的异常,按优先级从高到低的排列如下:复位异常(Reset)、数据异常(Data Abort)、快速中断异常(FIQ)、外部中断异常(IRQ)、预取异常(Prefetch Abort)、软中断异常(SWI)、未定义指令异常(Undefined interrupt)。
优先级最低的两种异常是软件中断异常和未定义指令异常。因为正在执行的指令不可能既是一条软中断指令,又是一条未定义指令,所以软中断异常和未定义指令异常享有相同的优先级。
FIQ的响应速度比IRQ快
1. FIQ在异常向量表位于最末, 可直接把异常处理写在异常向量表之后,省去跳转
2. FIQ模式有5个私有寄存器(R8-R12) 执行中断处理程序前无需压栈保存寄存器,可直接处理中断
3. FIQ的优先级高于IRQ
3.1 两个中断同时发生时先响应FIQ
3.2 FIQ可以打断RIQ,但RIQ不能打断FIQ
ARM指令流水线
ARM7采用3级流水线
ARM9采用5级流水线
Cortex-A9采用8级流水线
注1:虽然流水线级数越来越多,但都是在三级流水线的基础上进行了细分
PC的作用(取指)
不管几级流水线,PC指向的永远是当前正在取指的指令,而当前正在执行的指令的地址为PC-8
指令流水线机制的引入确实能够大大的提升指令执行的速度但在实际执行程序的过程中很多情况下流水线时是无法形成的比如芯片刚上电的前两个周期、执行跳转指令后的两个周期等,所以指令流水线的引入以及优化只能使平均指令周期不断的接近1而不可能真正的达到1,且流水线级数越多芯片设计的复杂程度就越高,芯片的功耗就越高
多核处理器
即一个SOC中集成了多个CPU核
作用
不同的线程可以运行在不同的核心中做到真正的并发
资源
多核处理器共用外设与接口资源
指令
能够指示处理器执行某种运算的命令称为指令(如加、减、乘 …)
指令在内存中以机器码(二进制)的方式存在
每一条指令都对应一条汇编
程序是指令的有序集合
指令集
处理器能识别的指令的集合称为指令集
不同架构的处理器指令集不同
指令集是处理器对开发者提供的接口
汇编
> 每条汇编都会唯一对应一条机器码,且CPU能直接识别和执行 即汇编中所有的指令都是CPU能够识别和执行的
> 汇编中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等都需要自己维护
C语言
> 每条C语句都要被编译器编译成若干条汇编指令才能被CPU识别和执行即C语句中的指令CPU不一定能直接识别,需要编译器进行“翻译”
> C中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等都是编译器来分配和维护
底层开发可能会读/写汇编代码
理解CPU是怎样执行程序的
理解C的本质,用汇编的思想写出高效的C代码
硬件仿真
通过硬件接口将CPU和内存中实际的信息读出来
软件仿真
通过软件模拟CPU内部运行程序的状态
Keil
Keil MDK是用于基于ARM Cortex-M 微控制器
的完整软件开发环境。它集成了uVision IDE
C/C++编译器、调试器以及其他中间组件,支
持众多芯片供应商,易于学习和使用
1.安装Keil集成开发环境
2.安装gcc交叉编译工具链
3.创建汇编工程,熟悉仿真环境的使用
@1.指令: 能够编译生成一条32bit机器码,并且能被CPU识别和执行。CPU厂家制定
@2.伪指令:本身不是指令,编译器可以将其替换成若干条指令。CPU厂家制定
@3.伪操作:不会生成指令,只是在编译阶段告诉编译器怎么编译。不同编译器语法不一样
@ 1.数据处理指令: 进行数学运算、逻辑运算
@ 2.跳转指令:实现程序的跳转,本质就是修改了PC寄存器
@ 3.Load/Store指令:访问(读写)内存
@ 4.状态寄存器传送指令:用于访问(读写) CPSR寄存器
@ 5.软中断指令: 触发软中断
@ 6.协处理器指令:操作协处理器的指令
@ 注意:其中1 2 3 是通用指令,C 语言有对应语句,4 5 6 是专用指令,处理器独有。
.text @表示当前段为代码段
.global _start @声明start为全局符号
_start: @汇编程序的入口
MOV R1, #1 @汇编程序(下述各指令放在这里执行)
stop: @死循环,防止程序跑飞
B stop
@ 1.指令:能够编译生成一条32位的机器码,且能被CPU识别和执行
@ 1.1 数据处理指令:数学运算、逻辑运算
@ 数据搬移指令
MOV R1, #1
@ R1 = 1
MOV R2, R1
@ R2 = R1
MVN R0, #0xFF
@ R0 = ~0xFF
@ 立即数
@ 立即数的本质就是包含在指令当中的数,属于指令的一部分
@ 立即数的优点:取指的时候就可以将其读取到CPU,不用单独去内存读取,速度快
@ 立即数的缺点:不能是任意的32位的数字,有局限性
MOV R0, #0x12345678
@立即数数值过大,编译错误
MOV R0, #0x12
@ 编译器替换
MOV R0, #0xFFFFFFFF
@可执行,有些汇编器会使用一些其它技巧,如使用MVN代替MOV得到一些数的按位取反数。例如指令MOV r0, #0xFFFFFFFF可以被汇编为MVN r0, #0。
@ 数据运算指令基本格式
@ 《操作码》《目标寄存器》《第一操作寄存器》《第二操作数》
@ 操作码 指示执行哪种运算
@ 目标寄存器: 存储运算结果
@ 第一操作寄存器: 第一个参与运算的数据(只能是寄存器)
@ 第二操作数: 第二个参与运算的数据(可以是寄存器或立即数)
@ 加法指令
MOV R2, #5
MOV R3, #3
ADD R1, R2, R3
@ R1 = R2 + R3
ADD R1, R2, #5
@ R1 = R2 + 5
@ 减法指令
SUB R1, R2, R3
@ R1 = R2 - R3
SUB R1, R2, #3
@ R1 = R2 - 3
@ 逆向减法指令
RSB R1, R2, #3
@ R1 = 3 - R2
@ 乘法指令
MUL R1, R2, R3
@ R1 = R2 * R3
@ 乘法指令只能是两个寄存器相乘
@ 按位与指令
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)
@ 位清零指令
MOV R2, #0xFF
BIC R1, R2, #0x0F
@ 第二操作数中的哪一位为1,就将第一操作寄存器的中哪一位清零,然后将结果写入目标寄存器
@ 格式扩展
MOV R2, #3
MOV R1, R2, LSL #1
@ R1 = (R2 << 1)
@ 数据运算指令对条件位(N、Z、C、V)的影响
@ 默认情况下数据运算不会对条件位产生影响,在指令后加后缀”S“才可以影响
@ 带进位的加法指令
@ 两个64位的数据做加法运算
@ 第一个数的低32位放在R1
@ 第一个数的高32位放在R2
@ 第二个数的低32位放在R3
@ 第二个数的高32位放在R4
@ 运算结果的低32位放在R5
@ 运算结果的高32位放在R6
@ 第一个数
@ 0x00000001 FFFFFFFF
@ 第二个数
@ 0x00000002 00000005
MOV R1, #0xFFFFFFFF
MOV R2, #0x00000001
MOV R3, #0x00000005
MOV R4, #0x00000002
ADDS R5, R1, R3 @加上ADD后S才会产生进位
ADC R6, R2, R4
@ 本质:R6 = R2 + R4 + 'C'
@ 带借位的减法指令
@ 第一个数
@ 0x00000002 00000001
@ 第二个数
@ 0x00000001 00000005
MOV R1, #0x00000001
MOV R2, #0x00000002
MOV R3, #0x00000005
MOV R4, #0x00000001
SUBS R5, R1, R3
SBC R6, R2, R4
@ 本质:R6 = R2 - R4 - '!C' (! 是取反,考虑借位)
@ 1.2 跳转指令:实现程序的跳转,本质就是修改了PC寄存器
@ 方式一:直接修改PC寄存器的值(不建议使用,需要自己计算目标指令的绝对地址)
@ MAIN:
MOV R1, #1
MOV R2, #2
MOV R3, #3
MOV PC, #0x18
MOV R4, #4
MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ 方式二:不带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ B FUNC
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ 方式三:带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址,同时将跳转指令下一条指令的地址存储到LR寄存器
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ BL FUNC
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ MOV PC, LR
@ 程序返回
CPSR寄存器分为四个域,[31:24]为条件域用F表示
Bit[28] V
> 当运算器中进行加法运算且产生符号位进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生符号位借位时该位自动置0,否则为1
Bit[29] C
> 当运算器中进行加法运算且产生进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生借位时该位自动置0,否则为1
Bit[30] Z
当运算器中产生了0的结果该位自动置1,否则为0
Bit[31] N
当运算器中产生了负数的结果该位自动置1,否则为0
@ ARM指令的条件码
@ 比较指令
@ CMP指令的本质就是一条减法指令(SUBS),只是没有将运算结果存入目标寄存器
@ MOV R1, #1
@ MOV R2, #2
@ CMP R1, R2
@ BEQ FUNC
@ 执行逻辑:if(EQ){B FUNC} 本质:if(Z==1){B FUNC}
@ BNE FUNC
@ 执行逻辑:if(NQ){B FUNC} 本质:if(Z==0){B FUNC}
@ MOV R3, #3
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ ARM指令集中大多数指令都可以带条件码后缀
@ MOV R1, #1
@ MOV R2, #2
@ CMP R1, R2
@ MOVGT R3, #3
@ 练习:用汇编语言实现以下逻辑
@ int R1 = 9;
@ int R2 = 15;
@ START:
@ if(R1 == R2)
@ {
@ STOP();
@ }
@ else if(R1 > R2)
@ {
@ R1 = R1 - R2;
@ goto START;
@ }
@ else
@ {
@ R2 = R2 - R1;
@ goto START;
@ }
@ 练习答案
@ MOV R1, #9
@ MOV R2, #15
@ START:
@ CMP R1,R2
@ BEQ STOP
@ SUBGT R1, R1, R2
@ SUBLT R2, R2, R1
@ B START
@ STOP:
@ B STOP
@ 1.3 Load/Srore指令:访问(读写)内存
@ 写内存
@ MOV R1, #0xFF000000
@ MOV R2, #0x40000000 @是可读可写的地址,从debug mem map 可查看
@ STR R1, [R2]
@ 将R1寄存器中的数据写入到R2指向的内存空间
@ 读内存
@ LDR R3, [R2]
@ 将R2指向的内存空间中的数据读取到R3寄存器
@ 读/写指定的数据类型
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STRB R1, [R2]
@ 将R1寄存器中的数据的Bit[7:0]写入到R2指向的内存空间,通过指令后缀“B”决定
@ STRH R1, [R2]
@ 将R1寄存器中的数据的Bit[15:0]写入到R2指向的内存空间
@ STR R1, [R2]
@ 将R1寄存器中的数据的Bit[31:0]写入到R2指向的内存空间
@ LDR指令同样支持以上后缀
@ 寻址方式就是CPU去寻找操作数的方式
@ 立即寻址
@ MOV R1, #1
@ ADD R1, R2, #1
@ 寄存器寻址
@ ADD R1, R2, R3
@ 寄存器移位寻址
@ MOV R1, R2, LSL #1
@ 寄存器间接寻址
@ STR R1, [R2]
@ ...
@ 基址加变址寻址
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ MOV R3, #4
@ 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指向的内存空间
@ 后索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2],#8
@ 将R1寄存器中的数据写入到R2指向的内存空间,然后R2自增8
@ 自动索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]!
@ 将R1寄存器中的数据写入到R2+8指向的内存空间,然后R2自增8,"!"的作用就是每次传送后是否自增或者自减基址寄存器的值
@ 以上寻址方式和索引方式同样适用于LDR
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STM R11,{R1-R4}
@ 将R1-R4寄存器中的数据写入到以R11为起始地址的内存空间中
@ LDM R11,{R6-R9}
@ 将以R11为起始地址的内存空间中的数据读取到R6-R9寄存器中
@ 当寄存器编号不连续时,使用逗号分隔
@ STM R11,{R1,R2,R4}
@ 不管寄存器列表中的顺序如何,存取时永远是低地址对应小编号的寄存器
@ STM R11,{R3,R1,R4,R2}
@ 自动索引照样适用于多寄存器内存访问指令
@ STM R11!,{R1-R4}
@ 多寄存器内存访问指令的寻址方式
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMIA R11!,{R1-R4}
@ 先存储数据,后增长地址。(增前存) I就是增 D 就是减 A就是前 B就是后
@ STMIB R11!,{R1-R4}
@ 先增长地址,后存储数据。(增后存)
@ STMDA R11!,{R1-R4}
@ 先存储数据,后递减地址。(减前存)
@ STMDB R11!,{R1-R4}
@ 先递减地址,后存储数据。(减后存)
C编译器会生成操作栈的指令,不用程序员干预
栈的概念
栈的本质就是一段内存,程序运行时用于保存一些临时数据
如局部变量、函数的参数、返回值、以及程序跳转时需要保护的寄存器等
栈的分类
从增长方向上,可分为向上增长(递增堆栈)和向下增长(递减堆栈)
增栈(递增堆栈):压栈时栈指针越来越大,出栈时栈指针越来越小
减栈(递减堆栈):压栈时栈指针越来越小,出栈时栈指针越来越大
从栈指针所指的位置,可分为满堆栈与空堆栈
满栈:栈指针指向最后一次压入到栈中的数据,压栈时需要先移动栈指针到相邻位置然后再压栈
空栈:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后需要将栈指针移动到相邻位置
综合起来,就有四种方式:(E:empty A:ascending F:full D:descending)
分为空增(EA)、空减(ED)、满增(FA)、满减(FD)四种
@ 栈的种类与使用
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMFD R11!,{R1-R4} @或者写成STMDB R11!,{R1-R4}
@ LDMFD R11!,{R6-R9} @或者写成LDMIA R11!,{R6-R9}
@ 栈的应用举例
@ 1.叶子函数的调用过程举例
@ 初始化栈指针
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC
@ ADD R3, R1, R2
@ B STOP
@ FUNC:
@ 压栈保护现场
@ STMFD SP!, {R1,R2}
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ 出栈恢复现场
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
@ 2.非叶子函数的调用过程举例
@ 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
@ 执行叶子函数时不需要对LR压栈保护,执行非叶子函数时需要对LR压栈保护
叶子函数5
@ 1.4 状态寄存器传送指令:访问(读写)CPSR寄存器,ARM具有该指令,启动的时候需要用到,切换用户模式
@ 读CPSR
@ MRS R1, CPSR
@ R1 = CPSR
@ 写CPSR
@ MSR CPSR, #0x10
@ CPSR = 0x10
@ 在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式
@ 1.5 软中断指令:触发软中断,用于系统调用
@ 异常向量表
@ 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:
@ STMFD SP!,{R1,R2,LR}
@ 压栈保护现场
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ LDMFD SP!,{R1,R2,PC}^
@ 出栈恢复现场
@ 将压入到栈中的LR(返回地址)出栈给PC,实现程序的返回
@ ‘^’表示出栈的同时将SPSR的值传递给CPSR,实现CPU状态的恢复
@ 1.6 协处理器指令:操控协处理器的指令
@ 1.协处理器数据运算指令
@ CDP
@ 2.协处理器存储器访问指令
@ STC 将协处理器中的数据写入到存储器
@ LDC 将存储器中的数据读取到协处理器
@ 3.协处理器寄存器传送指令
@ MRC 将协处理器中寄存器中的数据传送到ARM处理器中的寄存器
@ MCR 将ARM处理器中寄存器中的数据传送到协处理器中的寄存器
@ 2.伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
@ 空指令
@ NOP
@ 指令
@ LDR R1, [R2]
@ 将R2指向的内存空间中的数据读取到R1寄存器
@ 伪指令
@ LDR R1, =0x12345678 @类似于MOV 指令操作立即数 ,但是可以搬移任意32位数。原理:三级流水线及汇编程序编译后,汇编指令放完后,后边开辟四个字节放,放0x12345678
@ R1 = 0x12345678
@ LDR伪指令可以将任意一个32位的数据放到一个寄存器
@ LDR R1, =STOP
@ 将STOP表示的地址写入R1寄存器
@ LDR R1, STOP
@ 将STOP地址中的内容写入R1寄存器
@ 3.伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译
@ GNU的伪操作一般都以‘.’开头
@ .global symbol
@ 将symbol声明成全局符号,实现多个.s 文件之间的变量共享。
@ .local symbol
@ 将symbol声明成局部符号,只能在当前.s文件中使用
@ .equ DATA, 0xFF
@ MOV R1, #DATA
@ .equ 用于定义一个宏
@ .macro FUNC
@ MOV R1, #1
@ MOV R2, #2
@ .endm
@ FUNC
@ 类似于c语言的函数
@ .if 0
@ MOV R1, #1
@ MOV R2, #2
@ .endif
@ 条件编译
@.rept 3
@ MOV R1, #1
@ MOV R2, #2
@.endr
@ 循环3遍
@ .weak symbol 弱化一个符号,即告诉编译器即便没有这个符号也不要报错,编译的时候会编译程nob (空指令)
@ .weak func
@ B func
@ .word VALUE 在当前地址申请一个字的空间并将其初始化为VALUE,空出VALUE 的空间
@ MOV R1, #1
@ .word 0xFFFFFFFF
@ MOV R2, #2
@ .byte VALUE 在当前地址申请一个字节的空间并将其初始化为VALUE,
@ MOV R1, #1
@ .byte 0xFF
@ .align N 告诉编译器后续的代码2的N次方对其
@ .align 4
@ MOV R2, #2
@ .arm
@ 告诉编译器后续的代码是ARM指令
@ .thumb
@ 告诉编译器后续的代码是Thumb指令
@ .text
@ 定义一个代码段
@ .data
@ 定义一个数据段
@ .space N, VALUE
@ 在当前地址申请N个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .space 12, 0x12
@ MOV R2, #2
@ 不同的编译器伪操作的语法不同
@ C和汇编的混合编程
@ C和汇编的混合编程原则:在哪种语言环境下符合哪种语言的语法规则
@ 1. 在汇编中将C中的函数当做标号处理
@ 2. 在C中将汇编中的标号当做函数处理
@ 3. 在C中内联的汇编当做C的语句来处理
@ 1. 方式一:汇编语言调用(跳转)C语言(见下方c 代码)
@ MOV R1, #1
@ MOV R2, #2
@ BL func_c
@ MOV R3, #3
@ 2. 方式二:C语言调用(跳转)汇编语言
@ .global FUNC_ASM
@ FUNC_ASM:
@ MOV R4, #4
@ MOV R5, #5
@ 3. C内联(内嵌)汇编
test.c
/*
void func_c(void)
{
int a;
a ++;
//C内联(内嵌)汇编
asm//汇编语言声明;
(
"MOV R6, #6\n" //需要用双引号引起来,并且加“\n”
"MOV R7, #7\n"
);
//C语言调用(跳转)汇编语言
FUNC_ASM();
a --;
}
*/
/*
int a=1,b=1,c=1,d=1,e=1,f=1,g;
int func(int a,int b,int c,int d,int e,int f)//当函数的参数不多于4个时使用R0-R3传递,当函数的参数多于4个时,多出的部分用栈传递
{
return a+b+c+d+e+f;
}
int main(void)
{
g = func(a,b,c,d,e,f);
return 0;
}
*/
@ ATPCS协议(ARM-THUMB Procedure Call Standard) ARM-THUMB 程序调用标准
@ ATPCS协议主要内容
@ 1.栈的种类
@ 1.1 使用满减栈 (自定义代码和库代码及编译器代码)
@ 2.寄存器的使用
@ 2.1 R15(PC,Program Counter)用作程序计数器,不能作其他用途
@ 2.2 R14(LR,Link Register)用作链接寄存器,不能作其他用途
@ 2.3 R13(SP,Stack Pointer)用作栈指针,不能作其他用途
@ 2.4 当函数的参数不多于4个时使用R0-R3传递,当函数的参数多于4个时,多出的部分用栈传递
@ 2.5 函数的返回值使用R0传递
@ 2.6 其它寄存器主要用于存储局部变量(R4-R12)
核心板
扩展板:
元器件查找
1.在Ubuntu下安装交叉编译工具链
2.在windows下安装SecureCRT
3.在windows下安装USB转串口驱动
4.下载测试程序
RSV:保留
IO(SFR): 放控制器的寄存器
RAM: 放变量
ROM: 放指令
硬件控制原理
CPU本身是不能直接控制硬件的,硬件一般是由其对应的控制器来控制, SOC中将各个硬件控制器
的寄存器映射到了CPU地址空间中的一段范围,这样CPU就可以通过读写寄存器来间接控制硬件
只有加载和存储指令可以间接操控硬件。通过改变硬件控制器中寄存器内的数据
STR LDR
读写范围有限,4G。
0x1000_0000 0x1400_0000 SFR region特殊功能寄存器special function register
0x4000_0000 0xA000_0000 扩展内存(对应开发板上的四个芯片,ARM 自带内存如上图第三行显示256KB 太小了)
注:这里的寄存器在SOC中,但在CPU之外,有地址,访问方式与内存一样,常用于控制硬件
在一个处理器中,一般会将Flash(硬盘)、RAM、寄存器等存储设备分别映射到寻址空间中的不同地址段,我们将这个映射关系成为这个处理器的地址映射表
GPIO(General-purpose input/output)即通用IO接口,GPIO可以控制连接在其之上的引脚实现信号的输入和输出,
芯片的引脚与外部设备相连,从而实现与外部硬件设备的通讯、控制及信号采集等功能
GPIO控制器一般两个寄存器,通用IO控制寄存器、通用IO数据寄存器。
led-asm.s
.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
Makefile
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
# -Ttext 0x40008000 制定链接地址,或程序的起始地址,把程序放在这个内存地址上去运行
$(LD) $(TARGET).o -Ttext 0x40008000 -o $(TARGET).elf
# “-O binary” 转化成bin格式 -S 转换的源文件 *.bin 转换的目标文件
$(OBJCOPY) -O binary -S $(TARGET).elf $(TARGET).bin
clean:
rm $(TARGET).o $(TARGET).elf $(TARGET).bin
.text
_start:
MAIN:
BL LED_COMFIG
LOOP:
BL LED_ON
BL DELAY
BL LED_OFF
BL DELAY
B LOOP
LED_COMFIG:
LDR R2,=0x11000C40
LDR R1,=0x10000000
STR R1,[R2]
MOV PC,LR
LED_OFF:
LDR R2,=0x11000C44
LDR R1,=0x00000000
STR R1,[R2]
MOV PC,LR
LED_ON:
LDR R2,=0x11000C44
LDR R1,=0x00000080
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
@ 汇编程序的结束
interface
├── common
│ ├── include 头文件
│ │ ├── ctype.h
│ │ ├── exynos_4412.h
│ │ ├── stdarg.h
│ │ └── uart.h
│ └── src 写好的原文件
│ ├── main.o
│ ├── printf.c
│ ├── uart.c
│ ├── _udivsi3.S
│ └── _umodsi3.S
├── interface.c 入口main函数
├── Makefile
├── map.lds 汇编只写了代码段,但是C有很多段。高数编译器,链接脚本指定代码的起始地址、代码段的分布、怎么对齐的、一般不用自己写
└── start
└── start.S 任何一个CPU 刚上电的时候都是用的汇编,因为,首先,c语言需要直接用栈,需要提前用汇编初始化栈。其次,刚上电是svc 模式,c希望user 模式。一般不用自己写
#=============================================================================#
NAME = interface
CROSS_COMPILE = arm-none-linux-gnueabi- #制定交叉编译器
#=============================================================================#
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJDUMP = $(CROSS_COMPILE)objdump
OBJCOPY = $(CROSS_COMPILE)objcopy
CFLAGS += -g -O0 -mabi=apcs-gnu -mfpu=neon -mfloat-abi=softfp -fno-builtin \
-nostdinc -I ./common/include
#============================================================================#
OBJSss := $(wildcard start/*.S) $(wildcard common/src/*.S) $(wildcard *.S) \
$(wildcard start/*.c) $(wildcard common/src/*.c) \
$(wildcard usr/*.c) $(wildcard *.c)
OBJSs := $(patsubst %.S,%.o,$(OBJSss))
OBJS := $(patsubst %.c,%.o,$(OBJSs))
#============================================================================#
%.o: %.S
$(CC) $(CFLAGS) -c -o $@ $< #编译汇编文件
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $< #编译c文件
all:clean $(OBJS)
$(LD) $(OBJS) -T map.lds -o $(NAME).elf #链接
$(OBJCOPY) -O binary $(NAME).elf $(NAME).bin #转换适应裸机
$(OBJDUMP) -D $(NAME).elf > $(NAME).dis
#============================================================================#
clean: #清除中间文件
rm -rf $(OBJS) *.elf *.bin *.dis *.o
#============================================================================#
.text //后续是代码段
.global _start
_start:
/*
* Vector table 异常向量表
*/
b reset //跳转
b . //b. 跳转到代码自身,是死循环
b .
b .
b .
b .
b .
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start //把异常向量表的地址给R0
mcr p15, 0, r0, c12, c0, 0 @Set VBAR //ARM 默认异常向量表地址是0,ARM Cortec A9 可以通过协处理器P15修改向量表的地址,cp15 中有个寄存器叫c12,c12 指向哪儿,异常向量表就从哪儿开始。这句话意思是把R0寄存器的值放到协处理器cp15中的c12去,
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ 通过状态寄存器传送指把cpu模式改成SVC32模式(这个权限高),执行ARM 指令,关中断(不希望上电启动过程被打断)。
*/
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 //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 //混合编程,跳转到c 函数
/*
* 计算各个模式下栈指针的起始位置
*/
_stack_svc_end:
.word stack_svc + 512 //stack_svc 定义在稍下的数据段中,然后因为是满减栈,加上空间的大小,指到最高地址
_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 //伪操作,占用空间512字节。将来这段空间做为栈来使用,压栈压到这里。有6个,分别给不同的模式用
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
MCR 将ARM处理器中寄存器中的数据传送到协处理器中的寄存器
TLBs 相当于页表缓存
icache 虚拟内存
FPEXC(Floating-Point Exception Control register ) 浮点异常控制寄存器
FPU (Float Point Unit)浮点运算单元
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
/*通过设置GPX2CON寄存器来将GPX2_7引脚设置成输出功能
指针的数据类型,并不是指针的数据类型,而是它指向数据的数据类型。
*0x11000c40 = 0x10000000; 编译不通过,因为编译器不知道*后的地址的数据类型。(这里注意“0x11000c40” 不符合命名规则(1.可以由数字、字母、下划线_、美元符号$组成。2.不能以数字开头3.不能是关键字4.区分大小写),但是编译器不会报命名错误,推测编译器能判断出这是一个地址)
*(unsigned int *)0x11000c40 = 0x10000000; 编译通过
unsigned int占四个字节;
(unsigned int *)地址常量,告诉编译器,其后地址常量指向四字节的空间。
*/
*(unsigned int *)0x11000c40 = 0x10000000;
while(1)
{
/*点亮LED2*/
*(unsigned int *)0x11000c44 = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
*(unsigned int *)0x11000c44 = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#define GPX2CON (*(unsigned int *)0x11000c40)
#define GPX2DAT (*(unsigned int *)0x11000c44)
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
typedef struct
{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(gpx2 *)0x11000c40)
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
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;
}
UART
Universal Asynchronous Receiver Transmitter 即通用异步收发器,是一种通用的串行、异步通信总线
该总线有两条数据线,可以实现全双工的发送和接收,在嵌入式系统中常用于主机与辅助设备之间的通信
波特率
波特率用于描述UART通信时的通信速度,其单位为 bps(bit per second) 即每秒钟传送的bit的数量
TXD:发送数据(Transmit Data)
RXD:接收数据(Receive Data)
UART控制器
一般情况下处理器中都会集成UART控制器,我们使用UART进行通信时候只需对其内部的相关寄存器进行设置即可
实验用开发板上有一个U3处理器,用于将TTL8(UART)信号转232信号,增强传输距离和抗干扰能力
UART更多关注规定编码格式的标准,如波特率(baud rate)、帧格式和波特率误差等等。RS232和TTL更多是电平标准和电压,他们在软件协议层面是一样的,如对于同样传输0b01010101来说,RS232和TTL的时序对比:
RS232和TTL之间的转换,不仅仅是简单的电平转换,还要考虑到其他一些因素,比如调节和矫正一些电平(提高或降低对应的电平),确保可能的有害的RS232电压不会破坏微控制器的串口针脚。这里有很成熟的方案,我们可以通过MAX3232之类的芯片,把TTL电平转为RS232电平,或者简单的在淘宝购买TTL转RS232电缆即可。
如何分辨究竟是TTL还是RS232呢?一般来说,由SOC芯片引脚直接引出的一般是TTL,其高低电平不需要任何转换,可以由芯片之间驱动,节省费用;而中间接有转换芯片的可能就是RS232了,可以根据电路图的芯片型号google即可。
另一个原则是RS232通常出现在传统的PC和服务器领域,TTL通常用于嵌入式设备。
引脚功能设置
设置引脚功能的实质是让引脚在芯片内部连接到某一个对应的控制器上
开发板底板串口引脚
开发板核心板引脚对应寄存器
芯片手册对应寄存器控制
见芯片手册9:
6.2.2 先将引脚功能设置成串口的
28.6.1 设置UART 通信的时候的帧格式
DMA:直接地址访问
#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 注意:在用的A9开发板的时钟频率是100M
DIV_VAL = (时钟频率/(波特率*16))-1
UBRDIVn = DIV_VAL 的整数部分
UFRACVALn/16 = DIV_VAL 的小数部分
SAMSUNG Confidential
*/
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++);
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
/*
RecDat = UART_Rec_Byte();
if(RecDat == 0)
{
}
else
{
RecDat = RecDat + 1;
UART_Send_Byte(RecDat);
}
*/
/*
UART_Send_Str("Hello World\n");
*/
printf("Hello World\n"); //和c库的printf不一样。没有操作系统的时候不能调用库函数,不能输出浮点数,输出定向不同,自己写的定向的串口
}
return 0;
}
输出重重定向到serial.(SecureCRT是一款支持SSH(SSH1和SSH2)的终端仿真程序)
注:在终端上输入‘2’,LED2点亮,再次输入‘2’,LED2熄灭… …
#include "exynos_4412.h"
void UART_Init(void){
//1.将GPA1_0和GPA1_1设置成UART的接收和发送引脚GPA1CON【7:0]
GPA1.CON = GPA1.CON &(~(0xFF << 0)) | (0x22 << 0);
//2.设置UART2的帧格式:8位数据位、1位停止位、无校验、正常模式ULCON[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 Data){
//等待发送寄存器为空,即上一个数据已经发送完成UTRSTAT2[1]
while(!(UART2.UTRSTAT2 & (1<<1)));
//将要发送的数据写入发送寄存器
UART2.UTXH2 = Data;
}
char UART_Rec_Byte(void){
char Dat = 0;
//判断接收寄存器是否收到了数据UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1){
//从寄存器中读取到接收的数据
Dat = UART2.URXH2;
return Dat;
}else{
return 0;
}
}
void UART_Send_Str(char *pstr){
while(*pstr!='\0')
UART_Send_Byte(*pstr++);
}
int main()
{
//设置LED2点亮功能启用
GPX2.CON = GPX2.CON & (~(0xF << 28 )) | (0x1 << 28);
char Rec_Data;
UART_Init();
printf("Please input '2' control OFF/ON of LED2\n");
while(1){
Rec_Data = UART_Rec_Byte();
if(Rec_Data == 0 ){
}else if(Rec_Data == '2'){
if(GPX2.DAT & (1<<7)){
//熄灭LED2
printf("LED2 OFF\n");
GPX2.DAT = GPX2.DAT & (~(1<<7));
}else{
//点亮LED2
printf("LED2 ON\n");
GPX2.DAT = GPX2.DAT | (1 << 7);
}
}else{
printf("Invalid inputs Please retype!\n");
}
}
return 0;
}
WDT
Watch Dog Timer即看门狗定时器,其主要作用是当发生软件故障时可产生复位信号使SOC复位,其本质是一个计数器
独立于CPU,监控CPU运行状况
出于安全考虑
可以当作普通定时器使用
做定时器用,到点后向CPU发送中断信号
做监控复位用,到点后向CPU发送复位信号
一级分频器: WTCON[15,8] 0-255 对应 1-256 倍分频
二级分频器:WTCON[4,3] 固定倍数
定时器用来对外设时钟(PCLK)进行计数,而计数器对外部脉冲信号进行计数,可以选择在规定的时间处产生
中断或执行其它操作,这都由 4 个匹配寄存器的值决定。它也包含4 个捕获输入,用来在输入信号变化时捕捉定
时器的瞬时值,也可以选择产生中断。
芯片手册26
PLCK 外设时钟
#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接收到中断信号后将当前的程序暂停下来,转而去执行中断服务程序,执行完成后再返回到被打断的点继续执行
是一个过程,是CPU在执行当前程序的过程中因硬件或软件的原因插入了另一段程序运行的过程。因硬件原因引起的中断过程的出现时不可预测的,即随机的,而软中断是事先安排好的。
DMA
硬件产生数据后,硬件控制器可将产生的数据直接写入到存储器中,整个过程无需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;
}
#include "exynos_4412.h"
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));
return 0;
}
4412 中有160个外部硬件设备的控制器遇到某些事后向CPU 发送中断信号
CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。
CPSR在用户级编程时用于存储条件码。
SPSR:程序状态保存寄存器(saved program status register),每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定 的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断 模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。
中断信号直接发信号给CPU 存在的问题:
1.处理中断时,禁止其他中断
2.中断信号发CUP,中断信号被忽略
3.多核CPU ,中断信号发给谁
4. 发送给IRQ 还是FIQ
5. CPU 区分不出信号时谁发的
中断控制器出马,将中断信号处理后发给CPU
1.同时收到好几个中断信号,对中断信号排队
2.分配优先级给中断信号,但是优先级高的不能打断优先级低的
3.将依次收到的中断信号挂起
4.决定中断信号给那个处理器处理
5.决定触发哪种中断类型
6.告知CPU 中断信号来源
7.打开或关闭中断
ARM通用中断控制器GIC(generic Interrupt Controller)
芯片手册第9章
实际,操作系统内部会将对中断的处理配置完善
用SPI10多
160个中断编号
0-15 软中断
16-31 私有中断
128剩下的共享中断SPI
GPX1_1 对应中断号59
9.5.1.12 中断控制器总开关
9.5.1.16 设置每一个中断信号的打开和关闭,5组160位
9.5.1.22 确定中断信号交个哪个处理器处理
9.5.1.1 ICCICR_CPU 中断控制器和CPU之间的接口的开闭
中断控制器默认中断类型时IRQ(Interrupt Request)
中断控制器有默认优先级
#include "exynos_4412.h"
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;
return 0;
}
概念
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生
这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件
异常事件处理完成之后再返回到被异常打断的点继续执行程序
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
概念
导致异常产生的事件称为异常源
ARM 基本工作模式
ARM异常源
FIQ 快速中断请求引脚有效
IRQ 外部中断请求引脚有效
Reset 复位电平有效
Software Interrupt 执行swi指令
Data Abort 数据终止
Prefetch Abort 指令预取终止
Undefined Instruction 遇到不能处理的指令
异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切
换成对应的异常模式
ARM产生异常后的动作(自动完成)
1.拷贝CPSR中的内容到对应异常模式下的SPSR_
2.修改CPSR的值
2.1.修改中断禁止位禁止相应的中断
2.2.修改模式位进入相应的异常模式
2.3.修改状态位进入ARM状态
3.保存返回地址到对应异常模式下的LR_
4.设置PC为相应的异常向量(异常向量表对应的地址)
异常向量表
异常向量表的本质是内存中的一段代码,表中为每个异常源分配了四个字节的存储空间,遇到异常后处理器自动将PC修改为对应的地址,因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址,但可以通过配置协处理器来修改其地址
ARM异常返回的动作(自己编写)
1.将SPSR_的值复制给CPSR, 使处理器恢复之前的状态
2.将LR_的值复制给PC,使程序跳转回被打断的地址继续执行
注:整个过程CPSR保存的永远是当前程序运行状态, SPSR只是异常时对原来的CPSR进行备份
注意: 在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用
R14(LR,Link Register)
链接寄存器,一般有以下两种用途:
1. 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址程序需要返回时将LR的值复制到PC即可实现
2. 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下一条指令的地址,异常处理结束后将LR的值
复制到PC可实现程序返回
原理
当执行跳转指令或产生异常时,LR寄存器中不会凭空产生一个返回地址, 其原理是当执行跳转指令或产生异常时,处
理器内部会将PC寄存器中的值拷贝到LR寄存器中,然后再将LR寄存器中的值自减4
BL
当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到 LR寄存器,然后再将LR寄存器中的值自减4, 所以LR寄存器中保存的就是 BL指令下一条指令的地址
IRQ中断
当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4,
所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址,该时刻PC=N+12 LR=N+8
6.2.3.227 中断挂起寄存器 写1清零
9.5.1.5 ICCEOIR_CPU
.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}^ //‘^’表示出栈的同时将SPSR的值传递给CPSR,实现CPU状态的恢复
_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
#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;
}
FIQ 比 IRQ 优先级高
FIQ 位于异常向量表最末,可以不跳转直接写异常处理程序
有R8-R2 的私有寄存器
ADC
ADC(Analog to Digital Converter)即模数转换器,指一个能将模拟信号转化为数字信号的电子元件
分辨率
ADC的分辨率一般以输出二进制数的位数来表示,当最大输入电压一定时,位数越高,分辨率越高; n位的ADC能区分输入电压的最小值为满量程输入的1/2^n;
比如一个12位的ADC,最大输入电压为1.8v,那么该ADC能区分的最小电压为1.8v/2^12≈0.00044v,当转换的结果为m时,则
实际的电压值为m*(1.8v/2^12);
芯片手册 56.7
分频后时钟频率不能超过5兆,否则超过极限采样频率
#include "exynos_4412.h"
int main()
{
unsigned int AdcValue;
/*设置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;
while(1)
{
/*开始转换*/
ADCCON = ADCCON | 1;
/*等待转换完成*/
while(!(ADCCON & (1 << 15)));
/*读取转换结果*/
AdcValue = ADCDAT & 0xFFF;
/*将结果转换成实际的电压值mv*/
AdcValue = AdcValue * 0.44;
/*打印转换结果*/
printf("AdcValue = %dmv\n",AdcValue);
}
return 0;
}
RTC
RTC(Real Time Clock)即实时时钟,它是一个可以为系统提供精确的时间基准的元器件,RTC一般采用精度较高的晶振作为时钟源,有些RTC为了在主电源掉电时还可以工作,功耗很小,需要外加电池供电。
BCD码,用4位二进制表示1位十进制
芯片手册27
手册错误:日期和星期的地址写反了
改时间先解锁
#include "exynos_4412.h"
int main()
{
unsigned int OldSec = 0, NewSec = 0;
/*使能RTC控制*/
RTCCON = RTCCON | 1;
/*校准时间信息*/
RTC.BCDYEAR = 0x023;
RTC.BCDMON = 0x12;
RTC.BCDDAY = 0x7;
RTC.BCDWEEK = 0x31;
RTC.BCDHOUR = 0x23;
RTC.BCDMIN = 0x59;
RTC.BCDSEC = 0x50;
/*禁止RTC控制*/
RTCCON = RTCCON & (~(1));
while(1)
{
NewSec = RTC.BCDSEC;
if(OldSec != NewSec)
{
printf("20%x-%x-%x %x %x:%x:%x\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
OldSec = NewSec;
}
}
return 0;
}
脉宽调变 (pulse width modulation,脉冲宽度调制)
PWM
PWM(Pulse Width Modulation)即脉冲宽度调制,通过对脉冲的宽度进行调制,来获得所需要波形
一级分频控制1-256 倍分频,0、1 共用一个,2、3、4 共用一个
PWM 内部每个定时器有各自私有的二级分频2、4、8、16倍
TCFG0 设置五个pwm一级分频,设置死驱宽度
TCFG1 设置五个pwm的二级分频
TCON 设置五个pwm 的细节功能
TCNTB0 设置周期
TCMPB0 设置占空比
TCNTO0 递减计数器
编程实现通过PWM控制蜂鸣器产生"嘀嘀"的声音
注:PWM的频率1000HZ,占空比%60
#include "exynos_4412.h"
void Delay(unsigned int Time){
while(Time--);
}
#if 1 //频率是1000
int main()
{
//1.将GPD0_0引脚设置成PWM0的输出引脚
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
//2.设置PWM0的一级分频 ,一级分频倍数设置为100倍
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
//3.设置PWM0的二级分频 二级分频倍数设置为1倍,递减计数器递减频率 =PLCK/(99+1)/1=1M
PWM.TCFG1=PWM.TCFG1 & (~(0xF));
//4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号
PWM.TCON = PWM.TCON | (1<<3);
//5.设置PWMO的频率为1000赫兹
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.使能PWMO,递减计数器开始递减
PWM.TCON = PWM.TCON | 1;
while(1){
PWM.TCON = PWM.TCON | 1;
Delay(1000000);
PWM.TCON = PWM.TCON &(~(1));
Delay(1000000);
}
return 0;
}
#else //频率是500
int main()
{
/*1.将GPD0_0引脚设置成PWM0的输出引脚*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2.设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
/*2.设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PLCK / (99 + 1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5.设置PWM0的频率为500HZ*/
PWM.TCNTB0 = 2000;
/*6.设置PWM0的占空比为50%*/
PWM.TCMPB0 = 1000;
/*7.将TCNTB0中的值手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8.关闭手动更新*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9.使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
while(1)
{
PWM.TCON = PWM.TCON | 1;
Delay(1000000);
PWM.TCON = PWM.TCON & (~(1));
Delay(1000000);
}
return 0;
}
#endif
总线(Bus)是计算机各种功能部件之间传送信息
的公共通信干线
,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。
在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。
IIC总线
IIC(Inter-Integrated Circuit)是一个多主从的串行总线
,又叫I2C,是由飞利浦公司发明的通讯总线,属于半双工同步传输类型总线。抗干扰能力差,速率低。
IIC总线是Philips公司在八十年代初推出的一种串行、半双工总线。主要用于近距离、低速的芯片之间的通信;IIC总线有两根双向的信号线一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;IIC总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用
IIC总线
IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生;
每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器;
1.主机发送起始信号启用总线
2.主机发送一个字节数据指明从机地址和后续字节的传送方向
3.被寻址的从机发送应答信号回应主机
4.发送器发送一个字节数据
5.接收器发送应答信号回应发送器
… … (循环步骤4、5)
n.通信完成后主机发送停止信号释放总线
IIC总线上传送的数据是广义的,既包括地址,又包括真正的数据。 主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,'0’表示主机发送数据,'1’表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器
SCL为高电平时,SDA由高变低表示起始信号
SCL为高电平时,SDA由低变高表示停止信号
起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态
停止信号产生后总线处于空闲状态
先发高位
IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位
,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器即一帧共有9位
IIC总线在进行数据传送时,时钟线SCL为低电平期间发送器向数据线上发送一位数据,在此期间数据线上的信号允许发生变化,时钟线SCL为高电平期间接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生变化,必须保持稳定
注:阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送;A表示应答, A非表示非应答,S表示起始信号,P表示终止信号
主机向从机发送数据
从机向主机发送数据
主机先向从机发送数据,然后从机再向主机发送数据
1.若使用IIC总线让从机给主机发送一个字节的数据0xA2,画出SCL和SDA上的时序图
芯片手册 29
极限通讯速度秒400KBIT/S
设置主机:124
设置从机:
MPU6050
MPU6050是一个运动处理传感器,其内部集成了3轴加速度传感器
和3轴陀螺仪(角速度传感器),以及一个可扩展数字运动处理器
MPU6050
可测量X、Y、Z轴三个方向的角速度
可编程设置角速度测量范围为±250、±500、±1000、±2000°/sec
可测量X、Y、Z轴三个方向的加速度
可编程设置加速度测量范围为±2g、±4g、±8g、±16g
可编程设置低功耗模式
可编程设置采样频率
… …
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.主机发送停止信号
#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
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned char zvalue_h,zvalue_l; //存储读取结果
short int zvalue;
/*设置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
uart_init(); //初始化串口
MPU6050_Init(); //初始化MPU6050
printf("\n********** I2C test!! ***********\n");
while(1)
{
zvalue_h = iic_read(SlaveAddress, GYRO_ZOUT_H); //获取MPU6050-Z轴角速度高字节
zvalue_l = iic_read(SlaveAddress, GYRO_ZOUT_L); //获取MPU6050-Z轴角速度低字节
zvalue = (zvalue_h<<8)|zvalue_l; //获取MPU6050-Z轴角速度
printf(" GYRO--Z :Hex: %d \n", zvalue); //打印MPU6050-Z轴角速度
mydelay_ms(100);
}
return 0;
}
SPI(Serial Peripheral Interface)
全双工,至少用四根线,SPI(serial peripheral interface)串行外设接口
也叫标准SPI接口,有四根线,分别为CS/SS(片选)、MISO(主机输入从机输出)、MOSI(主机输出从机输入)、CLK(串行时钟);并且四根线都是单向的;主机通过MOSI发送数据给设备;设备通过MISO发送数据给主机。
对于主机来说,片选线可能有多条
ARM 内部有SPI控制器
有多少个从机,主机就要有多少CS片选线,想跟谁通讯,让相应从设备CS线置低电平
CS 上有个杠就是高电平有效,否则低电平有效
先发高位,后发低位。高电平1,低电平0,无需应答。没有起始和终止信号
时钟线下降沿发数据,上升沿接数据
与IIC相同点
1.均采用串行、同步的方式
2.均采用TTL电平,传输距离和应用场景类似
3.均采用主从方式工作
与IIC不同点
1.IIC为半双工,SPI为全双工
2.IIC有应答机制,SPI无应答机制
3.IIC通过向总线广播从机地址来寻址,SPI通过向对应从机发送使能
4.IIC的时钟极性和时钟相位固定,SPI的时钟极性和时钟相位可调
CPU是什么
CPU中文名叫中央处理器(Central Processing Unit),是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。CPU从存储器或高速缓冲存储器中取出指令,放入指令寄存器,并对指令译码。指令是计算机规定执行操作的类型和操作数的基本命令。指令是由一个字节或者多个字节组成,其中包括操作码字段、一个或多个有关操作数地址的字段以及一些表征机器状态的状态字以及特征码。
CPU的工作流程。
1. 提取
,从存储器或高速缓冲存储器中检索指令,由程序计数器记录了CPU在目前程序里的踪迹。指令的提取必须常常从相对较慢的存储器寻找,因此导致CPU等候指令的送入。
2. 解码
,指令被拆解为有意义的片断。根据CPU的指令集架构(ISA)定义将数值解译为指令。一部分的指令数值为运算码(Opcode),其指示要进行哪些运算。
3. 执行
,该阶段中,连接到各种能够进行所需运算的CPU部件。如果加法运算产生一个对该CPU处理而言过大的结果,在标志暂存器里,运算溢出(Arithmetic Overflow)标志可能会被设置。
4. 写回
,以一定格式将执行阶段的结果简单的写回。运算结果经常被写进CPU内部的暂存器,以供随后指令快速存取。在执行指令并写回结果之后,程序计数器的值会递增,反覆整个过程,下一个指令周期正常的提取下一个顺序指令。
CPU物理结构:CPU包括运算逻辑部件、寄存器部件和控制部件等。
1. 逻辑部件
运算逻辑部件可以执行定点或浮点算术运算操作、移位操作以及逻辑操作,也可执行地址运算和转换。
2. 寄存器
寄存器部件包括寄存器、专用寄存器和控制寄存器。通用寄存器又可分定点数和浮点数两类,它们用来保存指令执行过程中临时存放的寄存器操作数和中间(或最终)的操作结果。通用寄存器是中央处理器的重要部件之一。
3. 控制部件
主要是负责对指令译码,并且发出为完成每条指令所要执行的各个操作的控制信号。其结构有两种:一种是以微存储为核心的微程序控制方式;一种是以逻辑硬布线结构为主的控制方式。 ↩︎
中断分为硬中断和软中断,其分类依据是实现机制,而不是触发机制,比如CPU硬中断,它是由CPU这个硬件实现的中断机制,但它的触发可以通过外部硬件,也可以通过软件的 INT 指令。 ↩︎
软中断是由软件实现的中断,是纯粹由软件实现的一种类似中断的机制,实际上是模仿硬件,在内存中存储着一组软中断的标志位,然后由内核的一个守护线程不断轮询这些标志位,如果有哪个标志位有效,则再去执行这个软中断对应的中断处理程序。参考文章 ↩︎
不是一种错误,需要利用这种机制实现功能 ↩︎
这种一个函数里面没有调用任何其他函数的,就是叶子函数 ↩︎
PCB(Printed Circuit Board),中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气相互连接的载体。由于它是采用电子印刷术制作的,故被称为“印刷”电路板。 ↩︎
网络标号(net label)是一个电气连接点,一般由字母或数字组成,具有相同网络标号的电气连接线、管脚、及网络是连接在一起的。网络标号通常是用于单张原理图中,网络标号也可以用于层次原理图设计。换句话说,网络标号是一种具有电气连接属性的标号,也就是说,如果你把两个元件引脚或两个引线打上了相同的网络标号,这两个引脚或两根引线就连接起来了,相当于用导线连接,但是,看起来比较简洁。 ↩︎
UART和TTL完全就是两码事。
UART是通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称作UART) 是一种串行异步收发协议。
TTL电平信号规定,+5V等价于逻辑“1”,0V等价于逻辑“0”(采用二进制来表示数据时)。这样的数据通信及电平规定方式,被称做TTL(晶体管-晶体管逻辑电平)信号系统。这是计算机处理器控制的设备内部各部分之间通信的标准技术。
TTL集成电路的全名是晶体管-晶体管逻辑集成电路(Transistor-Transistor Logic),主要有54/74系列标准TTL、高速型TTL(H-TTL)、低功耗型TTL(L-TTL)、肖特基型TTL(S-TTL)、低功耗肖特基型TTL(LS-TTL)五个系列。
简单地说可以将UART,SPI,IIC等归为一类,TTL,RS232,RS485等归为另一类。事实上RS232,RS485比TTL更为复杂,毕竟前者是一种串行数据通信的接口标准,而TTL只是一种电平标准。
二.这种空泛的概念不容易理解,来看看实际的信号。
————————————————
版权声明:本文为CSDN博主「Darkershadow」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014197096/article/details/107905848 ↩︎
1、先看看芯片的特性(Features)、应用场合(Applications)以及内部框图。这有助于我们对芯片有一个宏观的了解,此时需要弄清楚该芯片的一些比较特殊的功能,充分利用芯片的特殊功能,对整体电路的设计,将会有极大的好处。
2、重点关注芯片的参数,同时可以参考手册给出的一些参数图,这是是否采用该芯片的重要依据。
3、选定器件后,研究芯片管脚定义、推荐的PCB layout,这些都是在硬件设计过程中必须掌握的。所有管脚中,要特别留意控制信号引脚或者特殊信号引脚,这是将来用好该芯片的前提。
4、认真研读芯片内部寄存器,对寄存器的理解程度,直接决定了你对芯片的掌握程度。
5、仔细研究手册给出的时序图,这是对芯片进行正确操作的关键。单个信号的周期、上升时间、下降时间、建立时间、保持时间,以及信号之间的相位关系,所有这些都必须研究透彻。
6、凡是芯片数据手册中的“note”,都必须仔细阅读,一般这都是能否正确使用、或能否把芯片用好的关键之所在。 ↩︎
SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步。参考链接 ↩︎