目錄
- 第一節 硬體知識_LED原理圖
-
- 1、 點亮LED需要做的事情:
- 2、 LED原理圖
- 第二節:S3C2440啟動流程與GPIO
-
- 1、原理圖中的net
- 2、看晶片手冊
- 3、補充幾個概念、補充S3C2440架構知識、啟動過程知識
- 第三節:編寫第一個程式點亮LED
-
- 1、幾條彙編代碼:
- 2、彙程式設計式為:
- 3、 編譯、燒寫程式
- 第四節:彙編與機器碼
-
- 1、幾個CPU寄存器名稱(别稱)的概念
- 2、檢視機器碼的方法:
- 4、 一個作業:
- 第5節:程式設計知識_進制
- 第6節:位元組序和位操作
-
- 1、 位元組序
- 2、 位操作
-
- (1) 移位:移位運算符可以在二進制的基礎上對數字進行平移。
- (2) 取反:
- (3) 位與:
- (4) 位或:
- (5) 置位:這裡舉的例子是想要把bit7、8置為1
- (6) 清位:這裡舉的例子是想要把bit7、8置為0
- 第7講:編寫C程式控制LED
-
- 1、 C指針操作
- 2、 控制LED
- 第7節:幾個彙編指令
-
- 1、 add和sub
- 2、BL:(branch and linlc)(跳轉并傳回)
- 3、ldm和stm(操作多個寄存器)
- 第9講:解剖c程式的内部機制
- 第10節:完善LED程式_編寫案件程式
-
- 1、 看門狗概念:
- 2、 led循環點亮三盞燈程式
- 3、編寫按鍵程式
第一節 硬體知識_LED原理圖
1、 點亮LED需要做的事情:
(1) 看原理圖确定控制LED的引腳;
(2) 看主晶片手冊,确定如何設定/控制引腳;
(3) 寫程式。
2、 LED原理圖
再改進:如果引腳能力不足,可以用三極管,使引腳僅起開關作用。
注:主晶片引腳輸出高/低電平即可改變LED狀态,是以我們不用去關心GPIO引腳輸出的到底是3.3V還是1.2V。
第二節:S3C2440啟動流程與GPIO
1、原理圖中的net
注:韋東山的LED1的引腳為GPF4,我的友善之臂闆子是GPB5;另外,所說的“輸出I/O”指電平。
2、看晶片手冊
GPBCON是端口配置寄存器,用來配置為輸出引腳。設定GPBCON[11:10] = 0b01,GPB5配置為輸出;注:[11:10]表示第11和第10位(位就是比特,就是bits,一個二進制就是一比特); 0b01中,0b表示二進制,01表示11和10這兩位分别是01。就是說把0x400寫入到GPBCON寄存器,即寫到位址0x56000010上(0x400是用“寄存器位檢視小工具”把第11位和第10位置為01得到的十六進制數)。
GPBDAT是端口資料寄存器,用來設定端口電平狀态,方法為把0或1寫到對應的位。設定GPBDAT[5] = 1,則輸出高電平;設定GPBDAT=0,則輸出低電平。就是說若輸出高電平的話則把0x20寫到位址0x56000014上,若輸出低電平則把寫到位址0x56000014上(0x20是第五位置為1得到的十六進制數)。
3、補充幾個概念、補充S3C2440架構知識、啟動過程知識
概念補充:
Nor flash 一般是小程式用,nand flash用于大程式;
GPIO就是通用I/O口;
SOC是system on chip片内系統;
SRAM(static read access memory靜态随機存儲器,RAM存儲在斷電後資料消失);
基位址即起始位址。
架構為:
啟動過程:
(1) NOR啟動:NOR Flash基位址為0,片内RAM位址為0x40000000;
過程為:CPU讀出NOR上第1個指令(前4位元組),然後執行,然後再讀第2個指令(也是4位元組),然後執行。以此一直執行。
(2) NAND 啟動:片内4kRAM基位址為0
過程為:2440硬體把nand前4k内容複制到片内RAN,然後CPU從0位址取出第一條指令執行。
注意flash與SRAM的差別:
(1)FLASH是用來存儲程式的,SRAM是用來存儲程式運作中的中間變量(SRAM一般作為CPU的二級緩存)。
(2)flash寫入的内容不會因電源關閉而失去,讀取速度慢,成本較低,一般用作程式存儲器或者低速資料讀取的情況;
SRAM有最快的讀寫速度,但電源掉落後其内容也會失去,價格昂貴,一般用作cpu的二級緩存,記憶體條也不用這個,适合高速資料讀取的場合。
兩者的關系為flash為ROM,sram為RAM。
第三節:編寫第一個程式點亮LED
1、幾條彙編代碼:
(可以了解[R1]表示的是記憶體的位址,(1)、(2)所說的“R1的值x”實際上是位址)
(1)LDR:讀記憶體。格式為:LDR R0, [R1] (假設R1的值是x,讀取位址x上的資料(4位元組)儲存到R0中)。
(2)STR:寫記憶體指令。格式為:STR R0,[R1] (假設R1的值是x,把R0的值寫到位址x中去(也是4位元組))。
(3)B:跳轉
(4)MOV:格式為:MOV R0,R1 (把R1的值給指派給R0,R0 = R1)
MOV R0,#0x100 (就是R0 = 0x100)
(5)LDR R0,= 0x12345678 (這是僞指令,它會被拆分為幾條真正的RAM指令,最終結果為:R0 = 0x12345678)
注意第(4)個指令MOV和第(5)個指令的差別:MOV的R1隻能是簡單數(立即數),比如#0x100。具體原因如下圖:
2、彙程式設計式為:
(這是一個.S檔案)
/*
*點亮LED:GPB5
*/
.text
.global _start
_start:
/*配置GPB5為輸出引腳
*把0x400寫到位址0x56000010
*我了解,0x56000010這個端口配置寄存器(包含所有的GPB)的位址給CPU的r1寄存器,再把0x400這個十六進制數給CPU的r0寄存器,再把r0的值給r1寄存器,則5這個口被激活
*/
ldr r1, =0x56000010/*把0x56000010這個位址作為值給r1*/
ldr r0, =0x400/*把0x400這個位址作為值給r0。或者用mov r0, #0x400*/
str r0, [r1]/*把r0的值寫到r1的位址中去*/
/*設定GPB5輸出高電平
*把0寫到位址0x56000014
*/
ldr r1, =0x56000014/*把0x56000014這個位址給r1*/
ldr r0, =0/*把0這個位址給r0*/
str r0, [r1]/*把r0的值寫到r1的位址中去*/
/*死循環*/
halt:
b halt
3、 編譯、燒寫程式
韋東山是用的線上遠端Ubuntu系統,我用的是我電腦上的Ubuntu系統:
寫好的彙程式設計式,要在Ubuntu中輸入以下指令分别進行編譯、連結、轉bin檔案
arm-linux-gcc -c -o led_on.o led_on.S
arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
arm-linux-objcopy -O binary -S led_on.elf led_on.bin
注:為了友善,可以用makefile
第四節:彙編與機器碼
1、幾個CPU寄存器名稱(别稱)的概念
pc(program cunter程式計數器):把一個位址寫到R15寄存器(也就是pc寄存器)中去的時候,程式就跳到那個位址去。
當執行一條指令時,首先需要根據PC中存放的指令位址,将指令由記憶體取到指令寄存器中,此過程稱為“取指令”。與此同時,PC中的位址或自動加1或由轉移指針給出下一條指令的位址。此後經過分析指令,執行指令。完成第一條指令的執行,而後根據PC取出第二條指令的位址,如此循環,執行每一條指令。
lr(link register傳回位址):當一個函數執行結束後,要傳回剛開始的位址,這個寄存器就是存放這個位址的。
sp(stack pointer):棧指針
2、檢視機器碼的方法:
在Ubuntu系統執行指令:
arm-linux-objdump -D led_on.elf > led_on.dis
把led_on.dis檔案(反彙編檔案)用notepad++打開
注:編譯器把彙編碼轉換為機器碼,機器碼就是bin檔案裡的内容
led_on.dis如下:
bin檔案要用UltraEdit這個軟體打開如下:
對于上面這一副圖檔中藍色框選的部分,[0x1c]是位址,位址中的内容是下面藍色橫線中的0x000050。
ldr r1,[pc,#20]的意思就是把0x5000050存到r1中去。
4、 一個作業:
直接在上面的led_on.bin檔案(機器碼)做修改,使得led2點亮。注:led2的引腳為GPB6。若寫彙程式設計式則應該将前面彙編編碼的“ldr r0, = 0x400”換成ldr r0, = 0x1000
作業答案如下:
我們需要直接修改機器碼,就相當于是把彙編碼中的mov r0,#0x400 轉換為mov r0,#0x1000,即确定mov r0,#0x1000的機器碼。
先打開ARM架構手冊(ARM Architecture Reference Manual.pdf)檢視MOV指令機器碼的結構:
Shifter_operand是立即數,由rotate(4位)和immed(8位)兩部分組成。如果确定了rotate和immed的值那麼機器碼就确定了。确定rotate和immed方法如下:
比如說,我們要确定mov r0,#0x1000的機器碼,方法如下:
0x1000的二進制為:
第12位為1,也就是說需要由00000001(注:immed是始終等于1的)到第12位為1需要循環右移20位,20/2=10,也就是說rotate為10(即1010),即其立即數為1010 0000 0001;然後把e3a00b01(這是GPB5的)的二進制的後12為位的立即數改成1010 0000 0001就得到了mov r0,#0x1000的機器碼0xe3a00a01,然後把led1的bin檔案的e3a00b01替換為e3a00a01,led2就亮了。
第5節:程式設計知識_進制
1、 在windows cmd指令中輸入calc就會彈出電腦。Windows電腦中的程式員電腦可以進行進制的轉換
2、 N進制逢n進1
3、 為什麼引入二進制?
因為半導體隻有2個狀态(on:1;off : 0),資料使用多個半導體表示,使用二進制可以與硬體的半導體對應,如:8bit資料如下圖表示:
為什麼用八進制?答案如下圖:
如何快速地轉換2/8/16進制:
二進制到8/16進制,用8421法,8進制是3位一組(421),然後相加;16進制是4位一組(8421),然後相加。
8/16進制到二進制,是把8進制的每位展開為3位2進制數,把16進制數的每位展開為4位2進制數。
C語言各種進制表示方法:
第6節:位元組序和位操作
1、 位元組序
一般的arm晶片預設是小位元組序,對于2440可以設定某個寄存器來設定使用小位元組序還是大位元組序。注:下面圖檔中的A就是低位址,A+3是高位址。
2、 位操作
(1) 移位:移位運算符可以在二進制的基礎上對數字進行平移。
左移運算是将一個二進制位的操作數按指定移動的位數向左移動,移出位被丢棄,右邊移出的空位一律補0。右移運算是将一個二進制位的操作數按指定移動的位數向右移動,移出位被丢棄,左邊移出的空位一律補0,或者補符号位,這由不同的機器而定。在使用補碼作為機器數的機器中,正數的符号位為0,負數的符号位為1。
左移n位就相當于乘以2的n次方;右移n位相當于除以2的n次方。具體這樣算的原因是,左移了以後,剩下的所有值的權重增加了;右移了以後,剩下的所有值的權重減少加了。
(2) 取反:
(3) 位與:
(4) 位或:
(5) 置位:這裡舉的例子是想要把bit7、8置為1
(6) 清位:這裡舉的例子是想要把bit7、8置為0
第7講:編寫C程式控制LED
1、 C指針操作
(1) 變量和指針的原理
(2) 指針的使用方法
2、 控制LED
是以由上得到main函數:
int main() {
unsigned *pGPBCON = 0x56000010;
unsigned *pGPBDAT = 0x56000014;
*pGPBCON = 0x400;
*pGPBDAT = 0;
return 0;
}
那麼問題來了:
(1)寫出了main函數之後,誰來調用它;
(2)main函數中的變量(pGPBCON、pGPBDAT)儲存在記憶體中,這個記憶體位址是多少?
怎麼來回答呢,我們寫一個彙程式設計式,建立檔案start.S,用于給main函數設定記憶體和調用main函數,寫程式如下:
注:普及一下sp指針 在随機存儲器區劃出一塊區域作為堆棧區,資料可以一個個順序地存入(壓入)到這個區域之中,這個過程稱為‘壓棧’(push)。通常用一個指針(堆棧指針SP—StackPointer)實作做一次調整,SP總指向最後一個壓入堆棧的資料所在的資料單元(棧頂)。從堆棧中讀取資料時,按照堆棧指針指向的堆棧單元讀取堆棧資料,這個過程叫做 ‘彈出’(pop ),每彈出一個資料,SP 即向相反方向做一次調整,如此就實作了後進先出的原則。
在Ubuntu中執行:(注:我編寫的led.c和start.S)
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
至于為什麼這樣操作(.c檔案+.S檔案兩個檔案),原因見下一節。
第7節:幾個彙編指令
執行arm-linux-objdump -D led.elf > led.dis得到反彙編檔案led.dis并用notepad打開,如下:
1、 add和sub
sub r0 , r1 , #4 意為r0 = r1 – 4
sub r0, r1 , r2 意為r0 = r1 – r2
add r0, r1 , #4 意為 r0 = r1 +4
2、BL:(branch and linlc)(跳轉并傳回)
注:傳回位址就是下一條指令的位址。
3、ldm和stm(操作多個寄存器)
下圖中ia、db的意思:
過後增加(Increment After)、預先增加(Increment Before)、過後減少(Decrement After)、預先減少(Decrement Before)。
對下面這兩幅圖注解:
db表示先減後存,假設sp=4096,則需要先減為4092再存。存的時候規則是高編号寄存器存高位址,即pc(r15)先存,存入記憶體中的4092~4095。然後依次是lr、ip、fp的存儲
下面這兩幅圖檔講的是ldmid sp,{fp,sp,pc}
第9講:解剖c程式的内部機制
分析start.S和led.c,剖析其内部機制,把程式的反彙編搞明白就了解了C程式的内部機制:
提出以下五個問題:
先補充一下理論知識:
(1)r0-r3寄存器是傳參和傳遞函數傳回值。用于調用者和被調用者之間的參數傳遞。
(2)r4-r11寄存器,在你的函數中有可能其中的一些會被用到。入口處儲存,出口處恢複。
看上面的反彙編,mov sp, #4096,說明這是nand啟動,程式(即機器碼)拷貝到片内4k記憶體,從片内記憶體的0位址開始存放(e3a0da01存放到0位址,依次儲存)。
注:反彙編中的00000000<.comment>不存放。comment是注釋。
一上電,執行0位址的e3a0da01,即sp指到4096.然後執行eb0000000,即跳轉到c位址(去執行main()),并把傳回位址8賦予lr寄存器。
現在來分析主函數反彙編語言,看下圖。(注意,下圖是對主函數反彙編的解釋,你需要同時結合着下下一幅圖一塊看,看主函數在記憶體上的機制。如stmdb sp!,{fp,ip,lr,pc}在記憶體上的機制是在下下圖從左往右數的第三個記憶體圖)
對于10位址語句stmdb sp!,{fp,ip,lr,pc}進行注解:
(1)pc=目前指令位址+8,是以pc=10+8=0x18;
(2)上圖已經說了lr=8;
(3)fp是未定義值。
下圖是片内4k記憶體的分析:
結合上圖和下面這幅圖,你會看到:機器碼存到了記憶體中的位置,寄存器如何存到記憶體中,局部變量是如何存到棧中。最重要的是,結合上下兩幅圖,你會明白你控制led時對端口配置寄存器和端口狀态寄存器所進行的操作,在記憶體中是如何進行的。
從以上可知:所謂棧就是sp寄存器指向的記憶體。
注:關于第五個問題,怎麼從棧中恢複寄存器。反彙編中記憶體位址54-57、58-61兩個語句就是恢複記憶體的。
還有一個問題是,怎麼傳參數到被調用函數(main())的?下面寫一個程式進行驗證。
start.S檔案
.text
.global _start
_start:
/* 設定記憶體: sp 棧 */
ldr sp, =4096 /* nand啟動 */
// ldr sp, =0x40000000+4096 /* nor啟動 */
mov r0, #4/*把4賦給r0寄存器,也就是把4傳入led_on(int which)中的形參*/
bl led_on
ldr r0, =100000/*傳參*/
bl delay
mov r0, #5/*傳參*/
bl led_on
halt:
b halt
led.c檔案
void delay(volatile int d)/*volatile的作用是作為指令關鍵字,確定本條指令不會因編譯器的優化而省略*/
{
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;
}
現在将上面這個start.S和led_on編譯完成後,生成一個反彙編檔案并檢視,如下:
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>/*注:位址20的000186a0轉成十進制是10000*/
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.
第10節:完善LED程式_編寫案件程式
1、 看門狗概念:
看門狗定時器(WDT,Watch Dog Timer)是單片機的一個組成部分,它實際上是一個計數器,一般給看門狗一個數字,程式開始運作後看門狗開始從這個數字開始倒計時計數。倒計時到0的時候系統便會複位。
2、 led循環點亮三盞燈程式
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
main.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;
}
對上面的led.c進行改進:把寄存器定義為一個宏,并且把宏直接放到一個頭檔案中。這樣更友善了。
改變如下:
#include "s3c2440_soc.h"
/*
s3c2440_soc.h中包含:
#define GPFCON (*((volatile unsigned int *)0x56000050))
#define GPFDAT (*((volatile unsigned int *)0x56000054))
*/
void delay(volatile int d)
{
while (d--);
}
int main(void)
{
int val = 0; /* val: 0b000, 0b111 */
int tmp;
/* 設定GPFCON讓GPF4/5/6配置為輸出引腳 */
GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
GPFCON |= ((1<<8) | (1<<10) | (1<<12));
/* 循環點亮 */
while (1)
{
tmp = ~val;
tmp &= 7;
GPFDAT &= ~(7<<4);
GPFDAT |= (tmp<<4);
delay(100000);
val++;
if (val == 8)
val =0;
}
return 0;
}
3、編寫按鍵程式
首先看原理圖,找按鍵對應的端口寄存器,再對該寄存器進行操作。
key_led.c
#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)
* 注:00為input
*/
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;
}