@[TOC]
9 GPIO編程應(yīng)用開發(fā)
9.1 GPIO編程基礎(chǔ)介紹
? GPIO(General-Purpose IO Ports),即通用IO接口。GPIO的使用較為簡單,主要分為輸入和輸出兩種功能。GPIO主要用于實(shí)現(xiàn)一些簡單設(shè)備的控制。在作為輸入型GPIO的情況下,我們可以將該IO連接外部按鍵或者傳感器,用于檢測外部狀態(tài)。當(dāng)作為輸出時,我們可以通過輸出高低電平來控制外部設(shè)備的運(yùn)轉(zhuǎn)。
? 由于GPIO的功能多種多樣,我們需要首先將引腳設(shè)置為GPIO。設(shè)置為GPIO之后,我們需要設(shè)置GPIO的方向。當(dāng)設(shè)置為輸出時,我們可以控制輸出高電平或者低電平。當(dāng)設(shè)置為輸入時,我們可以讀取GPIO的電平來判斷外部輸入電平的高低。
9.2 GPIO編程軟件接口
? GPIO編程有多種實(shí)現(xiàn)方式,在這里,我們通過sysfs方式來實(shí)現(xiàn)GPIO的控制實(shí)現(xiàn)。
? 如果要通過sysfs方式控制gpio,首先需要底層內(nèi)核的支持。為了實(shí)現(xiàn)內(nèi)核對sysfs gpio的支持,我們需要在內(nèi)核中進(jìn)行設(shè)置。在編譯內(nèi)核的時候,加入 Device Drivers-> GPIO Support ->/sys/class/gpio/… (sysfs interface)。作為GPIO的引腳,不允許在內(nèi)核中被用作其他用途。
? 在系統(tǒng)正常運(yùn)行之后,我們可以在/sys/class/gpio下看到sysfs控制相關(guān)的接口。有三種類型的接口, 分別是控制接口,GPIO信號和GPIO控制器三種接口。這部分的具體介紹可參考《kernelDocumentationgpiosysfs.txt》。
9.2.1 控制接口
? 控制接口用于實(shí)現(xiàn)在用戶空間對GPIO的控制,主要包括/sys/class/gpio/export和/sys/class/gpio/unexport兩個接口。這這兩個控制接口都是只寫的,/sys/class/gpio/export實(shí)現(xiàn)將GPIO控制從內(nèi)核空間導(dǎo)出到用戶空間,/sys/class/gpio/unexport用于實(shí)現(xiàn)取消GPIO控制從內(nèi)核空間到用戶空間的導(dǎo)出。
? 下面以引腳編號為19的GPIO為例進(jìn)行說明,在/sys/class/gpio/目錄下,我們執(zhí)行"echo 19 > export"之后,將會產(chǎn)生一個”gpio19”節(jié)點(diǎn)來控制引腳編號為19的GPIO。我們執(zhí)行"echo 19 > unexport"之后,將會刪除之前通過export產(chǎn)生的”gpio19”節(jié)點(diǎn)。為了使用gpio,我們需要首先使用/sys/class/gpio/export導(dǎo)出gpio引腳編號。完成使用之后,通過/sys/class/gpio/unexport刪除掉之前導(dǎo)出的gpio引腳。
9.2.2 GPIO信號
? GPIO信號,即為GPIO本身,其路徑為/sys/class/gpio/gpioN/,擁有多個屬性。通過對這些屬性進(jìn)行控制,就可以實(shí)現(xiàn)對GPIO的控制。
-
“direction”屬性,讀取的值為”in”或者”out”。通過對該屬性寫入”in”或者”out”可以設(shè)置該GPIO為輸入或者輸出。如果直接寫入”out”,則會使GPIO直接輸出低電平。也可以通過寫入”low”或者”high”來直接設(shè)置輸出低電平或者高電平。
-
”value”屬性,用于讀取輸入電平或者控制輸出電平。如果GPIO為輸出,則對value寫入0為輸出低電平,寫入非0為輸出高電平。如果設(shè)置為輸入的話,則讀到0表示輸入為低電平,1為高電平。
- ”edge”屬性,用于設(shè)置觸發(fā)電平,只有在GPIO可以設(shè)置為中斷輸入引腳時才會出現(xiàn)該屬性。
9.2.3 GPIO控制器
? GPIO控制器,用于表示GPIO 控制實(shí)現(xiàn)的初始GPIO,其路徑為/sys/class/gpio/gpiochipN/。比如/sys/class/gpio/gpiochip42/ 則表示實(shí)現(xiàn)GPIO控制器的初始化編號為42。GPIO控制器的屬性為只讀屬性,包括base、label和ngpio等多個。
-
”base”屬性,和gpiochipN的N代表的含義相同,表示被該組GPIO控制器實(shí)現(xiàn)的第一個GPIO.
-
” ngpio”屬性,用于表示該控制器支持多少個GPIO,支持的GPIO編號為從N到N+ngpio-1
- ” label”屬性,用于判斷控制器,并不總是唯一的
9.3 IMX6ULL開發(fā)板GPIO編號的確定
? 每個芯片可以有N組GPIO,每組GPIO最多有32個GPIO,即最多有N*32個GPIO。但是在實(shí)際設(shè)計中,每組的GPIO數(shù)量各有不同。在IMX6ULL中,實(shí)際每組擁有的GPIO數(shù)量如下圖所示,具體詳見《IMX6ULLRM.pdf》手冊1347頁。
? 從上圖可以看到,在IMX6ULL中,共有5組GPIO,起始GPIO組為GPIO1。因此在實(shí)際GPIO編號計算中,第一組GPIO1對應(yīng)的編號為0~31。以此類推,IMX6ULL的GPION_X(N=1~5,X=0~31對應(yīng)的編號實(shí)際為(N-1)*32+X。接下來,我們以板載的LED和按鍵各自對應(yīng)的GPIO為例來說明如何在實(shí)際應(yīng)用中計算GPIO編號。
9.3.1 LED的GPIO編號計算
? 從原理圖中找到對應(yīng)LED的設(shè)計,具體的連接如下圖所示。從圖中我們可以看到,LED連接到的GPIO為GPIO5_3,其對應(yīng)的GPIO編號實(shí)際為(5-1)*32+3 = 131。因此,我們?nèi)绻趕ys_gpio中操作LED,我們就需要將編號131的GPIO進(jìn)行導(dǎo)出。
9.3.2 按鍵的GPIO編號計算
? 從原理圖中找到對應(yīng)按鍵的設(shè)計,底板有2個按鍵,具體的連接如下圖所示。從圖中我們可以看到,兩個按鍵連接到的GPIO分別為GPIO5_1和GPIO4_14,第一個按鍵KEY1對應(yīng)的GPIO編號為(5-1) 32+1 = 129,第二個按鍵KEY2對應(yīng)的GPIO編號為(4-1) 32+14=110。因此,我們?nèi)绻趕ys_gpio中讀取按鍵KEY1和KEY2的值,,我們就需要將編號129和110的GPIO進(jìn)行導(dǎo)出。
9.3.3 特殊情況下的GPIO編號計算
? 在有些情況下,起始的gpiochipN不是gpiochip0。這個時候 ,我們就需要在原有的GPIO編號基礎(chǔ)上加上起始gpiochipN值進(jìn)行計算。下圖所示的為其實(shí)gpiochip為gpiochip0的情況。
9.4 實(shí)際編程操作
? 在實(shí)際操作中,我們使用LED和按鍵實(shí)現(xiàn)了GPIO輸出和輸入的實(shí)驗(yàn),相關(guān)的實(shí)驗(yàn)過程和相關(guān)代碼如下。
9.4.1 導(dǎo)出GPIO口
? 為了導(dǎo)出GPIO口,我們需要向/sys/class/gpio/export寫入需要導(dǎo)出的引腳編號。在使用之后,我們也可以使用/sys/class/gpio/unexport取消導(dǎo)出引腳編號。
? 導(dǎo)出引腳編號的實(shí)現(xiàn)代碼如下所示,具體詳見《sysfs_gpio_1_export_gpio sysfs_gpio_export.c》的sysfs_gpio_export()函數(shù)。
32 int sysfs_gpio_export(unsigned int gpio)
33 {
34 int fd, len;
35 char buf[MAX_BUF];
36 // /sys/class/gpio/export
37 fd = open( "/sys/class/gpio/export", O_WRONLY);//打開文件
38 if (fd < 0) {
39 perror("gpio/export");
40 return fd;
41 }
42
43 len = snprintf(buf, sizeof(buf), "%d", gpio);//從數(shù)字變換為字符串,即1 變?yōu)椤?“
44 write(fd, buf, len);//將需要導(dǎo)出的GPIO引腳編號進(jìn)行寫入
45 close(fd);//關(guān)閉文件
46
47 return 0;
48 }
? 取消導(dǎo)出引腳編號的實(shí)現(xiàn)代碼如下所示,具體詳見《sysfs_gpio_export.c》的sysfs_gpio_unexport()函數(shù)。
59 int sysfs_gpio_unexport(unsigned int gpio)
60 {
61 int fd, len;
62 char buf[MAX_BUF];
63 // /sys/class/gpio/unexport
64 fd = open("/sys/class/gpio/unexport", O_WRONLY);//打開文件
65 if (fd < 0) {
66 perror("gpio/export");
67 return fd;
68 }
69
70 len = snprintf(buf, sizeof(buf), "%d", gpio);//從數(shù)字變換為字符串,即1 變?yōu)椤?“
71 write(fd, buf, len);//將需要取消導(dǎo)出的GPIO引腳編號進(jìn)行寫入
72 close(fd);//關(guān)閉文件
73 return 0;
74 }
? 在實(shí)現(xiàn)導(dǎo)出和取消導(dǎo)出引腳編號的函數(shù)之后,我們來實(shí)現(xiàn)具體的引腳編號的導(dǎo)出。LED和按鍵各自對應(yīng)的引腳編號如下所示
11 #define GPIO4_14 110
12 #define GPIO5_1 129
13 #define GPIO5_3 131
14
15 #define GPIO_KEY1 GPIO4_14
16 #define GPIO_KEY2 GPIO5_1
17 #define GPIO_LED GPIO5_3
? 在確定了各自對應(yīng)的引腳編號,我們就可以進(jìn)行導(dǎo)出了。具體實(shí)現(xiàn)代碼在程序文件《sysfs_gpio_1_export_gpio/sysfs_gpio_export.c》中main函數(shù),下為對應(yīng)代碼部分,我們將LED和按鍵對應(yīng)的引腳都進(jìn)行了導(dǎo)出。
183 int main(int argc, char **argv) {
184 unsigned int i;
185 unsigned int value1,value2;
186
187 printf(" ********************************************
");
188 printf(" ******** SYSFS_GPIO_TEST_DEMO**************
");
189 printf(" ******** version date: 2020/05 **********
");
190 printf(" ********************************************
");
191
192 printf("gpio begin to export gpio
");
193 sysfs_gpio_export(GPIO_KEY1);//export gpio key1
194 sysfs_gpio_export(GPIO_KEY2);//export gpio key2
195 sysfs_gpio_export(GPIO_LED);//export gpio led
196 printf("gpio export gpio ok
");
197
198
199 return 0;
200 }
? 在將代碼編譯之后,我們將代碼在板卡上進(jìn)行運(yùn)行。代碼運(yùn)行之后的的結(jié)果如下圖所示,可以看到成功的將GPIO110、GPIO129和GPIO131進(jìn)行了導(dǎo)出。
9.4.2 設(shè)置GPIO方向
? 為了實(shí)現(xiàn)導(dǎo)出的引腳的方向設(shè)置,我們需要對/sys/class/gpio/gpioN/direction寫入不同的值。寫入“in”則表示設(shè)置為輸入,寫入“out”則表示設(shè)置為輸出。設(shè)置引腳編號的的實(shí)現(xiàn)代碼如下所示,具體詳見《sysfs_gpio_2_export_gpio sysfs_gpio_export.c》的sysfs_gpio_set_dir ()函數(shù)。
86 int sysfs_gpio_set_dir(unsigned int gpio, unsigned int out_flag)
87 {
88 int fd, len;
89 char buf[MAX_BUF];
90 // /sys/class/gpio/gpioN/direction
91 len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/direction", gpio);
92
93 fd = open(buf, O_WRONLY);//打開文件
94 if (fd < 0) {
95 perror(buf);
96 return fd;
97 }
98
99 if (out_flag)//為1,則寫入“out",即設(shè)置為輸出
100 write(fd, "out", 4);
101 else//為0,則寫入“in",即設(shè)置為輸入
102 write(fd, "in", 3);
103
104 close(fd);//關(guān)閉文件
105 return 0;
106 }
? 在實(shí)現(xiàn)引腳方向的設(shè)置函數(shù)之后,我們分別針對按鍵和LED設(shè)置各自不同的方向。將按鍵設(shè)置為輸入“IN”,將LED設(shè)置為輸出“out”,對應(yīng)的代碼如下圖所示。相關(guān)的代碼在程序文件《sysfs_gpio_2_export_gpio/sysfs_gpio_export.c》中main函數(shù),下為對應(yīng)代碼部分。
183 int main(int argc, char **argv) {
184 unsigned int i;
185 unsigned int value1,value2;
186
187 printf(" ********************************************
");
188 printf(" ******** SYSFS_GPIO_TEST_DEMO**************
");
189 printf(" ******** version date: 2020/05 **********
");
190 printf(" ********************************************
");
191
192 printf("begin to export gpio and direction
");
193 sysfs_gpio_export(GPIO_KEY1);//export gpio key1
194 sysfs_gpio_export(GPIO_KEY2);//export gpio key2
195 sysfs_gpio_export(GPIO_LED);//export gpio led
196
197 sysfs_gpio_set_dir(GPIO_KEY1, 0);//set as input
198 sysfs_gpio_set_dir(GPIO_KEY2, 0);//set as input
199 sysfs_gpio_set_dir(GPIO_LED, 1);//set as output
200 printf(" export gpio and direction ok
");
201
202
203
204 return 0;
205 }
? 在將代碼編譯之后,我們將代碼在板卡上進(jìn)行運(yùn)行。代碼運(yùn)行之后的的結(jié)果如下圖所示,我們可以看到按鍵GPIO110和GPIO129的方向設(shè)置成了輸入,LED2的GPIO131設(shè)置成了輸入。
9.4.3 GPIO輸出實(shí)驗(yàn)-LED輸出控制
? 為了設(shè)置引腳的輸出電平高低,我們需要對/sys/class/gpio/gpioN/value寫入不同的值。寫入‘1’則表示輸出高電平,寫入‘0’則表示輸出低電平。設(shè)置引腳輸出高低電平的的實(shí)現(xiàn)代碼如下所示,具體詳見《sysfs_gpio_3_export_gpio sysfs_gpio_export.c》的sysfs_gpio_set_value ()函數(shù)。
119 int sysfs_gpio_set_value(unsigned int gpio, unsigned int value)
120 {
121 int fd, len;
122 char buf[MAX_BUF];
123 // /sys/class/gpio/gpioN/value
124 len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
125
126 fd = open(buf, O_WRONLY);//打開文件
127 if (fd < 0) {
128 perror(buf);
129 return fd;
130 }
131
132 if (value)//為1,則寫入“1",即設(shè)置為輸出高電平
133 write(fd, "1", 2);
134 else//為0,則寫入“0",即設(shè)置為輸出低電平
135 write(fd, "0", 2);
136
137 close(fd);//關(guān)閉文件
138 return 0;
139 }
? 在實(shí)現(xiàn)引腳輸出電平的控制函數(shù)之后,我們來實(shí)現(xiàn)LED的控制。我們通過將“1”或“0”寫入value來控制GPIO輸出高電平或者低電平,具體相關(guān)的代碼在程序文件《sysfs_gpio_3_export_gpio/sysfs_gpio_export.c》中main函數(shù),下為對應(yīng)代碼部分。
183 int main(int argc, char **argv) {
184 unsigned int i;
185 unsigned int value1,value2;
186
187 printf(" ********************************************
");
188 printf(" ******** SYSFS_GPIO_TEST_DEMO**************
");
189 printf(" ******** version date: 2020/05 **********
");
190 printf(" ********************************************
");
191
192 printf("led begin to init
");
193 sysfs_gpio_export(GPIO_LED);//export gpio led
194
195 sysfs_gpio_set_dir(GPIO_LED, 1);//set as output
196 printf("led init ok
");
197
198
199 /* Confirm INIT_B Pin as High */
200 while(1)
201 {
202
203
204 sysfs_gpio_set_value(GPIO_LED, 1);//output high
205 printf("led off
");
206 usleep(500000); //delay
207 sysfs_gpio_set_value(GPIO_LED, 0);//output low
208 printf("led on
");
209 usleep(500000);//delay
210 }
211
212 sysfs_gpio_unexport(GPIO_LED);//unexport gpio led
213
214 return 0;
215 }
? 在將代碼編譯之后,我們將代碼在板卡上進(jìn)行運(yùn)行。代碼運(yùn)行之后的的結(jié)果如下圖所示, 可以看到規(guī)律性的打印LED控制信息(實(shí)物可以看到LED燈閃爍)。
9.4.4 GPIO輸入試驗(yàn)-按鍵值讀取
? 為了讀取引腳輸入的電平高低,我們需要讀取/sys/class/gpio/gpioN/value的值。讀到的是‘1’則表輸入為高電平,讀到的是‘0’則表示輸入為低電平。讀取引腳輸入電平的、的的實(shí)現(xiàn)代碼如下所示,具體詳見《sysfs_gpio_4_export_gpio sysfs_gpio_export.c》的sysfs_gpio_get_value ()函數(shù)。
152 int sysfs_gpio_get_value(unsigned int gpio, unsigned int *value)
153 {
154 int fd, len;
155 char buf[MAX_BUF];
156 char ch;
157 // /sys/class/gpio/gpioN/value
158 len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
159
160 fd = open(buf, O_RDONLY);//打開文件
161 if (fd < 0) {
162 perror("gpio/get-value");
163 return fd;
164 }
165
166 read(fd, &ch, 1);//讀取外部輸入電平
167
168 if (ch != '0') {//為'1',則設(shè)置為1,即輸入為高電平
169 *value = 1;
170 } else {//為'0',則設(shè)置為0,即輸入為低電平
171 *value = 0;
172 }
173
174 close(fd);//關(guān)閉文件
175 return 0;
176 }
? 在實(shí)現(xiàn)引腳電平讀取函數(shù)之后,我們來實(shí)現(xiàn)外部按鍵值得讀取,我們通過讀取value的值來讀取按鍵值,具體相關(guān)的代碼在程序文件《sysfs_gpio_4_export_gpio/sysfs_gpio_export.c》中main函數(shù),下為對應(yīng)代碼部分。
183 int main(int argc, char **argv) {
184 unsigned int i;
185 unsigned int value1,value2;
186
187 printf(" ********************************************
");
188 printf(" ******** SYSFS_GPIO_TEST_DEMO**************
");
189 printf(" ******** version date: 2020/05 **********
");
190 printf(" ********************************************
");
191
192 printf("key begin to init
");
193 sysfs_gpio_export(GPIO_KEY1);//export gpio key1
194 sysfs_gpio_export(GPIO_KEY2);//export gpio key2
195
196 sysfs_gpio_set_dir(GPIO_KEY1, 0);//set as input
197 sysfs_gpio_set_dir(GPIO_KEY2, 0);//set as input
198
199 printf("key init ok
");
200
201
202 /* Confirm INIT_B Pin as High */
203 while(1)
204 {
205
206 sysfs_gpio_get_value(GPIO_KEY1, &value1); //read key1 value
207 //printf("@@key1 value 1is %d
",value1);
208 if(value1==0)//key1 pressed
209 {
210 printf("@@key1 is pressed 0
");
211 }
212 sysfs_gpio_get_value(GPIO_KEY2, &value2);//read key2 value
213 //printf("##key2 value 1is %d
",value2);
214 if(value2==0)//key2 pressed
215 {
216 printf("##key2 is pressed 0
");
217 }
218 usleep(100000);//delay
219
220 }
221
222 sysfs_gpio_unexport(GPIO_KEY1);//unexport gpio key1
223 sysfs_gpio_unexport(GPIO_KEY2);//unexport gpio key2
224
225
226 return 0;
227 }
? 在將代碼編譯之后,我們將代碼在板卡上進(jìn)行運(yùn)行。代碼運(yùn)行之后的的結(jié)果如下圖所示,我們可以看到在按鍵KEY1和KEY2按下之后打印的值各有不同。
9.4.5 LED和按鍵控制實(shí)驗(yàn)
? 在前幾個實(shí)驗(yàn)中,我們分別實(shí)現(xiàn)了LED和按鍵各自的控制。在這個實(shí)驗(yàn)中,我們將前幾個實(shí)驗(yàn)進(jìn)行整合,控制LED得閃爍,并讀取按鍵得值。當(dāng)按鍵按下時,打印相關(guān)信息。具體相關(guān)的代碼在程序文件《sysfs_gpio_5_export_gpio/sysfs_gpio_export.c》中main函數(shù),下為對應(yīng)代碼部分
183 int main(int argc, char **argv) {
184 unsigned int i;
185 unsigned int value1,value2;
186
187 printf(" ********************************************
");
188 printf(" ******** SYSFS_GPIO_TEST_DEMO**************
");
189 printf(" ******** version date: 2020/05 **********
");
190 printf(" ********************************************
");
191
192 printf("led&key begin to init
");
193 sysfs_gpio_export(GPIO_KEY1);//export gpio key1
194 sysfs_gpio_export(GPIO_KEY2);//export gpio key2
195 sysfs_gpio_export(GPIO_LED);//export gpio led
196 sysfs_gpio_set_dir(GPIO_KEY1, 0);//set as input
197 sysfs_gpio_set_dir(GPIO_KEY2, 0);//set as input
198 sysfs_gpio_set_dir(GPIO_LED, 1);//set as output
199 printf("led&key init ok
");
200
201
202 /* Confirm INIT_B Pin as High */
203 while(1)
204 {
205
206 sysfs_gpio_get_value(GPIO_KEY1, &value1); //read key1 value
207 //printf("@@key1 value 1is %d
",value1);
208 if(value1==0)//key1 pressed
209 {
210 printf("@@key1 is pressed 0
");
211 }
212 sysfs_gpio_get_value(GPIO_KEY2, &value2);//read key2 value
213 //printf("##key2 value 1is %d
",value2);
214 if(value2==0)//key2 pressed
215 {
216 printf("##key2 is pressed 0
");
217 }
218 //led flash
219 sysfs_gpio_set_value(GPIO_LED, 1);
220 printf("LED OFF
");
221 usleep(500000);
222 sysfs_gpio_set_value(GPIO_LED, 0);
223 printf("LED ON
");
224 usleep(500000);
225 }
226
227 sysfs_gpio_unexport(GPIO_KEY1);//unexport gpio key1
228 sysfs_gpio_unexport(GPIO_KEY2);//unexport gpio key2
229 sysfs_gpio_unexport(GPIO_LED);//unexport gpio led
230
231 return 0;
232 }
? 在將代碼編譯之后,我們將代碼在板卡上進(jìn)行運(yùn)行。代碼運(yùn)行之后的的結(jié)果如下圖所示,可以看到LED閃爍,按鍵KEY1和KEY2按下之后打印的值各有不同(因?yàn)長ED的閃爍導(dǎo)致按鍵需要經(jīng)過一次LED閃爍之后才能讀取,因此按鍵必須一直按著才能讀取到值的變化)。
本文摘自 :https://blog.51cto.com/w