本章分別使用C庫和系統調用的文件操作方式控制開發板的LED燈,展示如何在應用層通過系統提供的設備文件控制相關硬件。
本章的示例代碼目錄為:base_code/linux_app/led/sys_class_leds。
9.1.?LED子系統
在Linux系統中,絕大多數硬件設備都有非常成熟的驅動框架,驅動工程師使用這些框架添加與板子相關的硬件支持,建立硬件與Linux內核的聯系,內核再通過統一文件系統接口呈現給用戶,用戶通過對應的設備文件控制硬件。
對于LED設備,Linux提供了LED子系統驅動框架,在Linux內核源碼中的“Documentation/leds/leds-class.txt”有相關的描述,它實現了一個leds類,用戶層通過sysfs文件系統對LED進行控制。
9.1.1.?LED設備目錄
使用了LED子系統驅動的設備,會被展現在/sys/class/leds目錄下,可在主機和開發板使用如下命令查看,命令的輸出可能會因為硬件環境不同而不一樣:
123456789 10 11 | #在主機或ARM板的終端上執行如下命令: ls /sys/class/leds/#根據具體的目錄內容繼續查看:#在主機上有input2::capslock目錄,可在主機執行如下命令查看 ls /sys/class/leds/input2::capslock#在開發板上有cpu目錄,可在開發板上執行如下命令查看ls /sys/class/leds/cpu |
如下圖
上圖可看到,示例中的Ubuntu主機和開發板/sys/class/leds下包含了以LED設備名 字命名的目錄,如“input2::capslock”、“input2::numlock”和“blue”、“cpu”等LED燈,這 些目錄對應的具體LED燈如下表所示。
表 /sys/class/leds下目錄對應的設備
/sys/class/leds下的目錄 | 對應的LED燈設備 |
---|---|
input2::capslock | 鍵盤大寫鎖定指示燈(input后的數字編號可能不同) |
input2::numlock | 鍵盤數字鍵盤指示燈(input后的數字編號可能不同) |
input2::scrolllock | 鍵盤ScrollLock指示燈(input后的數字編號可能不同) |
cpu | 開發板的心跳燈 |
red | Pro開發板RGB燈的紅色,Mini開發板的用戶燈 |
green | Pro開發板RGB燈的綠色,Mini開發板的用戶燈 |
blue | Pro開發板RGB燈的藍色,Mini開發板的用戶燈 |
mmc0: | SD卡指示燈(出廠鏡像默認沒有啟用) |
9.1.2.?LED設備屬性
上圖中,在具體的LED目錄下又包含brightness、max_brightness、trigger等文件,這些文件包含了LED設備的屬性和控制接口。
-
max_brightness文件:表示LED燈的最大亮度值。
-
brightness文件:表示當前LED燈的亮度值,它的可取 值范圍為[0~max_brightness],一些LED設備不支持多級亮度,直接以非0值來 表示LED為點亮狀態,0值表示滅狀態。
-
trigger文件:則指示了LED燈的觸發方式,查看該文件的內容時,該文件會 列出它的所有可用觸方式,而當前使用的觸發方式會以“[]”符號括起。常見的觸 發方式如下表所示。
表 trigger常見的觸發方式
觸發方式 | 說明 |
---|---|
none | 無觸發方式 |
disk-activity | 硬盤活動 |
nand-disk | nand flash活動 |
mtd | mtd設備活動 |
timer | 定時器 |
heartbeat | 系統心跳 |
9.2.?控制LED實驗(C庫函數)
在《命令行點燈和檢測按鍵》章節中,我們演示了使用echo命令修改設備文件,實際上也可以使用gedit、Vim等編輯器進 行修改,修改時注意用戶權限即可。既然設備是以文件形式提供的,那么自然也可以使用C庫函數 或系統調用的方式讀寫文件,達到控制設備的目的。
9.2.1.?實驗代碼分析
本小節的示例代碼目錄為:led/sys_class_leds/c_stdio。
本小節先演示使用C庫函數控制LED,具體如下所示。
通過C庫函數控制LED(base_code/linux_app/led/sys_class_leds/c_stdio/sources/main.c文件)
123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | #include <stdio.h> #include <stdlib.h> #include <unistd.h>//ARM 開發板LED設備的路徑 #define RLED_DEV_PATH "/sys/class/leds/red/brightness" #define GLED_DEV_PATH "/sys/class/leds/green/brightness" #define BLED_DEV_PATH "/sys/class/leds/blue/brightness"//Ubuntu主機LED設備的路徑,具體請根據自己的主機LED設備修改 // #define RLED_DEV_PATH "/sys/class/leds/input2::capslock/brightness" // #define GLED_DEV_PATH "/sys/class/leds/input2::numlock/brightness" // #define BLED_DEV_PATH "/sys/class/leds/input2::scrolllock/brightness"int main(int argc, char *argv[]) {FILE *r_fd, *g_fd, *b_fd;printf("This is the led demo\n");//獲取紅燈的設備文件描述符r_fd = fopen(RLED_DEV_PATH, "w");if(r_fd < 0){printf("Fail to Open %s device\n", RLED_DEV_PATH);exit(1);}//獲取綠燈的設備文件描述符g_fd = fopen(GLED_DEV_PATH, "w");if(g_fd < 0){printf("Fail to Open %s device\n", GLED_DEV_PATH);exit(1);}//獲取藍燈的設備文件描述符b_fd = fopen(BLED_DEV_PATH, "w");if(b_fd < 0){printf("Fail to Open %s device\n", BLED_DEV_PATH);exit(1);}while(1){//紅燈亮fwrite("255",3,1,r_fd);fflush(r_fd);//延時1ssleep(1);//紅燈滅fwrite("0",1,1,r_fd);fflush(r_fd);//綠燈亮fwrite("255",3,1,g_fd);fflush(g_fd);//延時1ssleep(1);//綠燈滅fwrite("0",1,1,g_fd);fflush(g_fd);//藍燈亮fwrite("255",3,1,b_fd);fflush(b_fd);//延時1ssleep(1);//藍燈亮fwrite("0",1,1,b_fd);fflush(b_fd);} } |
可以發現,這個控制LED燈的過程就是一個普通的文件寫入流程:
-
第5~13行:定義了三盞LED燈的brightness文件路徑。配套的程序默認使用 開發板RGB燈的路徑,如果要在Ubuntu主機上測試請根據自己主機上的設備文件修改10~13行的內容。
-
第18~41行:使用fopen庫函數,以“w”的寫模式打開了三盞LED的brightness文件,并獲得文件描述符。
-
第43~70行:在循環中分別對三盞燈寫入“255”和“0”的字符串來控制LED燈的亮 度,寫入后調用了fflush庫函數要求立刻把緩沖區的內容寫入到文件上。
本代碼有兩處值得注意的地方:
如果是普通文件,按代碼while循環的執行流程,運行一段時間后,由于多次 寫入,文件中的內容應該為“255025502550255”這樣的字符串,但對于此 處的brightness設備文件,它的最終內容只是“255”或“0”,而不是像普通 文件那樣記錄了一連串前面輸入的字符。這是因為在LED的設備驅動層中 ,brightness文件就相當于一個函數的參數接口,每次對文件執行寫入操 作時,會觸發驅動代碼以這次寫入的內容作為參數,修改LED燈的亮度;而每次讀 取操作時,則觸發驅動代碼更新當前LED燈亮度值到brightness文件,所以brightness始終 是一個0~255的亮度值,而不是“255025502550255”這樣的字符串。特別地, 如果在一次寫入操作中,直接寫入“0255025502550”這樣的 字符串,驅動層會把它當成數字255025502550,而該數字大于最大亮度值,所以它最終會以255的 亮度控制LED燈,若此時讀取brightness文件,也會發現它的值確實是255。關于這些細節, 在學習了LED子系統框架后查看驅動源碼可更好地了解。
另一處要注意的是代碼中調用fwrite函數寫入內容時,它可能只是把內容保存 到了C庫的緩沖區,并沒有執行真正的系統調用write函數把內容寫入到設備文件,這種情況下LED燈 的狀態是不會被改變的,代碼中在fwrite函數后調用了fflush要求立刻把緩沖區的內容寫入到文件,確保 執行了相應的操作。在實驗時可以嘗試把代碼中的fflush都注釋掉, 這種情況下有極大的幾率是無法正常改變LED燈狀態的。
如果不考慮操作的時間開銷,其實控制硬件更推薦的做法是,每次控制LED燈都使用fopen—fwrite—fclose的 流程,這樣就不需要考慮flseek、fflush的問題了。當然,我們最推崇的還是下一小節直接通過 系統調用來控制硬件的方式。
9.2.2.?編譯及測試
本實驗使用的Makefile由上一章節修改而來,修改了最終的可執行文件名為led_demo,以及C源 文件目錄改為了main.c文件所在的sources,其它方面沒有差異。
9.2.2.1.?x86架構
本工程的main.c實驗代碼使用的設備文件默認是開發板 上的RGB燈,在Ubuntu主機上并沒有這樣的設備,如果想嘗試在主機上使用, 可以根據自己Ubuntu主機上存的LED設備修改代碼中的LED路徑,然后使用make直接編譯測試。
1 2 3 4 5 6 7 | #在主機的實驗代碼Makefile目錄下編譯 #默認編譯x86平臺的程序 make #運行需要root權限,要使用sudo運行 #運行需要root權限,要使用sudo運行 sudo ./build_x86/led_demo #程序運行后終端會輸出提示,相應的LED燈設備狀態會改變 |
9.2.2.2.?ARM架構
對于ARM架構的程序,可使用如下步驟進行編譯:
1 2 3 | #在主機的實驗代碼Makefile目錄下編譯 #編譯arm平臺的程序 make ARCH=arm |
編譯后生成的ARM平臺程序為build_arm/led_demo,使用網絡文件系統共享至開 發板,在開發板的終端上測試即可。
如下圖:
程序執行后終端會有輸出,開發板上的三盞用戶LED燈也會輪流閃爍。
9.3.?控制LED實驗(系統調用)
由于使用C庫的文件操作函數存在緩沖機制,使用它來控制硬件時存在不 確定性,所以我們更喜歡直接以系統調用來控制硬件設備。
9.3.1.?實驗代碼分析
本小節的示例代碼目錄為:led/sys_class_leds/c_systemcall。
本小節通過系統調用的文件操作方式控制LED,具體如下所示。
通過系統調用控制LED(base_code/linux_app/led/sys_class_leds/c_systemcall/sources/main.c文件)
123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h>//ARM 開發板LED設備的路徑 #define RLED_DEV_PATH "/sys/class/leds/red/brightness" #define GLED_DEV_PATH "/sys/class/leds/green/brightness" #define BLED_DEV_PATH "/sys/class/leds/blue/brightness"//Ubuntu主機LED設備的路徑,具體請根據自己的主機LED設備修改 // #define RLED_DEV_PATH "/sys/class/leds/input2::capslock/brightness" // #define GLED_DEV_PATH "/sys/class/leds/input2::numlock/brightness" // #define BLED_DEV_PATH "/sys/class/leds/input2::scrolllock/brightness"int main(int argc, char *argv[]) {int res = 0;int r_fd, g_fd, b_fd;printf("This is the led demo\n");//獲取紅燈的設備文件描述符r_fd = open(RLED_DEV_PATH, O_WRONLY);if(r_fd < 0){printf("Fail to Open %s device\n", RLED_DEV_PATH);exit(1);}//獲取綠燈的設備文件描述符g_fd = open(GLED_DEV_PATH, O_WRONLY);if(g_fd < 0){printf("Fail to Open %s device\n", GLED_DEV_PATH);exit(1);}//獲取藍燈的設備文件描述符b_fd = open(BLED_DEV_PATH, O_WRONLY);if(b_fd < 0){printf("Fail to Open %s device\n", BLED_DEV_PATH);exit(1);}while(1){//紅燈亮write(r_fd, "255", 3);//延時1ssleep(1);//紅燈滅write(r_fd, "0", 1);//綠燈亮write(g_fd, "255", 3);//延時1ssleep(1);//綠燈滅write(g_fd, "0", 1);//藍燈亮write(b_fd, "255", 3);//延時1ssleep(1);//藍燈亮write(b_fd, "0", 1);} } |
本實驗代碼與上一小節使用C庫函數操作的控制流程完全一樣,只是把C庫的文件操作 替換成了系統調用的文件操作方式,特別之處在于這種方式不需要調用fflush之類的 函數確保緩沖區的內容被寫出,而且系統調用也不存在類似這樣操作的函數。
相對C庫函數的操作方式,通過系統調用更加簡單直接,而且這種與設備文件聯系比較 緊密的應用,C庫函數兼容性好的優點也沒有用武之地,所以在編寫這類應用通常直接使用系統調用的方式。
9.3.2.?編譯及測試
本實驗使用的Makefile與上一小節的完全一樣,不再分析。
本實驗的x86和arm架構的編譯、測試步驟也與上一小節完全一樣,注意切換到對應的工程路徑即可。
對于ARM架構的程序,可使用如下步驟進行編譯:
1 2 3 | #在主機的實驗代碼Makefile目錄下編譯 #編譯arm平臺的程序 make ARCH=arm |
編譯后生成的ARM平臺程序為build_arm/led_demo,使用網絡文件系統共享至開發 板,在開發板的終端上測試即可。
如下圖:
程序執行后終端會有輸出,開發板上的三盞用戶LED燈也會輪流閃爍,實驗現象 與使用C庫函數操作方式是一樣的。
Next??Previous