英創(chuàng)公司EM928x系列和EM335x系列的工控主板均有32位GPIO0 – GPIO31并且為可獨(dú)立方向可設(shè)置的通用數(shù)字IO,所有GPIO的上電初始狀態(tài)均為輸入狀態(tài)帶上拉電阻。所以在初始化狀態(tài)下,每位GPIO管腳所呈現(xiàn)的電平均為高電平。面對(duì)豐富的GPIO資源,許多客戶希望將GPIO利用起來做一下其他的應(yīng)用,比如模擬SPI接口,I2C接口,以及一些總線時(shí)序等,下面就來看看GPIO的操作。
常規(guī)的GPIO操作在英創(chuàng)公司的主板使用手冊(cè)中已經(jīng)有了較為詳細(xì)的介紹,下面將主要的步驟例舉出來:
首先需要打開GPIO的設(shè)備文件:
fd = open('/dev/em335x_gpio', O_RDWR);
然后根據(jù)英創(chuàng)公司提供的接口即可實(shí)現(xiàn)對(duì)GPIO的操作:
rc = GPIO_OutEnable(fd, 0xffffffff); //使能GPIO
if(rc < 0)
{
printf('GPIO_OutEnable::failed %d\n', rc);
return rc;
}
rc = GPIO_OutClear(fd, GPIO0); //拉低GPIO
if(rc < 0)
{
printf('GPIO_OutClear::failed %d\n', rc);
return rc;
}
這樣通過調(diào)用驅(qū)動(dòng),實(shí)現(xiàn)了對(duì)GPIO的控制,但是調(diào)用驅(qū)動(dòng)就存在用戶空間和內(nèi)核空間的數(shù)據(jù)傳輸,還會(huì)涉及到系統(tǒng)調(diào)度的問題,所以當(dāng)我們執(zhí)行一次置低置高的操作,經(jīng)測(cè)試,大概會(huì)用掉2.5μs,波形如下圖:
通過驅(qū)動(dòng)程序操作GPIO的波形
對(duì)于一般的應(yīng)用來說,通過驅(qū)動(dòng)來操作GPIO已經(jīng)能夠滿足需求,直接使用英創(chuàng)公司封裝好的接口函數(shù),即可實(shí)現(xiàn)對(duì)應(yīng)用程序的開發(fā)。但是當(dāng)需要用GPIO做實(shí)時(shí)性要求較高的應(yīng)用時(shí),簡(jiǎn)單的通過驅(qū)動(dòng)來控制GPIO可能就無法實(shí)現(xiàn),比如需要用GPIO來模擬某種時(shí)序,調(diào)用驅(qū)動(dòng)來操作可能就會(huì)讓時(shí)序的一個(gè)周期過長(zhǎng),無法滿足時(shí)序的要求。
針對(duì)這種情況,英創(chuàng)公司為客戶提供了一種更快速的GPIO 操作方法,即通過內(nèi)存映射的方法來操作GPIO,使用mmap()函數(shù)將GPIO的硬件地址映射到進(jìn)程地址空間,實(shí)現(xiàn)對(duì)內(nèi)存物理地址的讀寫,直接控制GPIO的寄存器,這是一種直接操作硬件的方式,避免了用戶進(jìn)程和內(nèi)核進(jìn)程的數(shù)據(jù)交換,極大地提升了效率。
首先將/dev/mem/設(shè)備文件中GPIO的地址映射到用戶進(jìn)程空間的代碼:
void *GPIO_MMAP::gpio_em335x_pin_config(unsigned int BASE)
{
int mem_fd;
void *base;
mem_fd = open('/dev/mem', O_RDWR|O_SYNC);
printf('mem_fd is %d\n', mem_fd);
/* mmap GPIO */
base = mmap(
NULL,//起始地址
GPIO_DEV_SIZE, //映射的文件內(nèi)容的大小
PROT_READ|PROT_WRITE,// 映射區(qū)域可讀可寫
MAP_SHARED,//映射區(qū)域的寫入數(shù)據(jù)會(huì)寫回到原來的文件
mem_fd,
BASE//被映射的硬件地址
);
close(mem_fd);
return base;
}
成功執(zhí)行時(shí),mmap()函數(shù)返回被映射區(qū)的指針。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以像訪問普通內(nèi)存一樣對(duì)文件進(jìn)行訪問,不必再調(diào)用read(),write()等操作。只需要使用返回的地址指針在對(duì)應(yīng)的寄存器的偏移地址賦值,就可以完成操作。在例程中已經(jīng)將函數(shù)接口引出(詳細(xì)的代碼請(qǐng)參考例程):
GPIO.GPIO_OutEnable(GPIO0);//輸出使能
GPIO.GPIO_OutClear(GPIO0);//置為低電平
GPIO.GPIO_OutSet(GPIO0);//置為高電平
通過這樣的方式實(shí)現(xiàn)GPIO操作,時(shí)序圖如下:
用過內(nèi)存映射操作的GPIO時(shí)序
可以看到,置低置高的操作只用了220ns左右,相比調(diào)用驅(qū)動(dòng)程序所需要的2.5μs,大大的提升了效率,也已經(jīng)達(dá)到了實(shí)時(shí)操作的要求。下面來看一個(gè)實(shí)際的例子,使用GPIO模擬HPI接口的寫時(shí)序,HPI是一個(gè)與主機(jī)通信的并行接口,主要用于DSP與其他總線或CPU進(jìn)行通信,在工業(yè)控制中,HPI接口是很常用的,所以我們就用HPI接口的時(shí)序作為例子,下面這個(gè)HPI時(shí)序有16位數(shù)據(jù)線和12位地址線:
HPI寫時(shí)序圖
時(shí)序要求
我們使用30位GPIO來模擬這個(gè)時(shí)序,將GPIO0~GPIO15作為16為數(shù)據(jù)線,GPIO16~GPIO28作為12為地址線,而GPIO28作為片選,GPIO29作為讀寫控制信號(hào)。基本的思路流程為:
①將片選,讀寫信號(hào)和地址線置為輸出,并且為高電平。數(shù)據(jù)線置為輸入。
②將數(shù)據(jù)線的寄存器置為需要的值。
③將數(shù)據(jù)線置為輸出。
④拉低讀寫信號(hào)線,再拉低片選。
⑤這里可以根據(jù)時(shí)序要求做一點(diǎn)延時(shí),拉高片選,拉高讀寫信號(hào),再將數(shù)據(jù)線置為輸入。
按照這樣操作就基本完成了一次要求的寫時(shí)序,實(shí)現(xiàn)的程序代碼:
int GPIO_Write(unsigned int AddrBits, unsigned int DataBits)
{
unsigned int DSetBits,DClearBits;
unsigned int ASetBits,AClearBits;
ASetBits= (AddrBits << 16) & 0x0fff0000 ; // 地址位為1的位
AClearBits= ~ASetBits ^ 0xf000ffff ;//地址位為0的位
printf('0x0%x\n',AClearBits);
GPIO.GPIO_OutSet(ASetBits);
GPIO.GPIO_OutClear(AClearBits);
DSetBits= DataBits & 0x0000ffff ;//數(shù)據(jù)位為1的位
DClearBits= ~DSetBits ^ 0xffff0000 ;//數(shù)據(jù)位為0的位
GPIO.GPIO_OutSet(DSetBits);
GPIO.GPIO_OutClear(DClearBits);
GPIO.GPIO_OutEnable(0x0000ffff);//設(shè)置數(shù)據(jù)線為輸出
GPIO.GPIO_OutClear(GPIO29);//讀寫信號(hào)
GPIO.GPIO_OutClear(GPIO28);//將片選拉低
GPIO.GPIO_OutSet(GPIO28);//將片選拉高
GPIO.GPIO_OutSet(GPIO29);//讀寫信號(hào)
GPIO.GPIO_OutDisable(0x0000ffff);//設(shè)置數(shù)據(jù)線為輸入
return 1;
}
執(zhí)行程序,可以看到片選和讀寫信號(hào)的時(shí)序?yàn)椋?/p>
片選和讀寫信號(hào)的時(shí)序
可以看到一次寫操作的時(shí)間大約在1.25μs,如果是調(diào)用驅(qū)動(dòng)程序控制GPIO來實(shí)現(xiàn)這個(gè)時(shí)序的模擬,那效率就會(huì)變低,詳細(xì)的代碼請(qǐng)參考例程。
注意事項(xiàng):我們推薦客戶直接使用例程中引出的接口進(jìn)行操作,不推薦客戶對(duì)硬件訪問這一部分代碼進(jìn)行修改,以免在操作的時(shí)候出現(xiàn)無法預(yù)估的錯(cuò)誤。
-
嵌入式主板
+關(guān)注
關(guān)注
7文章
6084瀏覽量
35157
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論