GPIO


一、什么是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,那么就能夠這樣設置了。
 

#define A (*(volatile unsigned long *)0x48000000)

...

    A = 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))
 
二、S3C2410的GPIO的特點
 
    首先看看introduction。
 

· 117-bit general purpose I/O ports / 24-ch external interrupt source

 
    可見,s3c2410的GPIO有117pin,以下應該到9 IO ports看看具體部分了。
 

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

 
    這么多的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燈被點亮。僅僅須要關注三個寄存器 GPFCONGPFDATGPFUP。因為硬件電路的關系,設置上拉電阻與否並不影響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--) {
  ;
 }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM