经过前面的配置,S5PV210开发已经可以成功进入Linux控制台了,那么,有了这个环境就可以开始学习Linux驱动的编写和测试了。学习Linux设备驱动,通常是从字符设备驱动开始。我写的第一个驱动程序是Led的,其实也就是熟悉下字符设备驱动的基本结构,本文以中断方式的按键驱动为例,简单的介绍下字符设备驱动程序。
一 按键驱动程序的简单实现
下面是基于中断和消息的按键驱动程序,其工作原理是:当应用程序读取键值时,会调用按键驱动程序的read函数,而我们实现的read函数检测完读取长度后没有直接读取键值而是等待按键消息,如果没有按键,程序会进入休眠状态,这样可以节省大量的CPU,而当我们按键时硬件会产生中断,程序自动进入中断处理函数,在中断处理函数中,驱动程序读取键值存入全局变量并激活read函数中等待的消息,应用程序被迅速唤醒并通过read函数读取键值,如此,完成了获取键值的工作。下面是源码,比较简单,也就不多说了。
源码:
#include <linux/types.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
static dev_t devno;
static struct cdev cdev;
static struct class* buttons_class;
static struct device* buttons_device;
static wait_queue_head_t button_waitq;
static volatile int pressed = 0;
static unsigned char key_val;
struct key_desc{
unsigned int pin;
unsigned char value;
};
static struct key_desc key_descs[8] = {
[0] = {
.pin = S5PV210_GPH0(0),
.value = 0x00,
},
[1] = {
.pin = S5PV210_GPH0(1),
.value = 0x01,
},
[2] = {
.pin = S5PV210_GPH0(2),
.value = 0x02,
},
[3] = {
.pin = S5PV210_GPH0(3),
.value = 0x03,
},
[4] = {
.pin = S5PV210_GPH0(4),
.value = 0x04,
},
[5] = {
.pin = S5PV210_GPH0(5),
.value = 0x05,
},
[6] = {
.pin = S5PV210_GPH2(6),
.value = 0x06,
},
[7] = {
.pin = S5PV210_GPH2(7),
.value = 0x07,
},
};
static irqreturn_t buttons_irq(int irq, void *dev_id){
volatile struct key_desc *key = (volatile struct key_desc *)dev_id;
if(gpio_get_value(key->pin)){
key_val = key->value|0x80;
}
else{
key_val = key->value;
}
pressed = 1;
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int buttons_open(struct inode *inode, struct file *file){
int ret;
ret = request_irq(IRQ_EINT(0), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &key_descs[0]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(1), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &key_descs[1]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(2), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &key_descs[2]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(3), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &key_descs[3]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(4), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &key_descs[4]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(5), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key6", &key_descs[5]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(22), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key7", &key_descs[6]);
if(ret)
return ret;
ret = request_irq(IRQ_EINT(23), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key8", &key_descs[7]);
if(ret)
return ret;
return 0;
}
static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
if(count != 1){
printk(KERN_ERR "The driver can only give one key value once!\n");
return -ENOMEM;
}
wait_event_interruptible(button_waitq, pressed);
pressed = 0;
if(copy_to_user(data, &key_val, 1)){
printk(KERN_ERR "The driver can not copy the data to user area!\n");
return -ENOMEM;
}
return 0;
}
static int buttons_close(struct inode *inode, struct file *file){
free_irq(IRQ_EINT(0), &key_descs[0]);
free_irq(IRQ_EINT(1), &key_descs[1]);
free_irq(IRQ_EINT(2), &key_descs[2]);
free_irq(IRQ_EINT(3), &key_descs[3]);
free_irq(IRQ_EINT(4), &key_descs[4]);
free_irq(IRQ_EINT(5), &key_descs[5]);
free_irq(IRQ_EINT(22), &key_descs[6]);
free_irq(IRQ_EINT(23), &key_descs[7]);
return 0;
}
struct file_operations buttons_ops = {
.open = buttons_open,
.read = buttons_read,
.release = buttons_close,
};
int buttons_init(void){
int ret;
cdev_init(&cdev, &buttons_ops);
cdev.owner = THIS_MODULE;
ret = alloc_chrdev_region(&devno, 0, 1, "buttons");
if(ret){
printk(KERN_ERR "alloc char device region faild!\n");
return ret;
}
ret = cdev_add(&cdev, devno, 1);
if(ret){
printk(KERN_ERR "add char device faild!\n");
goto add_error;
}
buttons_class = class_create(THIS_MODULE, "buttonsdrv");
if(IS_ERR(buttons_class)){
printk(KERN_ERR "create class error!\n");
goto class_error;
}
buttons_device = device_create(buttons_class, NULL, devno, NULL, "buttons");
if(IS_ERR(buttons_device)){
printk(KERN_ERR "create buttons device error!\n");
goto device_error;
}
init_waitqueue_head(&button_waitq);
return 0;
device_error:
class_destroy(buttons_class);
class_error:
cdev_del(&cdev);
add_error:
unregister_chrdev_region(devno,1);
return -ENODEV;
}
void buttons_exit(void){
device_destroy(buttons_class, devno);
class_destroy(buttons_class);
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试程序代码:
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd = open("/dev/buttons", O_RDWR);
if(fd < 0){
printf("open error");;
return 0;
}
unsigned char key;
while(1){
read(fd, &key, 1);
printf("The key = %x\n", key);
}
close(fd);
}
相比轮询方式的按键驱动程序,中断方式编写的按键驱动程序可以很大程度上节省CPU资源,因此,推荐使用中断方式。
二 支持POLL机制
上面这种方式实现的按键驱动程序有个弊端,如果我们不按键,应用程序将会永远阻塞在这里,幸运的是,linux内核提供了poll机制,可以设置超时等待时间,如果在这个时间内读取到键值则正常返回,反之则超时退出。使内核支持poll非常简单,为file_operations的poll成员提供poll处理函数即可。
使内核支持poll还需要以下几步:
添加poll头文件
#include <linux/poll.h>
编写poll处理函数:
static unsigned buttons_poll(struct file *file, poll_table *wait){
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait);
if (pressed)
mask |= POLLIN | POLLRDNORM;
return mask;
}将poll处理函数添加给file_operations:
.poll = buttons_poll,这样,驱动程序就支持poll机制了。下面是poll方式的测试程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
int main(int argc, char **argv){
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];
fd = open("/dev/buttons", O_RDWR);
if (fd < 0){
printf("can't open!\n");
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1){
ret = poll(fds, 1, 5000);
if (ret == 0){
printf("time out\n");
}
else{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}
这样,应用程序可以限制时间,如果在一定时间内读取不到键值就可以做特殊处理,这种思想在网络通信中应用广泛。
三 支持异步机制
很多情况下,我们的程序在等待按键期间需要处理其它任务而不是在这里空等,这时,就需要采用异步模式了。所谓异步模式,实际上是采用消息机制(以本文的按键程序为例),即当驱动程序检测到按键后发送消息给应用程序,应用程序接收到消息后再去读取键值。与前面的两种模式相比,最大的不同在于异步方式是驱动告诉应用程序来读而不是应用程序主动去读。添加异步支持更加简单,首先是为file_operations注册fasync函数,函数内容如下:
static int buttons_fasync(int fd, struct file * file, int on){
return fasync_helper(fd, file, on, &button_async);
}然后再buttons_read函数中添加一行代码,修改后的代码如下:static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
if(count != 1){
printk(KERN_ERR "The driver can only give one key value once!\n");
return -ENOMEM;
}
wait_event_interruptible(button_waitq, pressed);
pressed = 0;
if(copy_to_user(data, &key_val, 1)){
printk(KERN_ERR "The driver can not copy the data to user area!\n");
return -ENOMEM;
}
return 0;
}这样,驱动程序就支持异步获取键值了,为了测试效果,测试程序也需要修改,代码如下:#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
/* sixthdrvtest
*/
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun);
fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
if (fd < 0){
printf("can't open!\n");
return -1;
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
int rest;
while (1){
printf("Hello\n");
while(rest = sleep(50)){
sleep(rest);
}
}
return 0;
}这里需要注意的是,应用程序接收到消息会打断sleep,比如执行sleep(5)之后程序接收到了一个消息,这时,应用程序就被唤醒了,虽然是去执行的消息处理函数。如果程序接收到消息时仅睡眠了2秒,那么sleep被中断时会返回5-2=3,所以代码中采用while循环方式进行sleep,这样,即使接收到了消息也能完整的休眠5秒,当然,sleep函数本身是不够精确的,不过相差无几。
到这里,这个驱动程序基本上就算可以了,当然,还有对阻塞和非阻塞的支持,同步与互斥的支持,而阻塞与非阻塞无非是加上个逻辑判断,同步与互斥根应用程序的同步控制也差不多,无非就是信号量或者原子操作,这里就不多说了,如果有朋友需要这些内容可以留言讨论。
对于一个平台无关的设计来说,要考虑同一个控件在不同的平台上可能有不同的外观和尺寸。这意味着使用绝对大小和位置来进行窗口布局几乎是行不通的。但是wxWidgets的布局控件,可以灵活的进行非常复杂的窗口布局。而目前wxWidgets总共支持五类布局控件,每一种布局控件或者用来实现一种特殊的布局方式,或者用来实现和布局相关的一种特殊的功能比如在某些控件周围围绕一个静态的文本框。
控件都会有自己的最小大小、边界、对齐方式、伸缩因子(默认为0,如果为1则在窗口空间不够的情况下,缩小为原来的一半)
使用布局控件进行编程:
1、使用wxBoxSizer进行编程
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, wxT("Hello wxWidgets"), wxPoint(50,50), wxSize(800,600))
{
// set icon for application
SetIcon(wxIcon(mondrian_xpm));
wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);
// 创建一个最小大小为100的多行文本框x60
top_sizer->Add( new wxTextCtrl(this,
wxID_ANY, "My test", wxDefaultPosition, wxSize(100, 60), wxTE_MULTILINE),
1, // 垂直方向可缩放因子为1
wxEXPAND | // 水平方向可缩放
wxALL, // 四周都有空白边框
10 ); // 四周空白边框大小都为10
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
button_sizer->Add( new wxButton(this, wxID_OK, "OK"),
0, // 水平方向不可缩放
wxALL, // 四周有空白边框,默认为顶部对齐
10 ); // 空白边框大小都为10
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
top_sizer->Add( button_sizer,
0, // 垂直方向不可缩放
wxALIGN_CENTER );// 无边框并且居中对齐
SetSizer(top_sizer); // 绑定对话框和布局控件
top_sizer->Fit(this); // 调用对话框大小
top_sizer->SetSizeHints(this); // 设置对话框最小大小
// Create a status bar just for fun
CreateStatusBar(1);
SetStatusText(wxT("Welcome to wxWidgets!"));
} 2、使用wxStaticBoxSizer
wxStaticBoxSizer除了实现wxBoxSizer的功能,另外还在整个布局的范围以外增加了一个静态的边框wxStaticBox,整个wxStaticBox需要手动创建并且在wxStaticBoxSizer的构造函数中作为参数传入。
#define ID_CHECKBOX 10031
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, wxT("Hello wxWidgets"), wxPoint(50,50), wxSize(800,600))
{
// set icon for application
SetIcon(wxIcon(mondrian_xpm));
wxBoxSizer *top_level = new wxBoxSizer(wxVERTICAL);
wxStaticBox *static_bos = new wxStaticBox(this, wxID_ANY, wxT("General settings"));
wxStaticBoxSizer *static_box_sizer = new wxStaticBoxSizer(static_bos, wxVERTICAL);
top_level->Add(static_box_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
wxCheckBox *check_box = new wxCheckBox( this, ID_CHECKBOX, wxT("&Show splash screen"),
wxDefaultPosition, wxDefaultSize);
static_box_sizer->Add(check_box, 0, wxALIGN_LEFT | wxALL, 5);
SetSizer(top_level);
top_level->Fit(this);
top_level->SetSizeHints(this);
// Create a status bar just for fun
CreateStatusBar(1);
SetStatusText(wxT("Welcome to wxWidgets!"));
}还有两种用Grid来布局的方式,对本人而言用处已经不算很大了。
如果ListView中的单个Item的view中存在checkbox,button等view,会导致ListView.setOnItemClickListener无效,
事件会被子View捕获到,ListView无法捕获处理该事件.
解决方法:
在checkbox、button对应的view处加
android:focusable="false" android:clickable="false" android:focusableInTouchMode="false"
其中focusable是关键
从OnClickListener调用getSelectedItemPosition(),Click 和selection是不相关的,Selection是通过D-pad or trackball 来操作的,Click通常是点击操作的。
arg2参数才是点击事件位置的参数
第二种方法 就是在自己的getView中
convertView = mInflater . inflate ( R . layout . list_item_text , null );
convertView . setClickable ( true );
convertView . setOnClickListener ( clickListener );
public OnClickListener myClickListener = new OnClickListener () {
public void onClick ( View v ) {
//code to be written to handle the click event
}
}; 刚刚在网上找到问题的关键,终于解决了:
如果你的自定义ListViewItem中有Button或者Checkable的子类控件的话,那么默认focus是交给了子控件,而ListView的Item能被选中的基础是它能获取Focus,也就是说我们可以通过将ListView中Item中包含的所有控件的focusable属性设置为false,这样的话ListView的Item自动获得了Focus的权限,也就可以被选中了,也就会响应onItemClickListener中的onItemClick()方法,然而将ListView的ItemLayout的子控件focusable属性设置为false有点繁琐,我们可以通过对ItemLayout的根控件设置其android:descendantFocusability=”blocksDescendants”即可,这样ItemLayout就屏蔽了所有子控件获取Focus的权限,不需要针对ItemLayout中的每一个控件重新设置focusable属性了,如此就可以顺利的响应onItemClickListener中的onItenClick()方法了。