LED灯驱动编写--寄存器操作
(转载请写明出处: http://blog.csdn.net/yby19870627/article/details/7407130 )
这里没有用的内存映射的方法,而是直接对寄存器进行操作,我建议在开发驱动的时候,用NFS挂载的方式进行开发,这样可以节省很多时间,NFS挂载方法可以从我以前的文章中找到。
平台:Fedora14
内核:linux-2.632.2
一、首先要编写最基本的模块,因为编程就是要一步一步调试的,这样才能发现问题,如果一开始不管三七二十一,先把代码写完再说,那当你make完看到那些错误的时候,那个时候,我估计你连死的心都有了。
1. 添加最基本的头文件:#include
#include
2. 编写模块初始化函数和模块推出函数
static int __init Led_init(void)
{
printk("<0>module--->Led_init\n");
}
//--------------------------------------------------------------------------------
static void __exit Led_exit(void)
{
printk("<0>module--->exitok!\n");
}
//---------------------------------------------------------------------------------
module_init(Led_init);
module_exit(Led_exit);
3. 编译程序,然后再insmod led.ko/rmmod led
看看是否输出了代码里面的两条打印语句。
二、第一步通过之后,那么就要开始添加新的代码了,我们是要注册设备的,所以需要定义一个设备号变量,同时要注册到内核中还需要一个struct cdev结构的变量,所以要定义
struct cdev led_cdev;
dev_t led_devno;
定义完之后还需要考虑到要有一个变量来存储主设备号,所以还要定义一个主设备号变量。而且还要定义主设备号和次设备号两个宏。
#define MAINLEDNO 108
#define MINORLEDNO 0
static int led_major;
定义struct file_operations结构,一开始里面可以什么都不写。
struct file_operations led_ops={
};
修改模块初始化函数。
static int __init Led_init(void)
{
printk("<0>module--->Led_init\n");
int result;
//申请设备号
led_devno =MKDEV(MAINLEDNO,MINORLEDNO);
result =register_chrdev_region(led_devno,1,"myled");
if(result<0)
{
result =alloc_chrdev_region(&led_devno,0,1,"myled");
led_major = MAJOR(led_devno);
}
//注册设备
cdev_init(&led_cdev,&led_ops);
led_cdev.owner = THIS_MODULE;
result =cdev_add(&led_cdev,led_devno,1);
if(result<0)
{
printk("<0>module--->adderror!\n");
return result;
}
printk("<0>module--->cdev_addok!\n");
return 0;
}
修改模块退出函数。
static void __exit Led_exit(void)
{
cdev_del(&led_cdev);
unregister_chrdev_region(led_devno,1);
printk("<0>module--->exitok!\n");
}
编译,通过后安装和卸载模块,看看打印信息是否正确。
三、设备注册完之后,就要开始编写struct file_operations结构变量里面的对应的函数了,这个结构里面的函数在这就不花时间讲述了,只要记住里面是系统调用函数和自己编写函数对应关系就OK了。
struct file_operations led_ops={
.open = led_open,
.release =led_close,
};
里面把自己的led_open函数与系统的open函数联系起来,说白了,就是app程序用open函数打开这个设备的时候,就会执行led_open函数里面的代码,所以,我们要实现led_open函数和led_close函数。
首先要知道我们是对ARM的寄存器进行操作,所以首先添加头文件
#include
#include
#include
我们的目的是控制LED等,所以我们要看开发板上LED灯是怎么连接的。
通过电路图,我们可以知道,连接的是GPB5、GPB6、GPB7、 GPB8这四个引脚,当引脚电平为低电平时,LED就会亮,当引脚电平为高电平时,LED就熄灭。
staticint led_open(struct inode *inode,struct file *file)
{
int value = 0;
value =(1<<10)|(1<<12)|(1<<14)|(1<<16);
iowrite32(value,S3C2410_GPBCON);
value = 0x0;
iowrite32(value,S3C2410_GPBUP);
value = 0xffffffff;
iowrite32(value,S3C2410_GPBDAT);
printk("<0>module--->Ledrun!\n");
return 0;
}
static int led_close(struct inode *inode,struct file *file)
{
return 0;
}
当中的iowrite32函数是2.6内核的写函数,老版本的是writel,我们可以看到,用iowrite32函数对寄存器进行赋值,来完成相应的功能。
像S3C2410_GPBCON这些函数都是#include
编写完之后,我们可以编译一下,但是安装完之后,没有反应,是因为没有应用程序来对这个驱动进行操作,所以我们现在来写应用程序。
#include
int main(int argc,char* argv[])
{
int fd = 0;
int cmd= 0;
char arg[10];
fd = open("/dev/myled0",0);
getchar();
close(fd);
return 0;
}
运行这个应用程序,看看LED灯是否熄灭,注意在insmod模块之后,别忘了mknod设备哦:mknod /dev/myled0 c 108 0.
四、如果做到上面那一步了,那我们就离成功不远了,现在只需要在内核中定义命令就可以实现对LED灯的控制了。
首先在struct file_operations 结构变量中添加ioctl的对象。
structfile_operations led_ops={
.open = led_open,
.release = led_close,
.unlocked_ioctl = led_ioctl,
};
然后再定义ioctl的相关命令,这里要提到命令中的宏定义。具体的ioctl函数的命令这里就不做过多的解释。
#defineLED_MAGIC 'y' //定义幻数
#defineLED1_ON _IO(LED_MAGIC,1) //定义相关命令
#defineLED1_OFF _IO(LED_MAGIC,2)
#defineLED2_ON _IO(LED_MAGIC,3)
#defineLED2_OFF _IO(LED_MAGIC,4)
#defineLED3_ON _IO(LED_MAGIC,5)
#defineLED3_OFF _IO(LED_MAGIC,6)
#defineLED4_ON _IO(LED_MAGIC,7)
#defineLED4_OFF _IO(LED_MAGIC,8)
宏定义好了之后,那么就来实现ioctl函数。
static long led_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
printk("<0>module--->Led_ioctlin!\n");
int gpbdate;
gpbdate = ioread32(S3C2410_GPBDAT);
printk("<0>module--->gpbdate:%d\n",gpbdate);
switch(cmd)
{
case LED1_ON:
printk("<0>module--->CMDLED1_ON!\n");
iowrite32((~(0x01<<5)& gpbdate),S3C2410_GPBDAT);
break;
case LED1_OFF:
printk("<0>module--->CMDLED1_OFF!\n");
iowrite32(((0x01<<5)| gpbdate),S3C2410_GPBDAT);
break;
case LED2_ON:
printk("<0>module--->CMDLED2_ON!\n");
iowrite32((~(0x01<<6)& gpbdate),S3C2410_GPBDAT);
break;
case LED2_OFF:
printk("<0>module--->CMDLED2_OFF!\n");
iowrite32(((0x01<<6)| gpbdate),S3C2410_GPBDAT);
break;
case LED3_ON:
printk("<0>module--->CMD LED3_ON!\n");
iowrite32((~(0x01<<7)& gpbdate),S3C2410_GPBDAT);
break;
case LED3_OFF:
printk("<0>module--->CMDLED3_OFF!\n");
iowrite32(((0x01<<7)| gpbdate),S3C2410_GPBDAT);
break;
case LED4_ON:
printk("<0>module--->CMDLED4_ON!\n");
iowrite32((~(0x01<<8)& gpbdate),S3C2410_GPBDAT);
break;
case LED4_OFF:
printk("<0>module--->CMDLED4_OFF!\n");
iowrite32(((0x01<<8)| gpbdate),S3C2410_GPBDAT);
break;
}
}
代码都是上面讲过的,所以不做过多的解释。
五、驱动代码写好之后,那么要怎么用应用代码来验证呢,我们在内核中定义的ioctl定义的命令,要怎么让应用层知道呢,我开始也迷茫了很久,并且也在网上搜索答案,一直没有找到解决办法,后面还是自己一步一步试出来的。
只需要在应用代码中重新定义,当然我们也可以全部定义在一个头文件里。
#define LED_MAGIC 'y' //定义幻数
#define LED1_ON _IO(LED_MAGIC,1) //定义相关命令
#define LED1_OFF _IO(LED_MAGIC,2)
#define LED2_ON _IO(LED_MAGIC,3)
#define LED2_OFF _IO(LED_MAGIC,4)
#define LED3_ON _IO(LED_MAGIC,5)
#define LED3_OFF _IO(LED_MAGIC,6)
#define LED4_ON _IO(LED_MAGIC,7)
#defineLED4_OFF _IO(LED_MAGIC,8)
并且添加头文件#include
命令定义好之后,我们就可以直接调用ioctl了。
int main(intargc,char* argv[])
{
int fd = 0;
int cmd= 0;
char arg[10];
fd = open("/dev/myled0",0);
memset(arg,'\0',sizeof(arg));
while(1)
{
printf("Please inputon/off led number!\n");
scanf("%s",arg);
if(strcmp(arg,"on1")== 0)
{
cmd = LED1_ON;
}
if(strcmp(arg,"off1")== 0)
{
cmd = LED1_OFF;
}
if(strcmp(arg,"on2")== 0)
{
cmd = LED2_ON;
}
if(strcmp(arg,"off2")== 0)
{
cmd = LED2_OFF;
}
if(strcmp(arg,"on3")== 0)
{
cmd = LED3_ON;
}
if(strcmp(arg,"off3")== 0)
{
cmd = LED3_OFF;
}
if(strcmp(arg,"on4")== 0)
{
cmd = LED4_ON;
}
if(strcmp(arg,"off4")== 0)
{
cmd = LED4_OFF;
}
printf("cmd =%d\n",cmd);
memset(arg,'\0',sizeof(arg));
ioctl(fd,cmd,0);
}
getchar();
close(fd);
return 0;
}
这样一个LED的驱动程序就写好了。