一、嵌入式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架构的开发设计技术

  • 软件工具、评估板、调试工具、应用软件、总线架构、外围设备单元、等等。。。

芯片名字:Acorn RISC Machine

公司名字:Advanced RISC Machines

ARM官网:https://www.arm.com/

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)

CPSR_EXCEL下载:CPSR.xls

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_20.04突然网络不可用问题:

问题:

突然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

补充:

VMware网络选择和ubuntu 20_04 网络配置教程:https://www.bilibili.com/video/av542591289/

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接口。

如果未显示端口或者读取不出端口号,可能未安装驱动更新

三款驱动软件下载:串口驱动.rar

串口打开失败及解决方法:串口打开失败的解决方法.docx

2.2.2、测试程序

点灯测试程序下载:myLED_c.rar

点灯.bin文件下载:myled.bin

本程序基于HQ_FS4412开发板点灯程序,详情见:(五、GPIO编程)。

2.3.2、打开超级终端

超级终端下载:win7超级终端.zip

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、为什么要学习汇编

理解机器执行过程(比如向量表,异常跳转等)

性能要求较高的时候,使用汇编,或者混合编程

逆向工程:查找异常,外挂,破解等

总结:汇编学习后,可以向上理解软件,向下感知硬件,对理解系统有很大好处。

ARM:指令

ARM 指令集:ARM.chm

ARM指令速查手册:ARM指令速查手册.pdf

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)。

条件码:

重点掌握:相等、不相等、带符号大于或等于、带符号小于、带符号大于、带符号小于或等于。

练习汇编实现从1+2+3+...+100:

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)寻址方式

ARM处理器的寻址方式:

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、异常

正常工作之外的流程都叫异常

异常会打断正在执行的工作,并且一般我 们希望异常处理完成后继续回来执行原来 的工作

中断是异常的一种

异常源有7种:

①、复位异常 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 / 汇编第一个程序:

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语言函数传参

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-LED7对应:

/*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、代码实现

汇编实现点亮LED5:

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

汇编实现点亮LED5源文件下载:04_my_led_S.rar

C语言实现点亮LED2-LED5:

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

C语言实现点亮LED2-LED5源文件下载:05_my_led_c_led2-5.rar

模块化实现LED2-LED5闪烁:

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

模块化实现LED2-LED5闪烁:06_my_led_func.rar

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、代码实现

实现蜂鸣器(BEEP):

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

蜂鸣器(BEEP)源文件:07_my_beep.rar

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、代码实现

PWM控制蜂鸣器响:

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)
    }
}

PWM控制蜂鸣器响源文件下载:08_my_beep_PWM.zip

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)
    }
}

串口实现源文件下载:09_my_uart.zip

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时钟实现:

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)
    }
}

RTC时钟实现源文件下载:10_my_RTC.rar

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电压值实现:

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

电位器调节ADC电压值源文件下载:12_my_ADC.rar

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

KEY2_GPIO控制:

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、代码实现

KEY2按键中断实现:

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

KEY2按键中断源文件下载:13_my_key.zip

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

十四、其他

ARM:指令

ARM 指令集:ARM.chm

ARM指令速查手册:ARM指令速查手册.pdf

最后修改:2023 年 08 月 04 日 11 : 31 AM
投个币吧 (づ ●─● )づ