基于I.MX6ULL-MINI開發板
- LED學習
- GPIO應用編程
- 輸入設備
開發板中所有的設備(對象)都會在/sys/devices
體現出來,是 sysfs 文件系統中最重要的目錄結構
/sys下的子目錄 | 說明 |
---|---|
/sys/devices | 這是系統中所有設備存放的目錄,也就是系統中的所有設備在 sysfs 中的呈現、表達,也是 sysfs 管理設備的最重要的目錄結構。 |
/sys/block | 塊設備的存放目錄,這是一個過時的接口,按照 sysfs 的設計理念,系統所有的設備都存放在/sys/devices 目錄下,所以/sys/block 目錄下的文件通常是鏈接到/sys/devices 目錄下的文件。 |
/sys/bus | 這是系統中的所有設備按照總線類型分類放置的目錄結構,/sys/devices 目錄下每一種設備都是掛在某種總線下的,譬如 i2c 設備掛在 I2C 總線下。同樣,/sys/bus 目錄下的文件通常也是鏈接到了/sys/devices 目錄。 |
/sys/class | 這是系統中的所有設備按照其功能分類放置的目錄結構,同樣該目錄下的文件也是鏈接到了/sys/devices 目錄。按照設備的功能劃分組織在/sys/class 目錄下,譬如/sys/class/leds目錄中存放了所有的 LED 設備,/sys/class/input 目錄中存放了所有的輸入類設備。 |
/sys/dev | 這是按照設備號的方式放置的目錄結構,同樣該目錄下的文件也是鏈接到了/sys/devices 目錄。該目錄下有很多以主設備號:次設備號(major:minor)命名的文件,這些文件都是鏈接文件,鏈接到/sys/devices 目錄下對應的設備。 |
/sys/firmware | 描述了內核中的固件。 |
/sys/fs | 用于描述系統中所有文件系統,包括文件系統本身和按文件系統分類存放的已掛載點。 |
/sys/kernel | 這里是內核中所有可調參數的位置。 |
/sys/module | 這里有系統中所有模塊的信息。 |
/sys/power | 這里是系統中電源選項,有一些屬性可以用于控制整個系統的電源狀態。 |
應用層想要對底層硬件進行操控,通常可以通過兩種方式:
- /dev/目錄下的設備文件(設備節點);
- /sys/目錄下設備的屬性文件。
LED學習
led設備路徑
/sys/class/leds/sys-led
brightness:LED亮度,可讀可寫,0亮,非0滅
max_brightness:LED最大亮度,只讀
trigger:觸發模式,可讀可寫,模式如下:
none(無觸發)、mmc0(當對 mmc0 設備發起讀寫操作的時候 LED 會閃爍)、timer(LED 會有規律的一亮一滅,被定時器控制住)、heartbeat(心跳呼吸模式,LED 模仿人的心跳呼吸那樣亮滅變化)。
代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define LED_TRIGGER "/sys/class/leds/sys-led/trigger"
#define LED_BRIGHTNESS "/sys/class/leds/sys-led/brightness"
/*
在 C 語言中,反斜杠 \ 的作用是 行連接符(line continuation character)。
它允許你在多個行中編寫一條連續的語句,而不需要寫成一行。
具體來說,反斜杠告訴編譯器:下一行是當前行的延續,繼續合并在一起,而不加上額外的換行符。
*/
#define USAGE() fprintf(stderr,"usage:\n" \" %s<on|off>\n" \" %s<trigger><type>\n",argv[0],argv[0]) //argv[0]存放程序的名稱或路徑int main(int argc, char *argv[])//argc參數個數,argv[]參數
{int fd1, fd2;//校驗傳參if(2>argc){USAGE();return 1;}// 打開sysfs中的trigger和brightness文件fd1 = open(LED_TRIGGER, O_RDWR);if (0 > fd1) {perror("open LED_TRIGGER error");return 1;}fd2 = open(LED_BRIGHTNESS, O_RDWR);if (0 > fd2) {perror("open LED_BRIGHTNESS error");close(fd1);return 1;}//根據傳參控制LEDif(!strcmp(argv[1],"on")){//判斷傳入的第一個參數是否是onwrite(fd1,"none",4);//往trigger寫入none,共4個字節,trigger設置為無觸發write(fd2,"1",1);//點亮LED}else if(!strcmp(argv[1],"off")){//判斷傳入的第一個參數是否是offwrite(fd1,"none",4);write(fd2,"0",1);//熄滅LED}else if(!strcmp(argv[1],"trigger")){//如果傳入的第一個參數是trigger但是參數個數不是三個,提示用法if(3!=argc){USAGE();return 1;}if(0 > write(fd1,argv[2],strlen(argv[2]))){//判斷寫入是否正確,將第三個參數即觸發模式傳給fd1 (none、mmc0、timer、heartbeat)perror("write error");return 1;}}elseUSAGE();return 0;
}
GPIO應用編程
GPIO目錄
/sys/class/gpio
一共包含了 5 個 GPIO控制器,分別為 GPIO1、GPIO2、GPIO3、GPIO4、GPIO5,在這里分別對應 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 這 5 個文件夾,每一個 gpiochipX 文件夾用來管理一組 GPIO
每一個gpiochipX文件夾里面有以下內容
base:與 gpiochipX 中的 X 相同,表示該控制器所管理的這組 GPIO 引腳中最小的編號,如
gpiochip32
的base就是32
label:該組 GPIO 對應的標簽,也就是名字
ngpio:該控制器所管理的 GPIO 引腳的數量,引腳編號范圍是:base ~ base+ngpio-1
GPIO5_IO10在sysfs中對應的編號:128+10=138
export
export:用于將指定編號的 GPIO 引腳導出。在使用 GPIO 引腳之前,需要將其導出,導出成功之后才能使用它。注意 export 文件是只寫文件,不能讀取,將一個指定的編號寫入到 export 文件中即可將對應的 GPIO 引腳導出
如下圖,這個文件夾就是導出來的 GPIO 引腳對應的文件夾,用于管理、控制該 GPIO 引腳
unexport
unexport:將導出的 GPIO 引腳刪除。當使用完 GPIO 引腳之后,我們需要將導出的引腳刪除,同樣該文件也是只寫文件、不可讀
gpioX文件夾里的文件
direction:配置 GPIO 引腳為輸入或輸出模式。該文件可讀、可寫,讀表示查看 GPIO 當前是輸入還是輸出模式,寫表示將 GPIO 配置為輸入或輸出模式;讀取或寫入操作可取的值為"out"(輸出模式)和"in"(輸入模式)
value:在 GPIO 配置為輸出模式下,向 value 文件寫入"0"控制 GPIO 引腳輸出低電平,寫入"1"則控制 GPIO 引腳輸出高電平。在輸入模式下,讀取 value 文件獲取 GPIO 引腳當前的輸入電平狀態。
active_low:這個屬性文件用于控制極性,可讀可寫,默認情況下為 0,如果設置為1,則邏輯1為低電平,邏輯0為高電平
edge:控制中斷的觸發模式,該文件可讀可寫。在配置 GPIO 引腳的中斷觸發模式之前,需將其設置為輸入模式
非中斷引腳:echo "none" > edge
上升沿觸發:echo "rising" > edge
下降沿觸發:echo "falling" > edge
邊沿觸發 :echo "both" > edge
輸出代碼
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>static char gpio_path[100];//存放gpio設備文件路徑//修改GPIO的狀態
static int gpio_config(const char *attr, const char *value)//attr:修改的選項(direction、active_value、value)value:傳入的值
{char file_path[100];int fd;int len;sprintf(file_path, "%s/%s", gpio_path, attr);//將/sys/class/gpio/gpiox/(direction或active_value或value)傳給file_pathif(0 > (fd = open(file_path,O_WRONLY))){perror("open error");return fd;}len = strlen(value);if(len!= write(fd,value,len))//向file_path寫入value,即修改gpio的狀態{perror("write error");close(fd);return 1;}close(fd);return 0;
}int main(int argc, char *argv[])
{//判斷傳參是否正確if(3!=argc){fprintf(stderr,"usage: %s<gpio><value>\n", argv[0]);return 1;}//判斷gpiox文件是否導出sprintf(gpio_path,"/sys/class/gpio/gpio%s",argv[1]);if(access(gpio_path,F_OK))//判斷路徑是否存在,不存在則創建,F_OK參數用于判斷文件是否存在{int fd;int len;if(0 > (fd = open("/sys/class/gpio/export",O_WRONLY)))//打開export文件并將路徑存放在fd,準備寫入{perror("open error");return 1;}len = strlen(argv[1]);if(len != write(fd,argv[1],len))//向export寫入gpio號,即導出gpio文件{perror("write error");close(fd);return 1;}close(fd);//關閉文件}//修改gpio的狀態if(gpio_config("direction","out"))//配置為輸出模式{return 1;}if(gpio_config("active_low","0"))//默認極性{return 1;}if(gpio_config("value",argv[2]))//控制GPIO高低電平{return 1;}//退出程序return 0;}
中斷輸入代碼
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>static char gpio_path[100];//存放gpio設備文件路徑//修改GPIO的狀態
static int gpio_config(const char *attr, const char *value)//attr:修改的選項(direction、active_low、value)value:傳入的值
{char file_path[100];int fd;int len;sprintf(file_path, "%s/%s", gpio_path, attr);//將/sys/class/gpio/gpiox/(direction或active_low或value)傳給file_pathif(0 > (fd = open(file_path,O_WRONLY))){perror("open error");return fd;}len = strlen(value);if(len!= write(fd,value,len))//向file_path寫入value,即修改gpio的狀態{perror("write error");close(fd);return 1;}close(fd);return 0;
}int main(int argc, char *argv[])
{struct pollfd pfd;//用于poll調用來監視文件描述符的事件,記得包含頭文件poll.hchar file_path[100];//存儲/sys/class/gpio/gpiox/value的路徑int ret; //poll函數的返回值char value; //從GPIO引腳讀取的值//判斷傳參是否正確if(2!=argc){fprintf(stderr,"usage: %s<gpio> <value>\n", argv[0]);return 1;}//判斷gpiox文件是否導出sprintf(gpio_path,"/sys/class/gpio/gpio%s",argv[1]);if(access(gpio_path,F_OK))//判斷路徑是否存在,不存在則創建,F_OK參數用于判斷文件是否存在{int fd;int len;if(0 > (fd = open("/sys/class/gpio/export",O_WRONLY)))//打開export文件并將路徑存放在fd,準備寫入{perror("open error");return 1;}len = strlen(argv[1]);if(len != write(fd,argv[1],len))//向export寫入gpio號,即導出gpio文件{perror("write error");return 1;}close(fd);//關閉文件}//修改gpio的狀態if(gpio_config("direction","in"))//配置為輸入模式{return 1;}if(gpio_config("active_low","0"))//默認極性{return 1;}if(gpio_config("edge","both"))//設置中斷觸發方式,both是邊沿觸發{return 1;}//打開value屬性文件sprintf(file_path,"%s/%s",gpio_path,"value");if(0 > (pfd.fd = open(file_path,O_RDONLY)))//用pfd.fd存儲vlaue的文件路徑{perror("open error");return 1;}/*調用pollstruct pollfd{int fd; //文件描述符short events; //等待的事件short revents; //返回的事件}*/pfd.events = POLLPRI;//只關心高優先級數據可讀(中斷)read(pfd.fd,&value, 1);//在進入循環之前,先讀取一次GPIO狀態,以清除先前的狀態for( ; ; ){ret = poll(&pfd,1,-1);//調用poll,阻塞直到事件發生if(0 > ret){perror("poll error");return 1;}else if(0 == ret){fprintf(stderr,"poll timeout.\n");continue;}//校驗高優先級數據是否可讀if(pfd.revents & POLLPRI){if(0 > lseek(pfd.fd,0,SEEK_SET))//使用lseek重置文件指針,確保每次讀取最新的GPIO狀態{perror("lseek error");return 1;}if(0 > read(pfd.fd,&value,1)){perror("read error");return 1;}printf("gpio中斷觸發<value=%c\n",value);}}//退出程序return 0;
}
poll函數復習
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
fds:指向一個 struct pollfd 類型的數組,儲存關心的文件描述符
nfds:fds數組中元素的個數
timeout:決定poll函數的阻塞行為-1:一直阻塞,直到有信號來0:不阻塞>0:阻塞時間上限
*///struct pollfd 結構體
struct pollfd {int fd; /* 文件描述符 */short events; /* 等待的事件 */short revents; /* 返回的事件 */
};
輸入設備
讀取輸入設備時,應用程序打開輸入設備對應的設備文件,向其發起讀操作,每一次 read 操作獲取的都是一個 structinput_event 結構體類型數據,該結構體定義在<linux/input.h>頭文件中
struct input_event {struct timeval time;__u16 type; //描述發生了哪一種類型的時間__u16 code; //該類事件具體是哪個事件,如鍵盤上不同的按鍵1、2、3...__s32 value;
/*
內核每次上報事件都會向應用層發送一個數據 value,對 value 值的解釋隨著 code 的變化而變化。譬如對于按鍵事件(type=1)來說,如果 code=2(鍵盤上的數字鍵 1,也就是 KEY_1),那么如果 value 等于 1,則表示 KEY_1 鍵按下;value 等于 0 表示 KEY_1 鍵松開,如果 value 等于 2則表示 KEY_1 鍵長按。再比如,在絕對位移事件中(type=3),如果 code=0(觸摸點 X 坐標 ABS_X),那么 value 值就等于觸摸點的 X 軸坐標值;同理,如果 code=1(觸摸點 Y 坐標 ABS_Y),此時value 值便等于觸摸點的 Y 軸坐標值;所以對 value 值的解釋需要根據不同的 code 值而定
*/
};
type
code
Key
相對位移
絕對位移
通過數據同步使得應用程序得知本輪已經讀取到完整的數據同步類事件有如下,所有的輸入設備都需要上報同步事件,上報的同步事件通常是SYN_REPORT,而 value 值通常為 0
查看輸入設備是哪個設備節點
cat /proc/bus/input/devices
輸入設備代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>int main(int argc, char *argv[])
{struct input_event in_ev = {0};int fd = -1;int value = -1;//校驗傳參if(2 != argc){fprintf(stderr, "Usage: %s <input_dev>\n", argv[0]);return 1;}// 打開輸入事件文件if(0 > (fd = open(argv[1], O_RDONLY))){perror("open error");return 1;}for( ; ; ){// 讀取輸入事件if(sizeof(struct input_event) != (read(fd, &in_ev, sizeof(struct input_event)))){perror("read error");break;}// 按鍵輸入的類型if(EV_KEY == in_ev.type){switch (in_ev.value){case 0:printf("code<%d>:松開\n", in_ev.code);break;case 1:printf("code<%d>:按下\n", in_ev.code);break; case 2:printf("code<%d>:長按\n", in_ev.code);break; }}}
}
開發板KEY0按鍵測試
鍵盤測試
接入鍵盤,我使用的是2.4G連接方式
查看設備時發現有兩個
event3是鍵盤上的按鍵
event4是鍵盤上的旋鈕
由此可知這兩個東西不是同一個設備
鼠標測試
只能讀取鼠標的左、右和滾輪的按下和松開