汇编语言
map.lds文件
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.data :
{ *(.data) }
. = ALIGN(4);
.bss :
{ *(.bss) }
}
mov与ldr伪指令
.global _start
_start:
mov r1,0x00000001 @不可以装载超过8bit数
ldr r2,=0x00000001 @可以装载32bit数
.end
指令机器码
@-----指令机器码-----
.if 0
mov r2,#0xA
.endif
cmp
@-----cmp-----
.if 0
mov r1,#5
mov r2,#5
cmp r1,r2 @cmp不需要加s就可以影响cpsr的标志位
@moveq r3,#0xA
@movne r3,#0xB
movge r3,#0xB @大于等于
movle r3,#0xA @小于等于
.endif
cmn
@—cmn—
mov r0,#0x1
mov r1,#0x2
cmn r0,r1
movgt r3,r0
movlt r3,r1
.endif
lsl lsr asr ror rrx
@---lsl lsr asr ror rrx---
mov r1,#0x7000000F
@lsls r1,#1
@asr r1,#1 @右移,高位用原数的31bit填充,
@ror r1,#1 @循环右移,高位用低位移出的填充
rrx r1,r1 @右移一位,高位用上次运行的cspsr的C位填充 对于减法无借位c位置1
and orr eor bic
@--and orr eor bic---
mov r0,#0xFF
mov r1,#0xF0
@and r0,r1
@orr r1,r0
@eor r1,r0 @32位数的逻辑异或操作
bic r0,#0x0F @32位数的逻辑位清零指数
add adc
@---add adc---
.if 0
mov r0,#
.endif
合法立即数
@---合法立即数---
ldr伪指令
@---ldr伪指令---
tst teq
@---tst teq---
.if 0
@mov r0,#0xE0
@tst r0,#0x3 @z=0,该位置不为空,z=1,该位置为空
mov r1,#3
mov r2,#3
teq r1,r2 @ 两个不相等 z=0
寄存器和内存
单寄存器传送指令:
ldr/ldrh/ldrb 内存->寄存器
str/strh/strb 寄存器->内存(mov r1,#8)
1.不带偏移的赋值
str r1,[r2] @不带偏移
2.前索引赋值法:带偏移的赋值
先地址偏移,再赋值,类似++i;
str r1,[r2,#4] @自动更新基值地址
str r1,[r2,#4]! @自动更新基值地址
2.后索引赋值法:带偏移的赋值
先赋值,再更新(偏移)地址,类似i++;
str r1,[r2],#4
测试代码
.text
.global _start
_start:
mov r0,#0 @把寄存器r0,设置为0x00000000
ldr r1,[r0] @r0为一个地址,[]取地址里的内容ldr一次取四个字节32bit,取完的数址放在r1中
ldr r2,=buf @r2指向申请的16字节内存单元
@str r1,[r2] @不带偏移
@str r1,[r2,#4] @前索引,先更新地址,再赋值
@str r1,[r2,#4]! @前索引,自动更新基值地址
str r1,[r2],#4 @后索引,先赋值,再更新地址
ldr r3,=buf1
ldr r4,=buf2
ldr r5,=buf3
.data @数据段
buf: @用buf指向数据段
.space 16 @.space定义空闲段 malloc 16字节,用buf标识
buf1: @定义一个数组,用buf1指向
.word 0xFF @.word四个字节
.word 0x12
.word 0x34,0x56,0xFFFFFFFF
buf2:
.byte 'a'
.byte 'b'
.byte 'c'
.byte 'd'
buf3:
.string "A hello word!"
.end
块寄存器传送指令
ldm 将多个内存单元的内容传送到多个寄存器中 内存->寄存器
stm 将多个寄存器的内容传送到多个内存单元中 寄存器->内存
不能单独使用,需要加上后缀,表明从小地址到大地址,还是从大地值往小地址方向传输
后缀:
ia:先赋值在增加地址 小地址->大地址 i++
ib:先增加地址再赋值 小地址->大地址 ++i
da:先赋值在减地址 大地址->小地址 i–
db:先减地址再赋值 大地值->小地址 --i
ldmia的特性
1.顺序赋值
2.省略性写法
stmia的特性(从寄存器存贮到内存当中)
测试代码
.text
.global _start
_start:
ldr r0,=buf
@ldmia r0,{r1,r2,r3,r4,r5} @先赋值再增加地址
ldmia r0!,{r1-r5} @加了!会强制更新地址
@ldmib r0!,{r1-r3,r4-r5} @先增加地址,再赋值
@ldmda r0,{r1-r5}
@ldmda r0!,{r1-r5}
@ldmdb r0!,{r1-r5}
ldr r6,=buf1
@stmia r6,{r1-r5}
@stmia r6!,{r1-r5}
@stmib r6,{r1-r5} @先增加地址再赋值,不加!不更新地址
@stmib r6!,{r1-r5} @先增加地址再赋值,加了!更新地址
@stmda r6,{r1-r5}
@stmda r6!,{r1-r5} @先赋值,在增加地址
@stmdb r6,{r1-r5} @先增加地址,再赋值,不更新地址
@stmdb r6!,{r1-r5} @先增加地址,再赋值,自动跟新地址
.data
buf:
.word 0x1,0x2,0x3,0xFFFFFFFF,0xCC,0xAA
bufend:
buf1:
.space 60
buf1end:
.end
测试现象
!!!
!!!!!这里对于buf1,在测试ldmda/db/,strda/db寄存器写入内存的时候,我应该吧buf1end,给r6,而不是buf1,设置buf1给r6会导致“内存覆盖”!!
压栈和出栈 R13=SP存放栈指针
先进后出
压栈:
寄存器放到内存当中去
stmfd(与stmdb类似):倒着放进去,先减小地址再,赋值–i
出栈:
内存放到寄存器当中去
ldmfd(ldmia):正着出去,先赋值再增加地址i++
psr的操作指令
默认svc,特权模式
高权限可以切换低权限,地权限不能切换高权限
cpsr的第五位:
10000 User mode;
10001 FIQ mode;
10011 SVC mode;
10111 Abort mode;
11011 Undfined mode;
11111 System mode;
10110 Monitor mode;
10010 IRQ
测试代码 :svc模式转换成usr模式
.text
.global _start
_start:
@--- svc->usr ---
mrs r0,cpsr @获取cpsr的第五位是啥
bic r0,#0x1F @清空cpsr的第五位
orr r0,#0x10 @给cpsr增加新权限
msr cpsr,r0 @把这个权限放到cpsr中
.end
软交换指令(内存和地址互相交换指令)
swp(4个字节交换)
swpb(1个字节交换)
.text
.global _start
_start:
ldr r0,=buf
mov r1,#0xCC
swp r1,r1,[r0]
nop
.data
buf:
.word 0xFFFFFFFF
.end
软中断
中断:是一个过程,是指CPU在执行程序的过程中插入了另外一段程序的执行过程。
中断类型:软件中断–》是由程序员设计的中断,是可控的–>通过软件的方式发起的 中断
硬件中断–》是由硬件故障导致的中断,是不可控的
目标:设计一个软件中断,分析软件中断的处理流程
分析:1如何产生一个软件中断?
swi中断号(88)用户指令用户模式下使用
lr
2哪里能够接收软件中断?异常向量表
异常向量表–》是一个特殊位置上的一段代码
特殊位置:一般位于程序的最开始位置0x00000000,也可以修改
一段代码:由8条跳转指令组成的语句块,并且顺序是固定的
b reset复位异常
nop未定义异常
b swi_handler软中断异常
nop存取异常
nop 数据异常
nop 预留异常
nop 普通中断irq
nop 快速中断fiq
3软件中断的处理
swi_handler:保存现场处理中断恢复现场
保存现场:保存原工作模式下寄存器的值到栈里
stmfd sp!,{r0~r12,lr}
处理中断:1得到中断号
lr-4=得到了swi 88对应的地址
[lr-4]=swi 88的指令机器码格式
指令机器码的低24位存放的是中断号
2处理88号中断
比较swi 88的指令机器码的低24位是不是88?
如果是,处理88号中断;不是,程序顺序往下执行
恢复现场:将保存在栈里的内容恢复到原工作模式下
当swi 88执行时,ARM内核:
1拷贝cpsr到spsr
2设置cpsr=0x000000d3
3保存swi 88的下一条指令地址到lr
4设置PC=0x00000008
当处理完中断,恢复现场时,ARM内核:
1将spsr拷贝到cpsr
2将swi 88的下一条指令地址lr给pc
svc----->user---------->svc---------->usr
初始指令切换swi 88恢复现场
异常优先级:
异常指定了优先级和固定的服务顺序:
reset优先级最高
data abort
FIQ
IRQ
profectch abort
SWI
undefine优先级最低
测试代码
.text
.global _start
_start:
b reset @复位异常
nop @未定义异常
b swi_handler @软中断异常
nop @读取异常
nop @数据异常
nop @预留异常
nop @irq
nop @fiq
reset:
@set svc mode stack
ldr sp,=stackend
@svc->user
mrs r0,cpsr
bic r0,#0x1F
orr r0,#0x10
msr cpsr,r0
@test
mov r0,#0xAA
swi 88
mov r1,#0xBB
swi_handler:
@save r0_r12 and lr
stmfd sp!,{r0-r12,lr}
@处理中断
@get swi number
sub r0,lr,#4
ldr r1,[r0]
bic r1,#0xFF000000
cmp r1,#88
bleq func
cmp r1,#99
bleq func1
@test
mov r0,#0xFF
ldmfd sp!,{r0-r12,pc}^
func:
mov r3,#8
nop
mov pc,lr @存放程序调用的返回地址
func1:
nop
.data
stack:
.space 160
stackend:
.end
流程分析表
C和汇编混合编程
汇编调用C
测试代码
@--- start.h ---
.text
.global _start
_start:
ldr sp,=bufend
mov r10,sp
mov r0,#3
mov r1,#6
bl func @调用函数的时候可能会用到栈,因此先创建一个buf栈
mov r10,#0xAA
nop
.data
buf:
.space 160
bufend:
.end
//main.c
int func(int a,int b)
{
return a+b;
}
C调用汇编函数
@---start.s---
.text
.global _start
_start:
ldr sp,=bufend
b main
.global mycopy
mycopy:
loop:
ldrb r2,[r0],#1 @r0的内容给r2
strb r2,[r1],#1 @将r2的内容给r1
cmp r2,#0
bne loop
mov r0,#5
mov pc,lr
.data
buf:
.space 160
bufend:
.end
//main.c
extern int mycopy(char *psrc,char *pdest);
int main()
{
char *src="hello";
char buf[10]={0};
int ret=0;
ret=mycopy(src,buf);
return ret;
}
外设裸机编程
点灯
1.首先查外设的板子的手册找到LED,记住对应端口名称,这里找到LED2对应端口名称为GHG_COK
2.再去找核心板的手册,查找这个GHG_COK,可见其对应位置为GPX2_7
3.再去芯片手册查找这个GPX2,看看如何操作它
(1)
(2)
测试代码_汇编
@---start.s---
.text
.global _start
_start:
@初始化led2
ldr r0,=0x11000c40
ldr r1,[r0]
bic r1,#0xF0000000
orr r1,#0x10000000
str r1,[r0]
loop:
@打开led2
ldr r0,=0x11000c44
ldr r1,[r0]
orr r1,#0x80
str r1,[r0]
@delay
bl delay
@关闭led2
ldr r0,=0x11000c44
ldr r1,[r0]
bic r1,#0x80
str r1,[r0]
bl delay
b loop
delay:
ldr r0,=0x10000000
mov r1,#0
start:
add r1,#1
cmp r1,r0
bne start
mov pc,lr
.end
测试代码_汇编和C混合编程
@--start.s---
.text
.global _start
_start:
ldr sp,=bufend
b main
.data
buf:
.space 160
bufend:
.end
//main.c
//把0x11000c40的地址中的内容给GPX2XXX
#define GPX2CON (*(volatile unsigned int *)0x11000c40)
#define GPX2DAT (*(volatile unsigned int *)0x11000c44)
void led2_init()
{
//GPX2CON高四位清零 31 30 29 28,然后给高四位置为0x1
GPX2CON=GPX2CON&~(0xF<<28)|(0x1<<28);
}
void led2_on()
{
GPX2DAT=GPX2DAT|(0x1<<7);
}
void led2_0ff()
{
GPX2DAT=GPX2DAT&~(0x1<<7);
}
void delay()
{
int i;
for(i=0;i<1000000;i++)
{
}
}
int main()
{
led2_init();
while(1)
{
led2_on();
delay();
led2_0ff();
delay();
}
return 0;
}
uart
uart:Universal Asynchronous Receiver and Transmitter(通用异步收发器)
也称为串口,收发数据串行进行。
总线:uart spi iic 485 can总线
1 uart:通用异步收发器 用于设备接收或发送数据
2 关于uart概念:
通信方式:
单工通信:通信方向只能是一个方向
半双工通信:双向通信,但不能同时
全双工通信:双向通信,且能同时
uart可以实现全双工通信
bps:比特率 每秒钟传送bit位的个数
波特率:每秒钟传送码元的个数
码元:是一个数据单位,一个码元中携带的bit位可以是1bit、2bit、4bit。。
串口传送数据的码元=1bit
串口的波特率=比特率
115200 9600
串行通信(串口):一个只能发送一个bit位数据
并行通信(并口):一个可以发送多个Bit 位,传输效率较高
同步通信:是指数据传送是以数据块(一组字符)为单位,字符和字符之间、字符
内部的位与位之间是完全同步的(字符间同步、字符内部同步 有同步时钟)
异步通信:是指数据传送是以字符为单位,字符和字符之间是完全异步的,字符内部的位与位之间是完全同步的(字符间异步、字符内部同步)。
串口通信就是一种异步通信方式,也称为起止异步同步式
目标:通过串口实现字符回显
配置步骤
1.在外围板上找到串口位置,以及所对应的芯片信号接口
2.打开和新版原理图检索UART_AUDIO_TXD
3.查芯片手册
配置操作
1.配置管脚信息(初始化UART)
(1)GPA1_0、GPA1_1管脚为为uart模式
(2)配置UART收发数据的格式(奇偶位、停止位、数据位)
(3)配置UART收发数据的波特率 115200
波特率因子计算
公式:
DIV_VAL = (SCLK_UART/(bps *16)) - 1
说明:
bps:是所期望的波特率
SCLK_UART:提供给串口的时钟频率Hz
DIV_VAL:整数放在UBRDIVn,小数放在UFRACVALn,需要先将小数除以16(相当于乘以16)
问:这里使用的fs4412的串口,系统提供的时钟周期是100MHz,期望波特率为115200
解:
由,DIV_VAL = (SCLK_UART/(bps *16)) - 1
得,DIV_VAL = (100 000 000hz/(115200 *16)) - 1
= 53.25
UBRDIVn=53=0x25
UFRACVALn=0.25*16=4=0x4
(4)配置UART收发数据的方式(中断或者轮询)
2.配置数据的收发所需要配置的东西
(1)收发位置
接收寄存器
发送寄存器
(2)收发时间
发送buf不为空进行发送,接收buf为空进行接收
3.编写程序进行调试
(1)uart的初始化函数
(2)数据的发送函数
(3)数据的接收函数