【笔记来源:广州大学汇编课程】
一、处理器结构和汇编语言
1.1 计算机语言
1.高级语言
主要是设计应用程序,有良好的库和模组,方便调用和代码的快速实现。
C/C++、java、python、C#、JS、…
2.汇编语言
底层语言,是直接和硬件(处理器的寄存器)打交道的。
汇编语言是和处理器的架构相关的----不同架构的处理器,汇编语言是不一样的。
3.机器语言
机器语言是二进制的机器码。处理器真正执行的代码。
1.2 汇编语言的特点
- 直接和硬件打交道,代码的执行效率比较高
//C语言
void delay(void)
{
int i=0x1000;
while(i--);
}
//高级语言:Linux
void delay(void)
{
sleep(2);
}
//汇编语言:
delay:
mov r4, #0x1000
temp:
sub r4,r4,#1
cmp r4,#0
bne temp
mov pc,lr
- 汇编语言比较节省内存。
现在汇编语言应用的场合:8位的单片机 - 有些场合用高级语言是实现不了的,必须用汇编语言
例如:处理器的初始化,内存初始化,stack初始化、中断的响应过程等。----BIOS程序或者嵌入式平台启动引导程序。 - 记很多汇编指令,代码编写效率低,不能使用现成的库。
1.3 处理器架构
每一种架构的处理器都有它的汇编语言,不同架构之间的汇编语言是不通用的,所以汇编语言是和处理器的架构是相关的。
1、处理器—PC平台
CPU ---- Central Process Unit 。
x86 ---- intel 和 AMD
PowerPC ---- nxp(恩智浦)
2、嵌入式微处理器
MPU ---- Micro Process Unit
ARM ----- ARM公司
RISC-V ---- 相当于Linux,开源免费
Coldfile ---- NXP
MIPS---- ARM公司
3、控制器—单片机
MCU — Micro Control Unit
通常是8位的,做一些简单的控制。
51单片机
AVR单片机
STM8单片机
PIC单片机
1.4 什么是ARM?
ARM公司–>华为海思—>台积电(ASML光刻机)–>麒麟处理器–>华为手机
- ARM是一种处理器的架构
ARM— Advanced RISC Machine 高级的精简指令集计算机系统的机器。
总部英国剑桥。ARM—>softbank—>Navid(英伟达) - ARM也是一家公司的名字
- ARM公司只设计处理器的架构,不生产芯片。
设计ARM芯片的公司:三星、高通、博通、恩智浦、Atmel、海思、…
ARM的中文名—安谋
1.5 ARM处理器的系列
1.V4T
ARM7
ARM9
2.V5TE
ARM10
3.V6
ARM11
4.V7
cortex-A8
Cortex-A9
Cortex-A15
5.V8------>8核64bits处理器。
ARM的内核:Cortex-A53、Cortex-A55,Cortex-A72/A73、Cortex-A77.
实验室:
ARM—>V8—>Cortex-A53—>S5P6818–>samsung
1.6 ARM的框架
以S5P6818处理器为例:
二、计算机平台的硬件功能
1、处理器
程序的运行、数据的处理、接口控制。
S5P6818 ----- 1.4GHz * 8
2、硬盘(电子硬盘–存储内存)
存放数据和代码,但是代码和数据不能在硬盘上运行和处理。硬盘上的数据掉电不丢失,可以永久保存。
辅助存储器—SD卡、U盘、移动硬盘
eMMC-----8GB
3、内存(运行内存–DDR3)
存放正在处理的数据和正在执行的代码。
DDR2 ---- 1GB
4、接口
液晶屏、触摸屏、串口、网口、声音接口、SD卡、USB、LED指示灯、蜂鸣器、按键、ADC、摄像头、看门狗。
ARM处理器的特点:
体积小、功耗低、集成度高、成本低、高性能
2.1 数据类型
struct test {
char a;
int b;
short c;
long d;
};
sizeof(struct test) = ?

基本数据类型的大小
应用环境的位数
数据在内存中存放的时候,有一个对齐的要求
如果在32 bits环境下,这个结构体的大小:1+4+2+4 = 11B(错误)
1、基本数据类型:
字节类型 ---- char ---- 1B
半字类型 ---- short ----2B
字类型 ---- int ------4B
长字类型 ---- long ----- 和处理器的字长保持一致
2、数据对齐的概念
数据是放在内存中,内存中每一个存储空间都有一个地址。针对内存来讲,是通过地址来访问内存中的内容的。
对于存储器来讲,一个地址对应的存储空间一个字节。
数据放到内存中,并不是任意一个地址都可以存放该数据的。地址需要满足一定的对齐要求。
char----任何一个地址都可以存放该数据
short----地址除以2可以整除
int ----地址除以4可以整除
long ----- 地址除以4/8可以整除(需要看处理器的字长)
2.2 数据存储格式
- 存储器:一个存储空间对应一个地址,每个空间存放的数据是一个字节。
- 最常见的数据类型—int型,处理器中,寄存器的地址一般是32bits的,寄存器的内容也还是32bits
问题:
一般我们处理的数据是32bits的数据(地址),是4字节的,该数据存放在内存中的时候,需要4个地址空间---->高字节的数据放在高地址还是放在低地址。
- 大端格式/大尾格式
高字节的数据存放到低地址,低字节的数据存放到高地址 - 小端格式/小尾格式
高字节的数据存放到高地址,低字节的数据存放到低地址
方法1:
unsigned int a=0x12345678;
char *p = &a;
*p == 0x12 ---->大端格式
*p == 0x78 ---->小端格式
方法2:
地址 数据
(*(unsigned int*)0x40000000) = 0x12345678;
大端格式:
(*(unsigned char *)0x40000000) == 0x12;
(*(unsigned char *)0x40000001) == 0x34;
(*(unsigned char *)0x40000002) == 0x56;
(*(unsigned char *)0x40000003) == 0x78;
小端格式:
(*(unsigned char *)0x40000000) == 0x78;
(*(unsigned char *)0x40000001) == 0x56;
(*(unsigned char *)0x40000002) == 0x34;
(*(unsigned char *)0x40000003) == 0x12;

2.3 ARM的寄存器
由两部分组成的
1、ARM内核中的寄存器—ARM公司
- 通用寄存器—所有的ARM处理器都是一样(ARM的版本相关)
只能通过寄存器的名字来访问,只能使用汇编语音进行编程
通用寄存器:R0~R15和CPSR、SPSR
2、ARM外设模块的寄存器----samsung公司
- 特殊功能寄存器-----与具体的ARM芯片相关的
只能通过寄存器的地址来访问,可以使用汇编语言编程,也可以使用C语言编程

2.3.1 ARM的寄存器(内核中的寄存器)
- 通用寄存器:R0~R15
寄存器的常用方法:
1、R0~R3
R0~R3----参数的传递和返回值,多余4个的参数用数据stack来传递。
例:
//main.c
extern int add_xy(int x, inty);
void main(void)
{
int sum;
int a=10,b=20;
sum = add_xy(10,20);
printf("sum = %d\n", sum);
}
//add.S
add_xy: //x和y怎么处理--->第一参数->R0,第二个参数->R1
add R3,R1,R0 //R3=R1+R2
mov R0,R3 //R0=R3
mov PC,LR //子程序的返回。将返回地址(LR)传送到程序计数器(PC)
2、R4~R12:存放局部变量
如果局部变量比较多,局部变会存放在stack中。register关键字定义的局部变量优先存放在寄存器中。
提示:
C语言中的关键字:extern、const、register、volatile、static
int a=100;
int b;
const int e=20
int main(void)
{
int c;
int d;
int *p;
c = a + 10;
b = 20;
d = b + e;
p = malloc(100);
*p = 1;
*(p+1) = 2;
free(p);
return 0;
}

3、R13
R13寄存器有一个别名叫SP—Stack Pointer寄存器中存放的是stack顶的地址。在处理器初始化的时候,会对stack进行初始化,让它等于内存的某个地址。
4、R14
R14寄存器有一个别名,叫LR—Link Register,保存子程序的返回地址。
例:
test程序中,调用了子程序fun,fun自程序执行结束后,会返回test。
test:
---------
---------
BL fun //调转到fun子程序的同时,保存返回地址到LR寄存器
--------- //这条指令在内存中的地址是fun子程序的返回地址
---------
fun:
---------
---------
MOV PC,LR //将返回地址LR传递给PC寄存器,实现子程序的返回。
5、R15
R15寄存器有一个别名–PC(Program Counter),存放正在执行语句的地址。
例:
MOV PC ,#0x00000000
相当于处理器重启。针对ARM处理器来讲,ARM启动的时候,执行的第一条指令在0x00000000地址。
R3 = R2+R1;//C语言不能直接访问通用寄存器,需要使用汇编语言访问。
2.3.2 状态寄存器
1、CPSR ---- 当前程序状态寄存器
CPSR ----- Current Program Status Register
反应当前处理器的状态:
- M[4:0]—工作模式(mode,7种)。
- 通过这个5个位可以获取处理器的工作模式。也可以通过这5个位来设置处理器的工作模式。
- T—工作状态(state,2种)ARM处理器一般有两种工作状态,分别是:
- ARM----- ARM处理器在执行32bits的机器码— 提高指令的执行效率
- Thumb-----ARM处理器在执行16bits的机器码—节省内存空间,降低功耗。
- 通常情况下,ARM处理器是执行32bits的机器码,处于ARM工作状态。处理器刚开始启动的时候,处于ARM状态。
- T–1 Thumb state
- T–0 ARM state
- 中断的开关(中断有两种类型:FIQ—Fast Interrupt reQuest,IRQ—Interrupt ReQuest )
- F—FIQ中断的使能位
- FIQ快速中断
- F–1 disable屏蔽
- F–0 enable
- I—IRQ中断的使能位
- IRQ是一般中断,优先级和响应时间是低于FIQ的。
- I—1 disable
- I—0 enable
- F—FIQ中断的使能位
- 算术运算的时候,运算的结果:是否是整数,是否为0,是否有进位,是否有借位。----状态标志位。
- NZCV—条件码标志位
- 算术运算指令的运算结果可以影响条件码标志位。有条件的汇编指令可以通过条件码标志位来判断是否要执行。
- 例子:
if(a==10)
a=a+1;
else
a=a-1;
CMP R4,#10 //R4里面存放了常数10 CMP---compare,会影响NZCV
ADDEQ R4,R4,#1//R4=R4+1,EQ--EQual
SUBNE R4,R4,#1
ARM汇编指令可以带条件,条件满足就执行,条件不满足,就是一个空指令(NOP)。
CMP R4,#10
SUBS R5,R4,#10//R5=R4-10,运算的影响标志位。
CPSR寄存器:
2、SPSR ---- 备份程序状态寄存器
在ARM处理器在工作模式切换的时候,用来保存CPSR。
2.3.3 ARM的工作模式
工作模式----Operating Mode,ARM处理器在处理不同的功能所处于的工作模式。
1、用户模式:ARM处理器在运行应用程序的时候
2、FIQ模式:ARM处理器正在处理快速中断的时候
3、IRQ模式:ARM处理器正在处理器一般中断的时候
4、管理模式:处理器进入保护模式,一般我们设置cache、设置MMU需要在管理模式,ARM在启动的时候,也是出于管理模式。
5、中止模式:处理器运行程序的过程中的一种出错模式。访问的地址不存在,后者地址禁止访问。
6、系统模式:处理器在运行操作系统应用程序时候的一种工作模式。
7、未定义模式:ARM处理器在执行汇编指令(机器码),是没有办法译码,指令未定义。出现未定义的问题:新版本的扩展指令应用到老版本的处理器上;指令在存储或者读取的过程中出错。
2.4 S5P6818处理器

三、ARM汇编指令
ARM汇编指令的应用场合:
ARM处理器最开始的启动代码、对处理器内核中的模块进行设置(如:cache、MMU、协处理器)、stack初始化(MOV SP, #0x70000000)、中断的进入和退出、任务的调度。
计算机系统的底层代码才用到汇编,往往底层代码都是做好的。
3.1 数据处理指令
只能完成通用寄存器(R0~R15)和立即数(常数前面加#)之间的运算。
1)算术运算指令–ADD、SUB
2)位运算指令—AND、ORR、BIC、EOR
3)比较指令----CMP、TEQ、TST
4)数据传送指令----MOV
注意:并不是所有的立即数都是直接使用在数据处理指令中。如果立即数不合法,可以使用LDR指令来替代。
ADD R1,R0,#0x11FF //不合法
替代:
LDR R2,=0x11FF
ADD R1,R0,R2
3.2 数据传送指令
MOV R1,R0 //将R0的值传输给R1
MOV R1,#20 //将20传送给R1
MOV R1,R0 ,LSL #3 //R1=(R0<<3)
//MVN----取反后再传送。
//不合法:
MOV 10,R1
MOV R1,CPSR //操作CPSR有专门的指令
MOV R1,#2 ,LSL #3 //R1=(2<<3)
3.3 算术运算指令
<<—LSL,>>—LSR, &—AND, |—ORR, ^----EOR(异或)
~&----BIC(位清除)
ADD R3,R2,R1//R3=R2+R1
ADD R3,R2,#30 //R3=R2+30
SUB R0,R1,R2 //R0 = R1 - R2
AND R3,R2,R1 //R3=R2&R1
/*思考:
&与&&
|与||
!与~
<<与>>
^---异或*/
//不合法:
ADD R3,#30,R1
ADD R3,#2,#1----> MOV R2,#2
ADD R3,R2,#1
位运算的例子:
1)unsigned int a,将a的第10位置1,其他位保持不变。
清0—和0进行按位与运算
置1—和1进行按位或运算
//C语言
a = a | 0x400
a |= 0x400
a |= (1<<10)
//汇编语言
ORR R4,R4,#0x400
ORR R4,R4,#(1<<10)
MOV R5,#1
ORR R4,R4,R5, LSL #10
2)unsigned int a,将a的第10位清0,其他位保持不变。
//C语言:
a = a & ~(1<<10)
a &= ~(1<<10)
//汇编语言:
BIC R4,R4,#0x400
BIC R4,R3,#(1<<10)
MOV R5,#1
BIC R4,R4,R5,LSL #10
AND R4,R4,#FFFFFDFF //不合法,立即数超出了范围
3)unsigned int a,将a的第10位取反,其他位保持不变。
//C语言:
a ^= (1<<10)
//汇编语言:
EOR R4,R4,#(1<<10)
MOV R5,#1
EOR R4,R4,R5,LSL #10
大疆笔试题:
有一个32bits的寄存器REGn,寄存器的地址是0x1F000010,写一个安全的函数实现将REGn的指定位反转,其他位保持不变。
思考:
将一个32bits的数0x12345678,写到一个地址为0x40000000的内存中。
*(unsigned int *)0x40000000 = 0x12345678
int regn_toggle(unsigned int x)
{
if(x<0 || x>31)
return -1;
P()
*(volatile unsigned int *)0x1F000010 ^ = (1<<x);
return 0;
V()
}
//汇编:
LDR R1,=0x1F000010 //R0=0x1F000010.MOV R1,#0x1F000010非法
LDR R0,[R1]//读出0x1F000010地址下的内容
EOR R0,R0,#(1<<10)
STR R0,[R1]
3.4 比较指令
- CMP R1 ,#100 ;将寄存器 R1的值与立即数100相减,并根据结果设置CPSR的标志位(NZCV)。
- CMN R1, R0 ;将寄存器 R1 的值与寄存器 R0 的值相加,根据结果设置 CPSR 的标志位。
- TST R1 ,#0xffe ;将寄存器R1的值与立即数0xffe 按位与,并根据结果设置 CPSR 的标志位。、
- TEQ R1 ,R2 ;将寄存器 R1 的值与寄存器 R2 的值按位异或 ;并根据结果设置 CPSR 的标志位,比较R1与R2是否相等
思考:
4)unsigned int a,判断a的第10位是否1?
//C语言:
if(a == (1<<10))错
if(a & (1<<10))对
if(a & (1<<10) == (1<<10))错:==优先级高于& ----if(a&1)
if((a & (1<<10)) == (1<<10))对
//汇编语言:
TST R4,R4,#(1<<10) //逻辑运算的结构是存放在NZCV中
数据处理指令包括:传送指令、算术运算指令、位运算指令、比较指令。比较指令可以自动影响标志位,如果其他的数据处理器指令要影响标志位需要在指令后面加一个“S”。
void delay(void)
{
int data=0x10000;
while(data--);
}
//汇编1:
delay:
MOV R1,#0x10000
tmp:
SUB R1,R1,#1
CMP R1,#0 //比较指令,会自动影响标志位。
BNE tmp
MOV PC,LR
//汇编2:
delay:
MOV R1,#0x10000
tmp:
SUBS R1,R1,#1 //s---会自动影响标志位。
BNE tmp
MOV PC,LR
难点:
数据处理指令中,会经常用到立即数,如:
MOV R0,#0x10000
ADD R1,R0,#0xFF00
BIC R1,R1,#(1<<10)
CMP R4,#0x100
注意:立即数并不是所有的数值都是合法的。主要是受到机器码编码位数的限制。在机器码中,给立即数的位数只有12bits。合法立即数的要求:
1、如果立即数的值是小于256,也就是可以使用8bits数来描述的。
如:100,200,80,250
2、如果一个立即数大于等于256,如果该立即数循环左移偶数位可以得到一个小于256的数,则该立即数是合法的。
0xFF00 —>0x0000FF00—>循环左移24bits得到0x000000FF。
0x101非法—>0001 0000 0001
设计程序的时候,如果要操作非法的立即数,如何处理?
使用LDR指令。
如:将0x1F000010传送到R1
MOV R1,#0x1F000010 //非法的
LDR R1,=0x1F000010 //合法的
3.5 跳转指令
B ----- Branch ----- 无条件跳转
BL ----- Brach Link -----跳转的同时,保存返回地址。
例:
main:
-------
-------
BL delay //跳转的同时保存了下一条指令的返回地址
-------
-------
loop: //while(1)
-------
-------
B loop //无条件跳转
delay:
-------
-------
MOV PC,LR
3.6 存储器访问指令
实现存储器(或者特殊功能寄存器)与通用寄存器(R0~R15)之间数据传送的指令。
一般CPU访问内存或者特殊功能寄存器,都是通过地址来访问的。
存储器访问指令:
1、LDR ---- loader–加载,通过存储器(或者特殊功能寄存器)的地址来读取该地址下的内容。即将存储器(或者特殊功能寄存器)的内容读取到通用寄存器。
例:有一个地址0x1FFF0000,读取该地址下的unsigned int数据。
//C语言:
unsigned int a;
a = *(unsigned int *)0x1FFF0000;
//汇编语言:
LDR R0,=0x1FFF0000
LDR R1,[R0] //寄存器间接寻址,读取R0地址下的内容到R1
//非法: LDR R1,[0x1FFF0000]
2、STR----store–存储。通过存储器(或者特殊功能寄存器)的地址将通用寄存器的数据保存到存储器(后者特殊功能寄存器)。
思考:将一个32bits的数0x12345678,写到一个地址为0x40000000的内存中。
*(unsigned int *)0x40000000 = 0x12345678
汇编语言:
LDR R0,=0x12345678
MOV R1,#0x40000000
STR R0,[R1]
四、处理器的接口
4.1 跑马灯实验
一、分析原理图,理解LED的控制原理
S5P6818处理器有四个GPIO引脚控制四个LED灯。
GPIO引脚输出高电平(3.3V),LED灭
GPIO引脚输出低电平(0V),LED亮。
二、分析S5P6818处理器的GPIO
S5P6818处理器GPIO分成了5组,分别是:A、B、C、D、E,每个组有32个引脚,分别:0~31.总共有160个引脚。
GPIOE13 ---- D7
GPIOC17 ---- D8
GPIOC8 ---- D9
GPIOC7 ---- D10
三、LED的控制流程
1、将GPIO引脚配置配置成IO功能
一般情况下,处理器的引脚有多种功能,在使用这个引脚的时候,首先要配置它的功能,GPIO只是引脚功能的一种。在S5P6818处理器中,一般一个引脚有四个功能,GPIO是其中的一种。
2、将这个GPIO再设置为OUPUT
处理器要控制外设,GPIO需要配置成输出;处理器要检测外设,GPIO要配置成输入。
3、设置GPIO引脚输出低电平,LED亮
4、设置GPIO引脚输出高电平,LED灭
四、分析寄存器
寄存器列表—控制GPIO常用的寄存器
1、GPIOxALTFN0/GPIOxALTFN1—配置一个引脚为I/O
设置引脚作为什么功能来使用。
GPIOxALTFN0 ---- Pin0~Pin15
GPIOxALTFN1 ---- Pin16~Pin31
以GPIOE13为例:
使用寄存器是GPIOEALTFN0寄存器配置GPIOE13作为GPIO功能。
GPIOEALTFN0寄存器的地址是:0xC001000+0xE020
GPIOEALTFN0寄存器有32个位,每个位为一个组,每个组控制一个引脚的复用功能。一个引脚的复用功能有四个;
GPIOE13:
GPIOEALTFN0[27:26] 00 ---- ALT Function0 —查找第二章手册
01 ---- ALT Function1
10 ---- ALT Function2
11 ---- ALT Function3
将GPIOE13引脚配置成GPIO功能----GPIOEALTFN0[27:26]=00
//C语言的程序:
*(unsigned int *)(0xC001000+0xE020) &= ~(3<<26)
//或者:
#define GPIOEALTFN0 (*(unsigned int *)(0xC001000+0xE020))
GPIOEALTFN0 &= ~(3<<26)
//汇编程序:
LDR R0,=0xC001E020
LDR R1,[R0]
BIC R1,R1,#(3<<26)
STR R1,[R0]
2、GPIOxOUTENB----将一个GPIO配置成输入或者输出
每个位对应一个引脚,通过寄存器的这个位设置GPIO位输入还是输出
以GPIOE13为例:
GPIOEOUTENB寄存器,该寄存器的地址0xC001E004,GPIOE13配置为输出,GPIOEOUTENB[13]=1.
//C语言程序:
#define GPIOEOUTENB (*(unsigned int *)(0xC001000+0xE004))
GPIOEALTFN0 |= (1<<13);
//汇编程序:
LDR R0,=0xC001E004
LDR R1,[R0]
ORR R1,R1,#(1<<13)
STR R1,[R0]
3、GPIOxOUT ----设置GPIO输出的电平
以GPIOE13为例:
GPIOEOUT寄存器的地址0xC001E000,
如果将GPIOE13设置输出高电平,GPIOEOUT[13]=1
//C语言的程序:
#define GPIOEOUT (*(unsigned int *)(0xC001000+0xE000))
GPIOEOUT |= (1<<13)
//汇编程序:
LDR R0,=0xC001E000
LDR R1,[R0]
ORR R1,R1,#(1<<13)
STR R1,[R0]
如果将GPIOE13设置输出低电平,GPIOEOUT[13]=0
//C语言的程序:
#define GPIOEOUT (*(unsigned int *)(0xC001000+0xE000))
GPIOEOUT &= ~(1<<13);
//汇编程序:
LDR R0,=0xC001E000
LDR R1,[R0]
BIC R1,R1,#(1<<13)
STR R1,[R0]
完整跑马灯代码:
C语言实现跑马灯:
//GPIOE
#define GPIOEOUT (*(volatile unsigned int *)0xC001E000)
#define GPIOEOUTENB (*(volatile unsigned int *)0xC001E004)
#define GPIOEALTFN0 (*(volatile unsigned int *)0xC001E020)
#define GPIOEALTFN1 (*(volatile unsigned int *)0xC001E024)
//GPIOC
#define GPIOCOUT (*(volatile unsigned int *)0xC001C000)
#define GPIOCOUTENB (*(volatile unsigned int *)0xC001C004)
#define GPIOCALTFN0 (*(volatile unsigned int *)0xC001C020)
#define GPIOCALTFN1 (*(volatile unsigned int *)0xC001C024)
static void delay(void);
void _start(void)
{
//配置GPIOE13为输出模式
GPIOEALTFN0 &= ~(3<<26);
GPIOEOUTENB |= 1<<13;
//配置GPIOC17为输出模式
GPIOCALTFN1 &= ~(3<<2);
GPIOCALTFN1 |= 1<<2;
GPIOCOUTENB |= 1<<17;
//配置GPIOC7、GPIOC8为输出模式
GPIOCALTFN0 &= ~((3<<16)|(3<<14));
GPIOCALTFN0 |= (1<<16)|(1<<14);
GPIOCOUTENB |= (1<<8)|(1<<7);
while(1)
{
//点亮D7,其他灯熄灭
GPIOEOUT &= ~(1<<13);
GPIOCOUT |= 1<<17;
GPIOCOUT |= 1<<8;
GPIOCOUT |= 1<<7;
delay();
//点亮D8,其他灯熄灭
GPIOEOUT |= 1<<13;
GPIOCOUT &= ~(1<<17);
GPIOCOUT |= 1<<8;
GPIOCOUT |= 1<<7;
delay();
//点亮D9,其他灯熄灭
GPIOEOUT |= 1<<13;
GPIOCOUT |= 1<<17;
GPIOCOUT &= ~(1<<8);
GPIOCOUT |= 1<<7;
delay();
//点亮D10,其他灯熄灭
GPIOEOUT |= 1<<13;
GPIOCOUT |= 1<<17;
GPIOCOUT |= 1<<8;
GPIOCOUT &= ~(1<<7);
delay();
}
}
void delay(void)
{
volatile unsigned int t=0x2FF0000;
while(t--);
}
汇编实现跑马灯:
#define GPIOEOUT 0xC001E000
#define GPIOEOUTENB 0xC001E004
#define GPIOEOUTALTFN0 0xC001E020
#define GPIOEOUTALTFN1 0xC001E024
.text //代码段的开始
.global _start
_start:
//GPIOEOUTALTFN0&=~(3<<26);
ldr r1,=GPIOEOUTALTFN0
ldr r0,[r1] //将GPIOEOUTALTFN0寄存器的值取出来
ldr r2,=~(3<<26);//将~(3<<26)赋值给r2寄存器
and r0,r0,r2; //r0=r0&r2
str r0,[r1] //修改GPIOEOUTALTFN0的值
//允许GPIOE13输出电平
//GPIOEOUTENB|=1<<13;
ldr r1,=GPIOEOUTENB
ldr r0,[r1] //将GPIOEOUTENB寄存器的值取出来
ldr r2,=(1<<13);//将(1<<13)赋值给r2寄存器
orr r0,r0,r2; //r0=r0|r2
str r0,[r1] //修改GPIOEOUTENB的值
//自定义地址标签
loop:
ldr r1,=GPIOEOUT
ldr r0,[r1] //将GPIOEOUT寄存器的值取出来
ldr r2,=~(1<<13);//将~(1<<13)赋值给r2寄存器
and r0,r0,r2; //r0=r0&r2
str r0,[r1] //修改GPIOEOUTALTFN0的值
bl delay //delay
ldr r1,=GPIOEOUT
ldr r0,[r1] //将GPIOEOUT寄存器的值取出来
ldr r2,=(1<<13);//将(1<<13)赋值给r2寄存器
orr r0,r0,r2; //r0=r0|r2
str r0,[r1] //修改GPIOEOUTENB的值
bl delay //delay
b loop
delay:
push {r0,lr}
mov r0,#0x20000000
delay_loop:
sub r0,r0,#1 //r0=r0-1
cmp r0,#0 //判断r0是否为0
bne delay_loop //若r0!=0,则跳转到delay_loop
bx lr //否则函数返回
.end //代码段的结束
4.2 按键控灯实验
一、原理图
通过分析原理图,得知四个按键接了四个GPIO。
如果按键没有按下,GPIO引脚的电平是高电平
如果按键按下,GPIO引脚的电平是低电平。
K2-----GPIOA28
K3-----GPIOB30
K4-----GPIOB31
K6-----GPIOB9
二、按键的检测流程(以K2按键为例)
1、将GPIOA28引脚配置为IO引脚
GPIOAALTFN1[25:24]=00b //alt fuction0是IO
2、将GPIOA28这个IO引脚配置成输入引脚
GPIOAOUTENB[28]=0b //GPIOA28 Input Mode
3、读取引脚输入的电平,判断按键是否按下
读取GPIOAPAD寄存器,判断该寄存器的第28位;为1,按键没有按下,为0,按键按下。
//C语言程序:
#define GPIOAPAD (*(volatile unsigne int *)(0xC001000+0xA018))
if(GPIOAPAD & (1<<28))
{
按键没有按下
}
else
{
按键按下
}
//汇编语言:
LDR R0,=0xC001A018
LDR R1,[R0]
TST R1,#(1<<28)
BLEQ led_on //按键按下
BLNE led_off //按键松开
led_on:
led_off:
按键控灯完整实现:
C语言:
//GPIOA
#define GPIOAPAD (*(volatile unsigned int *)0xC001A018)
#define GPIOAOUTENB (*(volatile unsigned int *)0xC001A004)
#define GPIOAALTFN0 (*(volatile unsigned int *)0xC001A020)
#define GPIOAALTFN1 (*(volatile unsigned int *)0xC001A024)
//GPIOB
#define GPIOBPAD (*(volatile unsigned int *)0xC001B018)
#define GPIOBOUTENB (*(volatile unsigned int *)0xC001B004)
#define GPIOBALTFN0 (*(volatile unsigned int *)0xC001B020)
#define GPIOBALTFN1 (*(volatile unsigned int *)0xC001B024)
//GPIOE
#define GPIOEOUT (*(volatile unsigned int *)0xC001E000)
#define GPIOEOUTENB (*(volatile unsigned int *)0xC001E004)
#define GPIOEALTFN0 (*(volatile unsigned int *)0xC001E020)
#define GPIOEALTFN1 (*(volatile unsigned int *)0xC001E024)
//GPIOC
#define GPIOCOUT (*(volatile unsigned int *)0xC001C000)
#define GPIOCOUTENB (*(volatile unsigned int *)0xC001C004)
#define GPIOCALTFN0 (*(volatile unsigned int *)0xC001C020)
#define GPIOCALTFN1 (*(volatile unsigned int *)0xC001C024)
static void led_init(void);
static void key_init(void);
static void delay(void);
void _start(void)
{
//led初始化
led_init();
//按键初始化
key_init();
while(1)
{
//检测K4按键是否按下
if((GPIOBPAD & (1<<31)) == 0)
{
delay();
while(!(GPIOBPAD & (1<<31)));
//D7灯状态翻转
GPIOEOUT ^= 1<<13;
delay();
}
//检测K3按键是否按下
if((GPIOBPAD & (1<<30)) == 0)
{
delay();
while(!(GPIOBPAD & (1<<30)));
GPIOCOUT ^= 1<<17;
delay();
}
//检测K2按键是否按下
if((GPIOAPAD & (1<<28)) == 0)
{
delay();
while(!(GPIOAPAD & (1<<28)));
GPIOCOUT ^= 1<<8;
delay();
}
//检测K6按键是否按下
if((GPIOBPAD & (1<<9)) == 0)
{
delay();
while(!(GPIOBPAD & (1<<9)));
GPIOCOUT ^= 1<<7;
delay();
}
}
}
void led_init(void)
{
//配置GPIOE13为输出模式
GPIOEALTFN0 &= ~(3<<26);
GPIOEOUTENB |= 1<<13;
//配置GPIOC17为输出模式
GPIOCALTFN1 &= ~(3<<2);
GPIOCALTFN1 |= 1<<2;
GPIOCOUTENB |= 1<<17;
//配置GPIOC7、GPIOC8为输出模式
GPIOCALTFN0 &= ~((3<<16)|(3<<14));
GPIOCALTFN0 |= (1<<16)|(1<<14);
GPIOCOUTENB |= (1<<8)|(1<<7);
//所有灯熄灭
GPIOEOUT |= 1<<13;
GPIOCOUT |= (1<<17)|(1<<8)|(1<<7);
}
void key_init(void)
{
//PA28 输入模式
GPIOAALTFN1 &= ~(3<<24); //将[25:24]清零,也那就是选择多功能0
GPIOAOUTENB &= ~(1<<28); //设置GPIOA28引脚为输入模式
//PB9 输入模式
GPIOBALTFN0 &= ~(3<<18); //将[19:18]清零,也那就是选择多功能0
GPIOBOUTENB &= ~(1<<9); //设置GPIOB9引脚为输入模式
//PB30 输入模式
GPIOBALTFN1 &= ~(3<<28); //将[29:28]清零
GPIOBALTFN1 |= 1<<28; //GPIOB30引脚设置为GPIO功能
GPIOBOUTENB &= ~(1<<30); //设置GPIOB30引脚为输入模式
//PB31 输入模式
GPIOBALTFN1 &= ~(3<<30); //将[31:30]清零
GPIOBALTFN1 |= 1<<30; //GPIOB31引脚设置为GPIO功能
GPIOBOUTENB &= ~(1<<31); //设置GPIOB31引脚为输入模式
}
void delay(void)
{
volatile unsigned int t=0x2000000;
while(t--);
}
汇编语言:
@GPIOA
#define GPIOAPAD 0xC001A018
#define GPIOAOUTENB 0xC001A004
#define GPIOAALTFN0 0xC001A020
#define GPIOAALTFN1 0xC001A024
@GPIOB
#define GPIOBPAD 0xC001B018
#define GPIOBOUTENB 0xC001B004
#define GPIOBALTFN0 0xC001B020
#define GPIOBALTFN1 0xC001B024
@GPIOE
#define GPIOEOUT 0xC001E000
#define GPIOEOUTENB 0xC001E004
#define GPIOEALTFN0 0xC001E020
#define GPIOEALTFN1 0xC001E024
@GPIOC
#define GPIOCOUT 0xC001C000
#define GPIOCOUTENB 0xC001C004
#define GPIOCALTFN0 0xC001C020
#define GPIOCALTFN1 0xC001C024
.text @Start the code by .text
.globl _start
_start:
bl led_init
bl key_init
loop:
@检测K4按键是否按下
k4:
ldr r0,=GPIOBPAD
ldr r1,[r0]
tst r1,#(1<<31)
beq led_on @按键按下
bne k3 @没有按下就找下一个按钮
led_on:
bl delay
ldr r0,=GPIOBPAD
ldr r1,[r0]
tst r1,#(1<<31) @第31位是否为0
bne loop1
beq led_on @再检查一次,就是按下熄灭
loop1:
@D7灯状态翻转
ldr r0,=GPIOEOUT
ldr r1,[r0]
ldr r2,=1<<13
eor r1,r1,r2
str r1,[r0]
bl delay
@检测K3按键是否按下
k3:
ldr r0,=GPIOBPAD
ldr r1,[r0]
tst r1,#(1<<30)
beq led3_on @按键按下
bne k2 @没有按下就找下一个按钮(多个if的判断)
led3_on:
bl delay
ldr r0,=GPIOBPAD
ldr r1,[r0]
tst r1,#(1<<30)
bne loop2
beq led3_on
loop2:
@灯状态翻转
ldr r0,=GPIOCOUT
ldr r1,[r0]
ldr r2,=1<<17
eor r1,r1,r2
str r1,[r0]
bl delay
@检测K2按键是否按下
k2:
ldr r0,=GPIOAPAD
ldr r1,[r0]
tst r1,#(1<<28)
beq led4_on @按键按下
bne k6 @没有按下就找下一个按钮
led4_on:
bl delay
ldr r0,=GPIOAPAD
ldr r1,[r0]
tst r1,#(1<<28)
bne loop3
beq led4_on
loop3:
@灯状态翻转
ldr r0,=GPIOCOUT
ldr r1,[r0]
ldr r2,=1<<8
eor r1,r1,r2
str r1,[r0]
bl delay
@检测K6按键是否按下
k6:
ldr r0,=GPIOBPAD
ldr r1,[r0]
tst r1,#(1<<9)
beq led6_on @按键按下
bne loop @全部没有就从头开始
led6_on:
bl delay
ldr r0,=GPIOBPAD
ldr r1,[r0]
tst r1,#(1<<9)
bne loop4
beq led6_on @??????
loop4:
ldr r0,=GPIOCOUT
ldr r1,[r0]
ldr r2,=1<<7
eor r1,r1,r2
str r1,[r0]
bl delay
b loop
led_init:
push {r0,lr}
@配置GPIOE13为输出模式
ldr r0,=GPIOEALTFN0
ldr r1,[r0]
ldr r2,=~(3<<26)
and r1,r1,r2
str r1,[r0]
ldr r0,=GPIOEOUTENB
ldr r1,[r0]
ldr r2,=1<<13
orr r1,r1,r2
str r1,[r0]
@配置GPIOC17为输出模式
ldr r0,=GPIOCALTFN1
ldr r1,[r0]
ldr r2,=~(3<<2)
and r1,r1,r2
str r1,[r0]
ldr r0,=GPIOCALTFN1
ldr r1,[r0]
ldr r2,=1<<2
orr r1,r1,r2
str r1,[r0]
ldr r0,=GPIOCOUTENB
ldr r1,[r0]
ldr r2,= 1<<17
orr r1,r1,r2
str r1,[r0]
@配置GPIOC7、GPIOC8为输出模式
ldr r0,=GPIOCALTFN0
ldr r1,[r0]
ldr r2,=3<<16
ldr r3,=3<<14
orr r4,r2,r3
bic r1,r1,r4
str r1,[r0]
ldr r0,=GPIOCALTFN0
ldr r1,[r0]
ldr r2,=1<<16
ldr r3,=1<<14
orr r4,r2,r3
orr r1,r1,r4
str r1,[r0]
ldr r0,=GPIOCOUTENB
ldr r1,[r0]
ldr r2,=1<<8
ldr r3,=1<<7
orr r4,r2,r3
orr r1,r1,r4
str r1,[r0]
@所有灯熄灭
ldr r0,=GPIOEOUT
ldr r1,[r0]
ldr r2,=1<<13
orr r1,r1,r2
str r1,[r0]
ldr r0,=GPIOCOUT
ldr r1,[r0]
ldr r2,=1<<17
ldr r3,=1<<8
ldr r4,=1<<7
orr r3,r3,r4
orr r2,r2,r3
orr r1,r1,r2
str r1,[r0]
pop {r0,lr}
bx lr
key_init:
push {r0,lr}
@PA28 输入模式
ldr r0,=GPIOAALTFN1 @将[25:24]清零,也那就是选择多功能0
ldr r1,[r0]
ldr r2,=~(3<<24)
and r1,r1,r2
str r1,[r0]
ldr r0,=GPIOAOUTENB @设置GPIOA28引脚为输入模式
ldr r1,[r0]
ldr r2,=~(1<<28)
and r1,r1,r2
str r1,[r0]
@PB9 输入模式
ldr r0,=GPIOBALTFN0 @将[19:18]清零,也那就是选择多功能0
ldr r1,[r0]
ldr r2,=~(3<<18)
and r1,r1,r2
str r1,[r0]
ldr r0,=GPIOBOUTENB @设置GPIOB9引脚为输入模式
ldr r1,[r0]
ldr r2,=~(1<<9)
and r1,r1,r2
str r1,[r0]
@PB30 输入模式
ldr r0,=GPIOBALTFN1 @将[29:28]清零
ldr r1,[r0]
ldr r2,=~(3<<28)
and r1,r1,r2
str r1,[r0]
ldr r0,=GPIOBALTFN1 @GPIOB30引脚设置为GPIO功能
ldr r1,[r0]
ldr r2,=1<<28
orr r1,r1,r2
str r1,[r0]
ldr r0,=GPIOBOUTENB @设置GPIOB30引脚为输入模式
ldr r1,[r0]
ldr r2,=~(1<<30)
and r1,r1,r2
str r1,[r0]
@PB31 输入模式
ldr r0,=GPIOBALTFN1 @将[31:30]清零
ldr r1,[r0]
ldr r2,=~(3<<30)
and r1,r1,r2
str r1,[r0]
ldr r0,=GPIOBALTFN1 @GPIOB31引脚设置为GPIO功能
ldr r1,[r0]
ldr r2,=1<<30
orr r1,r1,r2
str r1,[r0]
ldr r0,=GPIOBOUTENB @设置GPIOB31引脚为输入模式
ldr r1,[r0]
ldr r2,=~(1<<31)
and r1,r1,r2
str r1,[r0]
pop {r0,lr}
bx lr
delay:
push {r0,lr}
ldr r0, =0x2000000
delay_loop:
sub r0,r0,#1
cmp r0,#0
bne delay_loop
pop {r0,lr}
bx lr
.end
4.3 PWM2实现蜂鸣器控制实验
一、原理图得知硬件的控制原理
GPIOC14引脚控制蜂鸣器。
GPIOC14输出高电平,蜂鸣器响
GPIOC14输出低电平,蜂鸣器不响
二、蜂鸣器的控制流程
1、将GPIOC14引脚配置成IO功能
GPIOCALTFN0[29:28] = 01b
2、将GPIOC14再配置成输出
GPIOCOUTENB[14]=1b
3、配置GPIOC14输出高电平,蜂鸣器响
GPIOCOUT[14]=1b
4、配置GPIOC14输出低电平,蜂鸣器不响
GPIOCOUT[14]=0b
三、什么是PWM?
PWM----Pulse Width Modulation脉冲宽度调制。
处理器有一个引脚该引脚有PWM输出的功能。该引脚可以输出一个连续方波,我们可以通过编程,来设置方波的频率,可以调整它的占空比。
PWM的特性参数:
1)幅度—高电平的电压3.3V,可以通过硬件设计来修改。
2)周期----Th+Tl,可以通过软件编程来设置。
3)占空比duty----一个周期内,高电平的时间占周期时间的比例–Th/(Th+Tl)*100%,也可以通过软件进行设置。
PWM的控制:控制输出方波的频率(周期)和占空比。
四、PWM的作用
1、液晶屏背光的亮度
2、调整直流电机转速
3、配置舵机的转角
4、开关电源的输出电压
通过PWM的频率和占空比来设置
五、S5P6818处理器中PWM的特点
1、有5个Timer,其中Timer0~Timer带有PWM功能(有外部输出引脚,可以输出方波,可以编程设置方波的频率和占空比)。timer4是一个内部定时器。
2、PWM定时器的计数值是32bits。
六、实验原理
PWM2的输出控制一个蜂鸣器,方波是高电平时,蜂鸣器响;方波是低电平的时候,蜂鸣器不响。方波是连续输出的。
实验目的:
设置PWM2的周期T=5s(f=0.2Hz),占空比20%。蜂鸣器响1s,停4s,连续输出过程。
问题:
如何设置PWM的频率和占空比?
七、PWM框图
PWM2的频率=PCLK/第一次分频值/第二次分频/PWM2的计数值
PWM2的占空比=PWM2的比较值/PWM2的计数值
八、PWM的控制流程(寄存器的配置过程)
1、将GPIOC14引脚配置成PWM2的功能
GPIOCALTFN0[29:28]=10b
寄存器的地址:0xC0010000+0xC020
2、设置PWM2的第一次分频值和第二次分频值
第一次分频:
TCFG0[15:8]=199;//分频值是8bits,则第一次分频值是199+1=200
第二次分频:是一个多路选择器
TCFG1[11:8]=0100b //则第二次分频值是16
所以:
PWM2的计数频率:200MHz/(199+1)/(16)=62500Hz
3、设置计数值,得到PWM2的输出方波的频率(f=0.2Hz)
f = 计数频率/计数值
0.2=62500/计数值----->计数值=312500
设置计数值的寄存器:
TCNTB2=312500
4、设置比较值,得到PWM输出方波的占空比(duty=20%)
duty=比较值/计数值100%
20% = 比较值/312500100%---->比较值=62500
5、PWM2的配置,关闭反相器、启动手动加载功能,关闭PWM2,关闭自动加载
TCON[15]=0
TCON[14]=0
TCON[13]=1
TCON[12]=0
6、启动PWM2,关闭手动加载,打开自动加载,打开PWM2
TCON[15]=1
TCON[14]=0
TCON[13]=0
TCON[12]=1
PWM原理:
PWM2寄存器:
蜂鸣器完整实现
C语言:
//beep pwm2---复用的GPIO为GPIOC14,输出高电平蜂鸣器响,输出低电平,蜂鸣器不响
#define GPIOCALTFN0 *(volatile unsigned int *)0xc001c020
#define GPIOCOUT *(volatile unsigned int *)0xc001c000
#define GPIOCOUTENB *(volatile unsigned int *)0xc001c004
#define TCFG0 *(volatile unsigned int *)0xc0018000
#define TCFG1 *(volatile unsigned int *)0xc0018004
#define TCON *(volatile unsigned int *)0xc0018008
#define TCNTB2 *(volatile unsigned int *)0xc0018024
#define TCMPB2 *(volatile unsigned int *)0xc0018028
//pclk = 200MHz
void delay(int val);
void _start(void)
{
//1.led init
GPIOCALTFN0 &= ~(3<<24);
GPIOCALTFN0 |= (1<<24);
GPIOCOUTENB |= (1<<12);
//2.pwm init
//fun2, gpioc14---pwm2
GPIOCALTFN0 &= ~(3<<28);
GPIOCALTFN0 |= (2<<28);
//pwm2 stop
TCON &= ~(1<<12);
//prescaler value =199;
TCFG0 &=(0xff<<8);
TCFG0 |=(199<<8);
//divider value = 16
TCFG1 &=(0xf<<8);
TCFG1 |=(4<<8);
TCNTB2 = 62500; //T = 1s
TCMPB2 = 31250; //duty = 50%
//pwm2 enable manual update
TCON |= (1<<13);
//pwm2 disable manual update
TCON &= ~(1<<13);
//pwm2 inverter off
TCON &= ~(1<<14);
//pwm2 enbale auto reload, start
TCON |= ((1<<15) + (1<<12));
while(1)
{
//GPIOC12 toggle
GPIOCOUT ^= (1<<12);
delay(0x400000);
}
}
void delay(int val)
{
volatile int i;
for(i=0;i<val;i++);
}
汇编实现:
//beep pwm2---复用的GPIO为GPIOC14,输出高电平蜂鸣器响,输出低电平,蜂鸣器不响
#define GPIOCALTFN0 0xc001c020
#define GPIOCOUT 0xc001c000
#define GPIOCOUTENB 0xc001c004
#define TCFG0 0xc0018000
#define TCFG1 0xc0018004
#define TCON 0xc0018008
#define TCNTB2 0xc0018024
#define TCMPB2 0xc0018028
//pclk = 200MHz
.text //代码的开始
.global _start
_start:
//1.led init
//GPIOCALTFN0 &= ~(3<<24)
ldr r1,=GPIOCALTFN0
ldr r0,[r1]
ldr r2,=~(3<<24)
and r0,r0,r2
str r0,[r1]
//GPIOCALTFN0 |= (1<<24)
ldr r1,=GPIOCALTFN0
ldr r0,[r1]
ldr r2,=(1<<24)
orr r0,r0,r2
str r0,[r1]
//GPIOCOUTENB |= (1<<12)
ldr r1,=GPIOCOUTENB
ldr r0,[r1]
ldr r2,=(1<<12)
orr r0,r0,r2
str r0,[r1]
//2.pwm init
//fun2, gpioc14---pwm2
//GPIOCALTFN0 &= ~(3<<28)
ldr r1,=GPIOCALTFN0
ldr r0,[r1]
ldr r2,=~(3<<28)
and r0,r0,r2
str r0,[r1]
//GPIOCALTFN0 |= (2<<28)
ldr r1,=GPIOCALTFN0
ldr r0,[r1]
ldr r2,=(2<<28)
orr r0,r0,r2
str r0,[r1]
//pwm2 stop
//TCON &= ~(1<<12)
ldr r1,=TCON
ldr r0,[r1]
ldr r2,=~(1<<12)
and r0,r0,r2
str r0,[r1]
//prescaler value =199;
//TCFG0 &=(0xff<<8)
ldr r1,=TCFG0
ldr r0,[r1]
ldr r2,=(0xff<<8)
and r0,r0,r2
str r0,[r1]
//TCFG0 |=(199<<8)
ldr r1,=TCFG0
ldr r0,[r1]
ldr r2,=(199<<8)
orr r0,r0,r2
str r0,[r1]
//divider value = 16
//TCFG1 &=(0xf<<8)
ldr r1,=TCFG1
ldr r0,[r1]
ldr r2,=(0xf<<8)
and r0,r0,r2
str r0,[r1]
//TCFG1 |=(4<<8)
ldr r1,=TCFG1
ldr r0,[r1]
ldr r2,=(4<<8)
orr r0,r0,r2
str r0,[r1]
//TCNTB2 = 62500
ldr r1,=TCNTB2
ldr r0,[r1]
ldr r0,=#62500
str r0,[r1]
//TCMPB2 = 31250
ldr r1,=TCMPB2
ldr r0,[r1]
ldr r0,=#31250
str r0,[r1]
//pwm2 enable manual update
//TCON |= (1<<13)
ldr r1,=TCON
ldr r0,[r1]
ldr r2,=(1<<13)
orr r0,r0,r2
str r0,[r1]
//pwm2 disable manual update
//TCON &= ~(1<<13)
ldr r1,=TCON
ldr r0,[r1]
ldr r2,=~(1<<13)
and r0,r0,r2
str r0,[r1]
//pwm2 inverter off
//TCON &= ~(1<<14)
ldr r1,=TCON
ldr r0,[r1]
ldr r2,=~(1<<14)
and r0,r0,r2
str r0,[r1]
//pwm2 enbale auto reload, start
//TCON |= ((1<<15) + (1<<12))
ldr r1,=TCON
ldr r0,[r1]
ldr r2,=((1<<15) + (1<<12))
orr r0,r0,r2
str r0,[r1]
loop:
ldr r1,=GPIOCOUT
ldr r0,[r1]
ldr r2,=(1<<12)
eor r0, r0,r2
str r0,[r1]
bl delay
b loop
delay:
push {r0,lr}
ldr r0,=#0x4000000
delay_loop:
sub r0,r0,#1 //r0=r0-1
cmp r0,#0 //判断r0是否为0
bne delay_loop //若r0!=0,则跳转到delay_loop
bx lr //否则函数返回
.end
4.4 看门狗实验
一、什么是看门狗?
当一个计算机系统(嵌入式系统)收到了外界的干扰或者内部硬件错误而造成了系统死机,看门狗会产生一个复位信号,系统会重启,系统恢复到正常工作的状态。
二、看门狗的工作原理
看门狗是处理器内部的一个模块,是一个定时器,可以设置一个复位时间,复位时间一般都是秒级,如10秒。看门狗启动后,内部有一个计数器,计数器的计数值不停的减1,当计数值减到0,会产生一个复位信号,这样系统就复位重启。在系统正常工作的时候,需要有程序在计数值减到0之前将其恢复初始值,避免重启;当系统死机的时候,喂狗的过程(将计数值恢复到初始值的过程)就没有了,但是定时器还是继续工作的,这样就产生了复位信号。
三、看门狗的框体
结论1:
看门狗的复位时间(10S/0.1Hz)
f=PCLK/(第一次分频)/第二次分频/计数值
T=1/f
其中:
PCLK—外设的时钟源,200MHz
第一次分频值----prescaler value +1=WTCON[15:8]+1 ,注意:不能超过255
第二次分频值----devision factor =16、32、64、128四选一,通过WTCON[4:3]
计数值----WTCNT[15:0]
结论2:
看门狗喂狗:
WTCNT=初始值
四、分析看门狗的寄存器
五、分析看门狗的设计流程
1、设置看门狗的第一次分频值和第二次分频值,得到看门狗的计数频率
WTCON &= ~(0xff<<8);
WTCON |= (249<<8);//第一次分频值249+1
WTCON |= (3<<3);//第二次分频值128
//得到计数频率:200MHz/(249+1)/128=6250Hz
2、设置看门狗的计数值,得到看门狗的复位时间(如:10S)
6250/计数值=0.1Hz,得到计数值=62500
WTCNT = 62500;
3、打开看门狗的复位功能
WTCON |= (1<<0);
4、打开看门狗,看门狗就开始工作了
WTCON |= (1<<5);
5、看门狗喂狗
WTCNT = 62500;
WTCON |= (3<<3);//第二次分频值128
//翻译成汇编:
LDR R0,=0xC0019000
LDR R1,[R0]
ORR R1,R1,#(3<<3)
STR R1,[R0]
看门狗完整实现:
C语言:
#define GPIOCOUT *(volatile unsigned int *)0xc001c000
#define GPIOCOUTENB *(volatile unsigned int *)0xc001c004
#define GPIOCALTFN0 *(volatile unsigned int *)0xc001c020
#define WTCON *(volatile unsigned int *)0xc0019000
#define WTDAT *(volatile unsigned int *)0xc0019004
#define WTCNT *(volatile unsigned int *)0xc0019008
#define WTCLRINT *(volatile unsigned int *)0xc001900C
#define RSTCON1 *(volatile unsigned int *)0xc0012004
void delay(int val);
void _start(void)
{
GPIOCALTFN0 &= ~(3<<24);
GPIOCALTFN0 |= (1<<24);
GPIOCOUTENB |= (1<<12);
RSTCON1 |= (1<<26);//wdt reset--->26
//WTDAT = 61035; //10s
WTCNT = 61035; //10s
WTCLRINT = 0; //clear int
//prescaler value=256, divider value=128
//pclk =200MHz,//enable int & enable reset
WTCON = (255<<8) + (3<<3) + (1<<2) + (1<<0);
WTCON |= (1<<5); //enable wdt
while(1)
{
GPIOCOUT ^= (1<<12);
delay(0x4000000);
}
}
void delay(int val)
{
volatile int i;
for(i=0;i<val;i++);
}
汇编实现:
#define GPIOCOUT 0xc001c000
#define GPIOCOUTENB 0xc001c004
#define GPIOCALTFN0 0xc001c020
#define WTCON 0xc0019000
#define WTDAT 0xc0019004
#define WTCNT 0xc0019008
#define WTCLRINT 0xc001900C
#define RSTCON1 0xc0012004
.text @代码段开始的地方
.global _start
_start:
@GPIOCALTFN0 &= ~(3<<24);
ldr r0,=GPIOCALTFN0
ldr r1,[r0]
ldr r2,=~(3<<24)
and r1,r1,r2
str r1,[r0]
@GPIOCALTFN0 |= (1<<24);
ldr r0,=GPIOCALTFN0
ldr r1,[r0]
ldr r2,=(1<<24)
orr r1,r1,r2
str r1,[r0]
@GPIOCOUTENB |= (1<<12);
ldr r0,=GPIOCOUTENB
ldr r1,[r0]
ldr r2,= (1<<12)
orr r1,r1,r2
str r1,[r0]
@RSTCON1 |= (1<<26);
ldr r0,=RSTCON1
ldr r1,[r0]
ldr r2,= (1<<26)
orr r1,r1,r2
str r1,[r0]
@WTCNT = 61035;
ldr r0,=WTCNT
ldr r1,[r0]
ldr r1,=#61035
str r1,[r0]
@WTCLRINT = 0;
ldr r0,=WTCLRINT
ldr r1,[r0]
ldr r1,=#0
str r1,[r0]
@WTCON = (255<<8) + (3<<3) + (1<<2) + (1<<0);
ldr r0,=WTCON
ldr r2,= (255<<8)
ldr r3,=(3<<3)
and r2,r2,r3
ldr r3,=(1<<2)
and r2,r2,r3
ldr r3,=(1<<0)
and r2,r2,r3
str r2,[r0]
@WTCON |= (1<<5);
ldr r0,=WTCON
ldr r1,[r0]
ldr r2,=(1<<5)
orr r1,r1,r2
str r1,[r0]
loop:
@GPIOCOUT ^= (1<<12);
ldr r0,=GPIOCOUT
ldr r1,[r0]
ldr r2,=(1<<12)
eor r1,r1,r2
str r1,[r0]
@delay(0x4000000);
bl delay
b loop
delay:
push {r0,lr}
ldr r0, =0x4000000
delay_loop:
sub r0,r0,#1
cmp r0,#0
bne delay_loop
pop {r0,lr}
bx lr
.end @结束代码
五、ADC
一、什么是ADC
ADC----Analog Digital Converter模数转换器,将模拟的电信号转换成数字量的一个转换器。
模拟信号:连续变化的一个电压值
数字量:010101数字
数字量方便存储、传输、处理
二、ADC的结构图
ADC作用:通过转后的数字量可以求出ADC输入的模拟电压,根据模拟电压可以得出传感器的特性参数(灰尘的浓度、温度等)。
三、分析原理图
通过调整可调电阻的阻值,得到一个模拟的电压值,这个模拟的电压送给ADC的输入(AIN0),经过ADC转换器,可以得到一个数字量。
四、ADC的框图
五、ADC的工作流程
1、打开分频器,设置ADC的分频值
2、设置ADC进入低功耗模式
3、设置ADC的转换通道是AIN0
4、打开ADC,ADC进入转换模式
5、判断ADC转换是否完成
6、如果ADC转换完成,得到数字量