一、嵌入式ARM技术简介
1.1、计算机系统组成
计算机系统 = 硬件系统 + 软件系统
硬件是计算机系统的物质基础,软件是计算机系统的灵魂。硬件和软件是相辅相成的,不可分割的整体。
1.1.1、软件
使计算机实现各种功能的程序集合。包括系统软件、应用软件两大类。
系统软件
- windows、Linux、Android、iOS、harmonyOS、Ubuntu . . .
中间件
- APP
应用软件
- 云存储
"看得见摸不着"
1.1.2、硬件
计算机的实体部分,可以实现计算机最基本的操作行为。
"看得见摸得着"
(1)电源
(2)输入设备
常用的有键盘、鼠标、扫描仪等。
(3)输出设备
常用的有显示器、打印机、绘图仪等。
(4)存储器
存储器是用来存放数据的部件,它是一个记忆装置,也是计算机能够实“存储程序控制”的基础。
1)高速缓冲存储器( Cache)
CPU可以直接访问,用来存放当前正在执行的程序中的活跃部分,以便快速地向CPU提供指令和数据。
2)主存储器
可由CPU直接访问,用来存放当前正在执行的程序和数据。
3)辅助存储器
设置在主机外部,CPU不能直接访问,用来存放暂时不参与运行的程序和数据,需要时再传送到主存。
4)储器的层次结构
(5)运算器
运算器是对信息进行处理和运算的部件,经常进行的运算是算术运算和逻辑运算,因此运算器的核心是算术逻辑运算部伟ALU。
(6)控制器
控制器是整个计算机的指挥中心。
控制器中主要包括时序控制信号形成部件和一些专用的寄存器。[](https://)
(7)计算器的总线结构
将各大基本部件,按某种方式连接起来就构成了计算机的硬件系统。
总线是一组能为多个部件服务的公共信息传送线路,它能分时地发送与接收各部件的信息。
1)单总线结构
2)多总线结构
(8)CPU
1)物理结构
2)CPU组成
- 运算单元
- 控制单元
- 存储单元
- bus
3)CPU工作原理:取值、译码、执行。
一条指令的执行,从硬盘到CPU的执行过程。
4)CPU性能提升
- 提升工艺
- cache
- 多ALU和FPU
- 最佳流水线:7~8级流水线。
1.2、架构演进
1.2.1、冯诺依曼结构
程序和数据都放在内存中,且不彼此分离 的结构称为冯诺依曼结构。譬如Intel的 CPU均采用冯诺依曼结构。
1.2.2、哈佛结构
程序和数据分开独立放在不同的内存块中, 彼此完全分离的结构称为哈佛结构。譬如 大部分的单(MCS51、 ARM9等)均采用哈佛结构。
优劣对比:
冯诺依曼结构中程序和数据不区分的放在 一起,因此安全和稳定性是个问题,好处是处理起来简单。
哈佛结构中程序(一般放在ROM、 flash中) 和数据(一般放在RAM中)独立分开存放, 因此好处是安全和稳定性高,缺点是软件处理复杂一些(需要统一规划链接地址等)
1.2.3、几种常见架构(CPU图纸)对比
c51/52: 低功耗、价格低、成本低、简单
x86: 性能高、速度快、兼容性好
ARM: 成本低、低功耗
A Application --》 X86
R real-time
M --> c51/52
MIPS:简洁、优化方便、高拓展性、64位
PowerPc:方便灵活、可伸缩性好、功耗极高、服务器
RISC-V:模块化、极简、可拓展、开源
1.3、ARM公司简介
1.3.1、ARM公司
成立于1990年11月
- 前身为 Acorn计算机公司
主要设计ARM系列RISC处理器内核
授权ARM内核给生产和销售半导体的合作伙伴
- ARM公司不生产芯片
另外也提供基于ARM架构的开发设计技术
- 软件工具、评估板、调试工具、应用软件、总线架构、外围设备单元、等等。。。
1.3.2、ARM全球分布
1.3.3、ARM公司合作伙伴
1.3.4、ARM产品应用
ARM 微处理器及技术的应用几乎已经深入到各个领域:
- 工业控制领域
- 无线通讯领域
- 网络应用
- 消费类电子产品
- 成像和安全产品
1.3.5、ARM处理器的最新发展
1.4、系统设计
1.4.1、嵌入式/单片机
嵌入式系统,是以应用为中心,以计算机技术为基础软硬件可裁剪,适用于对功可靠性成本,体积、功耗有严格要求的专用计算机系统。
单片机更多是指单片机的功能单一,它是完成运算、逻辑控制、通信等功能的单一模块。即便它性能再强大,功能依然是单一的。
(1)软件
从软件上,行业里经常把芯片中不带MMU(memory management unit,内存管理单元)从而不支持虚拟地址,只能裸奔或运行RTOS(实时操作系统,例如ucos、华为LiteOS、RT-Thread、freertos等)的system,叫做单片机(如STM32、NXP LPC系列、NXP imxRT1052系列等)。
同时,把芯片自带MMU可以支持虚拟地址,能够跑Linux、Vxworks、WinCE、Android这样的“高级”操作系统的system,叫做嵌入式。
(2)硬件
从硬件组成上区别,单片机是由一块集成电路芯片组成,具体包含微控制器电路,输入输出接口控件。而嵌入式,随着电子技术发展,如今既可以用单片机实现,也可以用其他可编程的电子器件实现。
在某些时候,单片机本身已经足够强大,可以作为嵌入式系统使用。它的成本更低,开发和维护的难度相对较小,尤其是针对一些针对性更强的应用。而嵌入式系统理论上性能更强,应用更广泛,但复杂度高,开发难度大。
嵌入式与单片机之间的关系是什么?详情参考链接:https://www.zhihu.com/question/315310041/answer/2179945564
1.4.2、SOC
SOC:System on Chip的缩写,称为系统级芯片 ,也有称片上系统 ,意指它是一个产品,是一个有专用目标的集成电路,其中包含完整系统并有嵌入软件的全部内容。
CPU + ROM + RAM + MUX + FLASH + usb + can + gpio + adc ...
1.4.3、AMBA
AMBA (芯片通信标准)
- AHB (高速总线)
- ASB (系统总线)
- APB (外部总线)
1.5、ARM编程模型
1.5.1、数据和指令类型
(1)ARM采用的是32位架构(最新ARMV8 64位架构)
(2)ARM约定
- Byte: 8 bits
- Halfword: 16 bits(2 byte)
- Word: 32 bits(4 byte)
- Doubleword 64-bits (8byte) ( Cortex-A处理器)
(3)大部分 ARM core提供
- ARM指令集(
- Thumb指令集(16bit64/32-bit))
- Cortex-A处理器
- 16位和32位 Thumb-2指令集
- 16位和32位 ThumbEE指令集
(4)大部分 ARM core提供
1.5.2、工作模式
ARM有8个基本工作模式
- User :普通应用程序运行的模式(应用程序)
- FIQ :快速中断模式,以处理快速情况,高速数据传输
- IRQ :外部中断模式,普通中断处理
- SVC :保护模式(管理模式),操作系统使用的特权模式(内核)
- Abort :数据访问中止模式,用于虚拟存储和存储保护
- Undefind :未定义指令终止模式,用于支持通过软件仿真硬件的协处理器
- System :系统模式,用于运行特权级的操作系统任务(armv4以上版本才具有)
- Monitor(Cortex-A特有模式) : 是为了安全而扩展出的用于执行安全监控代码的模式;也是一种特权模式
注意:user是普通模式,其他六种是特权模式,而除了usr和sys模式以外的五种模式是异常模式
1.5.3、寄存器
寄存器本质就是CPU的存储介质。
- r0-r7 :不分组寄存器,在所有的运行模式下都使用同一个物理寄存器,它们未被系统用作特殊的用途。
- r8-r12 :当使用FIQ(快速中断模式)时访问寄存器R8_fiq-R12_fiq,当使用除FIQ模式以外的其他模式时,访问寄存器R8~R12
- r13(sp) :堆栈指针,指向该运行模式的栈空间,每个模式下都有自己私有的
- r14(lr) :子程序链接寄存器,有两个特殊功能,一种是每一种模式下都可以用于保存函数的返回地址,另外就是异常处理后的返回地址,如中断。
- r15(pc) :用作程序计数器(PC)对应一个物理寄存器,对于ARM指令集而言,PC总是指向当前指令的下两条指令的地址,即PC的值为当前指令的地址值加8个字节程序状态寄存器。PC指向正被取指的指令,而非正在执行的指令.
- cpsr:状态寄存器
- spsr:除usr、sys外,对应用于异常保护的CPSR的备份,异常时,保存CPSR值,异常退出时,将该值恢复到CPSR,以保证程序的正常运行,每一中异常运行模式(除usr和sys)有各自的物理寄存器。
(1)User 通用寄存器
(r0~r15) 通用寄存器
(2)三个特殊的寄存器
(sp)stack point “栈指针” //存储的是栈顶地址
(lr)Link Register "连接寄存器 " //存储的是返回地址
(pc))program counter “程序计数器” //存储的是程序下一条地址
(3)PSR
Runtime Status Register “运行状态寄存器”
(cpsr) 程序状态寄存器
(spsr) 对程序状态寄存器备份
程序状态寄存器 (cpsr)
1.5.4、异常
不是出问题了,而是芯片内部的调度。
异常本文章详解:六、ARM异常及中断处理
1.5.5、大小端
(1)大端模式(big endian)
高字节对应高地址(大端模式)
(2)小端模式(little endian)
高字节对应低地址(小端模式)
二、ARM开发环境搭建
2.1、Linux环境与安装
2.1.1、vm
2.1.2、Ubuntu
2.1.3、共享文件夹配置
2.1.4、Linux网络配置
可正常上网、可忽略跳过!
问题:
突然ubuntu 网络不能用, 只能 ping 通本地 127.0.0.1, 并且通过命令 ip addr 查询无地址信息:
解决:
Ubuntu20配置值静态ip时需要修改/etc/netplan下面 1-network-manager-all.yaml这个文件
命令:sudo vi /etc/netplan/01-network-manager-all.yaml
将下面内容复制到文件中, 其中参数要根据自己情况修改:
network:
version: 2
ethernets:
ens33:
addresses: [192.168.179.100/24]
dhcp4: no
dhcp6: no
gateway4: 192.168.179.2
nameservers:
addresses: [192.168.179.1,114.114.114.114]
配置完成后,输入命令:sudo netplan apply
ping网关:ping www.baidu.com
2.2、ARM环境搭建及测试
2.2.1、安装虚拟开发板(qemu)
(1)Linux下在线安装qemu
安装命令:sudo apt-get install qemu qemu-system
Ubuntu低版本可能不能上网下载,建议安装18以上可上网版本。
(2)查看安装的版本
查看命令:qemu-system-arm -version
显示版本QEMU emulator version 2.5.0 (Debian 1:2.5+dfsg-5ubuntu10.15), Copyright © 2003-2019 Fabrice Bellard 表示安装成功!
2.2.2、安装交叉编译器
(1)安装交叉编译器_保存并解压
交叉编译器文件下载:gcc-4.6.4.tar.xz
可放在Linux下任意目录或者用户目录(家目录)下。
解压命令:tar -xvf gcc-4.6.4.tar.xz
(2)解压完成后进入~/gcc-4.6.4/bin目录
下面看到 arm-linux-gcc 命令。
进入命令:cd ~/gcc-4.6.4/bin
获取绝对路径:pwd
/home/hqyj/gcc-4.6.4/bin
(3)修改配置文件~/.bashrc,添加环境变量
1)vi ~/.bashrc,在最后一行添加路径消息:
export PATH=$PATH:/home/hqyj/gcc-4.6.4/bin
2)添加虚拟开发板信息:
后续如(显示以下)错误及解决。添加:export LC_ALL=C
3)保存退出后:wq,执行 source ~/.bashrc
4)关闭所有终端
2.2.3、测试代码
新建一个.S文件:vim start.S
添加以下内容:
.global _start
_start:
mov r0, #3
mov r1, #5
nop
nop
保存并退出:wq
2.2.4、程序编译
编译:arm-linux-gcc start.S -o start.o -c -g
链接:arm-linux-ld start.o -o start.elf -Ttext=0x0
2.2.5、启动文件(模拟一个设备)
在这个环境下虚拟模拟一个板子(开发板)
qemu-system-arm -machine vexpress-a9 -m 256M -serial stdio -kernel start.elf -S -s
qemu模拟的设备 不要选择vexpress-a9, 选择 xilinx-zynq-a9 可以正常运行。
qemu-system-arm -machine xilinx-zynq-a9 -m 256M -serial stdio -kernel start.elf -S -s
2.2.6、调试
重新打开一个终端(快捷键:Ctrl+alt+t);cd进入start.S 创建这个文件目录下。
执行下面命令:arm-none-linux-gnueabi-gdb start.elf
进入GDB后,执行:
(gdb) target remote 127.0.0.1:1234
(gdb)l
命令解释:
l: 查看代码
s: 单步调试
b: 设置断点
c: 继续运行
p: 显示变量值 p $r1 p/x val 十六进制打印
x: 显示内存值 x/4 0x10 显示内存地址0x10 开始的4块连续地址
quit: 退出
如(显示以下)错误及解决。添加:export LC_ALL=C
2.2.7、安装32位支持库
ARM是32的,Ubuntu高版本64位的,需要安装32位支持库
Ubuntu18.04 及以上版本 64 位系统 安装32位支持库
通过下面两条命令进行安装即可:
sudo apt-get install lib32ncurses5
sudo apt-get install lib32z1
2.3、ARM开发板使用
本节暂可跳过 ---> (6、GPIO编程) 阶段可参考。
2.3.1、开发板连接串口
开发板接上电源,开发板串口接上电脑USB接口。
2.2.2、测试程序
2.3.2、打开超级终端
2.3.3、启动开发板
选择开发板启动模式:(我这里选择的是TF/SD模式下启动)
基于FS4412启动方式:
EMMC: 0111
SD卡:1000
打开开发板电源开关,迅速按电脑ENTER(任意键)键,否则会进入系统。
加载地址:loadb 41000000
2.3.4、打开.bin程序文件
鼠标右键 ---> 发送文件
2.3.5、启动开发板
启动开发板:go 41000000
三、ARM指令
3.1、为什么要学习汇编
理解机器执行过程(比如向量表,异常跳转等)
性能要求较高的时候,使用汇编,或者混合编程
逆向工程:查找异常,外挂,破解等
总结:汇编学习后,可以向上理解软件,向下感知硬件,对理解系统有很大好处。
3.2、ARM汇编指令
3.2.1、数据处理指令
(1)传输数据指令:MOV
【MOV指令】:它的传送指令只能是把一个寄存器的值(要能用立即数表示)赋给另一个寄存器,或者将一个常量赋给寄存器,将后边的量赋给前边的量。
MOV R1,R0 ;将寄存器R0的值传送到寄存器R1
MOV PC,R14 ;将寄存器R14的值传送到PC,常用于子程序返回
MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1(即乘8)
MOVS PC, R14 ;将寄存器R14的值传送到PC中,返回到调用代码并恢复标志位
MVN R0,#0 ;将立即数0取反传送到寄存器R0中,完成后R0=-1(有符号位取反)
(2)算术运算指令:ADD、SUB、ADC、SBC、MUL
1、【加法指令】:ADD
ADD R0,R1,R2 ; R0 = R1 + R2
ADD R0,R1,#256 ; R0 = R1 + 256
ADD R0,R2,R3,LSL#1 ; R0 = R2 + (R3 << 1)
2、【带进位的加法指令】:ADC
ADDS R0,R4,R8 ; 加低端的字,R0=R4+R8
ADCS R1,R5,R9 ; 加第二个字,带进位,R1=R5+R9
ADCS R2,R6,R10 ; 加第三个字,带进位,R2=R6+R10
ADC R3,R7,R11 ; 加第四个字,带进位,R3=R7+R11
3、【减法指令】:SUB
SUB R0,R1,R2 ; R0 = R1 - R2
SUB R0,R1,#256 ; R0 = R1 - 256
SUB R0,R2,R3,LSL#1 ; R0 = R2 - (R3 << 1)
4、【带借位减法指令】:SBC
SUBS R0,R1,R2 ;R0 = R1 - R2 - !C,并根据结果设置CPSR的进位标志位
5、【会改变条件位】:ADDS
ADDS r0,r1,r2 ;r0 = r1+r2 && (cpsr)v c
6、【带借位的减法】:SBC
7、【逆向减法】:RSB
RSB r1,r2,r3 ;r1 = r3 - r2
8、【乘法指令】:MUL
mul r2, r0, r1 ;r2 = r0 * r1
(3)位操作指令: AND、ORR、EOR、BIC
1、【逻辑与指令】:AND
AND R0,R0,#3 ; 该指令保持R0的0、1位,其余位清零。
2、【逻辑或指令】:ORR
ORR R0,R0,#3 ; 该指令设置R0的0、1位,其余位保持不变。
3、【逻辑异或指令】:EOR
EOR R0,R0,#3 ; 该指令反转R0的0、1位,其余位保持不变。
4、【位清零指令】:BIC
BIC R0,R0,#%1011 ; 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变。
(4)跳转指令: B、BL
1、【B指令】 ;类似goto
B Label ;程序无条件跳转到标号Label处执行
CMP R1,#0 ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label
2、【BL指令】
BL Label ;当程序无条件跳转到标号Label处执行时,同时将当前的PC值保存到R14中
(5)比较指令: CMP
1、【直接比较指令】:CMP
CMP R1,R0 ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位
2、【负数比较指令】:CMN
CMN R1,R0 ;将寄存器R1的值与寄存器R0的值相加,并根据结果设置CPSR的标志位
CMN R1,#100 ;将寄存器R1的值与立即数100相加,并根据结果设置CPSR的标志位
if((r0>10) || (r1<4))
r2 = 6;
cmp r0, #10
movgt r2, #6
cmple r1, #4
movlt r2, #6
if((r0>10) && (r1<4))
r2 = 6;
cmp r0, #10
mov r3, #4
cmpgt r3, r1
movgt r2, #6
(6)汇编加载/存储指令: LDR、STR
1、【LDR指令】
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0,[R1,R2] ! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0,[R1,#8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0,[R1,R2,LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR R0,[R1],R2,LSL#2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
2、【STR指令】
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
(7)多寄存器语句传输指令: LDM、STM
LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
IA 每次传送后地址加1;
IB 每次传送前地址加1;
DA 每次传送后地址减1;
DB 每次传送前地址减1;
FD 满递减堆栈;
ED 空递减堆栈;
FA 满递增堆栈;
EA 空递增堆栈;
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。
同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。
条件码:
重点掌握:相等、不相等、带符号大于或等于、带符号小于、带符号大于、带符号小于或等于。
int i = 0, sum = 0;
for(int i=0; i<100; i++)
{
sum += i;
}
.global _start
_start:
mov r0, #1 /*i: r0*/
mov r1, #0 @ sum: r1
loop:
add r1, r1, r0
add r0, r0, #1
cmp r0, #100
blt loop
nop
nop
3.2.2、立即数
合法立即数与非法立即数
ARM指令都是32位,除了指令标记和操作标 记外,本身只能附带很少位数的立即数。 因此立即数有合法和非法之分。
合法立即数:经过任意位数的移位后非零 部分可以用8位表
示的即为合法立即数
合法立即数: 0x000000ff 0x00ff0000 0xf000000f
非法立即数: 0x000001ff
高4位:循环右移2位/次
低8位:基数
判断是否是立即数的三步骤:
判断是否是立即数:
1.将数据转换为二进制
2.一次性去掉连续的偶数个0,并且只能去一次
3.剩下的数据<=8位,则表明该数据是立即数
0x123; 0000 0000 0000 0000 0000 0001 0010 0011; 不是
0x128; 0000 0000 0000 0000 0000 0001 0010 1000; 是
0x38100; 0000 0000 0000 0011 1000 0001 0000 0000; 不是
0x80800; 0000 0000 0000 1000 0000 1000 0000 0000; 不是
0x70800; 0000 0000 0000 0111 0000 1000 0000 0000; 不是
0x80000008; 1000 0000 0000 0000 0000 0000 0000 1000; 是
0xf00000f; 0000 1111 0000 0000 0000 0000 0000 1111; 不是
3.2.3、寻址
(1)什么是寻址
数据都存在存储器中,寻址简单地说就是找到存储数据或指令的地址。
存储器有很多存储单元,用于存储数据。
寻址就是读取数据所在储存装置中对应地址编号中存储的内容。
(2)什么是寻址方式
通常是指某一个CPU指令系统中规定的寻找操作数所在地址的方式,或者说通过什么的方式找到操作数。
寻址方式的方便与快捷是衡量CPU性能的一个重要方面
(3)寻址方式
1)立即数寻址
也叫立即寻址,操作数本身就在指令中给出。
mov r0, #254 //将254写入r0寄存器
2)寄存器寻址
寄存器寻址就是直接将寄存器中的数值作为操作数
mov r1, r0 //将r0寄存器中的值写到r0
3)寄存器移位寻址
寄存器移位寻址的操作由寄存器中的数值相应移位而得到;
移位的方式在指令中以助记符的形式给出,移位数可用立即数或寄存器给出。
mov r0, r1, lsl #3 //将r1的值逻辑左移3位后写入r0
mov r0, r1, ror r2 //将r1的值循环右移r2中的值对应位后,写入r0
4)寄存器间接寻址
寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身放在存储器中
ldr r1, [r2] //类似于指针,r2中存操作数的地址, 把r2地址中的数据加载到r1中
str r1, [r2]
mov r0, #0X54000032
ldr r1, [r0] //将地址为0X54000032的数据写入r1寄存器中
5)基址变址寻址
将基址寄存器的内容与指令中给出的偏移量相加,得到一个有效的操作数的地址。
通常用于访问连续的地址空间。
ldr r1, [r2, #4] //r2为基址,r2里面的地址加4,这个地址存的值读到寄存器中
6)多寄存器寻址
批量寄存器寻址就是使用一个大括号{}包含多个寄存器
多寄存器寻址可实现一条指令完成多个寄存器值的传送,最多可以一次传送16个通用寄存器的值,连续的寄存器“-”连接,否则用逗号分割。
ldmia r0, {r1, r2, r3, r4} //将r1,r2,r3,r4中的数据依次放入R0指向的内存地址,r0+4指向的内存地址...
ldmia r1, {r2-r7, r12} //r1中的8个地址读到r2-r7和r12中(类似于数值中的8个元素)
7)相对寻址
已PC的当前值为基地址,指令中的地址标号为偏移量,两者之和得到操作数的地址。
b flag
flag:
8)堆栈寻址
堆栈寻址是一种数据结构,按先进先出的方式操作,R13(SP)寄存器指示当前的栈顶位置
stmfd sp!, {r0-r12, lr} //将寄存器列表中的寄存器(R2到R7,lr)存入堆栈
3.2.4、跳转(分支)指令
b & bl & bx
- b 直接跳转(就没打开算返回)
- bl branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用
- bx跳转同时切换到ARM模式,一般用于异常处理的跳转
3.2.5、软中断指令
swi(software interrupt)
软中断指令用来实现操作系统中系统调用
3.3、协处理器指令
协处理器是真实存在的硬件设备,可以协助cpu处理一些数据,如cache
操作协处理器需要使用专门的指令
3.3.1、协处理器cp15操作指令
mcr & mrc
- mrc用于读取CP15中的寄存器
- mcr用于写入CP15中的寄存器
3.3.2、什么是协处理器
SoC内部另一处理核心,协助主CPU实现某 些功能,被主CPU调用执行一定任务。
ARM设计上支持多达16个协处理器,但是一 般SoC只实现其中的CP15.(cp: coprocessor)
协处理器和MMU、 cache、 TLB等处理有关, 功能上和操作系统的虚拟地址映射、 cache 管理等有关。
3.3.3、MRC & MCR的使用方法
mcr{} p15, <opcode_1>, , , , {<opcode_2>}
- opcode_1: 对于cp15永远为0
- Rd: ARM 的普通寄存器
- Crn: cp15 的寄存器,合法值是c0~c15
- Crm: cp15 的寄存器,一般均设为c0
- opcode_2: 一般省略或为0
ARM协处理器CP15寄存器详解 参考链接:https://www.cnblogs.com/lifexy/p/7203786.html
3.4、ARM汇编伪指令
3.4.1、伪指令的意义
伪指令不是指令,伪指令和指令的根本区 别是经过编译后会不会生成机器码。
伪指令的意义在于指导编译过程。
伪指令是和具体的编译器相关的,我们使 用gnu工具链,因此学习gnu环境下的汇编 伪指令。
buf: .word 0x10 0x20 ---> int buf[2]={0x10 0x20};
buf1: .byte 0x20 0x33 0x55 ---> char buf1[]={0x20,0x33,0x55};
data: .space 128*2 ---> malloc(128*2);
ldr r0, =0x100 ---> r0 = 0x100
mov r0, #0x100
ldr r0, =0xffff
ldr r0, =buf @伪指令 把buf的值赋给r0
ldr r1, [r0] @指令 以r0值作为地址,取内容给r1
ldr r1, [r0,#4]
ldr r0, buf @指令: 以buf的值作为地址,取地址里面的内容,给r0
@ *buf
3.4.2、常用gnu伪指令
- global _start @ 给_start外部链 接属性
- section .text @ 指定当前段为代 码段
- ascii .byte .short .long .word @ 类型C语言定义变量
- quad .float .string @ 定义数据
- align 4 @ 以16字节对齐
- balignl 16 0xabcdefgh @ 16字节对齐
- equ @ 类似C语言宏定义
IRQ_STACK_START:
.word 0x0badc0de
等价于 unsigned int IRQ_STACK_START = 0x0badc0de;
.align 4 @ 16字节对齐
.align 2 @ 4字节对齐
.balignl 16, 0xdeadbeef @ 对齐 + 填充
b表示位填充;align表示要对齐;l表示long,以4字节为单位填充;16表示16字节对齐;0xdeadbeef是用来填充的原料。
0x00000008: .balignl 16, 0xdeadbeef
0x0000000c 0xdeadbeef
0x00000010: 下一条指令
3.4.3、最重要的几个伪指令
- ldr 大范围的地址加载指令
- adr 小范围的地址加载指令
- adrl 中等范围的地址加载指令
- nop 空操作
ARM中有一个ldr指令,还有一个ldr伪指令,一般都使用ldr伪指令而不用ldr指令
ldr指令: ldr r0, #0xff
伪指令: ldr r0, =0xfffl @涉及到合法/非法立即数,涉及到ARM文字池
3.4.4、adr与ldr
- adr编译时会被1条sub或add指令替代,而 ldr编译时会被一条mov指令替代或者文字 池方式处理;
- adr总是以PC为基准来表示地址,因此指令 本身和运行地址有关,可以用来检测程序 当前的运行地址在哪里。
- ldr加载的地址和链接时给定的地址有关, 由链接脚本决定。
adr和ldr的差别:
ldr加载的地址在链接时确定,而adr加载的地址在运行时确定;
所以我们可以通过adr和ldr加载的地址比较来判断当前程序是否在链接时指定的地址运行。
3.5、寄存器访问指令
3.5.1、为什么需要多寄存器访问指令
- ldr/str每周期只能访问4字节内存,如果 需要批量读取、写入内存时太慢,解决方 案是stm/ldm。
- ldm (load register mutiple)
- stm(store register mutiple)
3.5.2、load/store指令 str ldr
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
str r0, [r1, #4]!
LDR r0,[r1,#8] // r0 = *((int *)r1 + 8)
LDR r0,[r1,#8]! // r0 = *((int *)r1 + 8) r1 += 8
后面的!表示要更新寄存器的值
ldr r0,=0x11223344 //伪指令,加载32位常数 (避免立即数合法性)
3.5.3、stmxx/ldmxx指令
stm--store much,多数据存储,将寄存器的值存到地址上
ldm--load much,多数据加载,将地址上的值加载到寄存器上
备注:xx有下面8种类型
(1)IA:(Increase After) 每次传送后地址加4,其中的寄存器从左到右执行
eg: stmia R0,{R1,LR} //先存R1,将r0里面的地址加上4再存LR
eg: ldmia r0,{r0,r1,r2} //将r0地址中的值逐个写入到寄存器r0 R1 R2中
(2)IB:(Increase Before)每次传送前地址加4,同上
(3)DA:(Decrease After)每次传送后地址减4,其中的寄存器从右到左执行
r0:0x108 r1 --> 0x104 r2 --> 0x108
stmda r0,{r1,r2}
(4)DB:(Decrease Before)每次传送前地址减4,同上
r0:0x108 r1 --> 0x100 r2 --> 0x104
stmdb r0,{r1,r2}
(5)FD: 满递减堆栈(每次传送前地址减4)(LDMFD--LDMIA; STMFD--STMDB)
(6)FA: 满递增堆栈(每次传送后地址减4)(LDMFA--LDMDA; STMFA--STMIB)
(7)ED: 空递减堆栈(每次传送前地址加4)(LDMED--LDMIB; STMED--STMDA)
(8)E7A: 空递增堆栈(每次传送后地址加4)(LDMEA--LDMDB; STMEA--STMIA)
3.5.4、四种栈
空栈:栈指针指向空,每次存入时可以 直接存入然后栈指针移动一格;而取出时 需要先移动一格才能取出。
满栈:栈指针指向栈中最后一格数据,每 次存入时需要先移动栈指针一格再存入; 取出时可以直接取出,然后再移动栈指针。
增栈:栈指针移动时向地址增加的方向移动的栈。
减栈:栈指针移动时向地址减小的方向移动的栈。
一般出栈/入栈
ldmfd sp!,{r0,r1,r2} //将sp中值逐个写入到寄存器r0 R1 R2中 出栈
stmfd sp!,{r0,r1,r2} //将寄存器r0 R1 R2中逐个写入到sp中 入栈
ldmfd sp!,{r0-r12,pc}^ --- 出栈 恢复现场
stmfd sp!,{r0-r12,lr} --- 入栈 保护现场
ldmfd sp!,{r0-r7,r9,r12}
SP后面的!表示要更新sp的值 sp -= n
^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。
3.5.5、!的作用
ldmia r0, {r2 - r3}
ldmia r0!, {r2 - r3}
感叹号的作用 :就是r0的值在ldm过程中发生 的增加或者减少最后写回到r0去,也就是 说ldm时会改变r0的值。
3.5.6、^的作用
ldmfd sp!, {r0 - r6, pc}
ldmfd sp!, {r0 - r6, pc}^
^的作用 :在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式 返回。
3.5.7、总结
批量读取或写入内存时要用ldm/stm指令
谨记:操作栈时使用相同的后缀就不会出错,不管是满栈还是空栈、增栈还是减栈
buf: .word 0x1, 0x2, 0x3, 0x4
.global _start
_start
ldr r0, =buf
ldr r1, [r0, #8]
ldr r2, [r0, #12]
str r1, [r0, #12]
str r2, [r0, #8]
nop
nop
四、ARM异常及中断处理
4.1、异常
正常工作之外的流程都叫异常
异常会打断正在执行的工作,并且一般我 们希望异常处理完成后继续回来执行原来 的工作
中断是异常的一种
①、复位异常 reset
从头开始执行,所有数据都刷新了
②、未定义指令异常 undefined instruction
执行未定义的指令,执行时产生异常
如:user模式下执行msr指令
③、软中断异常 swi
指令本身产生的异常,执行时产生
④、预取指异常 prefetch abort
取值发生了异常,不会影响正在执行的指令,执行完后处理异常
⑤、数据异常 data abort
执行指令时用到的数据有问题,处理完异常后,cpu认为需要重新执行这条指令
如: ldr r0,[r1] 可能r1这个地址不存在
⑥、IRQ异常 irq
中断,在执行指令时来了异常,会在语句执行完后再去处理异常
⑦、FIQ异常 fiq
异常返回时地址时恢复:
4.1.1、模式和异常的关系
异常与工作模式的关系:
①、复位异常------------------>SVC
②、未定义指令异常------------>Undefined
③、软中断异常---------------->SVC
④、预取指异常---------------->Abort
⑤、数据异常------------------>Abort
⑥、IRQ异常------------------->IRQ
⑦、FIQ异常------------------->FIQ
异常优先级:
异常在当前指令执行完成之后才被响应,
多个异常可以在同一时间产生,
异常指定了优先级和固定的服务顺序:(优先级从高到低)
①、复位异常 reset
②、数据异常 data abort
③、FIQ异常 fiq
④、IRQ异常 irq
⑤、预取指异常 prefetch abort
⑥、软中断异常 swi
⑦、未定义指令异常 undefined instruction
4.1.2、从user到异常再回到user的流程
4.1.3、异常向量表
所有的CPU都有异常向量表,这是CPU设计 时就设定好的,是硬件决定的。
当异常发生时, CPU会自动动作 (PC跳转到 异常向量处处理异常,有时伴有一些辅助 动作)
异常向量表是硬件向软件提供的处理异常 的支持。
6.1.4、ARM的异常处理机制
当异常产生时, ARM core:
– 拷贝 CPSR 到 SPSR_
– 设置适当的 CPSR 位:
• 改变处理器状态进入 ARM 态
• 改变处理器模式进入相应的异常模式
• 设置中断禁止位禁止相应中断 (如果需要)
– 保存返回地址到 LR_
– 设置 PC 为相应的异常向量
返回时, 异常处理需要:
– 从 SPSR_恢复CPSR
– 从LR_恢复PC
– Note:这些操作只能在 ARM 态执行.
4.1.4、异常处理流程
4.1.5、模拟一个异常
.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_handler
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
reset:
/* 设置cpu模式为SVC */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/* 设置异常向量表的起始地址 */
ldr r0, =0x0
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/* 设置栈空间 */
ldr sp, stacktop
sub r1, sp, #64
/* 设置cpu模式为user */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd0
msr cpsr,r0
mov sp ,r1
/* 跳转到应用main */
bl _main
mov r4,r0
_main:
stmfd sp!,{lr}
mov r5,#3
mov r6,#4
bl myadd
nop
ldmfd sp!,{pc}
loop:
b loop
myadd:
stmfd sp!,{lr}
swi 0
nop
nop
ldmfd sp!,{pc}
swi_handler:
stmfd sp!,{lr}
bl sysadd
ldmfd sp!,{pc}^
sysadd:
stmfd sp!,{lr}
add r0, r5, r6
ldmfd sp!,{pc}
stack: .space 64*2
stacktop: .word stack+64*2
4.2、中断
ARM有两级外部中断FIQ、IRQ
可是大多数的基于ARM的系统有>2个的中断源!
Note:通常中断处理程序总是应该包含清除中断源的代码。
4.2.1、FIQ VS IRQ
FIQ和IRQ提供了非常基本的优先级级别
FIQS有高于IRQs的优先级,表现在下面2个方面
- 当多个中断产生时,CPU优先处理FIQ
- 处理FIQ时禁止IRQs
FIQS的设计使中断响应尽可能的快
- FIQ向量位于异常向量表的最末
- FIQ模式有5个额外的私有寄存器(r8r12)
五、ARM汇编程序设计
C语言main函数
extern int myadd(int a, int b) /*声明myadd*/
void main(void)
int a=3
int b= 4
int d= 0
c= myadd (a, b)
c= 0xffffff
while(1);
自己写一个Makefile
all:
arm-linux-gcc test.S -o test.o -c -g
arm-linux-gcc main.c -o main.o -c -g
arm-linux-ld test.o main.o -o start.elf -Ttest=0x0
clean:
rm *.o *.elf
zs:
@echo "zs ...."
编译
声明头文件
.globl _start
.globl myadd
_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_handler
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
reset:
/* 设置cpu模式为SVC */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/* 设置异常向量表的起始地址 */
ldr r0, =0x0
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/* 设置栈空间 */
ldr sp, stacktop
sub r1, sp, #64
/* 设置cpu模式为user */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd0
msr cpsr,r0
mov sp ,r1
/* 跳转到应用main */
bl _main
mov r4,r0
_main:
stmfd sp!,{lr}
mov r5,#3
mov r6,#4
bl myadd
nop
ldmfd sp!,{pc}
loop:
b loop
myadd:
stmfd sp!,{lr}
swi 0
nop
nop
ldmfd sp!,{pc}
swi_handler:
stmfd sp!,{lr}
bl sysadd
ldmfd sp!,{pc}^
sysadd:
stmfd sp!,{lr}
//add r0, r5, r6
add r0, r0, r1 //C语言函数传参
ldmfd sp!,{pc}
stack: .space 64*2
stacktop: .word stack+64*2
编译
C语言函数传参
main.c
extern int myadd(int a, int b);
void main(void)
{
int a = 3;
int b = 4;
int c = 0;
c = myadd(a,b);
c = 0xffffff;
while(1);
}
test.S
.globl _start
.globl myadd
_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_handler
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
reset:
/* 设置cpu模式为SVC */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/* 设置异常向量表的起始地址 */
ldr r0, =0x0
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/* 设置栈空间 */
ldr sp, stacktop
sub r1, sp, #64
/* 设置cpu模式为user */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd0
msr cpsr,r0
mov sp ,r1
/* 跳转到应用main */
bl main
mov r4,r0
myadd:
stmfd sp!,{lr}
swi 0
nop
nop
mov r0, #100
ldmfd sp!,{pc}
swi_handler:
stmfd sp!,{lr}
bl sysadd
ldmfd sp!,{pc}^
sysadd:
stmfd sp!,{lr}
add r0, r0, r1
ldmfd sp!,{pc}
stack: .space 64*2
stacktop: .word stack+64*2
Makefile
all:
arm-linux-gcc test.S -o test.o -c -g
arm-linux-gcc main.c -o main.o -c -g
arm-linux-ld test.o main.o -o start.elf -Ttest.lds
clean:
rm *.o *.elf
zs:
@echo "zs ...."
test.lds
ENTRY(_start)
SECTIONS{
. = 0x0;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
gdbS
#!/bin/bash
qemu-system-arm -machine vexpress-a9 -m 256M -serial stdio -kernel start.elf -S -s
ARM汇编 swi 源文件下载:swi.rar
六、GPIO编程
GPIO(general purpose i/o ports)意思为通用输入/输出端口,通俗的说就是一些引脚。
我们可以通过它们输出高低电平 或 读入引脚的状态。
- 通用IO脚 仅仅只能高低电平输入输出
- 专用IO脚 只提供给某一个设备使用的
- 复用IO脚 提供给多个管脚使用 / 通用IO使用
6.1、LED
需求:点亮LED灯
6.1.1、查看原理图
LED2 ---> CHG_COK
LED3 ---> CHG_FLT
LED4 ---> XvVSYNC_LDI
LED5 ---> XvSYS_OE
根据三极管(NPN型),这里对应原理图点亮LED需要输出高电平(1),LED点亮。
扩展板原理图下载:FS4412-DevBoard-V5.pdf
例:
LED5 ---> XvSYS_OE;通过查找原理图找到对应GPIOGPX3_5.
LED2 ---> GPX2_7
LED3 ---> GPX2_0
LED4 ---> GPX3_4
LED5 ---> GPX3_5
核心板原理图下载:FS4412_CoreBoard_V2.pdf
6.1.2、查看数据手册
(1)打开数据手册对应章节
6 General Purpose Input/Output (GPIO) Control 找到对应GPIO端口
Exynos4412数据手册下载:SEC_Exynos4412_Users Manual_Ver.1.00.00.pdf
(2)找到对应GPIO地址;第几组;输出模式;数据位对应端口号。
例子说明:LED5 ---> GPX3_7
/*LED2*/
//GPX2CON 地址:0x11000C40 [31:28] 0x1 = Output
//GPX2DAT 地址:0x11000C44 [7:0] 7 1
#define GPX2CON *(volatile unsigned int *)0x11000C40
#define GPX2DAT *(volatile unsigned int *)0x11000C44
/*LED3*/
//GPX1CON 地址:0x11000C20 [3:0] 0x1 = Output
//GPX1DAT 地址:0x11000C24 [7:0] 0 1
#define GPX1CON *(volatile unsigned int *)0x11000C20
#define GPX1DAT *(volatile unsigned int *)0x11000C24
/*LED4*/
//GPF3CON 地址:0x114001E0 [19:16] 0x1 = Output
//GPF3DAT 地址:0x114001E4 [5:0] 4 1
#define GPF3CON *(volatile unsigned int *)0x114001E0
#define GPF3DAT *(volatile unsigned int *)0x114001E4
/*LED 5*/
//GPF3CON 地址: 0x114001E0 [23:20] 0x1 = Output
//GPF3DAT 地址: 0x114001E4 [5:0] 5 1
#define GPF3CON *(volatile unsigned int *)0x114001E0
#define GPF3DAT *(volatile unsigned int *)0x114001E4
芯片手册操作步骤:
(1)、通过目录找到控制器
(2)、简单流量描述
(3)、找全对应的寄存器
(4)、依据功能进行筛选
1)全局控制配置寄存器
2)和功能相关的要
3)辅助寄存器(查看状态的、查看设备型号、设置延时、设置读写异常状态的)
6.1.3、代码实现
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl _main
_main:
ldr r0, =0x114001E0
ldr r1, [r0]
bic r1, r1, #0x00f00000
orr r1, r1, #0x00100000
str r1, [r0]
ldr r0, =0x114001E4
ldr r1, [r0]
orr r1, r1, #0x00000020
str r1, [r0]
ldr r3, =0xffffffff
cmp r3, #0
sub r3, r3, #1
ldr r0, =0x114001E4
ldr r1, [r0]
orr r1, r1, #0x00000020
str r1, [r0]
nop
nop
nop
loop:
b loop
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c -g
arm-linux-ld test.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
main.c
/*LED2*/
//GPX2CON 地址:0x11000C40 [31:28] 0x1 = Output
//GPX2DAT 地址:0x11000C44 [7:0] 7 1
#define GPX2CON *(volatile unsigned int *)0x11000C40
#define GPX2DAT *(volatile unsigned int *)0x11000C44
/*LED3*/
//GPX1CON 地址:0x11000C20 [3:0] 0x1 = Output
//GPX1DAT 地址:0x11000C24 [7:0] 0 1
#define GPX1CON *(volatile unsigned int *)0x11000C20
#define GPX1DAT *(volatile unsigned int *)0x11000C24
/*LED4*/
//GPF3CON 地址:0x114001E0 [19:16] 0x1 = Output
//GPF3DAT 地址:0x114001E4 [5:0] 4 1
#define GPF3CON *(volatile unsigned int *)0x114001E0
#define GPF3DAT *(volatile unsigned int *)0x114001E4
/*LED 5*/
//GPF3CON 地址: 0x114001E0 [23:20] 0x1 = Output
//GPF3DAT 地址: 0x114001E4 [5:0] 5 1
#define GPF3CON *(volatile unsigned int *)0x114001E0
#define GPF3DAT *(volatile unsigned int *)0x114001E4
/*LED 5*/
void led5_init()
{
GPF3CON &= ~(0xf << 20);
GPF3CON |= (0x1 << 20);
}
void led5_on()
{
GPF3DAT |= (0x1 << 5) ;
}
void led5_off()
{
GPF3DAT &= ~(0x1 << 5) ;
}
/*LED 4*/
void led4_init()
{
GPF3CON &= ~(0xf << 16);
GPF3CON |= (0x1 << 16);
}
void led4_on()
{
GPF3DAT |= (0x1 << 4) ;
}
void led4_off()
{
GPF3DAT &= ~(0x1 << 4) ;
}
/*LED 3*/
void led3_init()
{
GPX1CON &= ~(0xf << 0);
GPX1CON |= (0x1 << 0);
}
void led3_on()
{
GPX1DAT |= (0x1 << 0) ;
}
void led3_off()
{
GPX1DAT &= ~(0x1 << 0) ;
}
/*LED 2*/
void led2_init()
{
GPX2CON &= ~(0xf << 28);
GPX2CON |= (0x1 << 28);
}
void led2_on()
{
GPX2DAT |= (0x1 << 7) ;
}
void led2_off()
{
GPX2DAT &= ~(0x1 << 7) ;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
int main()
{
led5_init();
led4_init();
led3_init();
led2_init();
while(1)
{
led5_on();
led4_on();
led3_on();
led2_on();
delay_ms(1000);
led5_off();
led4_off();
led3_off();
led2_off();
delay_ms(1000);
}
return 0;
}
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
main.c
#include "led.h"
void delay_ms(int time);
int main()
{
led5_init();
led4_init();
led3_init();
led2_init();
while(1)
{
led5_on();
led4_on();
led3_on();
led2_on();
delay_ms(1000);
led5_off();
led4_off();
led3_off();
led2_off();
delay_ms(1000);
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
led.c
#define GPF3CON *(volatile unsigned int *)0x114001E0
#define GPF3DAT *(volatile unsigned int *)0x114001E4
#define GPX1CON *(volatile unsigned int *)0x11000C20
#define GPX1DAT *(volatile unsigned int *)0x11000C24
#define GPX2CON *(volatile unsigned int *)0x11000C40
#define GPX2DAT *(volatile unsigned int *)0x11000C44
void led5_init()
{
GPF3CON &= ~(0xf << 20); //对应位清零
GPF3CON |= (0x1 << 20); //设置对应位
//GPF3CON = GPF3CON & (~(0xf << 20)) | (0x1 << 20);
}
void led5_on()
{
GPF3DAT |= (0x1 << 5) ; //设置对应位
}
void led5_off()
{
GPF3DAT &= ~(0x1 << 5) ; //清零对应位
}
void led4_init()
{
GPF3CON &= ~(0xf << 16); //对应位清零
GPF3CON |= (0x1 << 16); //设置对应位
}
void led4_on()
{
GPF3DAT |= (0x1 << 4) ; //设置对应位
}
void led4_off()
{
GPF3DAT &= ~(0x1 << 4) ; //清零对应位
}
void led3_init()
{
GPX1CON &= ~(0xf << 0); //对应位清零
GPX1CON |= (0x1 << 0); //设置对应位
}
void led3_on()
{
GPX1DAT |= (0x1 << 0) ; //设置对应位
}
void led3_off()
{
GPX1DAT &= ~(0x1 << 0) ; //清零对应位
}
void led2_init()
{
GPX2CON &= ~(0xf << 28); //对应位清零
GPX2CON |= (0x1 << 28); //设置对应位
}
void led2_on()
{
GPX2DAT |= (0x1 << 7) ; //设置对应位
}
void led2_off()
{
GPX2DAT &= ~(0x1 << 7) ; //清零对应位
}
led.h
#ifndef __LED_H
#define __LED_H
void led5_init();
void led5_on();
void led5_off();
void led4_init();
void led4_on();
void led4_off();
void led3_init();
void led3_on();
void led3_off();
void led2_init();
void led2_on();
void led2_off();
#endif
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc led.c -o led.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
6.1.4、编译运行
6.1.5、程序下载
详情可参考本文章:2.3、ARM开发板使用。
6.2、BEEP
6.2.1、查看原理图
BEEP ---> MOTOR_PWM
让蜂鸣器响 -- 无源蜂鸣器,需要高(1)低(0)电平切变。
MOTOR_PWM --> XpwmTOUT0/LCD_FRM/GPD0_0
BEEP ---> GPD0_0
6.2.2、查看数据手册
/*BEEP*/
//GPD0CON 地址:0x114000A0 [3:0] 0x1 = Output
//GPD0DAT 地址:0x114000A4 [3:0] 0 和 1 变化
#define GPD0CON *(volatile unsigned int *)0x114000A0
#define GPD0DAT *(volatile unsigned int *)0x114000A4
6.2.3、代码实现
main.c
#include "beep.h"
//#include "led.h"
void delay_ms(int time);
int main()
{
beep_init();
//led5_init();
int cnt = 0;
while(1)
{
beep_set(0);
delay_ms(1);
beep_set(1);
delay_ms(1);
#if 0
cnt ++; //过了2ms
if(cnt>=0 && cnt<=500)
{
led5_on();
}
else if(cnt>=500 && cnt<=1000)
{
led5_off();
}
else
{
cnt = 0;
}
#endif
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
beep.c
// GPD0CON 0x1140_00A0 [3:0] 0x1 = Output
// GPD0DAT 0x1140_00A4 [0] 0 和 1 变化
#define GPD0CON *(volatile unsigned int *)0x114000A0
#define GPD0DAT *(volatile unsigned int *)0x114000A4
void beep_init()
{
GPD0CON &= ~(0xf << 0);
GPD0CON |= (0x1 << 0);
}
void beep_set(int data)
{
GPD0DAT &= ~(0x1 << 0);
GPD0DAT |= (data << 0);
}
beep.h
#ifndef __BEEP_H
#define __BEEP_H
void beep_init();
void beep_set(int data);
#endif
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc beep.c -o beep.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
6.2.4、编译运行
6.2.5、程序下载
详情可参考本文章:2.3、ARM开发板使用。
七、PWM定时器
7.1、PWM简介
7.1.1、PWM的概念
PWM( Pulse width Modulation):脉冲宽度调制
占空比:就是输出的PWM中,高电平保持的时间与该PWM的时钟周期的时间比
常见应用有:电机控制、DAC输出等。
7.1.2、PWM控制器特性
Exynos4412的PWM定时器有5个32位定时器,其中定时器0、定时器1、定时器2与定时器3具有脉冲宽度调制(PWM)功能,定时器4仅供内部定时而没有输出引脚。定时器0具有死区生成器,可以控制大电流设备。
7.1.3、PWM控制器
7.1.4、PWM相关控寄存器
- 定时器配置寄存器0( TFCGO)
- 定时器配置寄存器1(TCFG1)
- 定时器控制寄存器 (TCON)
- 定时器n计数缓冲寄存器(TCNTBn)
- 定时器n比较缓冲寄存器(TCMPBn)
7.2、PWM实现
7.2.1、查看原理图
BEEP ---> MOTOR_PWM
PWM控制蜂鸣器响。
MOTOR_PWM --> XpwmTOUT0/LCD_FRM/GPD0_0
BEEP/PWM ---> GPD0_0
7.2.2、查看数据手册
/*GPIO*/
GPD0CON 0x1140_00A0 [3:0] 0x2 = TOUT_0
/*PWM*/
TCFG0 0x139D_0000 [7:0] 0xff 预分频值0-255
TCFG1 0x139D_0004 [3:0] 0100 = 1/16
TCON 0x139D_0008 [3] 0 = One-shot 1 = Interval mode (auto-reload)
[2] 0 = Inverter Off 1 = TOUT_0 inverter-on
[1] 0 = No operation 1 = Updates TCNTB0 andTCMPB0
[0] 0 = Stops Timer 1 = Starts Timer
TCNTB0 0x139D_000C [31:0] Timer 0 Count Buffer register
TCMPB0 0x139D_0010 [31:0] Timer 0 Compare Buffer register
7.2.3、代码实现
beep.c
// GPD0CON 0x1140_00A0 [3:0] 0x2 = TOUT_0
// TCFG0 0x139D_0000 [7:0] 0xff 预分频值0-255
// TCFG1 0x139D_0004 [3:0] 0100 = 1/16
// TCON 0x139D_0008 [3] 0 = One-shot 1 = Interval mode (auto-reload)
// [2] 0 = Inverter Off 1 = TOUT_0 inverter-on
// [1] 0 = No operation 1 = Updates TCNTB0 andTCMPB0
// [0] 0 = Stops Timer 1 = Starts Timer
//
// TCNTB0 0x139D_000C [31:0] Timer 0 Count Buffer register
// TCMPB0 0x139D_0010 [31:0] Timer 0 Compare Buffer register
#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 beep_init()
{
GPD0CON &= ~(0xf << 0);
GPD0CON |= (0x2 << 0);
TCFG0 |= (0xff << 0);
TCFG1 &= ~(0xf << 0);
TCFG1 |= (0x4 << 0);
TCNTB0 = 100;
TCMPB0 = 50;
TCON |= (0x1 << 1); //开启手动装载
TCON &= ~(0x1 << 1); //关闭手动装载
TCON |= (0x1 << 3); //开启自动装载
}
void beep_set(int cnt, int cmp)
{
TCNTB0 = cnt;
TCMPB0 = cmp;
TCON |= (0x1 << 1); //开启手动装载
TCON &= ~(0x1 << 1); //关闭手动装载
TCON |= (0x1 << 3); //开启自动装载
}
void beep_on()
{
TCON |= (0x1 << 0);
}
void beep_off()
{
TCON &= ~(0x1 << 0);
}
beep.h
#ifndef __BEEP_H
#define __BEEP_H
void beep_init();
void beep_on();
void beep_off();
void beep_set(int cnt, int cmp);
#endif
main.c
#include "beep.h"
void delay_ms(int time);
int main()
{
beep_init();
beep_on();
delay_ms(3000);
beep_set(100,50);
delay_ms(3000);
beep_set(50,25);
delay_ms(3000);
beep_set(30,15);
delay_ms(3000);
beep_set(20,10);
delay_ms(3000);
beep_off();
while(1)
{
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc beep.c -o beep.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
7.2.4、编译运行
7.2.5、程序下载
详情可参考本文章:2.3、ARM开发板使用。
八、串口通信接口
8.1、串口简介
8.1.1、基本概念
在通信领域内,有两种数据通信方式:并行通信和串行通信
串口通信指串口按位(bit)发送和接收字节,串口通信的概念非常简单,串口按位(bit)发送和接收字节。
尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线 接收数据。它很简单并且能够实现远距离通信。
8.2.1、常用术语
(1)单工、半双工和全双工
单工( Simplex)
特点:仅能进行一个方向的数据传送
半双工(Half Duplex)
特点:数据可以在两个方向上进行传送,但是这种传送绝不能同时进行,(双向,但不同时)。
全双工(Full Duplex)
特点:能够在两个方向同时进行数据传送
(2)数据传输率
每秒传输的二进制位数,单位为bps(bit per second)也称比特率。
(3)异步方式与同步方式
同步通信方式(Synchronous)所用的数据格式没有起始位、停止位,一次传送的字符个数可变。
异步方式( Asynchronous):也称“起止同步式”。
(4)硬件流控制
如果打开串口硬件流控制后,串口A只有在nCTS被(串口B的nRTS)激活后才能把数据发送出去;
当串口A可以接收数据时,激活nRTS
8.1.3、UART接口功能模块
8.1.4、串口相关的控制寄存器
- UART行控制器 ULCONn
- UART模式控制寄存器 UCONn
- UART FIFO控制寄存器 UFCONn
- UART MODEM控空制寄存器 UMCONn
- 发送寄存器UTXH和接收寄存器URXH
- 波特率分频寄存器 UBRDIV和 UDIVSLOT
8.2、串口实现
8.2.1、查看原理图
CON7 ---> BUF_XuTXD2/UART_AUDIO_TXD
TXD:发送
RXD:接收
CON7 ---> BUF_XuTXD2/UART_AUDIO_TXD ;
XuTXD2/UART_AUDIO_TXD --> XuTXD2/UART_AUDIO_TXD/GPA1_1
8.2.2、查看数据手册
/*GPIO*/
GPA1CON 0x1140_0020 [7:4] 0x2 = UART_2_TXD
/*UART*/
ULCONn 线控寄存器
串口时钟: 波特率
UBRDIVn
UFRACVALn
ULCON2 0x1382_0000 0 000 0 11 = 0x3
UCON2 0x1382_0004 [3:2] 01 = Interrupt request or polling mode
UTXH2 0x1382_0020 [7:0] data 'a'
UBRDIV2 0x1382_0028 53
UFRACVAL2 0x1382_002c 4
UTRSTAT2 0x1382_0010 [2] 1 = Transmitter(includes transmit buffer and shifter)empty
[1] 0 = Buffer is not empty
串口接收数据:
原理图: GPA1_0
数据手册:
GPA1CON 0x1140_0020 [3:0] 0x2 = UART_2_RXD
UTRSTAT2 0x1140_0010 [0] 判断状态 1 = Buffer has a received data
URXH2 0x1140_0024 接收数据
8.2.3、代码实现
uart.c
// GPA1CON 0x1140_0020 [7:4] 0x2 = UART_2_TXD
// ULCON2 0x1382_0000 0 000 0 11 = 0x3
// UCON2 0x1382_0004 [3:2] 01 = Interrupt request or polling mode
// UTXH2 0x1382_0020 [7:0] data 'a'
// UBRDIV2 0x1382_0028 53
// UFRACVAL2 0x1382_002c 4
// GPA1CON 0x1140_0020 [3:0] 0x2 = UART_2_RXD
// UTRSTAT2 0x1140_0010 [0] 判断状态 1 = Buffer has a received data
// URXH2 0x1140_0024 接收数据
// UCON2 0x1382_0004 [1:0] 01 = Interrupt request or polling mode
#define GPA1CON *(volatile unsigned int *)0x11400020
#define ULCON2 *(volatile unsigned int *)0x13820000
#define UCON2 *(volatile unsigned int *)0x13820004
#define UTXH2 *(volatile unsigned int *)0x13820020
#define UBRDIV2 *(volatile unsigned int *)0x13820028
#define UFRACVAL2 *(volatile unsigned int *)0x1382002c
#define UTRSTAT2 *(volatile unsigned int *)0x13820010
#define URXH2 *(volatile unsigned int *)0x13820024
void uart_init()
{
GPA1CON &= ~(0xff << 0);
GPA1CON |= (0x22 << 0);
ULCON2 = 0x03;
UCON2 &= ~(0xf << 0);
UCON2 |= (0x5 << 0);
UBRDIV2 = 53;
UFRACVAL2 = 4;
}
char uart_recv()
{
while( (UTRSTAT2 & 0x1) == 0);
return URXH2;
}
void uart_send(char data)
{
while( (UTRSTAT2 & 0x04) == 0 );
//while( (UTRSTAT2 & 0x02) == 0 );
UTXH2 = data;
}
void uart_send_str(char *data)
{
while(*data != '\0')
{
uart_send(*data);
data++;
}
}
uart.h
#ifndef __UART_H
#define __UART_H
void uart_init();
void uart_send(char data);
void uart_send_str(char *data);
char uart_recv();
#endif
main.c
#include "uart.h"
void delay_ms(int time);
int main()
{
uart_init();
char data = 0;
while(1)
{
uart_send('A');
data = uart_recv();
uart_send(data);
//led5_on();
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc uart.c -o uart.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
8.2.4、编译运行
8.2.5、程序下载
详情可参考本文章:2.3、ARM开发板使用。
九、RTC定时器
9.1、RTC简介
9.1.1、RTC基本概论
实时时钟(REAL TIME CLOCK)英文缩写也叫RTC
计算机系统通常需要一个能够记录时间的功能单元,在系统关闭后依然可以记录时间,这个功能单元就叫实时时钟单元。
实时时钟通常可以提供年、月、日、时、分、秒等信息。有些还可以提供定时等功能。
9.1.2、实时时钟单元
- BCD码的年、月、日、星期、小时、分钟、秒输出功能
Alarm定时唤醒功能 - 独立的电源供电管脚( RTCVDD)
- 为RTOS提供毫秒级的定时
- 时钟晶振选取32.768Hz
9.1.3、实时时钟相关的寄存器
- RTC控制寄存器 RTCCON
Tick时钟数值寄存器TICNT - RTC警报控制寄存器
- RTCRST复位控制寄存器
- 时间寄存器 BCDSEC、 BCDMIN、 MINDATA、 BCDHOUR、 BCDDATE、 BCDDA、CDMON、 BCDYEAR
9.2、RTC实现
9.2.1、查看数据手册
RTCCON 0x1007_0040 [0] write-1 read-0
BCDSEC 0x1007_0070
BCDMIN 0x1007_0074
BCDHOUR 0x1007_0078
BCDDAYWEEK 0x1007_007C
BCDDAY 0x1007_0080
BCDMON 0x1007_0084
BCDYEAR 0x1007_0088
RTCCON [0] 1
BCDSEC = 0x29;
BCDMIN = 0x03;
BCDHOUR = 0x15;
RTCCON [0] 0
9.2.2、代码实现
rtc.c
#include "uart.h"
#include "rtc.h"
#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
void rtc_init()
{
RTCCON |= (0x1 << 0);
BCDSEC = 0x55;
BCDMIN = 0x59;
BCDHOUR = 0x23;
BCDDAYWEEK = 0x05;
BCDDAY = 0x31;
BCDMON = 0x12;
BCDYEAR = 0x021;
RTCCON &= ~(0x1 << 0);
}
//2021-12-31 23:59:55 5
void rtc_show()
{
uart_send('2');
uart_send(((BCDYEAR >> 8) & 0xf) + '0');
uart_send(((BCDYEAR >> 4) & 0xf) + '0');
uart_send(((BCDYEAR >> 0) & 0xf) + '0');
uart_send('-');
uart_send(((BCDMON >> 4) & 0xf) + '0');
uart_send(((BCDMON >> 0) & 0xf) + '0');
uart_send('-');
uart_send(((BCDDAY >> 4) & 0xf) + '0');
uart_send(((BCDDAY >> 0) & 0xf) + '0');
uart_send_str(" ");
uart_send(((BCDHOUR >> 4) & 0xf) + '0');
uart_send(((BCDHOUR >> 0) & 0xf) + '0');
uart_send(':');
uart_send(((BCDMIN >> 4) & 0xf) + '0');
uart_send(((BCDMIN >> 0) & 0xf) + '0');
uart_send(':');
uart_send(((BCDSEC >> 4) & 0xf) + '0');
uart_send(((BCDSEC >> 0) & 0xf) + '0');
uart_send_str(" ");
uart_send(((BCDDAYWEEK >> 0) & 0xf) + '0');
uart_send_str("\r\n");
}
rtc.h
#ifndef __RTC_H
#define __RTC_H
void rtc_init();
void rtc_show();
#endif
main.c
#include "uart.h"
#include "rtc.h"
void delay_ms(int time);
int main()
{
uart_init();
rtc_init();
while(1)
{
rtc_show();
delay_ms(1000);
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
uart.c
// GPA1CON 0x1140_0020 [7:4] 0x2 = UART_2_TXD
// ULCON2 0x1382_0000 0 000 0 11 = 0x3
// UCON2 0x1382_0004 [3:2] 01 = Interrupt request or polling mode
// UTXH2 0x1382_0020 [7:0] data 'a'
// UBRDIV2 0x1382_0028 53
// UFRACVAL2 0x1382_002c 4
// GPA1CON 0x1140_0020 [3:0] 0x2 = UART_2_RXD
// UTRSTAT2 0x1140_0010 [0] 判断状态 1 = Buffer has a received data
// URXH2 0x1140_0024 接收数据
// UCON2 0x1382_0004 [1:0] 01 = Interrupt request or polling mode
#define GPA1CON *(volatile unsigned int *)0x11400020
#define ULCON2 *(volatile unsigned int *)0x13820000
#define UCON2 *(volatile unsigned int *)0x13820004
#define UTXH2 *(volatile unsigned int *)0x13820020
#define UBRDIV2 *(volatile unsigned int *)0x13820028
#define UFRACVAL2 *(volatile unsigned int *)0x1382002c
#define UTRSTAT2 *(volatile unsigned int *)0x13820010
#define URXH2 *(volatile unsigned int *)0x13820024
void uart_init()
{
GPA1CON &= ~(0xff << 0);
GPA1CON |= (0x22 << 0);
ULCON2 = 0x03;
UCON2 &= ~(0xf << 0);
UCON2 |= (0x5 << 0);
UBRDIV2 = 53;
UFRACVAL2 = 4;
}
char uart_recv()
{
while( (UTRSTAT2 & 0x1) == 0);
return URXH2;
}
void uart_send(char data)
{
while( (UTRSTAT2 & 0x04) == 0 );
//while( (UTRSTAT2 & 0x02) == 0 );
UTXH2 = data;
}
void uart_send_str(char *data)
{
while(*data != '\0')
{
uart_send(*data);
data++;
}
}
uart.h
#ifndef __UART_H
#define __UART_H
void uart_init();
void uart_send(char data);
void uart_send_str(char *data);
char uart_recv();
#endif
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc uart.c -o uart.o -c
arm-linux-gcc rtc.c -o rtc.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
9.2.3、编译运行
9.2.4、程序下载
详情可参考本文章:2.3、ARM开发板使用。
十、看门狗定时器
10.1、看门狗简介
10.1.1、看门狗概念
看门狗又叫 watchdog timer 是一个定时器电路,一般有一个输入叫喂狗,一个输出到MCU的RST端MCU正常工作的时候每隔一端时间输出一个信号到喂狗端给WDT清零如果超过规定的时间不喂狗(一般在程序跑飞时)WDT定时超过就回给出一个复位信号到MCU是MCU复位防止MCU死机。
看门狗的作用就是防止程序发生死循环,或者说程序跑飞。
10.1.2、工作原理
工作原理:在系统运行以后也就启动了看门狗的计数器,看门狗就开始自动计数,如果到了一定的时间还不去清看门狗,那么看门狗计数器就会溢出从而引起看门狗中断,造成系统复位。
10.1.3、看门狗相关寄存器
- 看门狗控制寄存器 WTCON
- 看门狗数据寄存器 WTDAT
- 看门狗当前计数值寄存器 WTCNT
10.1.3、看门狗实现过程
注意:WTDAT不会在看门狗使能后第一次运行时传入WTCNT,而是在中断或复位后才被传入 WTCNT
/*预分配公式计算*/
t_watchdog = 1/(PCLK/(Prescaler value + 1)/Division_factor)
10.2、看门狗实现
10.2.1、查看数据手册
To start WDT, set WTCON[0] and WTCON[5] as 1.
t_watchdog = 1/(PCLK/(Prescaler value + 1)/Division_factor)
WTCON 0x1006_0000
[15:8] 预分频Prescaler value
[5] 1 = Enables WDT bit
[4:3] 11 = 128 Division_factor
[0] 1
WTDAT 0x1006_0004 [15:0] RW WDT count value for reload
WTCNT 0x1006_0008 [15:0] RW The current count value of the WDT
10.2.2、代码实现
watchdog.c
// WTCON 0x1006_0000
// [15:8] 预分频Prescaler value 255 0xff
// [5] 1 = Enables WDT bit
// [4:3] 11 = 128 Division_factor
// [0] 1
//
// WTDAT 0x1006_0004 [15:0] RW WDT count value for reload
// WTCNT 0x1006_0008 [15:0] RW The current count value of the WDT
// t_watchdog = 1/(PCLK/(Prescaler value + 1)/Division_factor)
#define WTCON *(volatile unsigned int *)0x10060000
#define WTDAT *(volatile unsigned int *)0x10060004
#define WTCNT *(volatile unsigned int *)0x10060008
void watchdog_init()
{
WTCON |= (0xff << 8); //预分频Prescaler value 255
WTCON |= (0x3 << 3); //11 = 128 Division_factor
WTCNT = 30510;
WTCON |= (0x1 << 5);
WTCON |= (0x1 << 0);
}
void watchdog_set(int n)
{
}
watchdog.h
#ifndef __WATCHDOG_H
#define __WATCHDOG_H
void watchdog_init();
void watchdog_set(int n);
#endif
main.c
#include "uart.h"
#include "watchdog.h"
void delay_ms(int time);
int main()
{
uart_init();
watchdog_init();
while(1)
{
uart_send('a');
delay_ms(1000);
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
uart.c
// GPA1CON 0x1140_0020 [7:4] 0x2 = UART_2_TXD
// ULCON2 0x1382_0000 0 000 0 11 = 0x3
// UCON2 0x1382_0004 [3:2] 01 = Interrupt request or polling mode
// UTXH2 0x1382_0020 [7:0] data 'a'
// UBRDIV2 0x1382_0028 53
// UFRACVAL2 0x1382_002c 4
// GPA1CON 0x1140_0020 [3:0] 0x2 = UART_2_RXD
// UTRSTAT2 0x1140_0010 [0] 判断状态 1 = Buffer has a received data
// URXH2 0x1140_0024 接收数据
// UCON2 0x1382_0004 [1:0] 01 = Interrupt request or polling mode
#define GPA1CON *(volatile unsigned int *)0x11400020
#define ULCON2 *(volatile unsigned int *)0x13820000
#define UCON2 *(volatile unsigned int *)0x13820004
#define UTXH2 *(volatile unsigned int *)0x13820020
#define UBRDIV2 *(volatile unsigned int *)0x13820028
#define UFRACVAL2 *(volatile unsigned int *)0x1382002c
#define UTRSTAT2 *(volatile unsigned int *)0x13820010
#define URXH2 *(volatile unsigned int *)0x13820024
void uart_init()
{
GPA1CON &= ~(0xff << 0);
GPA1CON |= (0x22 << 0);
ULCON2 = 0x03;
UCON2 &= ~(0xf << 0);
UCON2 |= (0x5 << 0);
UBRDIV2 = 53;
UFRACVAL2 = 4;
}
char uart_recv()
{
while( (UTRSTAT2 & 0x1) == 0);
return URXH2;
}
void uart_send(char data)
{
while( (UTRSTAT2 & 0x04) == 0 );
//while( (UTRSTAT2 & 0x02) == 0 );
UTXH2 = data;
}
void uart_send_str(char *data)
{
while(*data != '\0')
{
uart_send(*data);
data++;
}
}
uart.h
#ifndef __UART_H
#define __UART_H
void uart_init();
void uart_send(char data);
void uart_send_str(char *data);
char uart_recv();
#endif
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc uart.c -o uart.o -c
arm-linux-gcc watchdog.c -o wdt.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
10.2.3、编译运行
10.2.4、程序下载
详情可参考本文章:2.3、ARM开发板使用。
十一、A/D转换
11.1、A/D简介
11.1.1、A/D转换概念
A/D转换是将模拟信号转换为数字信号,被广泛应用于控制领域。
常见A/D转换器分类:
- 积分型
- 逐次比较型
- 并行比较型/串并行比较型
11.1.2、A/D转换器
11.1.3、AD相关寄存器
A/D控制寄存器 ADCCON
ADC间隔时间寄存器 ADCDLY
ADC转换结果寄存器 ADCDAT0
ADC通道选择 ADCMUX
11.2、ADC实现
11.2.1、查看原理图
VR1 ---> XadcAIN3
XadcAIN3 ---> XadcAIN3
11.2.2、查看数据手册
Exynos 4412 has two ADC blocks, General ADC and MTCADC_ISP.
User can select one of ADC blocks by setting ADC_CFG[16] bit in System Register SFR.
ADC_CFG[16] address : 0x1001_0118 0 : General ADC
ADC_CFG 0x1001_0118 [16] 0 : General ADC
ADCDAT (ADC conversion data register). 转换后的数据
With polling method, to determine the read time for ADCDATXn register, check the ADCCONn[15] - end of conversion flag - bit
After ADCCONn[1] - A/D conversion start-by-read modeis set to 1.
ADC_CFG 0x1001_0118 [16] 0 : General ADC
ADCCON 0x126C_0000 [16] 1 = 12bit A/D conversion
[15] (Read only) 1 = End of A/D conversion
[14] 1 = Enable
[13:6] 19
[2] 0 = Normal operation mode
[1] 1 = Enables start by read operation
ADCDLY 0x126C_0008 使用默认值 - 不用设置
ADCDAT 0x126C_000C 需要自己转换
ADCMUX 0x126C_001C [3:0] 0011 = AIN 3
11.2.3、代码实现
adc.c
#include "uart.h"
// ADC_CFG 0x1001_0118 [16] 0 : General ADC
//
// ADCCON 0x126C_0000 [16] 1 = 12bit A/D conversion
// [15] (Read only) 1 = End of A/D conversion
// [14] 1 = Enable
// [13:6] 19 或者 0xff
// [2] 0 = Normal operation mode
// [1] 1 = Enables start by read operation
// ADCDLY 0x126C_0008 使用默认值 - 不用设置
// ADCDAT 0x126C_000C 需要自己转换
// ADCMUX 0x126C_001C [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
void adc_init()
{
ADC_CFG &= ~(0x1 << 16);
ADCCON = (0x1<<16) | (0x1<<14) | (0xff<<6);
ADCMUX &= ~(0xf << 0);
ADCMUX |= (0x3 << 0);
ADCCON |= (0x1<<1);
}
int adc_data()
{
int data = ADCDAT & 0xfff;
data = (18 * data) / 4095 ;
//uart_send( data/10 + '0');
//uart_send('.');
//uart_send( data%10 + '0');
//uart_send_str("v\r\n");
return data;
}
adc.h
#ifndef __ADC_H
#define __ADC_H
void adc_init();
int adc_data();
#endif
main.c
#include "uart.h"
#include "adc.h"
void delay_ms(int time);
int main()
{
int data = 0;
uart_init();
adc_init();
uart_send_str("start\r\n");
while(1)
{
data = adc_data();
uart_send( data/10 + '0');
uart_send('.');
uart_send( data%10 + '0');
uart_send_str("v\r\n");
delay_ms(1000);
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc uart.c -o uart.o -c
arm-linux-gcc adc.c -o adc.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
start.S
.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 _software_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 @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd0 @或上一个数据 设置成user模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #64
bl main
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
uart.c
// GPA1CON 0x1140_0020 [7:4] 0x2 = UART_2_TXD
// ULCON2 0x1382_0000 0 000 0 11 = 0x3
// UCON2 0x1382_0004 [3:2] 01 = Interrupt request or polling mode
// UTXH2 0x1382_0020 [7:0] data 'a'
// UBRDIV2 0x1382_0028 53
// UFRACVAL2 0x1382_002c 4
// GPA1CON 0x1140_0020 [3:0] 0x2 = UART_2_RXD
// UTRSTAT2 0x1140_0010 [0] 判断状态 1 = Buffer has a received data
// URXH2 0x1140_0024 接收数据
// UCON2 0x1382_0004 [1:0] 01 = Interrupt request or polling mode
#define GPA1CON *(volatile unsigned int *)0x11400020
#define ULCON2 *(volatile unsigned int *)0x13820000
#define UCON2 *(volatile unsigned int *)0x13820004
#define UTXH2 *(volatile unsigned int *)0x13820020
#define UBRDIV2 *(volatile unsigned int *)0x13820028
#define UFRACVAL2 *(volatile unsigned int *)0x1382002c
#define UTRSTAT2 *(volatile unsigned int *)0x13820010
#define URXH2 *(volatile unsigned int *)0x13820024
void uart_init()
{
GPA1CON &= ~(0xff << 0);
GPA1CON |= (0x22 << 0);
ULCON2 = 0x03;
UCON2 &= ~(0xf << 0);
UCON2 |= (0x5 << 0);
UBRDIV2 = 53;
UFRACVAL2 = 4;
}
char uart_recv()
{
while( (UTRSTAT2 & 0x1) == 0);
return URXH2;
}
void uart_send(char data)
{
while( (UTRSTAT2 & 0x04) == 0 );
//while( (UTRSTAT2 & 0x02) == 0 );
UTXH2 = data;
}
void uart_send_str(char *data)
{
while(*data != '\0')
{
uart_send(*data);
data++;
}
}
uart.h
#ifndef __UART_H
#define __UART_H
void uart_init();
void uart_send(char data);
void uart_send_str(char *data);
char uart_recv();
#endif
11.2.4、编译运行
11.2.5、程序下载
详情可参考本文章:2.3、ARM开发板使用。
十二、中断
12.1、中断简介
12.1.1、中断概念
CPU与外设之间的数据传送控制方式(即I/O控制方式)通常有以下三种:
程序控制方式
中断方式
DMA方式(Direct Memory Access)
中断定义:
在程序运行中,出现了某种紧急事件,CP必须中止现行程序,转去处理此紧急事件(执行中断服务程序),并在处理完毕后再返回运行程序的过程。
12.1.2、中断过程
中断请求
中断判优
中断响应
中断处理
中断返回
Exynos412中断控制器包含160个中断控制源,这些中断源来自软中断(SGI),私有外部中断(PPI),公共外部中断(SPI)。 Exynos4412采用GIC中断控制器。
12.1.3、中断相关寄存器
ICCICR_CPUO
ICCPMR_CPUO
ICCIAR_CPUO
ICCEOIR_CPUO
ICDISRI
ICDICERI
ICDICPRI
ICDIPTR8
12.2、中断实现
12.2.1、查看原理图
k2 ---> UART_RING
UART_RING ---> XEINT9/KP_COL1/ALV_DBG5/GPX1_1
GPIO控制:
看原理图: GPX1_1(XEINT9)
数据手册:
GPX1CON 0x1100_0C20 [7:4] 0x0 = Input
GPX1DAT 0x1100_0C24 [1] state
int main()
{
GPX1CON &= ~(0xf << 4);
while(1)
{
if( (GPX1DAT & (0x1<<1)) == 0)
{
led_on();
}
else{
led_off();
}
}
return 0;
}
12.2.2、查看数据手册
GPX1CON 0x1100_0C20 [7:4] 0xF = EXT_INT41[1]
EXT_INT41CON 0x1100_0E04 [6:4] 0x2 = Triggers Falling edge
EXT_INT41_FLTCON0 0x1100_0E88 [15] 0x1 = Enables Filter
[14] 0x0 = Delays filter
EXT_INT41_MASK 0x1100_0F04 [1] 0x0 = Enables Interrupt
EXT_INT41_PEND 0x1100_0F44 [1] 0x1 = Interrupt Occurs //中断处理完成后
中断相关寄存器:
中断id: 57 – EINT[9]
【分发服务器提供了一个编程接口】:
Enabling the forwarding of interrupts to the CPU interfaces globally.
0全局开启CPU接口的中断转发功能。 // ICDDCR
Enabling or disabling each interrupt.
0启用或禁用每个中断。 //ICDISER1_CPU0
Setting the priority level of each interrupt.
0设置每个中断的优先级。 //ICDIPR14_CPU0
Setting the target processor list of each interrupt.
0设置每个中断的目标处理器列表。 //ICDIPTR14_CPU0
Setting each peripheral interrupt to be level-sensitive or edge-triggered.
0设置每个外围中断设置为电平敏感或边沿触发。 //EXT_INT41CON
【cpu接口编程接口】:
Enabling the signaling of interrupt requests by the CPU interface.
0使能cpu接口的中断请求信号 // ICCICR_CPU0
Acknowledging an interrupt.
0应答一个中断 //ICCIAR_CPU0
Indicating completion of the processing of an interrupt.
0中断处理的结束。 //ICCEOIR_CPU0 ICDICPR1_CPU0
Setting an interrupt priority mask for the processor.
0设置处理器的中断优先级掩码 //ICCPMR_CPU0
Determining the highest priority pending interrupt for the processor.
0为处理器确定最高优先级的挂起中断。
ICCICR_CPU0 0x10480000 [0] 1 = Enables signaling of interrupts //CPU interface control register 初始化
ICCPMR_CPU0 0x10480004 [7:0] 0xff 255 //Interrupt priority mask register 初始化
ICCIAR_CPU0 0x1048000C [9:0] 读 The interrupt ID //Interrupt acknowledge register 中断处理函数
ICCEOIR_CPU0 0x10480010 [9:0] 写 ACKINTID //End of interrupt register Undefined 中断处理函数,中断处理完成后
ICDDCR 0x10490000 [0] 1 = interrupt signals to the CPU interfaces. //初始化
ICDISER1_CPU0 0x10490104 [25] 1 = Enables the corresponding interrupt. //Interrupt Set-Enable Registers 初始化
ICDICPR1_CPU0 0x10490284 [25] 1 = Interrupt Clear-Pending Registers //中断处理函数,中断处理完成后
ICDIPR14_CPU0 0x10490438 [8:15] 0 优先级 //初始化
ICDIPTR14_CPU0 0x10490838 [8:15] 0b00000001 --》cpu interface 0 //初始化
12.2.3、代码实现
key.c
#include "uart.h"
//GPX1CON 0x11000C20 [7:4] 0xF = EXT_INT41[1]
//EXT_INT41CON 0x11000E04 [6:4] 0x2 = Triggers Falling edge
//EXT_INT41_FLTCON0 0x11000E88 [15] 0x1 = Enables Filter 默认
// [14] 0x0 = Delays filter 默认
//EXT_INT41_MASK 0x11000F04 [1] 0x0 = Enables Interrupt
//EXT_INT41_PEND 0x11000F44 [1] 0x1 = Interrupt Occurs 中断处理函数
//ICCICR_CPU0 0x10480000 [0] 1 = Enables signaling of interrupts
//ICCPMR_CPU0 0x10480004 [7:0] 0xff 255
//ICCIAR_CPU0 0x1048000C [9:0] 读 中断处理函数
//ICCEOIR_CPU0 0x10480010 [9:0] 写 ACKINTID 中断处理函数
//ICDDCR 0x10490000 [0] 1 = 全局开启CPU接口的中断转发功能
//ICDISER1_CPU0 0x10490104 [25] 1 = Enables the corresponding interrupt
//ICDICPR1_CPU0 0x10490284 [25] 1 = Clear-Pending 中断处理函数
//ICDIPR14_CPU0 0x10490438 [8:15] 0 优先级
//ICDIPTR14_CPU0 0x10490838 [8:15] 0b00000001
#define GPX1CON *(volatile unsigned int *)0x11000C20
#define EXT_INT41CON *(volatile unsigned int *)0x11000E04
#define EXT_INT41_FLTCON0 *(volatile unsigned int *)0x11000E88
#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 ICDICPR1_CPU0 *(volatile unsigned int *)0x10490284
#define ICDIPR14_CPU0 *(volatile unsigned int *)0x10490438
#define ICDIPTR14_CPU0 *(volatile unsigned int *)0x10490838
void key_init()
{
GPX1CON |= (0xf << 4); //选择引脚功能为外部中断
EXT_INT41CON = EXT_INT41CON & (~(0x7 << 4)) | (0x2 << 4); //设置中断触发方式 下降沿
EXT_INT41_MASK &= ~(0x1 << 1); //不要屏蔽外部中断,允许外部中断的产生
ICCPMR_CPU0 |= (0xff << 0); //设置cpu接口的屏蔽码 255 最低
ICDIPR14_CPU0 &= ~(0xff << 8); //设置id为57中断的优先级
ICDIPTR14_CPU0 = ICDIPTR14_CPU0 & (~(0xff << 8)) | (0x1 << 8); //设置中断能被分配给哪个cpu接口 cpu0
ICDISER1_CPU0 |= (0x1 << 25); //使能具体的中断 - id为57的中断
ICDDCR |= (0x1 << 0); //分发器的使能
ICCICR_CPU0 |= (0x1 << 0); //使能cpu接口的中断请求信号
}
void do_irq()
{
int id = ICCIAR_CPU0 & (0x3ff << 0); //应答收到了中断
if(id == 57)
{
uart_send('a');
ICDICPR1_CPU0 |= (0x1 << 25); //清除中断挂起状态
ICCEOIR_CPU0 = ICCEOIR_CPU0 & (~(0x3ff << 0)) | (id << 0); //回复中断处理完成
}
else if(id == 58)
{
}
EXT_INT41_PEND |= (0x1 << 1); //清除外部中断挂起状态
}
key.h
#ifndef __KEY_H
#define __KEY_H
void key_init();
#endif
main.c
#include "uart.h"
#include "key.h"
void delay_ms(int time);
int main()
{
uart_init();
key_init();
while(1)
{
}
return 0;
}
void delay_ms(int time)
{
int i, j;
while(time--)
{
for (i = 0; i < 5; i++)
for (j = 0; j < 514; j++);
}
}
Makefile
all:
arm-linux-gcc start.S -o test.o -c
arm-linux-gcc main.c -o main.o -c
arm-linux-gcc uart.c -o uart.o -c
arm-linux-gcc key.c -o key.o -c
arm-linux-ld *.o -o test.elf -Ttest.lds
arm-linux-objcopy test.elf test.bin -O binary
clean:
rm *.o *.elf *.bin
start.S
.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 _software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word irq_handler
_fiq: .word _fiq
reset:
/* set the cpu to SVC32 mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd3 @或上一个数据 设置成svc模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
/* 设置向量表的地址到协处理器中 */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
ldr sp, =stacktop @设置svc的栈空间
/*修改模式 irq */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0x1f @按位清零 低5位
orr r0, r0, #0xd2 @或上一个数据 设置成irq模式,禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置irq的栈空间
sub sp, sp, #64
/* set the cpu to user mode */
mrs r0, cpsr @将cpsr的值读取到r0
bic r0, r0, #0xff @按位清零 低8位
orr r0, r0, #0x10 @或上一个数据 设置成user模式,不禁止irq fiq
msr cpsr,r0 @将修改后的值写入cpsr
ldr sp, =stacktop @设置user的栈空间
sub sp, sp, #128
bl main
irq_handler:
/* lr = lr - 4 */
sub lr, lr, #4
stmfd sp!, {r0-r12, lr}
bl do_irq
ldmfd sp!, {r0-r12, pc}^
stack: .space 64*8 @开辟栈空间
stacktop: .word stack+64*8 @栈顶地址
test.lds
ENTRY(_start)
SECTIONS{
. = 0x41000000;
.text : {
test.o(.text)
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
uart.c
// GPA1CON 0x1140_0020 [7:4] 0x2 = UART_2_TXD
// ULCON2 0x1382_0000 0 000 0 11 = 0x3
// UCON2 0x1382_0004 [3:2] 01 = Interrupt request or polling mode
// UTXH2 0x1382_0020 [7:0] data 'a'
// UBRDIV2 0x1382_0028 53
// UFRACVAL2 0x1382_002c 4
// GPA1CON 0x1140_0020 [3:0] 0x2 = UART_2_RXD
// UTRSTAT2 0x1140_0010 [0] 判断状态 1 = Buffer has a received data
// URXH2 0x1140_0024 接收数据
// UCON2 0x1382_0004 [1:0] 01 = Interrupt request or polling mode
#define GPA1CON *(volatile unsigned int *)0x11400020
#define ULCON2 *(volatile unsigned int *)0x13820000
#define UCON2 *(volatile unsigned int *)0x13820004
#define UTXH2 *(volatile unsigned int *)0x13820020
#define UBRDIV2 *(volatile unsigned int *)0x13820028
#define UFRACVAL2 *(volatile unsigned int *)0x1382002c
#define UTRSTAT2 *(volatile unsigned int *)0x13820010
#define URXH2 *(volatile unsigned int *)0x13820024
void uart_init()
{
GPA1CON &= ~(0xff << 0);
GPA1CON |= (0x22 << 0);
ULCON2 = 0x03;
UCON2 &= ~(0xf << 0);
UCON2 |= (0x5 << 0);
UBRDIV2 = 53;
UFRACVAL2 = 4;
}
char uart_recv()
{
while( (UTRSTAT2 & 0x1) == 0);
return URXH2;
}
void uart_send(char data)
{
while( (UTRSTAT2 & 0x04) == 0 );
//while( (UTRSTAT2 & 0x02) == 0 );
UTXH2 = data;
}
void uart_send_str(char *data)
{
while(*data != '\0')
{
uart_send(*data);
data++;
}
}
uart.h
#ifndef __UART_H
#define __UART_H
void uart_init();
void uart_send(char data);
void uart_send_str(char *data);
char uart_recv();
#endif
12.2.4、编译运行
12.2.5、程序下载
详情可参考本文章:2.3、ARM开发板使用。
十二、SPI
12.1、SPI简介
12.1.1、SPI基本概念
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线。
SPI有三个寄存器分别为:
控制寄存器SPCR,状态寄存器SPSR,数据寄存器SPDR。
SPI总线系统可直接与各个厂家生产的多种标准外围器件直接接口,该接口一般使用4条线:
串行时钟线(SCLK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOSI和
特点:
- 全双工,速度快
- 数据没有位数限制
- 可以接多个从设备,同时向多个指定从设备发送数据
缺点:
- 从设备越多,占用的引脚越多
- 需要程序员自己写协议,写校验
- 没有指定的流控制,没有应答机制确认是否接收到数据
12.1.2、SPI总线时序
12.2、SPI实现
SPI的实现参考:
FS4412接口编程(三十七)-spi通讯_哔哩哔哩_bilibili:https://www.bilibili.com/video/av545733997/
基于Linux和4412处理器实现SPI接口的RF控制:https://blog.csdn.net/fengel_cs/article/details/120637074
arm底层通讯协议之SPI通讯:http://news.eeworld.com.cn/mcu/ic467470.html
驱动开发参考:
4412--SPI驱动:https://www.cnblogs.com/ch122633/p/9528760.html
十三、I2C
13.1、I2C简介
13.1.1、I2C基本概念
I2C/IIC(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线 ,用于连接微控制器及其外围设备,是微电子通信控制领域挂规范采用的一种总线标准。
特点:
- 半双工,仅需要两根线(所以又被称为i2-wire总线)
- 有应答机制,有自己的协议
- 占用引脚少
- 可以接多个从设备
- 可以有多个主机,但是同一时间,只能有一个主机工作
缺点:
- 半双工,低速
- 数据有位数限
13.1.2、12C总线硬件协议
12.1.4、12C总线结构框图
12.1.5、总线控制器数据传输
数据传输,支持中断方式:
13.2、I2C实现
I2C参考文档下载:i2c.docx
I2C的实现参考:
FS4412 接口编程(三十二)-ic _哔哩哔哩_bilibili:https://www.bilibili.com/video/BV1Yb4y1o7wg/
fs4412 I2C驱动基于Cortex-A9,mpu6050裸机程序,驱动,I2C架构:https://www.cnblogs.com/yikoulinux/p/13555654.html
驱动开发参考:
Exynos4412 IIC总线驱动开发(一)— IIC 基础概念及驱动架构分析:https://blog.csdn.net/zqixiao_09/article/details/50916916
Exynos4412 IIC总线驱动开发(二)— IIC 驱动开发:https://blog.csdn.net/zqixiao_09/article/details/50917655
十四、其他
模块资料:
DHT11:DHT11.docx ; DHT11.pdf
DS18B20:DS18B20.pdf ; DS18B20中文资料.pdf
MPU-6050:MPU-6050_DataSheet_V3 4.pdf ; MPU-6050_Register_Map.pdf
3 条评论
真的很棒,学习的榜样。
打赏坏了吗?图片显示不出来。
为他人贡献者,不能使其饿其体肤。
6666
yyds