我们之前的驱动程序已经把框架写好,只是打印一些语句,没有做其他的事,现在我们让他点亮led灯
驱动的硬件操作分为3步:
看原理图
看芯片手册(这里是S3C2440)
写硬件相关代码
linux驱动的硬件相关代码和单片机的有什么不同呢
单片机的是直接操作物理地址
linux的是操作虚拟地址(由ioremap函数映射,物理地址——>虚拟地址)
1.查看原理图
(1)通过查看原理图,我们知道要led的引脚为GPF4,5,6
2.查看芯片手册
搜索GPFCON,知道要操作GPFCON和GPFDAT寄存器,他们的物理地址如下:
点亮led需要配置GPFCON寄存器,输出模式
设置GPFDATE寄存器,来输出高低电平
我们把配置GPFCON放在open函数,把设置GPFDATE放在write函数
(1)配置GPF4,5,6为输出,操作GPFCON寄存器
(2)设置GPFDATE寄存器,来输出高低电平
3.设置GPFCON寄存器
(1)由芯片手册知道GPFCON寄存器的物理地址为0x56000050,linux操作是使用虚拟地址,所以我们要使用ioremap把物理地址映射为虚拟地址,在入口函数里面配置,每次打开就自动配置好
搜索ioremap的使用方法,参数为起始地址和长度(映射空间为从起始地址+长度这么一段空间,这里由于有4个寄存器,用16字节),由于32位的指针都是4字节,所以gpfdat= gpfcon + 1,完善:
在出口地址设置取消映射:
以后操作寄存器的时候就用指针gpfcon和gpfdat操作引脚
(2)操作
A &= ~B为位清零
A |= B 为位置1
先为清零在置1设置输出模式,由于是2位,用0x03(两个1)清零,01为输出,用0x01置1.
4.设置GPFDAT寄存器
在write函数里操作
我们要点亮关闭,需要在应用层输入参数on/off,在应用层的参数怎么传到内核层的驱动函数,用copy_from_user()函数
然后设置val是1或者0开灯关灯
5.编写测试程序
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> /* *myled_test on *myled_test off */ int main(int argc, char **argv) { int fd; int val = 1; fd = open("/dev/myled", O_RDWR); if (fd < 0) { printf("can't open!\n"); } if (argc != 2) { printf("Usage :\n"); printf("%s <on|off>\n", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 4); return 0; }驱动代码为:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static struct class *firstdrv_led_class; static struct class_device *firstdrv_led_class_dev; static int firstdrv_led_open(struct inode *inode, struct file *file) { /*配置GPF 4 5 6 为输出*/ *gpfcon &= ~((0x03)<<(4*2)|(0x03)<<(5*2)|(0x03)<<(6*2)); *gpfcon |= ((0x01)<<(4*2)|(0x01)<<(5*2)|(0x01)<<(6*2)); return 0; } static ssize_t firstdrv_led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; copy_from_user(&val,buf,count); //由应用层的write(fd, &val, 4)函数的val参数传到内核层,让我们可以使用应用层的val值 //把buf=&val(write(fd, &val, 4)函数中的&val)传给copy_from_user(&val,buf,count)中的val //对应的内核层到应用层是用copy_to_user() if(val == 1) { //点灯,输出低电平 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { //灭灯,高电平 *gpfdat |=(1<<4)|(1<<5)|(1<<6); } return 0; } /* 这个结构是字符设备驱动程序的核心 * 当应用程序操作设备文件时所调用的open、read、write等函数, * 最终会调用这个结构中指定的对应函数 */ static struct file_operations firstdrv_led_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = firstdrv_led_open, .write = firstdrv_led_write, }; int major; int firstdrv_led_init(void) { major =register_chrdev(0,"myled",&firstdrv_led_fops);//注册firstdrv,告诉内核,注冊设备时给设备号写0,则内核会自己主动分配一个主设备号返回。 firstdrv_led_class = class_create(THIS_MODULE,"firstdrv_led"); firstdrv_led_class_dev = class_device_create(firstdrv_led_class,NULL,MKDEV(major,0),NULL,"myled"); gpfcon = (volatile unsigned long *)ioremap(0x56000050,16); gpfdat= gpfcon + 1; return 0; } void firstdrv_led_exit(void) { unregister_chrdev(major,"firstdrv_led");//卸载 class_device_unregister(firstdrv_led_class_dev); class_destroy(firstdrv_led_class); iounmap(gpfcon); } module_init(firstdrv_led_init); module_exit(firstdrv_led_exit); MODULE_LICENSE("GPL");6.测试
结果灯成功亮灭
7.总结
(1)写驱动框架(open,write等,file_operations结构体,注册,自动创建设备节点)
(2)硬件操作(看原理图,手册,ioremap映射)
8.拓展(次设备号的应用)
我们的实验结果是传进1,就点亮三盏灯,0就灭灯,那有没有方法单独点亮某盏灯,可以使用不同的val,但是太麻烦,这里我们使用次设备号对应不同的灯来操作,之前我们生成的是: