本篇文章的驱动程序实现的要点:一是实现了设备文件的自动创建,不用每次运行驱动都要使用mknod指令自动创建设备文件,本文通过udev(mdev)来实现设备文件的自动创建。二是对LED灯的控制不是通过直接设置相关GPIO的二进制位来实现,本文使用linux系统中提供的对S3C2410 GPIO的操作函数,直接实现对相关GPIO的控制。三是实现了LED灯的闪烁效果,本文虽然没有在驱动程序代码中直接实现LED灯的闪烁效果,但是通过上层应用程序调用驱动程序中的ioctl间接实现了LED灯的闪烁效果。
一,驱动程序源代码My_Led.c:
#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 <mach/regs-gpio.h> //定义s3c2410的GPIO,S3C2410_GPB5至S3C2410_GPB8 #include <mach/hardware.h> //定义操作s3c2410的GPIO的函数 #include <linux/device.h> //自动创建设备文件应该包含的头文件 #define DEVICE_NAME "My_led" //加载模块后执行cat/proc/devices中看到的设备名称 #define Led_MAJOR 103 //主设备号 #define LED_ON 1 #define LED_OFF 0 //Led的控制引脚 //注意S3C2410_GPB5就是GPIO的编号,类型定义为unsigned long //编号的规则是把所有的io口从0开始进行统一编号,如S3c2410_GPA0=0 S3c2410_GPA1=1 S3C2410_GPB0=32 static unsigned long led_table[] = { S3C2410_GPB5, S3C2410_GPB6, S3C2410_GPB7, S3C2410_GPB8, }; static int My_led_open(struct inode *inode,struct file *file) { printk("My_led open\n"); return 0; } static int My_led_ioctl(struct inode * inode, struct file * file,unsigned int cmd,unsigned long arg) { if(arg > 4) { return -1; } switch(cmd) { case LED_ON: s3c2410_gpio_setpin(led_table[arg], 0);//设置指定引脚为输出电平为0 return 0; case LED_OFF: s3c2410_gpio_setpin(led_table[arg], 1);//设置指定引脚为输出电平为1 return 0; default: return -1; } } //定义文件操作 file_operations static struct file_operations My_led_fops = { .owner = THIS_MODULE, .open = My_led_open, .ioctl = My_led_ioctl, }; static struct class *led_class; static int __init My_led_init(void) { int ret; printk("My_led start\n"); //册字符设备驱动程序 //参数为主设备号、设备名字、file_operations结构 //这样主设备号就与file_operations联系起来 ret = register_chrdev(Led_MAJOR, DEVICE_NAME, &My_led_fops); if(ret < 0) { printk("can‘t register major number\n"); return ret; } //注册一个类,使mdev可以在"/dev/目录下建立设备节点" led_class = class_create(THIS_MODULE, DEVICE_NAME); if(IS_ERR(led_class)) { printk("failed in My_led class.\n"); return -1; } device_create(led_class, NULL, MKDEV(Led_MAJOR,0), NULL, DEVICE_NAME); printk(DEVICE_NAME "initialized\n"); return 0; } static void __exit My_led_exit(void) { unregister_chrdev(Led_MAJOR, DEVICE_NAME); device_destroy(led_class, MKDEV(Led_MAJOR,0));//注销设备节点 class_destroy(led_class);//注销类 } module_init(My_led_init); module_exit(My_led_exit); MODULE_LICENSE("GPL");
源码分析:
1.本驱动程序中设备文件的自动创建是通过在驱动初始化代码里调用class_create为该设备创建一个class,然后再为设备调用device_create创建对应的设备。class_create和device_create定义在内核源码include/linux下,可去此目录查看他们的定义。
2.本驱动程序通过内核中提供的对S3C2410 GPIO的操作函数S3C2410_gpio_setpin来控制指定引脚高低电平的输出。此函数定义在hardware.h的头文件中,需在驱动程序中添加#include<mach/hardware.h> 。
3.至于LED灯闪烁效果的实现等下将上层应用程序代码列出来后再进行分析。
二,Makefile文件:
obj-m:=My_led.o CC=arm-linux-gcc KERNELDIR=/usr/local/opt/EmbedSky/linux-2.6.30.4 PWD:=$(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
三,驱动对应的上层应用程序源代码My_Led_Test.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd,i,cmd=0; fd=open("/dev/My_led",0); if (fd<0) { printf("open led_driver error"); exit(1); } while(1) { switch(cmd) { case 0: printf("All off\n"); for(i = 0;i < 4;i ++) ioctl(fd,0,i); for(i=0;i<100;++i); case 1: printf("light first led\n"); ioctl(fd,1,0); for(i=0;i<100;++i); case 2: printf("light second led\n"); ioctl(fd,0,0); ioctl(fd,1,1); for(i=0;i<100;++i); case 3: printf("light third led\n"); ioctl(fd,0,1); ioctl(fd,1,2); for(i=0;i<100;++i); case 4: printf("light fourth led\n"); ioctl(fd,0,2); ioctl(fd,1,3); for(i=0;i<100;++i); case 5: printf("All light \n"); for(i = 0;i < 4;i ++) ioctl(fd,1,i); for(i=0;i<100;++i); } } return 0; }
源码分析:
case0,case1,case2,case3,case4,case5分别表示四盏灯全灭,第一盏灯亮,第二盏灯亮,第三盏灯亮,第四盏灯亮,四盏灯全亮,这四个case彼此之间都使用一个空循环for来实现延迟的效果,从而最终实现LED灯闪烁的效果。
时间: 2024-10-20 07:55:01