一、什么是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也不同。所以,在了解共性的基礎上去了解個性。
另外須要注意的是,
對於不同的計算機體系結構,設備可能是port映射,也可能是內存映射的。假設系統結構支持獨立的IO地址空間,而且是port映射,就必須使用匯編語言完畢實際對設備的控制,由於C語言並沒有提供真正的“port”的概念。假設是內存映射,那就方便的多了。舉個樣例,比方像寄存器A(地址假定為0x48000000)寫入數據0x01,那么就能夠這樣設置了。
|
這實際上就是內存映射機制的方便性了。當中volatilekeyword是嵌入式系統開發的一個重要特點。這個就不再這里總結了。上述表達式拆開來分析,首先(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))
#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個port,針對於每一個port都存在上面提到的兩個寄存器,其寄存器是類似的。除了兩個通用寄存器
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
.equ WTCON, 0x53000000
.equ GPFCON, 0x56000050
@ offset value
.equ oGPFDAT, 0x04
.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
@ 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.
.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]
@ 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]
@ 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
@ 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
stop:
b stop
@
@ SUB Routine
@
@ SUB Routine
@
@ not accurately, but good effect
delay:
mov r2, #0x10000
2:
subs r2, r2, #0x1
bne 2b
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))
#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的偏移
#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)
#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};
{
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);
}
}
for (i=0; i<4; i++) {
GPFDAT = led[i];
delay(DELAYTIME);
}
}
return 0;
}
}
/* delay some time */
void delay(unsigned long n)
{
while (n--) {
;
}
}
void delay(unsigned long n)
{
while (n--) {
;
}
}