目录
一:点亮一个LED
1:原理图
当LED输入低电平时出现电压差, LED被点亮 (n的意思是低电平有效)
LED1 LED2 LED4分别接在 GPF4,5,6的IO口上
2:寄存器
配置GPFCON寄存器的[9:8]位为0b01-----输出模式 ; GPFCON--设置串口的模式
GPFDAT寄存器: 当该端口配置为输入端口时,对应的位为引脚状态。当端口配置为输出端口时,引脚状态与对应的位相同。当端口配置为功能引脚时,将读取未定义值。
GPF4对应GPFDAT寄存器的第4位, GPF[4]----0低电平/1高电平
GPFDAT寄存器--设置串口具体输出的内容
3:2440的框架和启动过程
A:框架
注意: CPU----里面有许多寄存器(R0~R15) ; 在CPU里面的寄存器是可以直接访问的.
GPIO控制器----里面有各种引脚,当然也包括我们今天使用的GPF4引脚; GPIO控制器里面也有寄存器(GPFCON, GPCDAT),不过这里面的寄存器需要地址访问, 不能向CPU里面的寄存器直接访问. 在芯片手册中有寄存器的地址.
B:启动过程
大多数的ARM芯片都是从0地址启动的, 当然这也包括我们讲述的2440
NOR启动 : NOR Flash基地址为0 , 片内RAM的地址为0x4000 0000
CPU读取出NOR第一个指令(前4个字节),执行
CPU继续在读取出其他的指令在执行; 一边读取一边执行
Nand启动 : 片内4KARM基地址为0, NOR启动不可访问
2440硬件把Nand的前4K内容复制到片内RAM上, 然后CPU从0地址取出第条指令执行
4:代码
cpp
/*
*点亮一个LED
*/
.text
.global _start
_start:
/* 配置GPFCON(0X56000050)寄存器的[9:8]位为01--输出模式*/
ldr r1,=0X56000050
ldr r0,=0x100
str r0,[r1]
/*
*配置GPFDAT寄存器为低电平(0x56000054)--输出低电平
*/
ldr r1,=0x56000054
ldr r0,=0
str r0,[r1]
/*死循环*/
halt:
b halt
我们采用的是交叉编译的方法---使用window书写汇编代码-----将汇编代码传给虚拟机-----在虚拟机下将传来的汇编代码编译为bin文件-----在将bin文件传给window-------window烧写bin文件给Linux开发板; 我们使用的是GPIO控制器里面的寄存器所以必须使用地址进行访问
5:ARM知识补充
程序计数器 R15: 寄存器 R15 保存程序计数器(PC),它总是用于特殊的用途。它经常可用于通用寄存器RO~R14 所使用的位置(即在指令编码中 R15 与 RO~R14 的地位一样,只是指令执行的结果不同),因此,可以认为它是一个通用寄存器。但是对于它的使用还有许多与指令相关的限制或特殊情况。这些将在具体的指令描述中介绍。通常,如果 R15 使用的方式超出了这些限制,那么指令将是不可预测的。
当指令对 R15 的读取没有超过任何对 R15 使用的限制时,读取的值是指令的地址加上 8个字节。由于 ARM 指令总是以字为单位,结果的 Bit[1:0]总是为 0。这种读取 PC 的方式主要用于对附近的指令和数据进行快速、与位置无关的寻址,包括程序中与位置无关的转移。
当使用 STR或 STM 指令保存 R15 时,出现了上述规则的一个例外。这些指令可将指令地址加 8字节保存(与其它指令读取 R15 一样)或将指令自身地址加 12 字节(将来还可能出现别的数据)。偏移量 8 还是 12(或是其它数值)取决于 ARM 的实现(也就是说,与芯片有关)。对于某个具体的芯片,它是个常量。这样使用 STR 和 STM 指令是不可移植的。
由于这个例外,最好避免使用 STR 和 STM 指令来保存 R15。如果很难做到,那么应当在程序中使用合适的指令序列来确定当前使用的芯片所使用的偏移量
在2440中R15(pc)的偏移量为8, 注意取决于他对数据的读取方式
x的地址=x的地址+8
当他在读取地址A指令的时候
已经在对地址A+4的指令进行译码
已经在读取地址A+8的指令
分析反汇编的代码:
cpp
led_on.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e59f1014 ldr r1, [pc, #20] ; 1c <halt+0x4>
4: e3a00c01 mov r0, #256 ; 0x100
8: e5810000 str r0, [r1]
c: e59f100c ldr r1, [pc, #12] ; 20 <halt+0x8>
10: e3a00000 mov r0, #0
14: e5810000 str r0, [r1]
00000018 <halt>:
18: eafffffe b 18 <halt>
1c: 56000050 undefined instruction 0x56000050
20: 56000054 undefined instruction 0x56000054
由于我们使用的是伪指令; 他是不存在的指令,最会被拆分真正的几条ARM指令;
上面的汇编代码都是由伪指令拆分而来的
优点 : 他可以表示任意值;
ARM指令===>32位,但是如果使用MOV的话并不能表示32位, 因为MOV32位中的一些位是用来表示他自己的,剩下的不够32位, 剩下的不够32位也只能表示一些立即数
cpp
ldr r1,=0X56000050=====>伪指令
6:c语言和汇编的应用
A:代码
我们需要写一个汇编代码, 给main函数设置内存, 调用main函数
cpp
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
/* 配置GPF4为输出引脚 */
*pGPFCON = 0x100;
/* 设置GPF4输出0 */
*pGPFDAT = 0;
return 0;
}
cpp
.text
.global _start
_start:
/* 设置内存: sp 栈 */
ldr sp, =4096 /* nand启动 */
// ldr sp, =0x40000000+4096 /* nor启动 */
/* 调用main */
bl main
halt:
b halt
cpp
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o start.o start.S
arm-linux-ld -Ttext 0 start.o led.o -o led.elf
arm-linux-objcopy -O binary -S led.elf led.bin
arm-linux-objdump -D led.elf > led.dis
clean:
rm *.bin *.o *.elf *.dis
我们使用makefile来编译, 避免重复多次的编译
可以看到x.ids文件中的地址和给板子烧录的bin文件地址一致;
B:分析汇编语言
r0~r3寄存器负责----调用者和被调用者的传递参数的问题;
r4~r11寄存器在函数中,可能被使用, 所以在人口中保存他们, 在出口中恢复他们;
cpp
led.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000
4: eb000000 bl c <main>
00000008 <halt>:
8: eafffffe b 8 <halt>
0000000c <main>:
c: e1a0c00d mov ip, sp
10: e92dd800 stmdb sp!, {fp, ip, lr, pc}
14: e24cb004 sub fp, ip, #4 ; 0x4
18: e24dd008 sub sp, sp, #8 ; 0x8
1c: e3a03456 mov r3, #1442840576 ; 0x56000000
20: e2833050 add r3, r3, #80 ; 0x50
24: e50b3010 str r3, [fp, #-16]
28: e3a03456 mov r3, #1442840576 ; 0x56000000
2c: e2833054 add r3, r3, #84 ; 0x54
30: e50b3014 str r3, [fp, #-20]
34: e51b2010 ldr r2, [fp, #-16]
38: e3a03c01 mov r3, #256 ; 0x100
3c: e5823000 str r3, [r2]
40: e51b2014 ldr r2, [fp, #-20]
44: e3a03000 mov r3, #0 ; 0x0
48: e5823000 str r3, [r2]
4c: e3a03000 mov r3, #0 ; 0x0
50: e1a00003 mov r0, r3
54: e24bd00c sub sp, fp, #12 ; 0xc
58: e89da800 ldmia sp, {fp, sp, pc}
Disassembly of section .comment:
00000000 <.comment>:
0: 43434700 cmpmi r3, #0 ; 0x0
4: 4728203a undefined
8: 2029554e eorcs r5, r9, lr, asr #10
c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1}
10: Address 0x10 is out of bounds.
C:内存空间
内存空间被分为三个部分:代码段(text segment,即程序代码)、数据段(data segment,即变量)和栈段(stack segment)。数据段从下往上增长,而栈从上向下增长图。在这两者之间是空闲的地址空间。栈的增长是随着程序的执行自动进行的,而数据的扩展则需要通过brk 系统调用来显式地完成,brk有一个参数来指定数据段的结束地址,它可比当前值大(表示扩展数据段 ),或是比当前值小(表示缩小数据段 )。当然,这个参数必须小于指针,否则栈和数据段将会重叠,这是不允许的。
7:内部机制
二:点亮2个灯
上面我们实现了被调用者给调用者传递参数;
我们这里学习---调用者给被调用者传递参数
cpp
int len_on(int num)
{
/*设置寄存器 点亮LED2*/
unsigned int* GPFCON = 0x56000050;
unsigned int* GPFDAT = 0x56000054;
if (num == 4)
{
/*设置输出模式*/
*GPFCON = 0x100;
}
if (num == 5)
{
/*设置输出模式*/
*GPFCON = 0x400;
}
/*输出低电平*/
*GPFDAT = 0;
return 0;
}
void Delay(int n)
{
while (n--);
}
cpp
/*
*点亮一个LED
*/
.text
.global _start
_start:
/*设置内存: sp栈*/
ldr sp,=4096 /*nand启动*/
ldr sp,=0x40000000+4096/*nor启动*/
mov r0 ,#4
bl len_on
ldr r0 ,=10000
bl Delay
mov r0 ,#5
bl len_on
halt:
b halt
cpp
led.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000
4: e59fd018 ldr sp, [pc, #24] ; 24 <halt+0x4>
8: e3a00004 mov r0, #4
c: eb000006 bl 2c <len_on>
10: e59f0010 ldr r0, [pc, #16] ; 28 <halt+0x8>
14: eb000022 bl a4 <Delay>
18: e3a00005 mov r0, #5
1c: eb000002 bl 2c <len_on>
00000020 <halt>:
20: eafffffe b 20 <halt>
24: 40001000 andmi r1, r0, r0
28: 00002710 andeq r2, r0, r0, lsl r7
0000002c <len_on>:
2c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
30: e28db000 add fp, sp, #0
34: e24dd014 sub sp, sp, #20
38: e50b0010 str r0, [fp, #-16]
3c: e59f3058 ldr r3, [pc, #88] ; 9c <len_on+0x70>
40: e50b300c str r3, [fp, #-12]
44: e59f3054 ldr r3, [pc, #84] ; a0 <len_on+0x74>
48: e50b3008 str r3, [fp, #-8]
4c: e51b3010 ldr r3, [fp, #-16]
50: e3530004 cmp r3, #4
54: 1a000002 bne 64 <len_on+0x38>
58: e51b300c ldr r3, [fp, #-12]
5c: e3a02c01 mov r2, #256 ; 0x100
60: e5832000 str r2, [r3]
64: e51b3010 ldr r3, [fp, #-16]
68: e3530005 cmp r3, #5
6c: 1a000002 bne 7c <len_on+0x50>
70: e51b300c ldr r3, [fp, #-12]
74: e3a02b01 mov r2, #1024 ; 0x400
78: e5832000 str r2, [r3]
7c: e51b3008 ldr r3, [fp, #-8]
80: e3a02000 mov r2, #0
84: e5832000 str r2, [r3]
88: e3a03000 mov r3, #0
8c: e1a00003 mov r0, r3
90: e28bd000 add sp, fp, #0
94: e8bd0800 pop {fp}
98: e12fff1e bx lr
9c: 56000050 undefined instruction 0x56000050
a0: 56000054 undefined instruction 0x56000054
000000a4 <Delay>:
a4: e52db004 push {fp} ; (str fp, [sp, #-4]!)
a8: e28db000 add fp, sp, #0
ac: e24dd00c sub sp, sp, #12
b0: e50b0008 str r0, [fp, #-8]
b4: e51b3008 ldr r3, [fp, #-8]
b8: e3530000 cmp r3, #0
bc: 03a03000 moveq r3, #0
c0: 13a03001 movne r3, #1
c4: e20330ff and r3, r3, #255 ; 0xff
c8: e51b2008 ldr r2, [fp, #-8]
cc: e2422001 sub r2, r2, #1
d0: e50b2008 str r2, [fp, #-8]
d4: e3530000 cmp r3, #0
d8: 1afffff5 bne b4 <Delay+0x10>
dc: e28bd000 add sp, fp, #0
e0: e8bd0800 pop {fp}
e4: e12fff1e bx lr
Disassembly of section .ARM.attributes:
00000000 <.ARM.attributes>:
0: 00002541 andeq r2, r0, r1, asr #10
4: 61656100 cmnvs r5, r0, lsl #2
8: 01006962 tsteq r0, r2, ror #18
c: 0000001b andeq r0, r0, fp, lsl r0
10: 00543405 subseq r3, r4, r5, lsl #8
14: 01080206 tsteq r8, r6, lsl #4
18: 04120109 ldreq r0, [r2], #-265 ; 0x109
1c: 01150114 tsteq r5, r4, lsl r1
20: 01180317 tsteq r8, r7, lsl r3
24: Address 0x00000024 is out of bounds.
Disassembly of section .comment:
00000000 <.comment>:
0: 3a434347 bcc 10d0d24 <__bss_end__+0x10c8c3c>
4: 74632820 strbtvc r2, [r3], #-2080 ; 0x820
8: 312d676e teqcc sp, lr, ror #14
c: 312e362e teqcc lr, lr, lsr #12
10: 2e342029 cdpcs 0, 3, cr2, cr4, cr9, {1}
14: 00332e34 eorseq r2, r3, r4, lsr lr
对于2440他的内部同样存在看门狗, 我们在程序中没有对看门狗进行操作; 所以他在一段时间就会复位.
三:流水灯
cpp
void Delay(int n)
{
while (n--);
}
int main(void)
{
int i = 4;
/*设置寄存器 点亮LED2*/
volatile unsigned int* GPFCON = (volatile unsigned int*)0x56000050;
volatile unsigned int* GPFDAT = (volatile unsigned int*)0x56000054;
/*设置GPF4/5/6位位输出模式*/
*GPFCON &= ~((3 << 8) | (3 << 10) | (3 << 12)); //3对应0b11 清位
*GPFCON |= ((1 << 8) | (1 << 10) | (1 << 12)); //置1-设置位输出模式
/*GPFDAT寄存器配置 */
*GPFDAT &= ~((1 << 4) | (1 << 5) | (1 << 6)); //清位
*GPFDAT |= ((1 << 4) | (1 << 5) | (1 << 6)); //把GPFDAT寄存器的4 5 6 位置1--灭灯
/*led4 0x100 led5 0x400 */
while (1)
{
if (i == 7)i = 4;
*GPFDAT &= ~(1 << i);
Delay(10000);
*GPFDAT |= (1 << i);
Delay(10000);
i++;
}
return 0;
}
Matlab
/*
*点亮一个LED
*/
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/*设置内存: sp栈* 我们判断是nor启动还是nand启动/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl main
halt:
b halt
1:看门狗问题的解决
看门狗定时器控制(WTCON)寄存器WTCON寄存器允许用户启用/禁用看门狗定时器,选择来自4个不同源的时钟信号,启用/禁用中断,启用/禁用看门狗定时器输出。看门狗定时器用于S3C2440A上电后功能异常重启时恢复;如果不希望控制器重启,则关闭看门狗定时器
我们可以看到当 Reset enable/disable (重新启用/禁用) 设置WTCON寄存器位0时, 2400就会关闭我们的寄存器
2:如何区分是nar启动还是nand启动nor启动 : 可以向内存一样读, 但是不能向内存一样写; (如果一定要写的话需要发送一定格式的数据才可以写)
nand : 可读取写
方法: 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动 (nand-- 可读取写)
* 否则就是nor启动 (只能读)
四:按键控制LED
1:原理图
平时他为高电平, 当按键按下他为低电平;
2:寄存器配置
GPG3
GPF
按键位输入模式
3:代码
cpp
void Delay(int n)
{
while (n--);
}
#define GPFCON (*((volatile unsigned int*)0x56000050))
#define GPFDAT (*((volatile unsigned int*)0x56000054))
#define GPGCON (*((volatile unsigned int*)0x56000060))
#define GPGDAT (*((volatile unsigned int*)0x56000064))
int main(void)
{
/*volatile unsigned int* GPFCON = (volatile unsigned int*)0x56000050;
volatile unsigned int* GPFDAT = (volatile unsigned int*)0x56000054;
volatile unsigned int* GPGCON = (volatile unsigned int*)0x56000060;
volatile unsigned int* GPGDAT = (volatile unsigned int*)0x56000064;*/
/*设置GPF4/5/6位p 位输出模式*/
GPFCON &= ~((3 << 8) | (3 << 10) | (3 << 12)); //3对应0b11 清位
GPFCON |= ((1 << 8) | (1 << 10) | (1 << 12)); //置1-设置位输出模式
/*设置GPF0和GPF2按键位输入模式*/
GPFCON &= ~((3 << 0) | (3 << 4));
/*GPG3位输入模式*/
GPGCON &= ~(3 << 6);
while (1)
{
if (GPFDAT & (1 << 0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1 << 6);
}
else
{
/* 按下 */
GPFDAT &= ~(1 << 6);
}
if (GPFDAT & (1 << 2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1 << 5);
}
else
{
/* 按下 */
GPFDAT &= ~(1 << 5);
}
if ((GPGDAT & (1 << 3))==0) /* s4 --> gpf4 */
{
/* 按下 */
GPFDAT &= ~(1 << 4);
}
else
{
/* 松开 */
GPFDAT |= (1 << 4);
}
}
return 0;
}
cpp
/*
*点亮一个LED
*/
.text
.global _start
_start:
/* 配置GPFCON(0X56000050)寄存器的[9:8]位为01--输出模式*/
ldr r1,=0X56000050
ldr r0,=0x100
str r0,[r1]
/*
*配置GPFDAT寄存器为低电平(0x56000054)--输出低电平
*/
ldr r1,=0x56000054
ldr r0,=0
str r0,[r1]
/*死循环*/
halt:
b halt