APP下载

基于Qt的多线程嵌入式系统设计与实现

2017-03-25向明尚张志华潘丽艳

科技与创新 2017年4期
关键词:驱动程序嵌入式系统

向明尚++张志华++潘丽艳

摘 要:以飞凌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.

〔编辑:白洁〕

猜你喜欢

驱动程序嵌入式系统
避免Windows系统更新反复安装显示驱动
方便直观改善Windows驱动程序管理
设备升级问题回退驱动解决
阻止Windows Update更新驱动程序
办公自动化系统的设计
基于物联网项目驱动的嵌入式系统教学改革的研究与实践
嵌入式系统课程“中断、异常与事件”教学实践及启示
面向实践创新人才培养的嵌入式系统教学研究
妙用鼠标驱动
驱动程序更新与推荐