向明尚++張志華++潘麗艷
摘 要:以飛凌6410開發板上的外部設備為例,介紹了GPIO工作原理,講解了Linux字符驅動程序的工作原理和設計步驟,介紹了Linux內核模塊設計、編譯、加載和卸載等的實現方法,闡述了嵌入式系統圖形用戶界面Qt的應用和多線程工作機制,給出了軟硬件的設計方法,以完成多線程嵌入式控制系統的設計。
關鍵詞:字符設備;驅動程序;多線程;嵌入式系統
中圖分類號:TP368.1 文獻標識碼:A DOI:10.15913/j.cnki.kjycx.2017.04.011
嵌入式系統被廣泛應用于航天、航空、鐵路、公路、汽車和家居生活等各個領域。系統中有大量的外部設備,比如用于測量溫度、壓力、流量和速度等的傳感器,以及馬達、電機等執行機構。這些外部設備往往需要協同、并行工作,即所謂的多任務、實時系統,對系統的設計有較高的要求。設計系統時,既要考慮每一個外部硬件設備能夠可靠工作,又要考慮多個設備之間的資源共享,所以,軟件、硬件系統的設計要考慮到多任務、實時性的要求。Linux操作系統因其多任務、實時性的特點,被廣泛應用于嵌入式系統的設計中。Qt作為一種基于C++跨平臺GUI系統,能夠為用戶提供構造圖形界面的強大功能,提供豐富的多線程支持,并被應用于嵌入式系統的GUI設計中。本文基于Linux+Qt,實現了多任務、多線程嵌入式控制系統。
1 字符設備驅動程序工作原理
對于嵌入式系統中的外部設備,有一大類在Linux系統中被定義為字符設備,它需要專用的設備驅動程序驅動才能正常工作。Linux設備驅動程序是為了特定的硬件提供給用戶程序的一組標準化接口,它隱藏了設備工作的細節。它是用來控制和管理硬件設備、完成數據傳送的專用軟件,是用戶的應用程序與外部設備信息交換的橋梁。設備驅動程序作為操作系統的一部分運行在操作系統內核中,用戶的應用程序通過系統調用與內核打交道,內核根據系統通調用號來調用相應的驅動程序所對應的接口函數,實現對外部設備的管理。Linux中字符設備驅動程序以內核模塊的形式編寫,驅動程序的加載方式之一就是采用內核模塊方式動態加載,具有靈活、高效的特點。
Linux的字符設備是能夠像字節流一樣被訪問的設備,由字符設備驅動程序來實現這種特性。字符設備驅動程序的設計,就是實現設備的讀寫、控制接口,即實現Linux的核心結構體file_operation。該結構體是Linux虛擬文件系統的文件接口,其中定義了用于設備操作的各種接口函數。在設計設備驅動程序時,只需要實現結構體中的有關函數即可,比如設備的打開、讀、寫、控制、關閉所對應的open、read、wtite、ioctl、release等函數,即可像操作普通文件一樣來管理設備。Linux系統為每一個設備分配一個主設備號和次設備號,驅動程序以主設備號、次設備號唯一標示一個具體的外設,比如開發板上的LED、按鍵、蜂鳴器、溫度傳感器和AD轉換器,等等。
2 Qt多線程工作機制
嵌入式系統大多是多任務、實時系統,多個外部設備同時并行工作,實時性要求比較嚴格。因為對用戶系統的設計要求比較高,所以,引入了多線程技術。
在Qt系統中,與線程有關的類是與平臺無關的QThread類,提供線程的創建、運行、管理等多種方法。線程通QThread類run函數重載執行。應用程序要想執行自己的線程,需要繼承QThread類,實現run函數即可。GUI運行主線程,從窗口獲取事件,并分發給相應的組件處理,即實現主線程、子線程多個線程之間并發執行,保證系統對多個外部設備的及時響應。為了實現多個線程之間的通信,Qt提供了一整套工作機制。
Qt系統中引入“可重入”“線程安全”的概念來說明函數與線程之間的關系。“可重入”繼承于C++,它指的是一個類的函數在該類的不同實例上可以被多個線程調用,則該類是“可重入”的;“線程安全”指的是不同的線程作用于同一個實例上可以正常工作。
Qt提供的信號和槽機制支持跨線程連接,方便了GUI主線程與子線程之間的通信,其連接方式有3種:①直接連接。當信號發出時,對應的槽函數立即調用執行。槽函數在發出信號的線程中執行,而不一定在接收對象所屬的線程中執行。②排隊連接。信號發出后,要等到接收對象所屬線程的時間循環取得控制權時再調用對應的槽函數,并在信號接收對象所屬的同一個線程中執行。③自動連接。這是Qt默認的連接方式。信號的發出與接收信號的對象在同一個線程,則工作在直接連接方式,否則工作在排隊方式。
對于Qt線程與事件循環機制,每個Qt的線程都有自己的事件循環。GUI主線程是唯一可以創建Qapplication對象并調用exec函數啟動事件循環的,而其他子線程則通過調用QTread類的exec函數啟動自己的事件循環。
3 系統設計
系統以飛凌6410開發板為例,利用其上的外部設備,比如LED、按鍵、蜂鳴器、溫度傳感器和GPIO等字符設備來設計實現多線程系統。
3.1 GPIO端口工作原理
GPIO通用輸入/輸出端口。每個GPIO端口有多個對應的寄存器,比如配制寄存器、數據寄存器等。它們可以通過軟件修改配制寄存器的值,將端口的功能分別配置成輸入、輸出等多種工作方式,而數據寄存器則用于保存端口數據。當端口配置成輸入功能時,可以讀取數據寄存器得到1個二進制數值,它的每一位1,0分別代表1個引腳電平的高、低;當端口配置成輸出功能時,可以寫入1個二進制數值到數據寄存器,它的每一位1,0分別控制1個引腳輸出高、低電平。
3.2 驅動程序設計
以開發板上的LED為例,介紹字符設備驅動程序的設計過程。6410開發板上有4個共陽極接法的LED指示燈,分別接到GPM0-GPM3上。當GPM0-GPM3的4個引腳有低電平輸出時,對應的LED點亮,否則熄滅。
LED驅動程序的設計就是實現Linux的核心結構體file_operation結構體的接口函數的過程,即實現下列幾個函數:
struct file_operation LED_fops={
.owner = THIS_LED, //模塊塊所有者
.open = led_open, //設備打開函數
.ioctl = led_ioctl, //設備操作函數
.release = led_release} //設備關閉函數
led_ioctl函數用來控制端口的操作,包括功能配置、輸出高低電平控制LED的亮滅燈等。
驅動程序設計采用內核模塊方式,可以實現動態加載,運行靈活,便于調試。其在驅動程中添加模塊代碼如下:
模塊初始化函數:
static int led_init(void)
{ int ret;
ret=register_chrdev(led_MAJOR,“led”,& LED_fops)
//注冊字符設備,其中參數分別為:主設備號、設備名稱、設備操作函數入口其中設備名稱提供給用戶,以文件的形式打開并操控設備
if (ret < 0) { printk(“Unable to register!”); return ret; }
return 0;
}
模塊退出函數:
static void led_exit(void)
{unregister_chrdev(led_MAJOR,“led”);
Printk(“Unregister !”);}
添加初始化、卸載接口:
module_init(led_init)//模塊初始化接口
module_exit(led_exit)//模塊卸載接口
3.3 模塊編譯
可加載的模塊化驅動程序寫好后,需要編譯成內核模塊才能加載到內核執行,并編寫相應的makefile文件,即:
obj-m := led_module.o
CROSS_COMPILE=arm-linux-
KERNELDIR?=/home/linux-3.0.1
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
有了makefile文件后,可以用命令編譯生成模塊:
make -C /linux-3.0.1/ M=/home/drivers6410 modules
3.4 模塊加載
模塊生成后,根據需要使用下列命令在內核中加載、卸載模塊,即:
insmod led_module.ko 加載模塊到內核
rmmod led_module.ko 卸載模塊
4 系統實現
4.1 系統測試程序設計
為了保證驅動程設計的正確性,需要編寫用戶程序來測試LED驅動程序和用戶程序的正確性、可靠性。經過測試可以正確運行,測試代碼如下:
int led_fd,i = 0,j;
led_fd = open("/dev/led",0); //以輸出的形式打開設備,控制LED的亮滅
if(led_fd < 0 )
{printf("Error opening gpcio!\n"); exit(0); }
while(1)
{
for(i = 0;i < 4; i++)
{ ioctl(led_fd, 1, i); //輸出高電平,熄滅LED }
for(i = 0;i < 4; i++)
{ ioctl(led_fd, 0, i); //端口低電平,點亮LED }
}
close(led_fd);
4.2 多線程用戶程序設計
由于該系統中控制多個外部設備,為了保證設備能夠及時得到系統的響應,在程序設計時引入了多線程操作,確保外部設備的工作不受影響,從而實現多線程實時系統。程序部分代碼如下:
GUI主線程部分代碼如下:
int main(int argc, char *argv[])
{
QApplication app1(argc, argv);//定義Qt應用程序對象,其構造函數接收與主函數相同的參數,是Qt圖形界面程序的入口。
MainWindow mainWindow;//定義主窗體、設置其屬性
mainWindow.setOrientation(MainWindow::ScreenOrientationAuto);
mainWindow.showExpanded();
QCoreApplication app(argc, argv);
Led led; //以下創建多個線程對象的實例,控制LED、ADC的設備的操作
Adc adc;
Buzzer buzzer;
Keyscan keyscan;
Ds18b20 ds18b20;
led.start();//以下啟動多個線程,實現多個設備的操作
adc.start();
buzzer.start();
keyscan.start();
ds18b20.start();
led.wait(); //線程等待,調用它的線程暫停執行,直到被喚醒或等待時間到。
buzzer.wait();
adc.wait();
keyscan.wait();
ds18b20.wait();
return app1.exec();} //調用了QCoreApplication的exec函數,創建消息循環
LED 控制子線程部分代碼:
class Led : public QThread //創建一個子線程,實例化QThread類
{ public: void run(); };
void Led::run() //重寫run函數
{ int led_fd,i = 0,j;
led _fd = ::open("/dev/led",0);//打開設備寫數據
if(led _fd < 0 ){printf("Error opening gpcio!\n"); exit(0); }
while(1)
{ for(i = 0;i < 4; i++)
{ioctl(led _fd, 1, i); sleep(1); //延時作用 }
文章編號:2095-6835(2017)04-0013-02
for(i = 0;i < 4; i++)
{ioctl(led _fd, 0, i); sleep(1); }
}
::close(led _fd);
}
5 結束語
該系統利用Linux內核模塊技術開發了多個字符設備驅動程序,同時,利用Qt作為圖形界面開發了多線程、多任務嵌入式系統,實現了外部設備的并發操作。經測試,系統運行穩定、
可靠。
參考文獻
[1]黃宇東,胡躍明,陳安.基于Qt的多線程技術應用于研究[J].軟件導刊,2009,8(10):40-42.
[2]王粉花.基于Linux字符設備驅動程序的設計與實現[J].計算機工程,2006,32(23):278-280.
[3]張威,黃沖.嵌入式Linux設備驅動程序的設計方法研究[J].江西師范大學學報,2007,31(4):391-393.
〔編輯:白潔〕