1.查询方式获取按键
1.框架
头文件
file_operations结构体
.open =
.read = second_drv_read,
read函数的参数
入口函数注册结构体 second_drv_init
major = register_chrdev(0,”secon_drv”,&second_drv_fops);
出口函数second_drv_exit(void)
unregister_chrdev(major,”secon_drv”);
修饰
module_init(second_drv_init)
module_exit(second_drv_exit)
让udev自动创建设备节点(mdev是udev的简化版本)
定义类和设备
static struct class *second_drv_class;
创建class,创建class device
seconddrv_class = classs_create(THIS_MODULE, “second_drv”);
gbseconddrv_class_device = class_device_create(seconddrv_class,NULL,MKDEV(major,0),NULL,”buttons”);
卸载设备和class
class_device_unregister(firstdrv_class_dev);
class_destroy(seconddrv_class);
MODULE_LINCENS(“GPL”)
上传驱动文件,修改Makefile,编译,拷贝到根文件系统,加载驱动,检查驱动是否正常加载
2.硬件操作
看原理图,确定引脚
看2440手册,确定引脚如何操作,相关寄存器
编写程序,单片机编程直接用物理地址;Linux驱动中使用虚拟地址。
vA = ioremap(pA,len);
程序规划:
在open函数中配置引脚
在read中返回IO值
在inint中进行寄存器映射
编程:
init中建立映射
gpfcon = (volatile unsigned log *)ioremap(0x56000050,16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned log *)ioremap(0x56000060,16);
gpgdat = gpgcon + 1;
exit中取消映射
iounmap(gpfcon);
iounmap(gpgcon);
open中配置GPF0,2,GPG3,11为输入,对应位清零
*gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
*gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
read中返回引脚电平,ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loft_t *ppos)
unsigned char key_vals[4];
int regval;
regval = *gpfdat;
key_vals[0] = regval & BIT0; 或者 key_vals[0] = (regval & (1<<0)) ? 1: 0
key_vals[1] = (regval & (1<<2)) ? 1: 0
数据返回给用户空间
if (size != sizeof(key_vals))
return -EINVAL;
copy_to_user(buf,key_vals,sizeof(key_vals));
测试程序
open(“/dev/buttons”,O_RDWR);
read(fd,keyvals,sezeof(key_vals));
if (!keyval[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])
{
printf(“keyval: %4d %4d %4d %4d”,key_vals[0],,key_vals[1],key_vals[2],key_vals[3])
}
2.Linux的中断处理框架
Linux的中断处理与单片机本质上是一致的,单片机中,中断发生后硬件根据中断向量表跳转到对应的地址去执行;在ARM架构Linux中,中断产生后,CPU进入异常处理模式,即中断异常,调用中断总入口函数 asm_do_IRQ() ,该函数又会根据中断号调用对应的中断处理函数,这个处理函数就相当于我们在单片机编程中写的中断服务子程序。
而Linux的中断编程就是填充相关的数据结构和编写中断服务函数。
关键的数据结构(这里只列出了数据结构中比较关键的成员):
struct irq_desc {
irq_flow_handler_t handle_irq; /*highlevel irq-events handler [if NULL, __do_IRQ()]*/
struct irq_chip *chip; /*low level interrupt hardware access*/
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int irq_count; /* For detecting broken IRQs */
const char *name; /*flow handler name for /proc/interrupts output*/
} ____cacheline_internodealigned_in_smp;
其中irq_chip 结构体用于定义与硬件相关的底层操作,如启动或关闭中断、使能或禁止中断、设置中断屏蔽设置中毒触发方式等。
irqaction 结构体用于存放用户定义的中断处理函数,对于共享中断,可以有多个中断处理函数,故irqaction是一个结构链表。
另外,在单片机低功耗编程中,我设置好中断后进入休眠状态,当中断发生后唤醒系统进行中断处理,中断完成后再次进入休眠等待中断发生;在Linux按键中断编程中,中断处理函数唤醒read函数,read函数将数据发送给用户应用后再次进入休眠,这样就不会一直占用CPU时间。则与单片机的低功耗编程很相似。
3.中断方式的按键驱动框架
与查询方式的按键驱动不一样的地方在于:
1.在open函数中使用request_irq()函数注册中断
2.在close函数中使用free_irq()函数释放中断
3.文件操作结构体 gbthird_drv_fops 中添加 .release = gbthird_drv_close, 即关闭设备时需要调用驱动的close函数,该函数中释放中断。
4.编写中断服务函数 buttons_irq()
其实对于Linux的中断编程,我们只需要做几件简单的事就可以,前面说到的数据结构填充都会有Linux自动完成。当然这是站在一个比较低的层次来看,我相信中断编程还有很多需要深入的地方。
1. gbthird_drv_open 函数中注册中断:
request_irq(IRQ_EINT8, buttons_irq,IRQT_BOTHEDGE,”S1″,&pins_desc[0]);
参数依次为:中断号,中断处理函数,触发方式,名称,设备号,这些参数与前面的结构体是对应的,request_irq() 函数会使用这些参数完成数据结构的填充。设备号可以是任意数,也可以是指针。
2. gbthird_drv_close 函数中释放中断
free_irq(IRQ_EINT8 , &pins_desc[0]);
参数依次为:中断号,设备号,与注册中断时一致。
3. 文件操作结构体 gbthird_drv_fops 中添加 .release = gbthird_drv_close
4. 编写中断服务子函数,当中断产生时将会调用该函数
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc*)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
//ok6410中是使用gpio_get_value(pindesc->pin); 括号中参数可以为 S3C64XX_GPN(0)等等
if (pinval)
{
key_val = 0x80 | pindesc->key_val; //relrese
}
else
{
key_val = pindesc->key_val;
}
ev_press = 1;
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);
}
5. 修改原有的Read函数,read函数等待中断唤醒,然后拷贝按键值到用户空间。
static ssize_t gbthird_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned char KeyVal[6];
int RegVal = 0;
if ( size != 1 )
return -EINVAL;
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return size;
}
这样就完成了中断方式的按键驱动。
PS:新接触的linux命令
cat /proc/interrupts 查看中断注册、使用情况
./button_test & 后台方式运行程序
ps 显示运行的进程
/////////////////////////////////////////////////////////////////////////////////////////
s3c2410_gpio_getpin()函数说明
unsigned int s3c2410_gpio_getpin(unsigned int pin)
{
void __iomem *base = S3C24XX_GPIO_BASE(pin);
unsigned long offs = S3C2410_GPIO_OFFSET(pin);
return __raw_readl(base + 0x04) & (1<< offs);
}
s3c2410_gpio_getpin()的返回值是GPxDAT寄存器的值与所要读取的GPIO对应的bit mask相与以后的值,0表示该GPIO对应的bit为0, 非0表示该bit为1,所以s3c2410_gpio_getpin(S3C2410_GPG(9))如果GPG9为低电平则返回的是0,如果是高电平则返回的是GPxDAT中的GPG9对应位的值为0x0100而不是0x0001,查处问题后修改也很简单了。