原文链接:blog.csdn.net/thisway_diy…
第001节 硬件知识-LED原理图
如何点亮一个LED灯:
- 看原理图确定控制LED的引脚
- 看主芯片的芯片手册,确定如何设置控制这个引脚
- 写程序
LED驱动方式:
- 方式1:使用引脚输出3.3V点亮LED,输出0V熄灭LED
- 方式2:使用引脚拉低到0V点亮LED,输出3.3V熄灭LED,有的芯片为了省电等原因,其引脚驱动能力不足,这时可以使用三极管驱动。
- 方式3:使用引脚输出1.2V点亮LED,输出0V熄灭LED
- 方式4:使用引脚输出0V点亮LED,输出1.2V熄灭LED 主芯片引脚输出高电平/低电平,即可改变LED的状态,无须关注GPIO引脚输出3.3V还是1.2V,只需关注引脚输出高电平还是低电平,一般将高电平用逻辑1表示,低电平有逻辑0来表示。
第002节 硬件知识-S3C2440启动流程和GPIO操作
在原理图上找到LED,同名的Net表示连在一起的
可以看到nLED1引脚与GPF4引脚连在一起,怎么控制GPF4输出1或者0?
- 配置GPF4为输出引脚
- 设置状态
查看芯片手册可以知道要设置GPFCON寄存器的bit[9:8]来配置GPF4,设置bit[9:8]=0b01表示配置GPF4为输出引脚

设置GPFDAT寄存器的第四位1或者0来输出高电平或者地电平
英文意思:
把端口作为输入端口配置时,对应的比特为引脚状态,把端口作为输出端口配置时,引脚状态和对应的比特相同,端口作为功能型引脚配置时,可读出未定义值
S3C2440框架:
S3C2440启动流程:
- Nor启动:NorFlash的基地址为0,片内RAM地址为0x4000 0000;CPU读出Nor上的第一个指令(前四个字节)执行,而后CPU继续读出其他指令执行
- NandFlash启动:片内4k RAM基地址为0,Nor Flash不可访问,2440硬件把Nand前4K内容复制到片内RAM,然后CPU从0地址取出第一条指令执行
第003节-编写程序点亮第一个LED
在开始写第1个程序前,先了解一些概念。
2440是一个SOC,它里面的CPU有R1、R2、R3……等 寄存器;它里面的GPIO控制器也有很多寄存器,如 GPFCON、GPFDAT。这两个寄存器是有差异的,在写代码的时候,CPU里面的寄存器可以直接访问,其它的寄存器要以地址进行访问。
把GPF4配置为输出,需要把0x100写入GPFCON这个寄存器,即写到0x5600 0050上;
把GPF4输出1,需要把0x10写到地址0x5600 0054上;
把GPF4输出0,需要把0x00写到地址0x5600 0054上;
这里的写法会破坏寄存器的其它位,其它位是控制其它引脚的,为了让第一个裸板程序尽可能的简单,才简单粗暴的这样处理。
写程序需要用到几条汇编代码:
1.ldr(load):用来读寄存器
ldr r0,[r1],假设r1的数据为x,读取地址x上的值(4字节),保存到r0中
ldr r0,=0x12345678,结果是r0=0x12345678
2.str(store):用来写寄存器
str r0,[r1],假设r1的值为x,把r0的值写到地址x上(4字节)
3. b 跳转
4.MOV(move)移动,赋值
MOV R0,R1将R1的值赋给R0,
MOV R0 #0x100把0x1000赋值给R0,此时R0=0x100
其中ldr r0,=0x12345678是一条伪指令,在ARM的32位指令中,有些字节表示指令,有些字节表示数据,因此一些数据没有32位,不能表示一个32位的任意值,只能表示一个较小的简单值,这个简单值称为立即数。引入伪指令后,利用LDR可以为R0赋任意大小值,编译器会自动拆分成真正的的指令,实现目的。
第一个LED程序代码如下:
------------------led.s--------------------
/*
* 点亮LED1: gpf4
*/
.text
.global _start
_start:
/* 配置GPF4为输出引脚
* 把0x100写到地址0x56000050
*/
ldr r1, =0x56000050
ldr r0, =0x100 /* mov r0, #0x100 */
str r0, [r1]
/* 设置GPF4输出高电平
* 把0写到地址0x56000054
*/
ldr r1, =0x56000054
ldr r0, =0 /* mov r0, #0 */
str r0, [r1]
/* 死循环 */
halt:
b halt
将代码上传到服务器:
1.编译:
arm-linux-gcc -c -o led.o led.s
2.链接:命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的
arm-linux-led -Ttext 0 led.o -o led.elf
3.生成bin文件
arm-linux-objcopy -O binary -S led.elf led.bin
4.反汇编:
arm-linux-objdump -D led.elf > led.dis
Makefile:
all:
arm-linux-gcc -c -o led.o led.s
arm-linux-led -Ttext 0 led.o led.elf
arm-linux-objcopy -O binary -S led.elf led.bin
arm-linux-objdump -D led.elf > led.dis
clean:
rm *.dis *.bin *.o *.elf
第004节_汇编与机器码
生成的led_on.dis就是反汇编文件。led_on.dis如下:
led_on.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e59f1014 ldr r1, [pc, #20] ; 1c <.text+0x1c>
4: e3a00c01 mov r0, #256 ; 0x100
8: e5810000 str r0, [r1]
c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20>
10: e3a00000 mov r0, #0 ; 0x0
14: e5810000 str r0, [r1]
00000018 <halt>:
18: eafffffe b 18 <halt>
1c: 56000050 undefined
20: 56000054 undefined
在反汇编文件里可以看到,ldr r1, =0x56000050被转换成ldr r1, [pc,#20],pc+20地址的值为0x56000050,通过这种方式为r1赋值。对于立即数0x100而言,ldr r0,=0x100即是转换成了mov r0,#256;
在2440这个SOC里面,R0-R15都在CPU里面,其中:
- R13 别名:sp (Stack Pointer)栈指针
- R14 别名:lr (Link Register)返回地址
- R15 别名:pc (program Counter)程序计数器=当前指令+8
为什么 PC=当前指令+8?
ARM指令采用流水线机制,当前执行地址A的指令,已经在对地址A+4的指令进行译码,已经在读取地址A+8的指令,其中A+8就是PC的值。
C/汇编(给人类方便使用的语言)———编译器———>bin,含有机器码(给CPU使用)
第005节_编程知识进制
17个苹果,有四种表示方式,他们表示同一个数值:

- 计算验证:
十进制:17=1*10^1+7*10^0
二进制:17=1*2^4+1*2^0
八进制:17=2*8^1+7*8^0
十六进制:17=1*16^1+1*16^0
为何引入二进制? 在硬件角度看,晶体管只有两个状态:on是1,off是0; 数据使用多个晶体管进行表示,用二进制描述,吻合硬件状态。
为何引入八进制? 将二进制的三位作为一组,把这一组作为一位进行表示,就是八进制。
为何引入十六进制? 将二进制的四位作为一组,把这一组作为一位进行表示,就是十六进制。八进制和十六进制方便我们描述,简化了长度。
如何快速的转换2/8/16进制: 首先记住8 4 2 1 ——>二进制权重
举例1:
将二进制0b01101110101转换成八进制:
将二进制从右到左,每三个分成一组:
结果就是1565;
举例2: 将二进制0b01101110101转换成十六进制: 将二进制从右到左,每四个分成一组:

结果就是375;
举例3: 将十六进制0xABC1转换成二进制: 将十六进制从右到左,每个分成四位:

结果就是1010 1011 1100 0001;
在C语言中怎么表示这些进制呢?
十进制: int a = 96;
八进制: int a = 0140;//0开头
十六进制: int a = 0x60;//0x开头
用0b开头表示二进制,约定俗成的规定。
第006节 编程知识字节序_位操作
- 字节序:假设
int a = 0x12345678
在内存中,以8位作为1byte进行存储的,因此0x12345678中每两位作为1byte,其中0x78是低位,0x12是高位。
在内存中的存储方式有两种: 小字节序和大字节序
0x12345678的低位(0x78)存在低地址,即方式1,叫做小字节序(Little endian);
0x12345678的高位(0x12)存在低地址,即方式2,叫做大字节序(Big endian);
一般的arm芯片都是小字节序,对于2440可以设置某个寄存器,让整个系统使用大字节序或小字节序,它默认使用小字节序。
- 位操作
左移:
int a=0x123,b=a<<2,b=0x48c
右移:
int a=0x123,b=a>>2,b=0x48
取反:
int a=0x123,b=~a,b=2
位与:
int a=0b1110,int b=0b1011,a&b=0b1010
位或:
int a=0b1110,int b=0b1011,a|b=0b1111
置位:
将a的bit3,bit7设为1
a|=(1<<3)|(1<<7)
清位:
将a的bit3,bit7清为1
a &=~((1<<3)|(1<<7))
第007节_编写c程序控制LED
c语言的指针操作:
- 所有的变量在内存中都有一块区域
- 可以通过变量/指针操作内存

TYPE *p = val1;
*p = val2;
把val2写入地址val1的内存中,写入sizeof(TYPE)字节;
TYPE *p = addr;
*p = val;
把val写入地址addrd的内存,,写入sizeof(TYPE)字节;
a. 我们写出了main函数, 谁来调用它?
b. main函数中变量保存在内存中, 这个内存地址是多少?
答: 我们还需要写一个汇编代码, 给main函数设置内存, 调用main函数
led.c源码:
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
/*配置GPF4为输出引脚*/
*pGPFCON = 0x100;
/*配置GPF4输出0*/
*pGPFDAT = 0;
return 0;
}
start.S
.text
.global _start
_start:
/*设置内存:sp栈*/
ldr sp,=4096 /*nand启动*/
//ldr sp, =0x40000000 /*nor启动*/
/*调用main*/
bl main
halt:
b halt
Makefile源码:
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
第008节_几条汇编指令
a.加法/减法:add/sub
add r0,r1,#4 <==> r0=r1+4
sub r0,r1,#4 <==> r0=r1-4
sub r0,r1,r2 <==> r0=r1-r2
b.bl,带返回值的跳转,跳转到指定指令,并将返回地址(下一条指令)保存在lr寄存器
c.ldm/stm读内存,写入多个寄存器、把多个寄存器的值写入内存
可搭配的后缀有 过后增加ia(Increment After)、预先增加ib(Increment Before)、过后减少da(Decrement After)、预先减少db(Decrement Before);
stmdb sp!,(fp,ip,lr,pc)
设sp=4096
db意思是先减后存,按 高编号寄存器存在高地址存。

举例2:
ldmia sp, (fp,ip,pc)

第009节_解析c语言程序内部的机制
led.c内部机制分析:
start.S:
- 设置栈:
- 调用main,并把返回值地址保存到lr中
led.c的main()内容:
- 定义两个局部变量
- return 0
问题:
1.为什么要设置栈
c函数要用
2.怎么使用栈
保存局部变量,保存lr等寄存器
3.调用者如何传参数给被调用者,被调用者怎么传返回值给调用者
4.怎么从栈中恢复寄存器
在arm中有个ATPCS规则,约定r0-r15寄存器的用途。
r0-r3:调用者和被调用者之间传参数;
r4-r11:函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们;
实例分析:
下面分析个实例 start.S:
.text
.global _start
_start:
/* 设置内存: sp 栈 */
ldr sp, =4096 /* nand启动 */
// ldr sp, =0x40000000+4096 /* nor启动 */
/* 调用main */
bl main
halt:
b halt
led.c:
int main() { unsigned int *pGPFCON = (unsigned int *)0x56000050; unsigned int *pGPFDAT = (unsigned int *)0x56000054;/* 配置GPF4为输出引脚 */ *pGPFCON = 0x100; /* 设置GPF4输出0 */ *pGPFDAT = 0; return 0;
}
将前面的程序反汇编得到led.dis如下:
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: e1a0c00d mov ip, sp //设置栈地址在4k RAM的最高处,sp=4096;
10: e92dd800 stmdb sp!, {fp, ip, lr, pc} //将fp,ip,lr,pc压栈,栈向下生长
14: e24cb004 sub fp, ip, #4 ; 0x4 //fp = ip+4=4096+4
18: e24dd008 sub sp, sp, #8 ; 0x8 //sp = sp+84096+8
1c: e3a03456 mov r3, #1442840576 ; 0x56000000 //r3=0x56000000
20: e2833050 add r3, r3, #80 ; 0x50//r3=r3+0D80 =0x56000050
24: e50b3010 str r3, [fp, #-16]//*(fp-16) = r3
28: e3a03456 mov r3, #1442840576 ; 0x56000000
2c: e2833054 add r3, r3, #84 ; 0x54//r3=0x56000054
30: e50b3014 str r3, [fp, #-20]//*(fp-20) = r3
34: e51b2010 ldr r2, [fp, #-16]//*(fp-16) = r2
38: e3a03c01 mov r3, #256 ; 0x100//r3=0x100
3c: e5823000 str r3, [r2] //*(r2) = r3
40: e51b2014 ldr r2, [fp, #-20] //r2 = *(dp-20)
44: e3a03000 mov r3, #0 ; 0x0 //r3 = 0x0
48: e5823000 str r3, [r2]//*(r2) = r3
4c: e3a03000 mov r3, #0 ; 0x0//r3=0x0
50: e1a00003 mov r0, r3//r0 = r3=0x00
54: e24bd00c sub sp, fp, #12 ; 0xc//sp=fp-12 = 4092-12
58: e89da800 ldmia sp, {fp, sp, pc}//出栈,从栈中恢复寄存器
过程中的内存情况:
前面那个例子,汇编调用main.c并没有传递参数,这里修改下c程序,让其传递参数。
start.S
.text
.global _start
_start:
/* 设置内存: sp 栈 */
ldr sp, =4096 /* nand启动 */
// ldr sp, =0x40000000+4096 /* nor启动 */
mov r0, #4
bl led_on
ldr r0, =100000
bl delay
mov r0, #5
bl led_on
halt:
b halt
led.c
void delay(volatile int d)
{
while (d--);
}
int led_on(int which)
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
if (which == 4)
{
/* 配置GPF4为输出引脚 */
*pGPFCON = 0x100;
}
else if (which == 5)
{
/* 配置GPF5为输出引脚 */
*pGPFCON = 0x400;
}
/* 设置GPF4/5输出0 */
*pGPFDAT = 0;
return 0;
}
led.dis
led.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000
4: e3a00004 mov r0, #4 ; 0x4
8: eb000012 bl 58 <led_on>
c: e59f000c ldr r0, [pc, #12] ; 20 <.text+0x20>
10: eb000003 bl 24 <delay>
14: e3a00005 mov r0, #5 ; 0x5
18: eb00000e bl 58 <led_on>
0000001c <halt>:
1c: eafffffe b 1c <halt>
20: 000186a0 andeq r8, r1, r0, lsr #13
00000024 <delay>:
24: e1a0c00d mov ip, sp
28: e92dd800 stmdb sp!, {fp, ip, lr, pc}
2c: e24cb004 sub fp, ip, #4 ; 0x4
30: e24dd004 sub sp, sp, #4 ; 0x4
34: e50b0010 str r0, [fp, #-16]
38: e51b3010 ldr r3, [fp, #-16]
3c: e2433001 sub r3, r3, #1 ; 0x1
40: e50b3010 str r3, [fp, #-16]
44: e51b3010 ldr r3, [fp, #-16]
48: e3730001 cmn r3, #1 ; 0x1
4c: 0a000000 beq 54 <delay+0x30>
50: eafffff8 b 38 <delay+0x14>
54: e89da808 ldmia sp, {r3, fp, sp, pc}
00000058 <led_on>:
58: e1a0c00d mov ip, sp
5c: e92dd800 stmdb sp!, {fp, ip, lr, pc}
60: e24cb004 sub fp, ip, #4 ; 0x4
64: e24dd00c sub sp, sp, #12 ; 0xc
68: e50b0010 str r0, [fp, #-16]
6c: e3a03456 mov r3, #1442840576 ; 0x56000000
70: e2833050 add r3, r3, #80 ; 0x50
74: e50b3014 str r3, [fp, #-20]
78: e3a03456 mov r3, #1442840576 ; 0x56000000
7c: e2833054 add r3, r3, #84 ; 0x54
80: e50b3018 str r3, [fp, #-24]
84: e51b3010 ldr r3, [fp, #-16]
88: e3530004 cmp r3, #4 ; 0x4
8c: 1a000003 bne a0 <led_on+0x48>
90: e51b2014 ldr r2, [fp, #-20]
94: e3a03c01 mov r3, #256 ; 0x100
98: e5823000 str r3, [r2]
9c: ea000005 b b8 <led_on+0x60>
a0: e51b3010 ldr r3, [fp, #-16]
a4: e3530005 cmp r3, #5 ; 0x5
a8: 1a000002 bne b8 <led_on+0x60>
ac: e51b2014 ldr r2, [fp, #-20]
b0: e3a03b01 mov r3, #1024 ; 0x400
b4: e5823000 str r3, [r2]
b8: e51b3018 ldr r3, [fp, #-24]
bc: e3a02000 mov r2, #0 ; 0x0
c0: e5832000 str r2, [r3]
c4: e3a03000 mov r3, #0 ; 0x0
c8: e1a00003 mov r0, r3
cc: e24bd00c sub sp, fp, #12 ; 0xc
d0: 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.
简单分析汇编:
mov sp, #4096:设置栈地址在4k RAM的最高处,sp=4096;
mov r0, #4:r0=4,作为参数;
bl 58 <led_on>:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=8;在led_on中会使用到r0;
ldr r0, [pc, #12]:r0=[pc+12]处的值=[c+12=20]的值=0x186a0=1000000,作为参数;
bl 24 <delay>:调用24地址处的delay函数,并保存下一行代码地址到lr,即lr=24;在delay中会使用到r0;
mov r0, #5:r0=5,作为参数;
bl 58 <led_on>:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=58;在led_on中会使用到r0;
第10节_完善LED程序编写按键程序
在上一节视频里,我们编写的程序代码是先点亮led1,然后延时一会,再点亮led2,进入死循环。
但在开发板上的实际效果是led1先亮,延时一会,led2再亮,然后一会之后,led1再次亮了。
这和我们的设计的代码流程不吻合,这是因为2440里面有个看门狗定时器,开发板上电后,需要在一定时间内“喂狗”(设置相应的寄存器),否则就会重启开发板。
之所以这样设计,是为了让芯片出现死机时,能够自己复位,重新运行。
这里我们写个led灯循环的程序,步骤如下:
这里暂时用不到看门狗,先关闭看门狗,从参考手册可知,向0x53000000寄存器写0即可关闭看门狗; 设置内存的栈,通过写读操作来判断是Nand Flash还是Nor Flash; 设置GPFCON让GPF4/5/6配置为输出引脚; 循环点灯,依次设置GPFDAT寄存器;
完整代码:
start.S
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
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
led.c
void delay(volatile int d)
{
while (d--);
}
int main(void)
{
volatile unsigned int *pGPFCON = (volatile unsigned int *)0x56000050;
volatile unsigned int *pGPFDAT = (volatile unsigned int *)0x56000054;
int val = 0; /* val: 0b000, 0b111 */
int tmp;
/* 设置GPFCON让GPF4/5/6配置为输出引脚 */
*pGPFCON &= ~((3<<8) | (3<<10) | (3<<12));
*pGPFCON |= ((1<<8) | (1<<10) | (1<<12));
/* 循环点亮 */
while (1)
{
tmp = ~val;
tmp &= 7;
*pGPFDAT &= ~(7<<4);
*pGPFDAT |= (tmp<<4);
delay(100000);
val++;
if (val == 8)
val =0;
}
return 0;
}
2440里面有很多寄存器,如果每次对不同的寄存器进行查询和操作会很麻烦,因此可以先提前定义成宏,做成一个头文件,每次调用就行。
再举一个按键控制LED的程序,,步骤如下:
- 这里暂时用不到看门狗,先关闭看门狗,从参考手册可知,向0x53000000寄存器写0即可关闭看门狗;
- 设置内存的栈,通过写读操作来判断是Nand Flash还是Nor Flash;
- 设置GPFCON让GPF4/5/6配置为输出引脚;
- 设置3个按键引脚为输入引脚;
- 循环执行,读取按键引脚值,点亮对应的led灯; 代码:
#include "s3c2440_soc.h"
void delay(volatile int d)
{
while (d--);
}
int main(void)
{
int val1, val2;
/* 设置GPFCON让GPF4/5/6配置为输出引脚 */
GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
GPFCON |= ((1<<8) | (1<<10) | (1<<12));
/* 配置3个按键引脚为输入引脚:
* GPF0(S2),GPF2(S3),GPG3(S4)
*/
GPFCON &= ~((3<<0) | (3<<4)); /* gpf0,2 */
GPGCON &= ~((3<<6)); /* gpg3 */
/* 循环点亮 */
while (1)
{
val1 = GPFDAT;
val2 = GPGDAT;
if (val1 & (1<<0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1<<6);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<6);
}
if (val1 & (1<<2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1<<5);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<5);
}
if (val2 & (1<<3)) /* s4 --> gpf4 */
{
/* 松开 */
GPFDAT |= (1<<4);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<4);
}
}
return 0;
}
本文使用 mdnice 排版