LED,键盘,AD驱动程序开发
原文: http://blog.sina.com.cn/s/blog_4083b2d70100bnlf.html
一:硬件平台及系统平台
CPU: S3C2410A
SDRAM: 64M
FLASH: K9F1208(64MB)
NET: CS8900
友善之臂2410,优龙2410.参考原理图,编写相应的程序.原理是一样的.只要改下GPIO端口及相应的地址就可以了.后面涉及到的程序是针对优龙2410版子的程序.
二:使用的内核版本:LINUX2.6.24
交叉编译工具版本:arm-linux-gcc 3.4.1
三:实验步骤
1. 拷贝一个移植好的内核(可以参考陈亮的内核移植笔记或网上的步骤,自己动手做一个内核),放到虚拟机的根目录下.
2. 解压:zxvf linux-2.6.24.tar.gz大概需要2分钟的时间.
3. 编译一次: make (即:[root@229 linux-2.6.24]# make)然后配置:makemenuconfig
(即:[root@229 linux-2.6.24]# make menuconfig)
一般在内核移植的时候我们已经加上键盘,鼠标的支持了,如果没有的话,在makemenuconfig 的时候可以自己再加上,以后我们需要什么新的驱动,也可以在这个时候加上对该驱动的支持.对键盘,鼠标的支持
到此为止编译驱动的准备工作做好了 ,否则编译驱动会出现很多错误.
四:编写驱动程序.
1. 驱动程序结构:
五:实际程序如下:
1 LED
驱动程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "demo.h"
MODULE_AUTHOR("fgj");
MODULE_LICENSE("Dual BSD/GPL");
#define LED_SI_OUT__raw_writel(0x5500,S3C2410_GPFCON)
#defineLED_SI_H1 __raw_writel(0x80,S3C2410_GPFDAT)
#defineLED_SI_L1 __raw_writel(~0x80,S3C2410_GPFDAT)
#defineLED_SI_H2 __raw_writel(__raw_readl(S3C2410_GPFDAT)|(1<<6),S3C2410_GPFDAT)
#defineLED_SI_L2 __raw_writel(__raw_readl(S3C2410_GPFDAT)&(~(1<<6)),S3C2410_GPFDAT)
#defineLED_SI_H3 __raw_writel(__raw_readl(S3C2410_GPFDAT)|(1<<5),S3C2410_GPFDAT)
#defineLED_SI_L3 __raw_writel(__raw_readl(S3C2410_GPFDAT)&(~(1<<5)),S3C2410_GPFDAT)
#defineLED_SI_H4 __raw_writel(__raw_readl(S3C2410_GPFDAT)|(1<<4),S3C2410_GFBDAT)
#defineLED_SI_L4 __raw_writel(__raw_readl(S3C2410_GPFDAT)&(~(1<<4)),S3C2410_GPFDAT)
#define LED_OFF __raw_writel(__raw_readl(S3C2410_GPFDAT)|(15<<4),S3C2410_GPFDAT)
#define COMMAND_LEDOFF 0
#define COMMAND_LEDON 1
struct DEMO_dev *DEMO_devices;
static unsigned char demo_inc=0;
int DEMO_open(struct inode *inode, struct file*filp)
{
structDEMO_dev *dev;
if(demo_inc>0)return -ERESTARTSYS;
demo_inc++;
dev =container_of(inode->i_cdev, struct DEMO_dev,cdev);
filp->private_data = dev;
return0;
}
int DEMO_release(struct inode *inode, struct file*filp)
{
demo_inc--;
return0;
}
int DEMO_ioctl(struct inode *inode, struct file*filp,unsigned int line_num, unsigned long arg)
{
switch(line_num)
{
case 7:
{
LED_OFF;
LED_SI_L1;
printk("ioctl1
");
return0;
}
case 8:
{
LED_OFF;
LED_SI_L2;
printk("ioctl2
");
return0;
}
case 9:
{
LED_OFF;
LED_SI_L3;
printk("ioctl3
");
return0;
}
case 10:
{
LED_OFF;
LED_SI_L4;
printk("ioctl4
");
return0;
}
default:
{
printk("ioctl error successfully
");
return -EFAULT;
}
} }
struct file_operations DEMO_fops = {
.owner= THIS_MODULE,
.ioctl= DEMO_ioctl,
.open= DEMO_open,
.release= DEMO_release,
};
void DEMO_cleanup_module(void)
{
dev_t devno= MKDEV(DEMO_MAJOR, DEMO_MINOR);
if(DEMO_devices)
{
cdev_del(&DEMO_devices->cdev);
kfree(DEMO_devices);
}
unregister_chrdev_region(devno,1);
}
int DEMO_init_module(void)
{
intresult;
dev_t dev =0;
dev =MKDEV(DEMO_MAJOR, DEMO_MINOR);
result =register_chrdev_region(dev, 1, "DEMO");
if (result< 0)
{
printk(KERN_WARNING "DEMO: can't get major %d
", DEMO_MAJOR);
return result;
}
DEMO_devices= kmalloc(sizeof(struct DEMO_dev), GFP_KERNEL);
if(!DEMO_devices)
{
result = -ENOMEM;
goto fail;
}
memset(DEMO_devices, 0, sizeof(struct DEMO_dev));
cdev_init(&DEMO_devices->cdev,&DEMO_fops);
DEMO_devices->cdev.owner = THIS_MODULE;
DEMO_devices->cdev.ops =&DEMO_fops;
result =cdev_add (&DEMO_devices->cdev, dev,1);
if(result)
{
printk(KERN_NOTICE "Error %d adding DEMO
", result);
goto fail;
}
LED_SI_OUT;
return0;
fail:
DEMO_cleanup_module();
returnresult;
}
module_init(DEMO_init_module);
module_exit(DEMO_cleanup_module);
使用说明:我们对内存每个地址的访问的时候不能直接用
writel(__raw_readl(S3C2410_GPFDAT)|(1<<6),S3C2410_GPFDAT)
或者#define S3C2410_ADCCON 0x58000000 等
因为我们对内存的访问的时候要用虚拟内存映射技术所以要使用如下的程序:
__raw_writel(__raw_readl(S3C2410_GPFDAT)|(1<<6),S3C2410_GPFDAT)
#define S3C2410_ADCCON (0x58000000)
adccon = ioremap(S3C2410_ADCCON,0x00000004);
0x00000004 是指字节长度.
网上有很多程序是关于linux2.4内核的,与2.6内核的驱动程序写法不一样.编译时有时候通不过,比如当编译报错没有定义:SA_INTERRUPT时候
我们可以使用命令:grep -R -n 'SA_INTERRUPT' /linux-2.6.24/include
因此可以在程序中加上该语句:# define SA_INTERRUPT 0x20000000.
编写Makefile
#linux derive develop
#makefile
CC= /usr/local/arm/3.4.1/bin/arm-linux-gcc
ifneq ($(KERNELRELEASE),)
obj-m :=led.o
else
KDIR :=/linux-2.6.24
PWD :=$(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
endif
clean:
@rm -rfled_test*.o
编写不同的驱动程序的时候 Makefile基本不用改变 只需要把该句obj-m :=led.o进行修改就可以了.最后一句是对make clean的支持 清除生成的不必要的文件.
板子上运行结果如下:
2 键盘驱动程序(头文件略)
参考资料:《嵌入式Linux驱动程序设计从入门到精通》 冯国进编著,2008.
MODULE_AUTHOR("fgj");
MODULE_LICENSE("Dual BSD/GPL");
static int irqArray[4]=
{
IRQ_EINT0,
IRQ_EINT2,
IRQ_EINT11,
IRQ_EINT19
};
void initButton(void)
{
writel((readl(S3C2410_GPGCON)&(~((3<<22)|(3<<6))))|((2<<22)|(2<<6)),S3C2410_GPGCON); //GPG11,3 set EINT
writel((readl(S3C2410_GPFCON)&(~((3<<4)|(3<<0))))|((2<<4)|(2<<0)),S3C2410_GPFCON); //GPF2,0 set EINT
writel((readl(S3C2410_EXTINT0)&(~(6|(6<<8)))),S3C2410_EXTINT0);
writel((readl(S3C2410_EXTINT0)|(0|(0<<8))),S3C2410_EXTINT0); //set eint0,2 falling edge int
writel((readl(S3C2410_EXTINT1)&(~(6<<12))),S3C2410_EXTINT1);
}
void polling_handler(unsigned long data)
{
intcode=-1;
writel(readl(S3C2410_SRCPND)&0xffffffda,S3C2410_SRCPND);//clearsrcpnd 0 2 11 19
mdelay(1);
//扫描按键表,根据中断号,找出所按下的按键。
writel(readl(S3C2410_GPBDAT)|0x80,S3C2410_GPBDAT);//set GPB76 to10
writel(readl(S3C2410_GPBDAT)&0xffffffBf,S3C2410_GPBDAT);//...
// }
writel(readl(S3C2410_SRCPND)&0xffffffda,S3C2410_SRCPND);//clearsrcpnd 0 2 11 19
mdelay(1);
writel(readl(S3C2410_GPBDAT)|0x40,S3C2410_GPBDAT);//set GPB76 to01
writel(readl(S3C2410_GPBDAT)&0xffffff7f,S3C2410_GPBDAT);//...
// }
for (i = 0;i <4; i++) {
if (request_irq(irqArray[i], &simplekey_interrupt,0x20000000, "simplekey", NULL)) {
printk("request button irq failed!
");
return -1;
}
}
硬件接口电路如下:
友善之臂用到的GPIO(EINT0…等)端口不一样,根据硬件接口电路,参照数据手册,在涉及到的端口上,相应的修改下程序就可以了.此程序还可以注释掉很多没必要的部分.涉及到set GPB76 to 01的都可以注释掉,因为该程序用GPB7GPB6来回置低的办法,而我们直接让,键盘接地;
我们只用到4个键盘而,程序用到8个键盘,我们也没必要判断是哪一列键盘按下.修改应该简单,不过还没编译验证过.所以程序还可以精简很多.但该程序完全可以实现我们要的功能,只是多做了一些无用功而已.
原始程序有部分错误,如:设置下降沿触发的时候应该用:
writel((readl(S3C2410_EXTINT1)&(~(6<<12))),S3C2410_EXTINT1);//set eint11 falling
writel((readl(S3C2410_EXTINT1)|(0<<12)),S3C2410_EXTINT1);该语句貌似没用,应该可以注释掉.可以编译试试.
板子上运行结果如下:
3 AD转换程序:(因为文字过长,有删节)
# //预分频倍数
#define PRESCALER 19
#define S3C2410_ADCCON (0x58000000)
#define S3C2410_ADCTSC (0x58000004)
static ssize_t adc_read(struct file *filp, char*buf, size_t count, loff_t *f_pos)
{
;
}
//temp1 =(int)filp->private_data;
local_irq_save(flags);
writel((readl(S3C2410_CLKCON) |S3C2410_CLKCON_ADC),S3C2410_CLKCON); //时钟使能
//outl(AdcrSave | (1u <_u32 ?temp) | (1u<< 24), S3C2410_ADCCON);
printk("temp0=%d
",temp0);
//开始AD转换
writel(temp0|0x01, adccon);
//等待AD转换完成
while((readl(adccon) & 0x01) != 0);
//等待AD数据写入ADCDAT0
//for(j=0;j<50;j++)
//while((readl(adccon) & (0x1< _u60 ? 15)) == 0);
//mdelay(100);
printk("adccon = %d, adctsc = %d, adcdly = %d, adcdat0 = %d,adcdat1= %d, adcupdn = %d
", readl(adccon), readl(adctsc),readl(adcdly), readl(adcdat0), readl(adcdat1), readl(adcupdn));
// }
temp =readl(adcdat0);
temp2 = temp&0x3ff;
);
printk("adc device installed, with major %d
",ADC_MAJOR_NR);
adccon = ioremap(S3C2410_ADCCON,0x00000004);
adctsc = ioremap(S3C2410_ADCTSC,0x00000004);
void adc_cleanup(void)
{
iounmap(adccon);
iounmap(adctsc);
iounmap(adcdly);
iounmap(adcupdn);
unregister_chrdev(ADC_MAJOR_NR, DEVICE_NAME);
}
这个程序我们花的时间最长:参照网上的程序,然后调试,至少花了一个星期的时间.
开始主要是有2点没弄清楚:
* 1. 内存映射机制.
开始我们直接#define adccon (0x58000000)
结果编译的时候就通不过,打印出不能操作内存地址的错误.后来我们知道了是没有利用,内存映射机制.改用如上ioremap(adccon);方式就ok了
* 2. 开启时间使能
修改了第一部之后,能编译成.ko文件了,运行结果出现了错误.根本没有进行转换,不管输入什么,输出都为0.
writel((readl(S3C2410_CLKCON) |S3C2410_CLKCON_ADC),S3C2410_CLKCON); //时钟使能
加上该语句后,AD转换器才真正的工作.实际运行结果如下:
Temp2 (即adc)为转换结果.
驱动程序和QT结合是一个很好的方向,我们的下一步,可以在QT上实现该驱动程序.
查看设备号码方法:cat /proc/devices