目录
工程搭建
程序编写
带标志位的加法ADC ADCS
跳转指令B\BL
栈的应用->叶子函数的调用过程
栈的应用->非叶子函数的调用过程
异常中断产生指令
协处理器指令
伪操作
安装交叉编译工具
Makefile
宏定义
预编译指令
申请一个字的空间 .word
申请多个字节空间
嵌套编程
方式一:汇编跳转到C
方式二:C跳转到汇编
方式三:C内嵌汇编
点亮LED灯
下载调试程序
闪灯
工程搭建
重要指令
; 1.指令: 编译完生成一条机器码存储在内存单元当中,CPU执行时能完成对应的操作
; 1) 数据处理指令: 对数据进行逻辑、算数运算
; 2) 跳转指令: 实现程序的跳转,实质是修改PC
; 3) Load/Store指令:对内存的读写操作
; 4)状态寄存器传送指令:对CPSR进行读写操作
; 5)异常中断产生指令:触发软中断,常用于内核的系统调用
; 6)协处理器指令;操作协处理器的指令
; 2.伪操作:不会生成机器码也不会占用内存,其作用是告诉编译器怎样编译(类似C中的宏定义)
; 3.伪指令:不是指令,编译器在编译时将其替换成等效的指令
AREA RESET,CODE,READONLY ;定义一个代码段RESET,属性为只读
ENTRY ;程序的入口
; 1) 数据处理指令:
MOV R0,#1
MOV R2,#3
; MOV R3,#4
; MVN R4,#0X000000FF ;R3=~0X000000FF
; MOV R5,#260
;LDR R5,=0X12345678
;立即数:合法数,包含在指令中的数
;问:立即数和普通变量的区别? 立即数优点:执行的快
;立即数缺点:长度有限 这里规定最大为12bit 连续数(0-255)其它的时离散的
;加法指令 ADD
;ADD R1,R0,R2 ;R1=R0+R2
;减法指令SUB
;SUB R1,R2,#1
;乘法指令 MUL
;MUL R1,R2,R0
AND R3,R1,#1 ;指令 与 R3=R1&1
ORR R3,R2,R1 ;指令 或 R3=R2|R1
EOR R3,R2,R1 ;指令 按位异或 R3=R2^R1
LSL R3,R2,R1 ;指令 左移 R3=R2<<R1
LSR R3,R2,R1 ;指令 右移 R3=R2>>R1
END
程序编写
; 1.指令:编译完生成一条机器码存储在内存单元当中,
; CPU执行时能完成对应的操作
; 1) 数据处理指令: 对数据进行逻辑、算数运算
; 2) 跳转指令: 实现程序的跳转,实质是修改PC
; 3) Load/Store指令:对内存的读写操作 LDR STR STM LDM
; 4)状态寄存器传送指令:对CPSR进行读写操作
; 5)异常中断产生指令:触发软中断,常用于内核的系统调用
; 6)协处理器指令;操作协处理器的指令
; 2.伪操作:不会生成机器码也不会占用内存,
; 其作用是告诉编译器怎样编译(类似C中的宏定义)
; 3.伪指令:不是指令,编译器在编译时将其替换成等效的指令
AREA RESET,CODE ,READONLY ;开辟一个段
ENTRY ;标号 程序的入口 main
MOV R0,#0x01 ;MOV搬移指令 相当于R0=0X20
MOV R1,#0X02
ADD R2,R1,R0 ;R2=R1+R0
SUB R2,R1,R0
MUL R2,R0,R1
MOV PC,#0
; LDR R1,=0X12345678
; 立即数:
; 立即数是合法的数 在这里 12bit 0-255连续的 255以后是离散
; 问:立即数和普通变量的区别?
; 立即数优点:速度快 缺点:范围小 执行长度有限
AND R3,R0,#1 ;与
ORR R3,R0,#1 ;或
EOR R3,R0,#0 ;异或
LSL R3,R2,R1 ;指令 左移 R3=R2<
LSR R3,R2,R1 ;指令 右移 R3=R2>>R1
END
;1、指令 (8)数据运算指令的结果对条件位(NZCV)的影响
;N 运算器中产生了负数的结果N置1
;Z 运算器产生了0的结果Z置1
;C 加法时产生了C自动置1否则为0,减法时产生了借位C位,置0
;V 有符号数符号位发生变化时自动置1
;[31:24]条件域用F表示 [23:16]状态域用S表示 [23:16]预留域用X表示 [7:0]控制域用C表示
AREA RESET,CODE,READONLY;定义一个代码段RESET,属性为只读
ENTRY ;汇编的入口,相当于c中的main,伪操作
; MOV R0,#1 ;指令
; MOV R1,#2 ;指令
; ;SUB R2,R0,R1 ;使用减法指令产生负数,验证N位,发现没有被置位
; ;默认情况下数据运算不会对条件位(NZCV)产生影响,我们可以在指令后添加后缀'S'
; SUBS R2,R0,R1 ;加S并使用减法指令产生负数,验证N位,发现被置位
;测试Z和减法C位
; MOV R0,#1 ;指令
; MOV R1,#1 ;指令
; SUBS R2,R0,R1 ;加S并使用减法指令产生0,验证Z位,发现Z和C都被置1,因为减法时产生借位C会被置0,结果0没有借位
;测试加法C位
; MOV R0,#0XFFFFFFFE ;0XFFFFFFFE不是立即数,但是编译没有报错,看一下仿真里编译窗口里,MOV被替换为MVN了,数也变了
; MOV R1,#3
; ADDS R2,R1,R0 ;加法指令产生了进位(注意这里是32位),C位被置1
;验证V
MOV R0,#0X7FFFFFFE ;0X7FFFFFFE加了3之后产生溢出
MOV R1,#3 ;0X7FFFFFFE加了3之后产生溢出
ADDS R2,R1,R0 ;加法指令产生了进位(注意这里是32位),C位被置1
END
带标志位的加法ADC ADCS
;第一个数; 0x00000001 FFFFFFFF
;第二个数; 0x00000003 00000004
;第一个数的低32位放到R1,高32bit放R2
;第二个数的低32位放R3,高32bit放R4
;低加低高加高 运算结构的低32bit放R5,高32bit放R6
MOV R1,#0XFFFFFFFF
MOV R2,#0x00000001
MOV R3,#0X00000004
MOV R4,#0X00000003
ADDS R5,R1,R3
ADC R6,R4,R2 ;本质R6=R4+R2+‘c’
跳转指令B\BL
ENTRY
MOV R0,#2
CMP R0,#1
BEQ JUMP ;if(EQ)(B JUMP)
;本质:if(Z==0){B JUMP}
;BNE JUMP 不等
MOV R0,#2
MOV R0,#3
JUMP
MOV R0,#4
MOV R1,#5
MOV PC,LR
END
栈的应用->叶子函数的调用过程
AREA RESET,CODE ,READONLY ;开辟一个段
ENTRY ;标号 程序的入口 main
MOV SP,0X40000020
MAIN
MOV R1,#1
MOV R2,#5
BL F
ADD R3,R1,R2
T
B T
F
STMFD SP!,{R1,R2}
MOV R1,#2
MOV R2,#3
SUB R3,R2,R1
LDMFD SP!,{R1,R2}
MOV PC,LR
END
栈的应用->非叶子函数的调用过程
ENTRY
MOV SP,#0X40000020
MAIN
MOV R1,#3
MOV R2,#5
BL FUNC1
ADD R3,R1,R2
STOP
B STOP
FUNC1
STMFD SP!,{R1,R2,LR}
MOV R1,#20
MOV R2,#10
BL FUNC2
SUB R3,R1,R2
LDMFD SP!,{R1,R2,LR}
MOV PC,LR
FUNC2
STMFD SP!,{R1,R2}
MOV R1,#30
MOV R2,#40
SUB R3,R1,R2
LDMFD SP!,{R1,R2}
MOV PC,LR
END
异常中断产生指令
;SWI 不讲因为咱们用不到,一般只有写内核的人会用到
;cpu执行完这个指令后就会产生一个软中断
协处理器指令
- 数据运算
- 内存访问
- 与主处理器通信
协处理器指令
- 协处理器数据运算指令
CDP
- 协处理器储存器访问指令
STC 将协处理器中的数据储存到存储器
LDC 将存储器中的数据读取到协处理器中
- 协处理器寄存器传送指令
MRC 将协处理器中寄存器的数据传送到ARM处理器中的寄存器
MCR 将ARM处理器寄存器中的数据读取到协处理器寄存器中
- 协处理器CP15
CP15管内存的
如果ARM直接操作内存,就操作到物理内存了。
ARM不直接操作内存,arm找一个助理,这个助理是CP15。这时候ARM想访问内存怎么办?
ARM先把想访问的内存地址(虚拟地址)给CP15,CP15将这个地址转换为物理地址。然后去物理地址取数据,取出数据之后,再返给ARM
->指令
LDR R1,[R2]
->伪指令
LDR R1,=0X12345678
LDR R1,=STOP 把STOP的地址给R1
LDR R1,STOP 把STOP的地址中的内容给R1
问:伪操作是谁规定的
伪操作
1、不同编译器的伪操作语法不同
安装交叉编译工具
A)说明
交叉编译工具链:arm-none-linux-gnueabi-gcc
注:arm-none-linux-gnueabi-:交叉编译工具链的名字,名字就是一个代号,在工作中用的不一定是这个,不同的公司用的交叉编译工具链的名字不同
B)获取
自己去gnu官网获取交叉编译工具链的源码,自己进行编译生成对应的交叉编译工具链。不推荐:编译过程很繁琐
直接从芯片厂家获取交叉编译工具链
直接跟开发板的生成厂家获取交叉编译工具链
直接找主管获取交叉编译工具链(单位)
C)安装:
- 在ubuntu的家目录(~)下,创建toolchain
mkdir toolchain
- 拷贝gcc-4.6.9.tar.xz到toolchain目录下
cp 目录/gcc-4.9.4.tar.xz ~/toolchain
- 解压缩交叉编译工具链
tar -xvf gcc-4.6.9.tar.xz
- 配置环境变量
打开 sudo vi /etc/bash.bashrc
- 在最后一行添加以下内容:
export PATH=$PATH:/home/hq/toolchain/gcc-4.6.9/bin/
- 修改为自己的路径
- 使环境变量立即生效
source /etc/bash.bashrc
- 测试交叉编译工具链是否安装成功
arm-none-linux-gnueabi-gcc -v
- 打印以下内容,表示成功
gcc version 4.6.9 (Sourcery G++ Lite 2010.09-50)
3、使用工程模板
Makefile
- Makefile的一些基本概念
目标 : 依赖 命令
- Makefile的基本工作原理
(1)当我们make xx时,Makefile会自动执行xx这个目标下的命令语句。
(2)当我们make xx时,是否执行命令是取决于依赖的,依赖如果成立才可以执行。
(3)当直接make时,和make 第一个目标 效果是一样的。第一个目标实际就是默认目标。
- make的依赖性
make会一层又一层的去找文件的依赖关系。直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会退出,并报错,而对于所定义的命令的错误或是编译不成功,make根本不care。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后边的文件还是不在,那么对不起,我就不工作啦。、
- Makefile分析
cc编译 ld 链接 objdump反汇编 -g gdb调试 -00 表示优化 -c 表示生成目标文件
elf是编译好的链接文件 是个可执行文件
- VI 打开文件
宏定义
.text.text @代码段
.global _start @将_start声明成一个全局的符号,其他.s文件也可以引用
_start: @汇编的入口
.EQU PI,12 @汇编的宏定义
MOV R1,#PI
.end 。
make之后看dis结尾的反汇编文件
预编译指令
MOV R1,#1
.if 0
MOV R2,#2
.endif
申请一个字的空间 .word
MOV R1,#1
.word 0x12345678
MOV R2,#2
申请多个字节空间
MOV R1,#1
.SPACE 20,0XFF
MOV R2,#2
嵌套编程
方式一:汇编跳转到C
------------S----------
MOV R1,#1
BL Func
MOV R2,#2
----C------
void Func(void);编译器最后一句话会自动编译为MOV PC,LR
{
}
方式二:C跳转到汇编
------C------------ 把汇编的标号看做C的函数
extern Func1(void);
void Func(void
{
int a;
a++;
Func1();
a=0;
}
------s-----
Func1
MOV R1,#1
MOV PC,LR
方式三:C内嵌汇编
-----C-----
void Func1(void)
{
int a;
a=1;
asm
{
"MOV R1,#1\n"
"MOV R2,#2\n"
};
a=2;
}
点亮LED灯
- 开发板介绍
- cpu控制硬件原理
- 我们学习的所有指令,六大指令里边,只有内存访问指令能访问cpu之外的内容。那cpu如何控制硬件?*load/store指令-->操作4G内存
- 任何一个芯片都有一个地址映射表。告诉你地址空间是如何映射的,便于我们找到对应的硬件地址。
- 我们的SOC型号是S5P6818,对应的芯片用户手册为:S5P6818X用户手册V0.00,其中一章是:Memory map或Memory Controller 中的一张表中可以看地址隐射关系。
- 硬件控制原理:
- CPU不能直接控制硬件,硬件是由其对应的控制器(寄存器)来控制的
- 每个控制器(寄存器)都会映射到CPU寻址范围内的一段空间
- CPU通过对控制器(寄存器)的读和写实现对硬件间接的控制
- CPU间接控制硬件。
- 本地开发和交叉开发
- 本地开发:本地编写代码,本地编译代码,本地运行代码
- 交叉开发:本地编写代码,本地编译代码,开发板运行代码
- PC中Linux下,程序也能执行,但不是仿真。他是真正的运行。但你移植到ARM平台就不一定能执行,需要转化代码的指令。这个转换过程你可以当成是交叉编译。
- 点灯实验
- 分析电路图
1、分析电路图的思路:从外设(地板)---》SOC(核心板)分析
2、 分析LED
3、 在电路板上找到led灯的位置
LED灯旁边会有白色的字,此白字为丝印,LED灯旁边的字,是led灯的编号
打开底板的原理图,在原理图上搜索led灯编号(RGB)
4、分析led的电路图
共阳三色二极管:三个二极管的,正极接到一起
RGB_R/RGB_G/RGB_B 表示网络标号
5、网络标号名字相同表示具有相同的,电气连接属性,反应到电路板上,,他们通过导线连接到一起
6、根据网络标号到核心板原理图,找到soc哪个引脚驱动着LED灯
- 控制思想
工作寄存器:R0-R15,cpsr,spsr,由ARM公司提供,没有地址
控制寄存器:就是内存的一块空间,具有地址,由芯片厂家提供。寄存器是在GPIO章节被使用,所以看芯片手册的时候看GPIO章节,里面一定有相关寄存器的使用和功能实现。
我们只需要向控制寄存器中写值或者读值,就可以让我们处理器完成一定的功能。这也就是我们软件编程控制硬件的思想。
- 读懂芯片手册
- 概述:简介->特点->框图
- 机械尺寸->IO功能描述表
- 系统引导:概述->功能描述->内部引导->外部引导
1、GPIOxOUT:控制引脚输出高低电平
2、RED_LED--->GPIOA28
3、GPIOAOUT ---> 0xC001A000
4、GPIOA28输出高电平:
GPIOAOUT[28] <--写-- 1
5、GPIOA28输出低电平:
GPIOAOUT[28] <--写-- 0
6、GPIOxOUTENB:控制引脚的输入输出模式
GPIOAOUTENB ---> 0xC001A004
7、 设置GPIOA28引脚为输出模式:
GPIOAOUTENB[28] <--写-- 1
8、GPIOxALTFN:控制引脚功能的选择
GPIOAALTFN1 ---> 0xC001A024
9、设置GPIOA28引脚为GPIO功能:
GPIOAALTFN1[25:24] <--写-- 0b00
00 = ALT Function0
01 = ALT Function1
10 = ALT Function2
11 = ALT Function3
GPIO引脚功能的选择:每两位控制一个GPIO引脚,
- 编写程序
- 汇编
.text @代码段
.global _start @将_start声明成一个全局的符号,其他.s文件也可以引用
_start: @汇编的入口
/*
实验步骤:
1)分析电路原理图,得出LED的控制方式->高电平亮,低电平灭
2)分析电路原理图,得出LED与SOC的连接关系->GPIOA28
3)分析芯片手册,找到对应的寄存器将GPIOA28设置成GPIO功能->GPIOAALTFN1(0XC001A024)
4)分析芯片手册,找到对应的寄存器将GPIOA28设置成OUTPUT功能->GPIOAOUTENB(0XC001A004)
5)分析芯片手册,找到对应的寄存器将GPIOA28设置输出高低电平->GPIOAOUT(0XC001A000)
*/
/*
地址:0xc001A024 数据:0x00000000
地址:0xc001A004 数据:0x10000000
地址:0xc001a000 数据:0x10000000/0x00000000
GPIOAOUTENB
地址:0xc001a000 数据:0x10000000/0x00000000
*/
@设置GPIOA28为GPIO功能
LDR R1,=0x00000000
lDR R2,=0XC001A024
STR R1,[R2]
@设置GPIOA28为OUTPUT功能
LDR R1,=0x10000000
lDR R2,=0XC001A004
STR R1,[R2]
@设置GPIOA28为高电平
LDR R1,=0x00000000
lDR R2,=0XC001A000
STR R1,[R2]
stop:
B stop
.end @汇编的结束
下载调试程序
1、拷贝.bin文件到windows中
2、开发板和电脑进行硬件连接
串口线的USB端插到电脑的USB口
串口线的串口端插到开发板的UART0端口上
开发板插上电源
3、配置windows超级终端
可以查看配置超级终端的使用说明文档资料中有, 如果串口线第一次使用需要安装串口驱动,串口驱动文件在资料中
4、配置超级终端:
在设备管理器中,查看串口线使用的那个端口号
配置端口属性:
波特率:115200
数据位:8
停止位:1
校验位:无
流控:无
5、开发板上电,超级终端会有打印信息
在倒计时减到0之前按任意键,进入到FS6818#界面
a、执行命令 loadb 0x43c00000 --》下载二进制文件到内存的0x43c00000
b、传输--》发送文件--》选择要下载.bin文件,选择Kermit协议 --》 确定下载
c、执行命令:go 0x43c00000 --》到0x43c00000位置运行代码
如果需要重新下载代码,重复步骤4
闪灯
用汇编写C逻辑
.text
.global _start
_start:
MAIN:
BL LED_CONFIG
LOOP:
BL LED_ON
BL DELAY
BL LED_OFF
BL DELAY
BL LOOP
LED_CONFIG:
LDR R1,=0X00000000
LDR R2,=0XC001A024
STR R1,[R2]
LDR R1,=0X10000000
LDR R2,=0XC001A004
STR R1,[R2]
MOV PC,LR
LED_OFF:
LDR R1,=0X00000000
LDR R2,=0XC001A000
STR R1,[R2]
MOV PC,LR
LED_ON:
LDR R1,=0X10000000
LDR R2,=0XC001A000
STR R1,[R2]
MOV PC,LR
DELAY:
LDR R1,=100000000
T:
SUB R1,R1,#1
CMP R1,#0
BNE T
MOV PC,LR
.end
作业:蜂鸣器
- C语言
纯地址方式
/*
* 地址:0xc001A024 数据:0x00000000
* 地址:0xc001A004 数据:0x10000000
* 地址:0xc001a000 数据:0x10000000/0x00000000
* 地址:0xc001a000 数据:0x10000000/0x00000000
* */
int main()
{
*(unsigned int *)0xc001A024=0x00000000;
*(unsigned int *)0xc001A004=0x10000000;
while(1)
{
*(unsigned int *)0xc001A000=0x00000000;
delay_ms(1000);
*(unsigned int *)0xc001A000=0x10000000;
delay_ms(1000);
}
}
宏定义
#define GPIOAALTFN1 (*(unsigned int *)0xc001A024)
#define GPIOAOUTENB (*(unsigned int *)0xc001A004)
#define GPIOAOUT (*(unsigned int *)0xc001A000)
int main()
{
GPIOAALTFN1=0x00000000;
GPIOAOUTENB=0x10000000;
while(1)
{
GPIOAOUT=0x00000000;
delay_ms(1000);
GPIOAOUT=0x10000000;
delay_ms(1000);
}
}
这时候去数据手册看地址规律,发现每一类比如A的地址是连续的
问C语言里什么的地址是联系的?
这里GPIOAPAD之后有一个没有连续 18 之后1C
typedef struct
{
unsigned int OUT;
unsigned int OUTENB;
unsigned int DETMODE0;
unsigned int DETMODE1;
unsigned int INTENB;
unsigned int DET;
unsigned int PAD;
unsigned int Gsssss;
unsigned int ALTFN0;
unsigned int ALTFN1;
}gpio;
#define GPIOA (*(gpio *)0xc001A000)
int main()
{
GPIOA.ALTFN1=0x00000000;
GPIOA.OUTENB=0x10000000;
while(1)
{
GPIOA.OUT=0x00000000;
delay_ms(1000);
GPIOA.OUT=0x10000000;
delay_ms(1000);
}
}