ARM汇编学习笔记

【笔记来源:广州大学汇编课程】

一、处理器结构和汇编语言

1.1 计算机语言

1.高级语言
主要是设计应用程序,有良好的库和模组,方便调用和代码的快速实现。
C/C++、java、python、C#、JS、…

2.汇编语言
底层语言,是直接和硬件(处理器的寄存器)打交道的。
汇编语言是和处理器的架构相关的----不同架构的处理器,汇编语言是不一样的。

3.机器语言
机器语言是二进制的机器码。处理器真正执行的代码。

1.2 汇编语言的特点

  1. 直接和硬件打交道,代码的执行效率比较高
//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
  1. 汇编语言比较节省内存。
    现在汇编语言应用的场合:8位的单片机
  2. 有些场合用高级语言是实现不了的,必须用汇编语言
    例如:处理器的初始化,内存初始化,stack初始化、中断的响应过程等。----BIOS程序或者嵌入式平台启动引导程序。
  3. 记很多汇编指令,代码编写效率低,不能使用现成的库。

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光刻机)–>麒麟处理器–>华为手机

  1. ARM是一种处理器的架构
    ARM— Advanced RISC Machine 高级的精简指令集计算机系统的机器。
    总部英国剑桥。ARM—>softbank—>Navid(英伟达)
  2. ARM也是一家公司的名字
  3. 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=100int 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
  • 算术运算的时候,运算的结果:是否是整数,是否为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% = 比较值/312500
100%---->比较值=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转换完成,得到数字量


版权声明:本文为weixin_43978453原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。