GPIO
睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接
一、什么是GPIO?
首先應該理解什么是GPIO。GPIO,英文全稱為General-Purpose IO ports,也就是通用IO口。在嵌入式系統中常常有數量眾多,但是結構卻比較簡單的外部設備/電路,對這些設備/電路有的需要CPU為之提供控制手段,有的則需要被CPU用作輸入信號。而且,許多這樣的設備/電路只要求一位,即只要有開/關兩種狀態就夠了,比如燈亮與滅。對這些設備/電路的控制,使用傳統的串行口或并行口都不合適。所以在微控制器芯片上一般都會提供一個“通用可編程IO接口”,即GPIO。接口至少有兩個寄存器,即“通用IO控制寄存器”與“通用IO數據寄存器”。數據寄存器的各位都直接引到芯片外部,而對數據寄存器中每一位的作用,即每一位的信號流通方向時輸入還是輸出,則可以通過控制寄存器中對應位獨立的加以設置。這樣,有無GPIO接口也就成為微控制器區別于微處理器的一個特征。
在實際的MCU中,GPIO是有多種形式的。比如,有的數據寄存器可以按照位尋址,有些卻不能按照位尋址,這在編程時就要區分了。比如傳統的8051系列,就區分成可位尋址和不可位尋址兩種寄存器。另外,為了使用的方便,很多mcu把glue logic等集成到芯片內部,增強了系統的穩定性能,比如GPIO接口除去兩個標準寄存器必須具備外,還提供上拉寄存器,可以設置IO的輸出模式是高阻,還是帶上拉的電平輸出,或者不帶上拉的電平輸出。這在電路設計中,外圍電路就可以簡化不少。
明白了這個道理,不同的MCU,提供的GPIO口的數目不同,可選擇的glue logic也不同。所以,在了解共性的基礎上去了解個性。
另外需要注意的是,對于不同的計算機體系結構,設備可能是端口映射,也可能是內存映射的。如果系統結構支持獨立的IO地址空間,并且是端口映射,就必須使用匯編語言完成實際對設備的控制,因為C語言并沒有提供真正的“端口”的概念。如果是內存映射,那就方便的多了。舉個例子,比如像寄存器A(地址假定為0x48000000)寫入數據0x01,那么就可以這樣設置了。
這實際上就是內存映射機制的方便性了。其中volatile關鍵字是嵌入式系統開發的一個重要特點。這個就不再這里總結了。上述表達式拆開來分析,首先(volatile unsigned long *)0x48000000的意思是把0x48000000強制轉換成volatile unsigned long類型的指針,暫記為p,那么就是#define A *p,即A為P指針指向位置的內容了。這里就是通過內存尋址訪問到寄存器A,可以讀/寫操作。
我們在這里就來看看通常在嵌入式c編程中是如何來操作這些可內存尋址的寄存器:
#define CTL_REG_READ(addr) (*(volatile unsigned long *)(addr))
#define CTL_REG_WRITE(addr, val) (*(volatile unsigned long *)(addr)=(var)) 二、S3C2410的GPIO的特點 首先看看introduction。
可見,s3c2410的GPIO有117pin,下面應該到9 IO ports看看詳細部分了。
這么多的IO口,相當于把117個io port劃分為8個組,每個組也叫一個Port,每個Port控制相應數量個port,其實很多是復合功能的,既可以作為普通的IO口使用,也可以作為特殊外設接口。在程序設計時,要對整體的資源有所規劃,初始化時就應該把所有資源安排合理。這樣才會避免出現問題。當然,僅僅做一個最簡單的led燈實驗,倒是省去了很多步驟。
現在的8個端口,針對于每個端口都存在上面提到的兩個寄存器,其寄存器是相似的。除了兩個通用寄存器GPxCON、GPxDAT外,還提供了GPxUP用于確定是否使用內部上拉電阻(其中x為A-H,需要注意的是沒有GPAUP)。應用的主要步驟就是:
·設置GPIO控制寄存器GPxCON
·設置GPIO上拉寄存器GPxUP
初始化完成后,就可以通過對GPxDAT的操作來實現相應的應用了。其中,PORT A與PORT B-H在功能選擇方面有所不同,GPACON的每一位對應一根引腳(共23pin有效)。當某位設為0,相應引腳為輸出引腳,因為Port A控制的23個pin只能進行輸出,所以也就沒有輸入的控制,此時往GPADAT對應的位中寫0/1,可以讓引腳輸出低電平/高電平;當某位設為1,則相應引腳為地址線,或者用于地址控制,此時GPADAT沒有用了。一般而言,GPACON通常全設為1,以便訪問外部存儲器件。PORT B-H在寄存器操作方面完全相同。GPxCON中每兩位控制一根引腳:00表示輸入,01表示輸出,10表示特殊功能,11保留。GPxDAT用于讀/寫引腳:當引腳設為輸入時,讀此寄存器可知相應引腳狀態是高/低;當引腳設為輸出時,寫此寄存器相應位可以使相應引腳輸出低電平或高電平。GPxUP:某位設為0,相應引腳無內部上拉;為1,相應引腳使用內部上拉。關于特殊功能,那就得結合特殊外設來進行設置了。
這算是最簡單的部分。完成一個led燈實驗,可以用來做后續實驗的調試手段。
基本實驗一:LED燈循環點亮
在EDUKIT-III實驗箱上,有四個LED燈,與IO口的對應關系為GPF[7:4]----LED[4:1]。當IO引腳輸出為低電平的時候,LED燈被點亮。只需要關注三個寄存器GPFCON、GPFDAT、GPFUP。由于硬件電路的關系,設置上拉電阻與否并不影響LED燈的點亮,所以GPFUP可以不必考慮。剩下的就是GPFCON和GPFDAT。
我參考了《S3C2410完全開發》和vivi源代碼,對前者的源代碼進行了完善和修正,形成了兩個版本。版本1是采用ARM匯編語言完成,版本2采用C語言完成。版本1練習了宏定義函數,子程序等,相對而言比較簡單。版本2重點練習了軟件架構,雖然短小,但是仍然模仿了vivi的軟件架構。只是沒有必要寫復雜的Makefile,所以只寫了比較簡單的Makefile。在編寫過程中,發現自己對ld,objcopy,和一些細節沒有很好的把握,經過查看資料,已經基本掌握,后續工作需要就這些工具進行深入的學習,目標是能夠熟練掌握。
ARM匯編版本:
@ register address
.equ WTCON, 0x53000000
.equ GPFCON, 0x56000050 @ offset value
.equ oGPFDAT, 0x04 @ macro defination LED_ON
@ you should initial IO pins about leds in order to use this macro
.macro LED_ON led_value //注意 在這里定義了一段宏,相當于函數,在下面可以直接通過宏名+參數值來調用該宏
ldr r1, =/led_value
str r1, [r0, #oGPFDAT] //將r1中的值賦給0x5300000054寄存器中,也就是GPFDAT寄存器
bl delay //調用延時子程序
.endm .text
.global _start @ specified by GNU ld. Here is
@ ultimately 0x00000000,and you
@ can fine this in Makefile. _start:
@ disable watch dog timer
@ otherwise mcu will reset at fixed interval, and
@ you will find led on and off in abnormal way.
mov r0, #WTCON
mov r1, #0x00
str r1, [r0] @ initial IO pins: GPF[7:4]
@ please read datasheet //開始初始化pin對應的Port控制寄存器和上拉寄存器
ldr r0, =GPFCON
ldr r1, =0x5500 //為什么要將GPFCON寄存器設置為0x5500就要一位位的來分析GPFCON寄存器,因為4個LED燈與IO口的對應關系為:GPF[7:4]----LED[4:1],我們知道在Port F中每兩位來控制一個引腳,而且要將引腳設置為輸出才能控制LED,所以GPF7也就是[15:14]=01,GPF6也就是[13:12]=01,同理GPF5也就是[11:10]=01,GPF4也就是[9:8]=01,所以GPFCON要被設置為0x5500
str r1, [r0] @ start to light the leds
@ led on when low voltage
1: //循環的依次點亮4個LED燈,也就是依次將下面的值設置給GPFDAT寄存器,通過下面的0xd0,0x70,0xe0,0xb0可以知道在GPFDAT寄存器中的[7:4]來控制LED的亮滅。
LED_ON 0xd0
LED_ON 0x70
LED_ON 0xe0
LED_ON 0xb0
b 1b @ meaningless but readable
stop:
b stop @
@ SUB Routine
@ @ not accurately, but good effect
delay:
mov r2, #0x10000
2:
subs r2, r2, #0x1
bne 2b mov pc, lr .end (1)我采用了延時子程序,但是上電復位后,WDT默認是打開的。所以在程序的開始要禁用WDT。 (2)關于ARM的跳轉指令B、BL、BX要區分開。B一般用于本段內的指令跳轉,而BL用于子程序調用,BX用于ARM和THUMB狀態的切換。特別地說,BL指令會將下一條指令的地址拷貝到LR中,然后跳轉到指定的地址運行程序。所以,子程序調用的模型為: bl delay ... delay: ... mov pc, lr 明確了這兩條,程序就不難理解了。源代碼見上傳的附件。 C語言版本: (1)軟件架構仿照了vivi,也可以說是Linux Kernel。當然,僅僅寫這么小的程序用不到這么麻煩,但是可以訓練這種架構,為寫中型大型程序打好基礎。 (2)注意C語言下實現寄存器讀寫的(*(volatile unsigned long *)(addr))。其實就是要掌握volatile和指針的用法,明白在嵌入式環境中,為什么要這樣操作。 (3)寫c時,要注意頭文件如何處理。寫Makefile時,要注意是否采用隱含規則,如果不采用,就要自己定義明確規則,就像vivi里面的Rules.make。在這里,因為只是涉及到.s的編譯不采用隱含規則,所以沒有把Rules.make單獨拿出,事實上可以單獨寫為Rules.make,然后在Makefile后加入include Rules.make就可以了。 (4)要調用C子程序,必須分配堆棧空間。因為子程序調用時,要進行入棧出棧處理。又因為從nand flash啟動,而nand flash在S3C2410下的特點規定堆棧不能超過4K。 先來看看利用C語言時對寄存器的操作: #define GPIO_CTL_BASE 0x56000000 //定義GPIO控制寄存器的起始地址也就是GPACON的地址
#define bGPIO(p, o) CTL_REG_READ(GPIO_CTL_BASE + (p) + (o)) /* offset */
#define oGPIO_CON 0x0
#define oGPIO_DAT 0x4
#define oGPIO_UP 0x8
#define oGPIO_F 0x50 //GPFCON寄存器相對應與GPADAT的偏移 /* registers */
#define GPFCON bGPIO(oGPIO_F, oGPIO_CON)
#define GPFDAT bGPIO(oGPIO_F, oGPIO_DAT)
#define GPFUP bGPIO(oGPIO_F, oGPIO_UP) 然后來看看C代碼: #define DELAYTIME 0x5000 void delay(unsigned long n); int main(void)
{
unsigned char i;
unsigned long led[4] = {0xd0, 0x70, 0xe0, 0xb0}; GPFCON = vGPFCON; //同樣vGPFCON已經初始化為0x00005500,將這個值賦值給GPFCON while (1) {
for (i=0; i<4; i++) {
GPFDAT = led[i];
delay(DELAYTIME);
}
} return 0;
} /* delay some time */
void delay(unsigned long n)
{
while (n--) {
;
}
} RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
#define A (*(volatile unsigned long *)0x48000000)
...
A = 0x01;
...
#define CTL_REG_WRITE(addr, val) (*(volatile unsigned long *)(addr)=(var)) 二、S3C2410的GPIO的特點 首先看看introduction。
· 117-bit general purpose I/O ports / 24-ch external interrupt source
The S3C2410X has 117 multi-functional input/output port pins. The ports are:
— Port A (GPA): 23-output port
— Port B (GPB): 11-input/output port
— Port C (GPC): 16-input/output port
— Port D (GPD): 16-input/output port
— Port E (GPE): 16-input/output port
— Port F (GPF): 8-input/output port
— Port G (GPG): 16-input/output port
— Port H (GPH): 11-input/output port
.equ WTCON, 0x53000000
.equ GPFCON, 0x56000050 @ offset value
.equ oGPFDAT, 0x04 @ macro defination LED_ON
@ you should initial IO pins about leds in order to use this macro
.macro LED_ON led_value //注意 在這里定義了一段宏,相當于函數,在下面可以直接通過宏名+參數值來調用該宏
ldr r1, =/led_value
str r1, [r0, #oGPFDAT] //將r1中的值賦給0x5300000054寄存器中,也就是GPFDAT寄存器
bl delay //調用延時子程序
.endm .text
.global _start @ specified by GNU ld. Here is
@ ultimately 0x00000000,and you
@ can fine this in Makefile. _start:
@ disable watch dog timer
@ otherwise mcu will reset at fixed interval, and
@ you will find led on and off in abnormal way.
mov r0, #WTCON
mov r1, #0x00
str r1, [r0] @ initial IO pins: GPF[7:4]
@ please read datasheet //開始初始化pin對應的Port控制寄存器和上拉寄存器
ldr r0, =GPFCON
ldr r1, =0x5500 //為什么要將GPFCON寄存器設置為0x5500就要一位位的來分析GPFCON寄存器,因為4個LED燈與IO口的對應關系為:GPF[7:4]----LED[4:1],我們知道在Port F中每兩位來控制一個引腳,而且要將引腳設置為輸出才能控制LED,所以GPF7也就是[15:14]=01,GPF6也就是[13:12]=01,同理GPF5也就是[11:10]=01,GPF4也就是[9:8]=01,所以GPFCON要被設置為0x5500
str r1, [r0] @ start to light the leds
@ led on when low voltage
1: //循環的依次點亮4個LED燈,也就是依次將下面的值設置給GPFDAT寄存器,通過下面的0xd0,0x70,0xe0,0xb0可以知道在GPFDAT寄存器中的[7:4]來控制LED的亮滅。
LED_ON 0xd0
LED_ON 0x70
LED_ON 0xe0
LED_ON 0xb0
b 1b @ meaningless but readable
stop:
b stop @
@ SUB Routine
@ @ not accurately, but good effect
delay:
mov r2, #0x10000
2:
subs r2, r2, #0x1
bne 2b mov pc, lr .end (1)我采用了延時子程序,但是上電復位后,WDT默認是打開的。所以在程序的開始要禁用WDT。 (2)關于ARM的跳轉指令B、BL、BX要區分開。B一般用于本段內的指令跳轉,而BL用于子程序調用,BX用于ARM和THUMB狀態的切換。特別地說,BL指令會將下一條指令的地址拷貝到LR中,然后跳轉到指定的地址運行程序。所以,子程序調用的模型為: bl delay ... delay: ... mov pc, lr 明確了這兩條,程序就不難理解了。源代碼見上傳的附件。 C語言版本: (1)軟件架構仿照了vivi,也可以說是Linux Kernel。當然,僅僅寫這么小的程序用不到這么麻煩,但是可以訓練這種架構,為寫中型大型程序打好基礎。 (2)注意C語言下實現寄存器讀寫的(*(volatile unsigned long *)(addr))。其實就是要掌握volatile和指針的用法,明白在嵌入式環境中,為什么要這樣操作。 (3)寫c時,要注意頭文件如何處理。寫Makefile時,要注意是否采用隱含規則,如果不采用,就要自己定義明確規則,就像vivi里面的Rules.make。在這里,因為只是涉及到.s的編譯不采用隱含規則,所以沒有把Rules.make單獨拿出,事實上可以單獨寫為Rules.make,然后在Makefile后加入include Rules.make就可以了。 (4)要調用C子程序,必須分配堆棧空間。因為子程序調用時,要進行入棧出棧處理。又因為從nand flash啟動,而nand flash在S3C2410下的特點規定堆棧不能超過4K。 先來看看利用C語言時對寄存器的操作: #define GPIO_CTL_BASE 0x56000000 //定義GPIO控制寄存器的起始地址也就是GPACON的地址
#define bGPIO(p, o) CTL_REG_READ(GPIO_CTL_BASE + (p) + (o)) /* offset */
#define oGPIO_CON 0x0
#define oGPIO_DAT 0x4
#define oGPIO_UP 0x8
#define oGPIO_F 0x50 //GPFCON寄存器相對應與GPADAT的偏移 /* registers */
#define GPFCON bGPIO(oGPIO_F, oGPIO_CON)
#define GPFDAT bGPIO(oGPIO_F, oGPIO_DAT)
#define GPFUP bGPIO(oGPIO_F, oGPIO_UP) 然后來看看C代碼: #define DELAYTIME 0x5000 void delay(unsigned long n); int main(void)
{
unsigned char i;
unsigned long led[4] = {0xd0, 0x70, 0xe0, 0xb0}; GPFCON = vGPFCON; //同樣vGPFCON已經初始化為0x00005500,將這個值賦值給GPFCON while (1) {
for (i=0; i<4; i++) {
GPFDAT = led[i];
delay(DELAYTIME);
}
} return 0;
} /* delay some time */
void delay(unsigned long n)
{
while (n--) {
;
}
} RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成