在上一节中已经将驱动程序框架搭建好了
接下来开始写硬件的操作(控制LED):
(1)看原理图,确定引脚
(2)看2440手册
(3)写代码(需要使用ioremap()函数映射虚拟地址,在linux中只能使用虚拟地址)
(4)修改上一节的测试程序
(5)使用次设备号来控制设备下不同的灯
1.看led引脚
最终确定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6
2.看2440手册
配置GPFCON[15:0](0x56000050)的位[8:9]、位[10:11]、位[12:13] 都等于0x01(输出模式)
控制GPFDAT[7:0](0x56000054)中的位4~6来使灯亮灭(低电平亮)
3.写代码
3.1添加全局变量:
volatile unsigned long *GPFcon=NULL;
volatile unsigned long *GPFdat=NULL;
3.2 first_drv_init入口函数中使用ioremap()映射虚拟地址:
GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虚拟地址
GPFdat=GPFcon+1; //long:32位,所以GPFdat=0x56000050+(32/8)
3.3 first_drv_exit出口函数中注销虚拟地址:
iounmap(GPFcon); //注销虚拟地址
3.4 first_drv_open函数中添加配置GPFCON:
*GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12));
*GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12));
3.5 first_drv_write函数中添加拷贝应用层数据,然后来控制GPFDAT:
/*copy_to_user():将数据上给用户*/
copy_from_user(&val,buf,count); //从用户(应用层)拷贝数据
if(val==1) //点灯(低电平亮)
{
*GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6));
}
else //灭灯
{
*GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6));
}
4.修改测试程序main()
代码如下:
int main(int argc,char **argv) //argc:参数个数,argv数组
{
int fd1, fd2;
int val=1;
fd1 = open("/dev/xyz",O_RDWR); //打开/dev/xxx设备节点
if(fd1<0) //无法打开,返回-1
printf("can't open%d!\n", fd1);
if(argc!=2)
{
printf("Usage:\n");
printf("%s <on|off>",argv[0]);
return 0;
}
if(strcmp(argv[1],"on")==0) //开灯
{
printf("led on...\n");
val=1;
}
else //关灯
{
printf("led off...\n");
val=0;
}
write(fd1, &val, 4);
return 0;
}
当输入first_driver_text on点3个灯, 否则关3个灯
若参数不等于2时,不能控制点灯
如果我们想分别控制不同的灯,该怎么做?
可以使用此设备号,此设备号就是用来区分同一设备下不同子设备
5使用次设备号来控制设备下不同的灯
我们先来看下面两个函数MAJOR和MINOR,分别是提取主次设备号
minor=MINOR(inode->i_rdev); //open函数中提取次设备号
major=MAJOR(inode->i_rdev); //open函数中提取主设备号
minor=MINOR (file->f_dentry->d_inode->i_rdev); //write/read函数中提取次设备号
major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函数中提取主设备号
思路如下:
在测试程序中:
通过dev[1]来open打开不同的子设备节点,然后通过dev[2]来write写入数据
实例: first_driver_text led1 on //点亮led1
在first_dev.c驱动文件中:
first_drv_init函数中创建不同的子设备节点
first_drv_exti函数中注销不同的子设备节点
first_drv_open函数中通过MINOR(inode->i_rdev)来初始化不同的灯
first_drv_write函数中通过MINOR(file->f_dentry->d_inode->i_rdev)来控制不同的灯
如下图,insmod后自动注册3个设备节点
测试程序如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/*
* ledtest <dev> <on|off>
*/
void print_usage(char *file) //报错打印帮助
{
printf("Usage:\n");
printf("%s <dev> <on|off>\n",file);
printf("eg. \n");
printf("%s /dev/leds on\n", file);
printf("%s /dev/leds off\n", file);
printf("%s /dev/led1 on\n", file);
printf("%s /dev/led1 off\n", file);
}
int main(int argc, char **argv)
{
int fd;
char* filename;
char val;
if (argc != 3)
{
print_usage(argv[0]);
return 0;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("error, can't open %s\n", filename);
return 0;
}
if (!strcmp("on", argv[2]))
{
// 亮灯
val = 0;
write(fd, &val, 1);
}
else if (!strcmp("off", argv[2]))
{
// 灭灯
val = 1;
write(fd, &val, 1);
}
else //数据输入错误,打印帮助提示
{
print_usage(argv[0]);
return 0;
}
return 0;
}
驱动程序如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static struct class *firstdrv_class; //创建一个class类
static struct class_device *firstdrv_class_devs[4]; //创建类的设备,led,led1,led2,led3
volatile unsigned long *GPFcon=NULL;
volatile unsigned long *GPFdat=NULL;
/*1写出驱动程序first_drv_open first_drv_write */
static int first_drv_open(struct inode *inode, struct file *file)
{
int minor=MINOR(inode->i_rdev);
printk("first_drv_open\n"); //打印,在内核中打印只能用printk()
GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虚拟地址
GPFdat=GPFcon+1; //long:32位,所以GPFdat=0x56000050+(32/8)
switch(minor)
{
case 0: //进入led设备,控制所有led
*GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12));
*GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12));
break;
case 1: //进入led1设备,控制 led1
*GPFcon&=~ ((0X3<<8) );
*GPFcon|= (0X1<<8) ;
break;
case 2: //进入led2设备,控制 led2
*GPFcon&=~ ((0X3<<10) );
*GPFcon|= (0X1<<10) ;
break;
case 3: //进入led3设备,控制 led3
*GPFcon&=~ ((0X3<<12) );
*GPFcon|= ((0X1<<12) );
break;
}
return 0;
}
/*参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
int minor=MINOR(file->f_dentry->d_inode->i_rdev);
copy_from_user(&val,buf,count); //通过用户(应用层)拷贝数据
switch(minor)
{
case 0: //进入led设备,控制所有led
printk("led0,%d\n",val);
if(val) //开灯
{*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6));
*GPFdat|= ((0X0<<4)| (0X0<<5)| (0X0<<6)); }
else //关灯
{*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6));
*GPFdat|= ((0X1<<4)| (0X1<<5)| (0X1<<6)); }
break;
case 1: //进入led1设备,控制 led1
printk("led1,%d\n",val);
if(val) //开灯
{*GPFdat&=~ (0X1<<4);
*GPFdat|= (0X0<<4); }
else //关灯
{ *GPFdat&=~ (0X1<<4);
*GPFdat|= (0X1<<4); }
break;
case 2: //进入led2设备,控制 led2
printk("led2,%d\n",val);
if(val) //开灯
{*GPFdat&=~ (0X1<<5);
*GPFdat|= (0X0<<5); }
else //关灯
{*GPFdat&=~ (0X1<<5);
*GPFdat|= (0X1<<5); }
break;
case 3: //进入led3设备,控制 led3
printk("led3,%d\n",val);
if(val) //开灯
{*GPFdat&=~ (0X1<<6);
*GPFdat|= ( 0X0<<6); }
else //关灯
{*GPFdat&=~ (0X1<<6);
*GPFdat|= (0X1<<6); }
break;
}
return 0;
}
/*2定义file_operations结构体来封装驱动函数first_drv_open first_drv_write */
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, //被使用时阻止模块被卸载
.open = first_drv_open,
.write = first_drv_write,
};
int major; //定义一个全局变量,用来保存主设备号
int first_drv_init(void)
{
int i;
/*3 register_chrdev注册字符设备*/
/*如果设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/
major=register_chrdev (0, "first_drv", &first_drv_fops);
firstdrv_class= class_create(THIS_MODULE,"firstdrv");
//创建类,它会在sys目录下创建firstdrv这个类
firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led");
//创建类设备,它会在firstdrv_class类下创建led设备,然后mdev通过这个自动创建/dev/xyz这个设备节点
for(i=1;i<4;i++) //创建led1 led2 led3 设备节点,控制led1 led2 led3
{
firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i);
}
return 0;
}
/*6 写first_drv_exit出口函数*/
void first_drv_exit(void)
{
int i;
unregister_chrdev (major, "first_drv"); //卸载驱动,只需要主设备号和设备名就行
class_destroy(firstdrv_class); //注销类,与class_create对应
for(i=0;i<4;i++) //注销类设备led,led1,led2,led3
class_device_unregister(firstdrv_class_devs[i]);
iounmap(GPFcon); //注销虚拟地址
}
/*5 module_init修饰入口函数*/
module_init(first_drv_init);
/*7 module_exit修饰出口函数*/
module_exit(first_drv_exit);
MODULE_LICENSE("GPL v2"); //声明许可证