ARM接口技术
ARM接口技术 : 芯片内部 + 向外扩展
ARM: 架构 芯片 公司
汇编 - C语言
系统移植 Linux
驱动开发
一、ARM系统硬件组成和运行原理
1.硬件组成(手机为例)
(1)flash储存器:存储程序
特点:永久的保存数据,且掉电不消失,运行速度快,价格便宜
(2)内存:程序运行在内存
特点:运行速度快,但掉电即消失
(3)CPU:
①寄存器:储存数据的场所
特点:运行速度快,价格昂贵
②控制器:取指,译码
③ALU运算器:运算
2.运行原理
上电之后,控制器就会从flash/内存中取指并译码,通过存储器存储运算量及结果,通过ALU运算器做运算
二、ARM接口技术 : 芯片内部 + 向外扩展
ARM: 架构 芯片 公司
(1)ARM是一家做RISC处理器内核的公司
(2)ARM不生产芯片
汇编 - C语言
预备知识 学习
汇编 操作寄存器 + C语言
接口技术: 点灯 通信
芯片 :
辅助芯片: 选择器 译码器
设备芯片: 加速度传感器 温度传感器 …传感器 电机驱动芯片 舵机芯片 通信模块
主控制: STM32 C51/C52 单片机 ucos
主运算: FS4412 系统级别的芯片 Linux
控制+运算 二合一的芯片 FS_MP157
SOC: system on chip 片上系统, stm32 cortex-A 电脑/手机芯片
1.ARM Cortex-A处理器的工作模式: 7 + 1(8种)
User:普通用户模式
Svc:超级用户模式,Reset或软中断发生时CPU切换到此模式
IRQ:普通中断模式
FIQ:高优先级中断模式
Abort:数据存取异常对应的模式
Undef:未定义指令对应的模式
System:与user相同,权限稍高于user
Monitor(Cortex-A特有模式):特权模式,监控安全代码
2.ARM Cortex-A处理器的寄存器: 37 + 3
(1)40个寄存器(非Cortex-A 37个寄存器)
(2)特殊寄存器(r0-r7为普通寄存器)
r13(sp):栈指针寄存器,异常时需要进栈(现场保护)、出栈(恢复现场),不同模式有自己的栈顶寄存器
r14(lr):链接寄存器,异常返回地址或函数返回地址
r15(pc):程序计数器,保存即将要执行的指令的地址
cpsr:当前程序状态寄存器,保存当前处理器状态的寄存器
spsr:保存当前程序状态寄存器(备份cpsr)
(3)cpsr寄存器
4~0 -----模式位
10000 User mode 应用程序常处于该模式
10001 FIQ mode
10011 SVC mode
10111 Abort mode
11011 Udefined mode
11111 System mode
10110 Monitor mode
10010 IRQ mode
5 -----T位:处理器的状态 0:ARM状态
6 -----F位:FIQ中断禁止位 1:禁止 0:使能
7 -----I位:IRQ中断禁止位 1:禁止 0:使能
条件位:
31 -----N = Negative result from ALU
30 -----Z = Zero result from ALU 1:结果为0 0:结果不为0
29 -----C = ALU operation carried out of borrow
28 -----V = ALU operation overflowed
注:当处理器执行在ARM状态,所有指令32bits宽,所有指令必须word对齐
(4)ARM体系架构及处理器
(4)ARM体系架构及处理器
ARM体系: | ARMv4 | ARMv5 | ARMv6 | ARMv7 |
ARM CPU: | arm7 | arm9 arm10 | arm11 | arm-cortex-a8 |
流水线: | 3 | 5 6 | 8 | 13 |
频率(MHZ): | 80 | 150 260 | 335 | 667 |
MMU: | 无/有 | 有 有 | 有 | 有 |
结构: | 冯诺依曼 | 哈佛 哈佛 | 哈佛 | 哈佛 |
注:
不同ARM体系采用不同指令集
哈佛结构是数据和指令分开存储并行
冯诺依曼(普林斯顿)结构是混合储存的
学习汇编:
了解机器的思维, 方便理解程序
3.ARM指令集
ARM是32位的cpu
大部分ARM core提供:
ARM指令集(32-bit):每条指令4字节
Thumb指令集(16-bit):每条指令2字节
指令机器码(如图):
MOV r0, #4
ADD r0, r1, r2 //r0 = r1 + r2
ADD r0, r1, #0x1234 //r0 = r1 + 5
条件码:
伪指令: 会被编译器转换为指令后再翻译
ldr r0, =0x12345678
立即数:
8位存放数字 4位存放偏移量
将一个数循环右移偶数位后, 得到一个8位以内的数, 这就是立即数
将一个数转换为二进制, 看1的个数, 将连续的偶数个0去掉
汇编中的一些符号:
# 后面跟数字, 表明是立即数 # 放在语句最前面, 充当注释 @ 注释, 相当于//
三、寄存器数据操作指令:
指令 <目标寄存器> <第一个操作数> <第二个操作数>
目标寄存器和第一个操作数 必须是寄存器 (r0-r15)
第二个操作数可以是寄存器, 也可以是立即数(#数字)
数据搬移: MOV MVN
算术指令: ADD ADC SUB SBC RSB RSC
逻辑指令: AND ORR EOR BIC
比较指令: CMP CMN TST TEQ乘法指令: MUL
算数指令:
MOV r0, r1 @r0 = r1
mvn r0, r1 @r0 = ~r1
ADD r0, r1, r2 @r0 = r1+r2 不会改变cpsr寄存器
ADD r0, r1, #4 @r0 = r1 + 4
ADDS r0, r1, r2 @r0 = r1+r2 如果有溢出或进位cpsr寄存器的c位被改变为1,没有则为0
ADD r0, r1 @r0 = r0 + r1 r0 += r1
eg:
Ox00000001 ffffffff
0x00000001 00000001
ldr r0,=#0xffffffff @低位
ldr r1,=#0x00000001 @低位
ldr r2,=#0x00000001 @高位
ldr r3,=#0x00000001 @高位
ADDS r4,r0,r1 @允许cpsr改变,如果有进位cpsr的c位就会变为1,没有就会变为0
ADC r5, r2,r3 @r5 = r2+r3+cpsr的c
结果为0x0000000300000000
SUB r0, r1, r2 @r0 = r1 - r2
SUB r0, r1, #4 @r0 = r1 - 4
SUBS r0, r1, r2 @当运算时没有借位CPSR_C位置1,产生了借位CPSR_C=0
RSB r0, r1, r2 @r0 = r2 - r1
RSB r0, r1, #4 @r0 = 4 - r1
ADC r0, r1, r2 @r0 = r1+r2 + cpsr_c
cmp r0, r1 @ r0 - r1 结果 通过 CPSR 前4位体现
subgt r2, r0,r1 @if(r0>r1) r2=r0-r1
addlt r2, r0,r1 @if(r0<r1) r2=r0+r1
moveq r2, r0 @if(r0==r1) r2=r0
CMN r0, r1 @ r0 - (-r1) r0 + r1 结果 改变CPSR前4位
tst r0, #0x02 @ (r0 & 0x02) == 0 CPSR_Z 置一
tst r0, #0x0f @ (r0 & 0x0f) == 0 CPSR_Z 置一
TEQ r0, r1 @r0==r1 r0^r1 == 0(相等) CPSR_Z 置一
AND r0, r1, r2 @r0 = r1 & r2
ORR r0, r1, r2 @r0 = r1 | r2
EOR r0, r1, r2 @r0 = r1 ^ r2
BIC r0, r1, r2 @r0 = r1 & (~r2) 按位清零
MUL r0, r1, r2 @r0 = r1 * r2
移位指令:
LSL:逻辑左移(Logical Shift Left),寄存器中字的低端空出的位补0。
LSR:逻辑右移(Logical Shift Right),寄存器中字的高端空出的位补0。
ASL:算术左移(Arithmetic Shift Left),和逻辑左移LSL相同。
ASR:算术右移(Arithmetic Shift Right),移位过程中符号位不变,即如果源操作数是正数,则字的高端空出的位补0,否则补1。ROR:循环右移(Rotate Right),由字的低端移出的位填入字的高端空出的位。
RRX:带扩展的循环右移(Rotate Right eXtended),操作数右移一位, 高端空出的位用进位标志C的值来填充,低端移出的位填入进位标志位。
mov r0, r1, lsl #4 @r0 = r1 << 4
add r0, r1, r2, lsl #4 @ r0 = r1 + (r2 << 4)
add r0, r1, lsl #4 @ r0 = r0 + (r1 << 4)
跳转指令:
b 直接跳转, 类似于goto
bl 在跳转的同时, lr寄存器会记录返回地址 lr = pc-4
只能在前后32M的空间内跳转
label:
b label
b flag
flag:
1:
b 1b
b 2f
2:
_start:
mov r0, #1
mov r1, #2
bl sum
nop
nop
sum:
add r3, r0, r1
mov pc, lr
操作CPSR SPSR的指令
msr mrs
msr cpsr, r0 @cpsr = r0
mrs r1, cpsr @r1 = cpsr
bic r1, r1, #0xf @r1后4位清零
msr cpsr, r1 @cpsr = r1
msr spsr, r0 @spsr = r0
mrs r1, spsr @r1 = spsr
练习1: 比较三个数的大小, 找出最大的数
练习2: 1+2+3+…+100
r0 = 1;
r1 = 0;
while(1)
{
r1 = r1 + r0;
r0 = r0 + 1;
if(r0 > 100)
break;
}
操作内存的指令
ldr/str架构
规定:存储器之间不能直接拷贝数据,需要借助CPU的寄存器做中转,即存储器的内容需要先load到寄存器,然后再store到存储器
store--存储,写; load--加载, 读
在ARM架构下, 数据从内存到CPU之间的移动只能通过LDR/STR指令来完成.
而MOV只能在寄存器之间移动数据,或者把立即数移动到寄存器中,并且数据的长度不能超过8位
str r2,[r0] //把r2的数据 存储到r0地址 *((int *)r0) = r2
ldr r1,[r0] //把r0地址中的数据加载到r1中 r1 = *((int *)r0)
三种索引方式:
LDR R0 , [R1 , #4] //r0 = *((int *)(r1+4))
LDR R0 , [R1 , #4]! //r0 = *((int *)(r1+4)) r1 += 4
LDR R0 , [R1], #4 //r0 = *((int *)r1) r1 += 4
后面的 ! 表示要更新寄存器的值
stmxx/ldmxx
stm--store much,多数据存储,将寄存器的值存到地址上
ldm--load much,多数据加载,将地址上的值加载到寄存器上
栈的类型:
空栈:栈顶指针在元素的后一个位置(指针指向即将插入数据的位置)
满栈:栈顶指针在元素当前位置(指针指向数据所在的位置)
递增栈:入栈时地址向高地址移动
递减栈:入栈时地址向低地址移动
xx有下面8种类型:
(1)IA:(Increase After) 每次传送后地址加4,其中的寄存器从左到右执行
eg: stmia r0,{r1,r4} //先存R1,将r0里面的地址加上4再存r4
eg: ldmia r0,{r0,r1,r2} //将r0地址中的值逐个写入到寄存器r0 r1 r2中
(2)IB:(Increase Before)每次传送前地址加4,其中的寄存器从左到右执行
(3)DA:(Decrease After)每次传送后地址减4,其中的寄存器从右到左执行
eg:STMDA R0,{R1,LR} //先存LR,再存R1
(4)DB:(Decrease Before)每次传送前地址减4,其中的寄存器从右到左执行
(5)FD: 满递减堆栈(每次传送前地址减4)(LDMFD--LDMIA; STMFD--STMDB)
(6)FA: 满递增堆栈(每次传送后地址减4)(LDMFA--LDMDA; STMFA--STMIB)
(7)ED: 空递减堆栈(每次传送前地址加4)(LDMED--LDMIB; STMED--STMDA)
(8)EA: 空递增堆栈(每次传送后地址加4)(LDMEA--LDMDB; STMEA--STMIA)
非栈地址
D decrease 入栈时地址向低地址移动
I increase 入栈时地址向高地址移动
A after 指针先赋值再移动
B before 指针先移动再赋值
stmfd sp!,{r0-r12,lr} --- 入栈 保护现场 (sp!加!栈顶指针sp的位置自动变化)
ldmfd sp!,{r0-r12,pc}^ --- 出栈 恢复现场
^ 在出栈的同时 恢复cpsr
软中断指令:
SWI{条件码} <软中断号> @通过代码产生中断, 类似于Qt的发射信号
eg: swi 0x2
伪指令:
被编译器翻译成汇编指令
gnu工具链
ldr r0, =0x1234
.word 0x1234
ldr r0, [pc, #-8]
nop @空指令
数据定义伪指令:
数据定义伪操作一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。
常用的数据定义伪操作有如下几种:
.byte 单字节定义,8bits .byte 0x12,’a’,23,0x13
.short 定义双字节数据 .short 0x1234,65535
.long /.word 定义4字节数据,32bits(4byte) .word 0x12345678
.halfword 16bits(2byte)
.doubleword 64bits(8byte) (Cortex—A处理器)
num: .word 0x100 @int num = 0x100;
ldr r0, num @r0 = *num = 0x100
arr: .word 0x100,0x200 @int arr[] = {0x100,0x200};
ldr r0, num @r0 = *num = 0x100
开辟空间:
.space 开辟空间, 相当于 malloc
buf: .space 64 @ buf = malloc(64)
ldr r0, =buf @将buf地址保存到寄存器r0中 r0 = buf
ldr r0, buf @将buf地址中的内容保存到寄存器r0中 r0 = *buf
str r0, buf @ *buf = r0
杂项伪指令:
arm .arm 定义一下代码使用ARM指令集编译
.thumb .thumb 定义一下代码使用Thumb指令集编译
.section .section expr 定义一个段。expr可以使.text .data. .bss
.text .text {subsection} 将定义符开始的代码编译到代码段
.data .data {subsection} 将定义符开始的代码编译到数据段,初始化数据段
.bss .bss {subsection} 将变量存放到.bss段,未初始化数据段
_start 汇编程序的缺省入口是_ start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点.
.global/ .globl :用来声明一个全局的符号
.end 文件结束
协处理器相关指令:
ARM 协处理器指令包括以下 5 条:
— CDP 协处理器数操作指令
— LDC 协处理器数据加载指令
— STC 协处理器数据存储指令
— MCR ARM处理器寄存器到协处理器寄存器的数据传送指令
— MRC 协处理器寄存器到ARM处理器寄存器的数据传送指令
寻址方式:
寻址: cpu 找到/拿到 数据
8种:
立即数寻址 mov r0, #4
寄存器寻址 mov r0, r1
寄存器移位寻址 mov r0, r1, lsl #4
寄存器间接寻址 ldr r0, [r1]
基址变址寻址 ldr r0, [r1, #4] @r0 = *(r1+4)
ldr r0, [r1, #4]! @r0 = *(r1+4) r1+=4
ldr r0, [r1], #4 @r0 = *(r1) r1+=4
多寄存器寻址 ldmxx r0!,{r1-r3}
ldmia r0!, [r1-r12]
堆栈寻址 ldmxx sp!,{r1-r3}
stmfd sp!, [r0-r12, lr]
相对寻址 b label
四、异常:
(1)概念
不是出问题了,而是芯片内部的调度。可以看作qt的信号。即CPU正在执行某个应用程序时突然来了一个异常(中断、复位、undef、abort…)打断当前应用程序的执行跳转到异常处理函数中处理异常,处理完之后回来接着执行app。
(2)种类
中断:IRQ、FIQ由外部硬件触发
软中断:软件模拟中断
复位异常:reset 例:手机关机,按power键
未定义异常:undef 当指令不识别时产生的异常
数据异常:Data Abort 例:越界
(3)异常向量表
规定了每一种异常的入口地址
地址 | 异常类型 | CPU模式 |
0x00 | reset异常 | SVC |
0x04 | udef未定义异常 | Undef |
0x08 | 软中断异常 | SVC |
0x0c | Prefetch Abort预取指令异常 | Abort |
0x10 | Data Abort数据异常 | Abort |
0x14 | 保留 | |
0x18 | IRQ | IRQ |
0x1c | FIQ | FIQ |
(4)异常发生,CPU如何跳转到异常处理函数:
CPU收到异常信号之后就会自动打断当前应用程序的执行,并跳转到异常向量表中规定好的异常入口地址,入口地址放的是一条跳转指令,例:bl swi_handler
异常源有7种:
1.复位异常 reset 从头开始执行,所有数据都刷新了
2.未定义指令异常 undefined instruction 执行未定义的指令,执行时产生异常
如:user模式下执行msr指令
3.软中断异常 swi 指令本身产生的异常,执行时产生
4.预取指异常 prefetch abort 取值发生了异常,不会影响正在执行的指令,执行完后处理异常
5.数据异常 data abort 执行指令时用到的数据有问题,会更新PC寄存器的值,处理完异常后,cpu认为需要重新执行这条指令
如: ldr r0,[r1] 可能r1这个地址不存在
6.IRQ异常 irq 中断,在执行指令时来了异常,会在语句执行完后再去处理异常
7.FIQ异常 fiq 中断响应尽可能的快,在执行指令时来了异常,会在语句执行完后再去处理异常
FIQ 快速中断:
1.有自己独立的r8-r12寄存器, 处理速度更快
2.异常优先级更高
3.位于异常向量表的末尾, 可以直接将异常处理函数跟在后面, 顺序执行, 效率更高
异常优先级:
异常在当前指令执行完成之后才被响应, 多个异常可以在同一时间产生
异常指定了优先级和固定的服务顺序:
Reset
Data Abort
FIQ
IRQ
Prefetch Abort
SWI
Undefined instruction
异常恢复时的返回地址:
fiq/irq : pc = lr - 4
reset: 从头开始执行, 不用管返回地址
undefined: pc = lr
swi: pc = lr
prefetch: pc = lr - 4
data: pc = lr - 8
异常处理流程:
(1)当异常产生时,ARM core:
①.拷贝cpsr到spsr_
②.设置适当的cpsr位
改变处理器状态进入ARM态
改变处理器模式进入相应的异常模式
设置中断禁止位禁止相应中断(如果需要)
③.保存返回地址到LR _
④.设置PC为相应的异常向量
(2)返回时,异常处理需要:
①.cpsr = spsr
②.pc = lr
注:这些操作只能在ARM态执行
.globl _start
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word swi_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
reset:
/* set the cpu to SVC32 mode */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @用sp保存栈顶地址
/* set the cpu to user mode */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd0 @改变模式,并禁止了irq fiq
msr cpsr,r0
ldr sp, =stacktop @用sp保存栈顶地址
sub sp, sp, #64
bl _main
_main:
mov r0, #3
mov r1, #9
bl user_add
nop
nop
nop
nop
user_add:
stmfd sp!, {lr}
ldr r2, =sharedata @r2 保存共享空间地址
str r0, [r2]
str r1, [r2, #4]
swi 0
ldr r3, [r2, #8]
ldmfd sp!, {pc}
swi_interrupt:
stmfd sp!, {r0-r12, lr}
@判断中断号
ldr r0, [lr, #-4] @取出swi 0 指令放到r0
bic r0, r0, #0xff000000 @将前8位清零(条件码+指令码)
cmp r0, #0
bleq sys_add
ldmfd sp!, {r0-r12, pc}^
@ r0-r12 -恢复-> r0-r12 pc = lr cpsr = spsr_svc(^起的作用)
sys_add:
stmfd sp!, {lr}
ldr r2, =sharedata @r2 保存共享空间地址
ldr r0, [r2]
ldr r1, [r2, #4]
add r10, r0, r1
str r10, [r2, #8]
ldmfd sp!, {pc}
@开辟栈空间, 因为是满递减栈, 所以记录栈顶地址
stack: .space 64*7
stacktop: .word stack+64*7
@开辟共享空间
sharedata: .space 64
汇编:
理解机器执行过程(异常跳转等)
汇编效率会高一些
向上理解软件, 向下感知硬件, 对理解系统也有帮助
汇编是最接近机器的语言, 简单理解
汇编启动机器后, 就可以转到C语言编程/应用层编程
五、接口技术:
led操作-GPIO输出:
需求: 点亮led灯 - led3
看原理图: GPX1_0 输出 高电平
看芯片手册/用户手册:
Disable Pull-up/Pull-down when you use port as output function.
GPX1CON 0x11000c20 GPX1CON[0] [3:0] 0x1 = Output
Base Address: 0x1100_0000
Address = Base Address + 0x0C20
GPX1DAT 0x11000c24 [0] 1
Base Address: 0x1100_0000
Address = Base Address + 0x0C24
GPX1PUD 0x11000c28 [1:0] 0x0 = Disables Pull-up/Pull-down
Base Address: 0x1100_0000
Address = Base Address + 0x0C28
GPX1DRV 0x11000c2C [1:0] 0x0 = 1x
Base Address: 0x1100_0000
Address = Base Address + 0x0C2C
写代码
#define GPX1CON *(volatile unsigned int *)0x11000c20
#define GPX1DAT *(volatile unsigned int *)0x11000c24
#define GPX1PUD *(volatile unsigned int *)0x11000c28
#define GPX1DRV *(volatile unsigned int *)0x11000c2C
int main(int argc, char *argv[])
{
/*
unsigned int *p = (unsigned int *)0x11000c20 ;
*p = *p & (~0xf);
*p = *p | 0x1;
*/
GPX1CON &= ~0xf;
GPX1CON |= 0x1;
GPX1DAT |= 1;
GPX1PUD &= ~0x3;
while(1);
return 0;
}
需求: 点亮led灯 - led4 led5
看原理图: GPF3_4 GPF3_5 输出 高电平
看芯片手册/用户手册:
GPF3CON 0x114001E0 GPF3CON[5] [23:20] 0x1 = Output
GPF3CON[4] [19:16] 0x1 = Output
GPF3DAT 0x114001E4 修改 4 5 两位
GPF3PUD 0x114001E8 [11:10] [9:8] 0x0 = Disables Pull-up/Pull-down
[2n+1 : 2n] n=5 n=4
key操作-GPIO输入:
需求: 通过按键key2点亮led灯
看原理图: key2 UART_RING GPX1_1 未按下-高电平 按下后-低电平
看芯片手册/用户手册:
GPX1CON 0x11000c20 GPX1CON[1] [7:4] 0x0 = Input
GPX1DAT 0x11000c24 [1] 读取对应位的状态判断输入电平
GPX1PUD 0x11000c28 [3:2] 0x3 = Enables Pull-up 上拉,默认电平是高电平
uart-通用异步收发器:
全双工 异步
考虑: 波特率 数据位数 停止位 校验码
需求: 通过 uart 发送一个 ‘A’ 给电脑
看原理图:
BUF_XuTXD2/UART_AUDIO_TXD 发送引脚 XuTXD2/UART_AUDIO_TXD/GPA1_1
BUF_XuRXD2/UART_AUDIO_RXD 接收引脚 XuRXD2/UART_AUDIO_RXD/GPA1_0
看芯片手册/用户手册:
ULCONn 开始 数据位 停止位 奇偶校验位
UBRDIVn UFRACVALn 波特率
发送:
GPA1CON 0x11400020 [7:4] 0x2 = UART_2_TXD
接收:
GPA1CON 0x11400020 [3:0] 0x2 = UART_2_RXD
uart模块控制
ULCON2 0x13820000 =0x3 (0 000 0 11)普通模式 无奇偶校验 1个停止位 8个数据位
UCON2 0x13820004 [3:0] 0101 = polling mode(轮询)
UTRSTAT2 0x13820010 [1] 1 - 发送完成, 发送缓冲区为空
UTXH2 0x13820020 [7:0] 要发送的数据 'A' UTXH2 = 'A'
URXH2 0x13820024 [7:0] 接收到的数据
UBRDIV2 0x13820028 53 100000000/(115200*16)-1 取整数部分
UFRACVAL2 0x1382002C 4 ((100000000/(115200*16)-1)-53)*16 取整数部分
#define GPA1CON (*(volatile unsigned int *)0x11400020)
#define ULCON2 (*(volatile unsigned int *)0x13820000)
#define UCON2 (*(volatile unsigned int *)0x13820004)
#define UTRSTAT2 (*(volatile unsigned int *)0x13820010)
#define UTXH2 (*(volatile unsigned int *)0x13820020)
#define URXH2 (*(volatile unsigned int *)0x13820024)
#define UBRDIV2 (*(volatile unsigned int *)0x13820028)
#define UFRACVAL2 (*(volatile unsigned int *)0x1382002C)
void uart2_init(void)
{
GPA1CON = GPA1CON & ~(0xff << 0) | (0x22 << 0);
ULCON2 = 0x3;
UCON2 = UCON2 & ~(0xf << 0) | (0x5 << 0);
UBRDIV2 = 53;
UFRACVAL2 = 4;
}
void uart2_send(char data)
{
UTXH2 = data;
while( (UTRSTAT2 & (0x1<<1)) == 0); //发送未完成
}
void send_str(char *str)
{
while( *str != '\0')
{
uart2_send(*str);
str++;
}
}
char uart2_recv(void)
{
if((UTRSTAT2 & (0x1 <<0)) == 1)
{
return (URXH2 & 0xff);
}
else
{
return 0;
}
}
通信相关知识:
单工通信: 一方发送, 一方接收, 只能单方向传输信息 如:广播
半双工通信: 双方都能发送接收, 但是同一时间只能发送或接收, 如: 对讲机
全双工: 双方都能发送接收, 并且能同时进行, 如: 电话, qq
异步: 双方规定好通信频率, 大部分通过波特率来确定双方通信频率, 双方通信特率一定要一致
同步: 多增加一个时钟线(clk - clock)
波特率: 1s内发送的 数据 位数(二进制的位)
数据位:传输数据的长度,一般是8位
奇偶校验: 多增加一个校验位
奇校验: 保证 传输数据(校验位+数据位) 中 1 的个数是奇数个
偶校验: 保证 传输数据(校验位+数据位) 中 1 的个数是偶数个
优点: 简单
缺点: 容错率不高; 每次传输都要多增加一位, 降低效率
RTC-内部设备
实时时钟 - 片内设备, 无外接引脚, 不用看原理图
直接看芯片手册/用户手册:
RTCCON 0x10070040 [0] 1-设置初始时间 0-rtc自己计时, 后续读取时间
BCDSEC 0x10070070
BCDMIN 0x10070074
BCDHOUR 0x10070078
BCDDAYWEEK 0x10070080
BCDDAY 0x1007007C
BCDMON 0x10070084
BCDYEAR 0x10070088
代码:
#define RTCCON (*(volatile unsigned int *)0x10070040)
#define BCDSEC (*(volatile unsigned int *)0x10070070)
#define BCDMIN (*(volatile unsigned int *)0x10070074)
#define BCDHOUR (*(volatile unsigned int *)0x10070078)
#define BCDDAYWEEK (*(volatile unsigned int *)0x10070080)
#define BCDDAY (*(volatile unsigned int *)0x1007007C)
#define BCDMON (*(volatile unsigned int *)0x10070084)
#define BCDYEAR (*(volatile unsigned int *)0x10070088)
#include "uart.h"
void rtc_init(void)
{
RTCCON |= 0x1;
BCDYEAR = 0x022;
BCDMON = 0x10;
BCDDAY = 0x18;
BCDDAYWEEK = 0x02;
BCDHOUR = 0x16;
BCDMIN = 0x59;
BCDSEC = 0x55;
RTCCON &= ~0x1;
}
void rtc_show(void)
{
uart2_send( (BCDHOUR>>4) + '0' );
uart2_send( (BCDHOUR&0xf) + '0' );
uart2_send(':');
uart2_send( (BCDMIN>>4) + '0' );
uart2_send( (BCDMIN&0xf) + '0' );
uart2_send(':');
uart2_send( (BCDSEC>>4) + '0' );
uart2_send( (BCDSEC&0xf) + '0' );
send_str("\r\n");
}
Watchdog Timer-看门狗
本质:计数器(功能:定时、计数,时间到则复位)
内部设备, 不用看原理图
直接看芯片手册/用户手册:
t_watchdog = 1/(PCLK/(Prescaler value + 1)/Division_factor)
PCLK = 100 MHz (7 Clock Management Unit)
WTCON[15:8] 8-bit Prescaler
WTCON[4:3] Division_factor
WTCON[2] Interrupt
WTCON[0] Reset Signal Generator
WTCNT Down Counter
WTDAT WTCNT = WTDAT (喂狗)
To start WDT, set WTCON[0] and WTCON[5] as 1.
t_watchdog = 1/(100000000/(255 + 1)/128)
f_watchdog = 100M/(255+1)/128 = 3051 Hz(次/S)
WTCON 0x10060000 = 0xff39 (11111111 00 1 11 0 0 1)
WTDAT 0x10060004 初始时不起作用, 不用设置, 后面喂狗用
WTCNT 0x10060008 启动前给一个初始值(时间 - 3S)
代码:
#define WTCON (*(volatile unsigned int *)0x10060000)
#define WTDAT (*(volatile unsigned int *)0x10060004)
#define WTCNT (*(volatile unsigned int *)0x10060008)
void wdt_init(int n)
{
WTCON = 0xff39;
WTCNT = n*3051;
}
PWM-无源蜂鸣器
本质: 计数 timer(定时器) pwm(脉冲宽度调制)-控制高低电平变化
需求: 让无源蜂鸣器响
看原理图: MOTOR_PWM GPD0_0
看手册: PCLK 100MHz
GPD0CON 0x114000A0 [3:0] 0x2 = TOUT_0 (timer out - pwm)
Timer Input Clock Frequency = PCLK/({prescaler value + 1})/{divider value}
TCFG0 0x139D0000 [7:0] 分频值 1-255 一级分频prescaler value
TCFG1 0x139D0004 [3:0] 0100 = 1/16 二级分频 divider value
TCON 0x139D0008 [3] 1 = Interval mode (auto-reload)
[2] 0 = Inverter Off (默认为0, 可以不用设置)
[1] 初始化先手动更新一次(1), 后续可以关闭手动更新(0)
[0] 1 = Starts Timer 0
TCNTB0 0x139D000C Count 计数值, 周期值 (count*fre == 周期时间)
TCMPB0 0x139D0010 Compare 比较值, 电平翻转的位置, 脉宽值(高电平持续的时间)
PWM 周期频率 : 20-20000Hz (受材质影响, 范围再小一点)
代码:
#define GPD0CON (*(volatile unsigned int *)0x114000A0)
#define TCFG0 (*(volatile unsigned int *)0x139D0000)
#define TCFG1 (*(volatile unsigned int *)0x139D0004)
#define TCON (*(volatile unsigned int *)0x139D0008)
#define TCNTB0 (*(volatile unsigned int *)0x139D000C)
#define TCMPB0 (*(volatile unsigned int *)0x139D0010)
void pwm_init(void)
{
GPD0CON = GPD0CON & ~(0xf << 0) | (0x2 << 0);
TCFG0 = TCFG0 | (0xff < 0);
TCFG1 = TCFG1 & ~(0xf << 0) | (0x4 << 0);
TCNTB0 = 500;
TCMPB0 = 200;
TCON = TCON | (0x1 << 3) | (0x1 << 1) & ~(0x1 << 0); //手动更新, 先关pwm
TCON = TCON & ~(0x1 << 1); //关闭手动更新
}
void pwm_on(void)
{
TCON = TCON | (0x1 << 0);
}
void pwm_off(void)
{
TCON = TCON & ~(0x1 << 0);
}
ADC-模数转换
采集电位器后的电压值
看原理图: XadcAIN3 adc专用引脚, 不受GPIO控制 0-1.8v
看手册: Analog Input Range: 0 ~ 1.8V
ADC_CFG 0x10010118 [16] 0 : General ADC
ADCCON 控制转换模数
ADCDAT 存放转换后的数据(0~2^12-1) 电压值=data*1.8v/(2^12 - 1)
ADCCONn [15] end of conversion flag (结束转换的标志)
ADC_CFG 0x10010118 [16] 0 : General ADC
ADCCON 0x126C0000 = 0x17fc2 (1 0 1 11111111 000 0 1 0)
ADCDLY 0x126C0008 使用默认值, 不用配置
ADCDAT 0x126C000C [11:0] 0x0~0xfff 转换后的数据
ADCMUX 0x126C001C [3:0] 0011 = AIN 3
#define ADC_CFG (*(volatile unsigned int *)0x10010118)
#define ADCCON (*(volatile unsigned int *)0x126C0000)
#define ADCDAT (*(volatile unsigned int *)0x126C000C)
#define ADCMUX (*(volatile unsigned int *)0x126C001C)
#include "uart.h"
void adc_init(void)
{
ADC_CFG = ADC_CFG & ~(0x1 << 16);
ADCMUX = ADCMUX & ~(0xf<<0) | (0x3<<0);
ADCCON = 0x17fc2;
}
void adc_show(void)
{
int mv = 0;
mv = (ADCDAT&0xfff) * 1800 / 4095;
uart2_send( mv/1000 + '0' );
uart2_send('.');
uart2_send( mv%1000/100 + '0' );
uart2_send( mv%100/10 + '0' );
send_str("v\r\n");
}
中断-key
硬件中断触发方式:①电平触发:高、低;②边沿触发:上升、下降
硬件中断处理流程图
异常: irq fiq swi
start.S 要修改, 异常向量表设置异常处理函数(irq), 设置对应模数下的栈空间
看原理图: key2 UART_RING GPX1_1 XEINT9
看手册: 6章节 - GPIO 9章节 - Interrupt Controller
GPX1CON 0x11000c20 GPX1CON[1] [7:4] 0xF = EXT_INT41[1] (extern interrupt)
EXT_INT41CON 0x11000E04 [6:4] 0x2 = Triggers Falling edge(由原理图得到-下降沿)
EXT_INT41_FLTCON0 0x11000E88 [15]-1 enable [14]-0 delay 就是默认值,可以不用设置
EXT_INT41_MASK 0x11000F04 [1] 0x0 = Enables Interrupt
EXT_INT41_PEND 0x11000F44 [1] 0x1 = Interrupt Occurs(代表中断处理完成-在中断处理函数内部置一)
25(SPI Port No) 57(ID) – EINT[9]
The CPU interface always uses the IRQ exception request for Non-secure interrupts.
(CPU接口对于非安全中断总是使用IRQ异常请求。)
ICCICR_CPU0 0x10480000 1 = Enables signaling of interrupts(使能cpu interface)
ICCPMR_CPU0 0x10480004 [7:0] priority mask (cpu interface 的屏蔽码)
ICCIAR_CPU0 0x1048000C [9:0] ACKINTID - The interrupt ID(只读,通过这个寄存器知道具体的中断)
ICCEOIR_CPU0 0x10480010 [9:0] ACKINTID (只写, 写入id, 表明中断处理完成)
ICDDCR 0x10490000 [0] 1 (使能分发器)
ICDISER1_CPU0 0x10490104 [25] 1 (使能id为57的中断)
ICDIPR14_CPU0 0x10490438 [15:8] 设置id为57的优先级
ICDIPTR14_CPU0 0x10490838 [15:8] 0b00000001 - CPU 0 (设置id为57的中断对应的cpu interface)
Distributor (分发器)
接收多种中断, 找出最高优先级的发给cpu interfaces
数字越小, 优先级越高 0的优先级最高
Enabling the forwarding of interrupts to the CPU interfaces globally.
ICDDCR 允许全局地将中断转发到CPU接口。 - 使能分发器
Enabling or disabling each interrupt.
ICDISER1_CPU0 正在启用或禁用每个中断。 - 使能单独的某一个中断源
Setting the priority level of each interrupt.
ICDIPR14_CPU0 设置每个中断的优先级。
Setting the target processor list of each interrupt.
ICDIPTR14_CPU0 设置每个中断的目标处理器列表。 - 设置中断要发送的cpu
Setting each peripheral interrupt to be level-sensitive or edge-triggered.
EXT_INT41CON 将每个外围中断设置为电级敏感或边缘触发。
Setting each interrupt as either secure or Non-secure if the GIC implements the Security Extensions.
如果GIC实现了安全扩展,则将每个中断设置为安全中断或不安全中断。 - 未设置,不用管
Sending an SGI to one or more target processors
向一个或多个目标处理器发送SGI。 - 分发器自己要做的事情
CPU interface(CPU接口)
设置屏蔽码, 中断优先级高于屏蔽码才会发送给具体的CPU进行处理
Enabling the signaling of interrupt requests by the CPU interface.
ICCICR_CPU0 通过CPU接口启用中断请求的信令。 - 使能 CPU接口工作
Acknowledging an interrupt.
ICCIAR_CPU0 承认了一个中断。 - 确认中断正在处理
Indicating completion of the processing of an interrupt.
ICCEOIR_CPU0 EXT_INT41_PEND 表示中断处理的完成。 - 回应处理完成
Setting an interrupt priority mask for the processor.
ICCPMR_CPU0 为处理器设置中断优先级掩码。
Defining the preemption policy for the processor.
定义处理器的抢占策略。 - 分组后的组优先级 - 不用分组, 就不用设置
Determining the highest priority pending interrupt for the processor.
确定处理器的未决中断的最高优先级。 - cpu interface 自己决定, 不用设置
代码:
#define GPX1CON (*(volatile unsigned int *)0x11000c20)
#define EXT_INT41CON (*(volatile unsigned int *)0x11000E04)
#define EXT_INT41_MASK (*(volatile unsigned int *)0x11000F04)
#define EXT_INT41_PEND (*(volatile unsigned int *)0x11000F44)
#define ICCICR_CPU0 (*(volatile unsigned int *)0x10480000)
#define ICCPMR_CPU0 (*(volatile unsigned int *)0x10480004)
#define ICCIAR_CPU0 (*(volatile unsigned int *)0x1048000C)
#define ICCEOIR_CPU0 (*(volatile unsigned int *)0x10480010)
#define ICDDCR (*(volatile unsigned int *)0x10490000)
#define ICDISER1_CPU0 (*(volatile unsigned int *)0x10490104)
#define ICDIPR14_CPU0 (*(volatile unsigned int *)0x10490438)
#define ICDIPTR14_CPU0 (*(volatile unsigned int *)0x10490838)
void key_init(void)
{
GPX1CON |= (0xf << 4); //[7:4] 0xF = EXT_INT41[1] (extern interrupt)
EXT_INT41CON &= ~(0x7 << 4); //[6:4] 0x2 = Triggers Falling edge(由原理图得到-下降沿)
EXT_INT41CON |= (0x2 << 4);
ICCPMR_CPU0 |= (0xff << 0); //[7:0] priority mask (cpu interface 的屏蔽码 255 )
ICDIPR14_CPU0 &= ~(0xff << 8); //[15:8] 设置id为57的优先级 0
ICDIPTR14_CPU0 &= ~(0xff << 8); //(设置id为57的中断对应的cpu interface)
ICDIPTR14_CPU0 |= (0x1 << 8); //[15:8] 0b00000001 - CPU 0
EXT_INT41_MASK &= ~(0x1 << 1); //[1] 0x0 = Enables Interrupt
ICDISER1_CPU0 |= (0x1 << 25); //[25] 1 (使能id为57的中断)
ICDDCR |= (0x1 << 0); //[0] 1 (使能分发器)
ICCICR_CPU0 |= (0x1 << 0); //[0] 1 = 使能cpu interface
}
void do_irq(void)
{
int id = ICCIAR_CPU0; //[9:0] ACKINTID - The interrupt ID(只读,通过这个寄存器知道具体的中断)
if((id & 0x3ff) == 57)
{
//led3_on(); led3_off();
uart_send('A');
//pwm_on();
//代表中断处理完成-在中断处理函数内部置一
EXT_INT41_PEND |= (0x1 << 1); //[1] 0x1 = Interrupt Occurs
}
ICCEOIR_CPU0 = id; //[9:0] ACKINTID (只写, 写入id, 表明中断处理完成)
}
六、通信相关知识:
1.基础知识:
单工通信: 一方发送, 一方接收, 只能单方向传输信息 如:广播
半双工通信: 双方都能发送接收, 但是同一时间只能发送或接收, 如: 对讲机
全双工: 双方都能发送接收, 并且能同时进行, 如: 电话, qq
异步: 双方规定好通信频率, 大部分通过波特率来确定双方通信频率, 双方通信特率一定要一致
同步: 多增加一个时钟线(clk - clock)
波特率: 1s内发送的 数据 位数(二进制的位)
奇偶校验: 多增加一个校验位
奇校验: 保证 传输数据(校验位+数据位) 中 1 的个数是奇数个
偶校验: 保证 传输数据(校验位+数据位) 中 1 的个数是偶数个
优点: 简单
缺点: 容错率不高; 每次传输都要多增加一位, 降低效率
通信协议: 通信双方必须遵循一定的规则进行数据的传输
串行通信: 只能一位一位传送, 效率较低, 通常用于设备间的传输
并行通信: 占用引脚数量多, 易受干扰, 不易同步, 通常用于短距离传输, 如内存数据传输
无线通信: WIFI, 蓝牙, ZIGBEE…
2.串行通信主要分为: uart spi IIC 单总线
(1)uart: 全双工(Rx Tx) 异步(波特率) 一对一通信
(2)SPI: 全双工(MISO MOSI) 同步(clk) 支持一对多通信(CS)
①特点:
其通讯双方有主从之分,通讯由主设备引导,从设备被动响应,
且只有一台设备可作为主设备,其他设备均为从设备,
每次通讯主设备通过片选线来确定从设备。
②SPI接口具有如下优点:
1) 全双工的协议,既能发送数据也能接受数据;
2) 操作简单,输入输出的bit数也没什么限制,不局限于一个byte;
3) 相对于I2C协议,时钟速度快,没有最大限制;
4) 三态输出的驱动能力强,相对I2C的开漏输出,抗干扰能力强,传输稳定;
5) 协议简单利于硬件设计与实现,比如不需要像I2C协议中每个从器件都需要一个地址。
③同时,它也具有如下缺点:
1) 需要占用主机较多的口线(每个从机都需要一根片选线);
2) 只支持单个主机;
3) 传输的过程没有确认信号,只负责传,不管从器件收不收到;
4) 没有校验机制。
④应用
ads1292 心电传感器 心率 呼吸波
(3)IIC(I2C): 半双工(data) 同步(CLK) 多对多(同一时间只能有一个主设备, clk是双向的)
I2C启动时序图
①特点
双线多主机同步、半双工、串行低速率。
广泛应用于传输速率要求不高、传输距离短的场合,
最大优势是可以在总线上扩展多个外围设备的支持。
每一个接入i2c总线的设备都有唯一地址标识符,由7位二进制数表示。
因为I2C通信速率不高,而且通信双方距离很近,所以常见各种物联网传感器芯片
(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)
I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,优化主板空间和成本。
②I2C总线最主要的优点:
· 无论总线上有多少设备,都只使用两条线,保持低引脚/信号数。
· 真正的支持多主机设备,但是同一时刻只允许一台主机。
· I2C总线具有低功耗、抗干扰强的优点,传输距离短的特点(引脚数量少, 低功耗, 抗干扰能力强)。
· 连接到相同总线的I2C 数量只受到总线的最大电容400pF 限制。
· 串行的8 位双向数据传输位速率在标准模式下可达100kbit/s,
快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
③缺点:
相对效率不高 (总线速度分为标准速度100kbps,快速模式400kbps,高速模式3.4Mbps)
④应用
通常用于一些物联网传感器设备 如: 陀螺仪/加速度传感器(mpu6050)
(4)单总线: one-wire 半双工(data) 异步(高低电平时间控制 - 看具体应用)
一根线传输数据
ds18b20 温度传感器
dht11 温湿度传感器
(5)总结:SPI协议的优缺点
优点
协议简单利于硬件设计与实现,比如不需要像I2C协议中每个从器件都需要一个地址;只用到4根线,封装也很容易做
全双工的协议,既能发送数据也能接受数据
三态输出的驱动能力强,相对I2C的开漏输出,抗干扰能力强,传输稳定;
相对于I2C协议,时钟速度快,没有最大限制
输入输出的bit数也没什么限制,不局限于一个byte
缺点
信号线4根,比I2C多,芯片选择线会随着从器件的个数的增加而增加
传输的过程没有确认信号,只负责,不管从器件收不收到;在SPI Flash中会有read status 这个命令确认从器件的状态,是否处于busy状态
没有校验机制(I2C也没有)
③同时,它也具有如下缺点:
1) 需要占用主机较多的口线(每个从机都需要一根片选线);
2) 只支持单个主机;
3) 传输的过程没有确认信号,只负责传,不管从器件收不收到;
4) 没有校验机制。
④应用
ads1292 心电传感器 心率 呼吸波
(3)IIC(I2C): 半双工(data) 同步(CLK) 多对多(同一时间只能有一个主设备, clk是双向的)
[外链图片转存中…(img-4NB5A539-1677236670339)]
I2C启动时序图
[外链图片转存中…(img-FTm8YJoY-1677236670340)]
①特点
双线多主机同步、半双工、串行低速率。
广泛应用于传输速率要求不高、传输距离短的场合,
最大优势是可以在总线上扩展多个外围设备的支持。
每一个接入i2c总线的设备都有唯一地址标识符,由7位二进制数表示。
因为I2C通信速率不高,而且通信双方距离很近,所以常见各种物联网传感器芯片
(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)
I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,优化主板空间和成本。
②I2C总线最主要的优点:
· 无论总线上有多少设备,都只使用两条线,保持低引脚/信号数。
· 真正的支持多主机设备,但是同一时刻只允许一台主机。
· I2C总线具有低功耗、抗干扰强的优点,传输距离短的特点(引脚数量少, 低功耗, 抗干扰能力强)。
· 连接到相同总线的I2C 数量只受到总线的最大电容400pF 限制。
· 串行的8 位双向数据传输位速率在标准模式下可达100kbit/s,
快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
③缺点:
相对效率不高 (总线速度分为标准速度100kbps,快速模式400kbps,高速模式3.4Mbps)
④应用
通常用于一些物联网传感器设备 如: 陀螺仪/加速度传感器(mpu6050)
(4)单总线: one-wire 半双工(data) 异步(高低电平时间控制 - 看具体应用)
一根线传输数据
ds18b20 温度传感器
dht11 温湿度传感器
(5)总结:SPI协议的优缺点
优点
协议简单利于硬件设计与实现,比如不需要像I2C协议中每个从器件都需要一个地址;只用到4根线,封装也很容易做
全双工的协议,既能发送数据也能接受数据
三态输出的驱动能力强,相对I2C的开漏输出,抗干扰能力强,传输稳定;
相对于I2C协议,时钟速度快,没有最大限制
输入输出的bit数也没什么限制,不局限于一个byte
缺点
信号线4根,比I2C多,芯片选择线会随着从器件的个数的增加而增加
传输的过程没有确认信号,只负责,不管从器件收不收到;在SPI Flash中会有read status 这个命令确认从器件的状态,是否处于busy状态
没有校验机制(I2C也没有)