实例"/>
led驱动实例
前面分析了fl2440的led驱动代码,现在就基于之前的LED驱动代码完成应用程序(跑马灯)的实现,并且在linux系统下手动创建设备节点,运行跑马灯程序。
1.跑马灯程序
首先来看 用户空间下跑马灯程序的实现
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h> #define LED_NUM 4
#define DEVNAME_LEN 10 #define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19) int main(int argc, char **argv)
{ int fd[LED_NUM]; int i; int j; char devname[DEVNAME_LEN] = {0}; for(i = 0; i < LED_NUM; i++) { snprintf(devname, sizeof(devname), "/dev/led%d",i); fd[i] = open(devname, O_RDWR,755); if(fd[i] < 0) { printf("Can not open %s: %s", devname, strerror(errno)); for(j = 0; j < i; j++) //如果某一设备不能打开,则要关掉之前已经打开的设备{ if(fd[j] > 0) close(fd[j]); } return -1; } } while(1) { for(i = 0; i < LED_NUM; i++) { ioctl(fd[i], LED_ON); //打开led灯sleep(1); //延时1sioctl(fd[i], LED_OFF);//关闭led灯 } } for(i = 0; i < LED_NUM; i++) { close(fd[i]); //关掉设备文件} return 0;
}
在 linux 下编写完跑马灯程序之后,我们必须使用交叉编译器对该程序进行编译。
[lwn@localhost s3c-led]$ /opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-gcc ledtest.c
/opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-gcc是我交叉编译器的目录下的交叉编译工具,
编译之后生成a.out可执行文件
2.led驱动程序:
/********************************************************************************** Copyright: (C) 2011 Guo Wenxue<guowenxue@gmail> * All rights reserved.** Filename: plat_button.c* Description: This is the common button driver runs on S3C2440* * Version: 1.0.0(4/27/2017~)* Author: Li Wanneng <liwjng@gmail>* ChangeLog: 1, Release initial version on "4/27/2017 11:39:10 AM"* ********************************************************************************/#include <linux/module.h> /* Every Linux kernel module must include this head */
#include <linux/init.h> /* Every Linux kernel module must include this head */
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* struct fops */
#include <linux/errno.h> /* error codes */
#include <linux/cdev.h> /* cdev_alloc() */
#include <asm/io.h> /* ioremap() */
#include <linux/ioport.h> /* request_mem_region() */#include <asm/ioctl.h> /* Linux kernel space head file for macro _IO() to generate ioctl command */
#ifndef __KERNEL__
#include <sys/ioctl.h> /* User space head file for macro _IO() to generate ioctl command */
#endif
//#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */#define DRV_AUTHOR "Li Wanneng <liwjng@gmail>"
#define DRV_DESC "S3C24XX LED driver"#define DEV_NAME "led"
#define LED_NUM 4/* Set the LED dev major number */
//#define LED_MAJOR 79
#ifndef LED_MAJOR
#define LED_MAJOR 0
#endif#define DRV_MAJOR_VER 1
#define DRV_MINOR_VER 0
#define DRV_REVER_VER 0#define DISABLE 0
#define ENABLE 1#define GPIO_INPUT 0x00
#define GPIO_OUTPUT 0x01/*防止ioctl传入的命令与其他参数冲突*/
#define PLATDRV_MAGIC 0x60 //魔术字
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)/*GPB有3个寄存器,GPBCON, GPBDATA和GPBUP寄存器,下面是其对应的物理地址*/
#define S3C_GPB_BASE 0x56000010//s3c2440GPB寄存器的基地址
#define GPBCON_OFFSET 0 //s3c2440GPBCON寄存器偏移地址0x56000010
#define GPBDAT_OFFSET 4 //s3c2440GPBDATA寄存器偏移地址 0x56000014
#define GPBUP_OFFSET 8 //s3c2440GPBUP寄存器的偏移地址 0x56000018
#define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */static void __iomem *s3c_gpb_membase; //__iomem表示指针s3c_gpb_membase是指向一个I/O的内存空间// #define _raw_readl(reg) *(volatile unsigned int *)(reg)
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase) //虚拟地址与物理地址的映射(写)
#define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase) //虚拟地址与物理地址的映射(读)int dev_count = ARRAY_SIZE(led); //设备个数
int dev_major = LED_MAJOR; //定义主设备号
int dev_minor = 0; //定义次设备号
int debug = DISABLE; //debug = 0,上面宏定义DISABLE为0/*struct cdev 是表示字符设备的内核内部结构,当innod指向一个字符设备文件时,该字段包含了指向struct cdev 结构的指针*/
static struct cdev *led_cdev; //定义cdev结构体指针,led内核结构static int s3c_hw_init(void)//硬件初始化,设置相应GPIO口为output模式
{int i;volatile unsigned long gpb_con, gpb_dat, gpb_up;/*为s3c2440_led申请一片物理地址,其起始地址为S3C_GPB_BASE,大小为s3c_GPB_LEN;如果申请失败,返回-EBUSY */if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led")){return -EBUSY;}/*如果物理地址申请成功,将其映射到相应的虚拟地址。以后操作寄存器一律操作虚拟地址即s3c_gpb_membase的地址*/if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )//ioremap函数作用为将物理地址映射到虚拟地址{release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);//如果映射失败,一定要将物理地址释放return -ENOMEM;//申请失败返回错误信息-ENOMEM}/*1,设置相应gpio端口为输出模式2,disable上拉寄存器哦3,设置gpio数据寄存器为1,即使相应GPIO端口输出高电平,默认关闭LED灯*/for(i=0; i<dev_count; i++)//dev_count为设备数量 {/* Set GPBCON register, set correspond GPIO port as input or output mode */gpb_con = s3c_gpio_read(GPBCON_OFFSET);//获取GPBCON寄存器的值gpb_con &= ~(0x3<<(2*led[i])); /* Clear the currespond LED GPIO configure register */gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */s3c_gpio_write(gpb_con, GPBCON_OFFSET);//将gpb_con的值写到寄存器GPBCON寄存器/* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable */gpb_up = s3c_gpio_read(GPBUP_OFFSET);gpb_up |= (0x1<<led[i]); /* Disable pull up resister */s3c_gpio_write(gpb_up, GPBUP_OFFSET);/* Set GPBDAT register, set correspond GPIO port power level as high level or low level */gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);//gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */gpb_dat |= (0x1<<led[i]); /* This port set to high level, then turn LED off */s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);//1:46}return 0;
}static void turn_led(int which, unsigned int cmd)//操作led
{volatile unsigned long gpb_dat;gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);if(LED_ON == cmd){gpb_dat &= ~(0x1<<led[which]); /* Turn LED On */}else if(LED_OFF == cmd){gpb_dat |= (0x1<<led[which]); /* Turn LED off */}s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}static void s3c_hw_term(void)//清除led操作
{int i;volatile unsigned long gpb_dat;for(i=0; i<dev_count; i++){gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);gpb_dat |= (0x1<<led[i]); /* Turn LED off */s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);}release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);//释放已申请的物理内存iounmap(s3c_gpb_membase);//取消物理地址到虚拟地址的映射关系
}static int led_open(struct inode *inode, struct file *file)//"打开led"函数
{int minor = iminor(inode);//获取次设备号file->private_data = (void *)minor;//在file结构体中,private_data是一个空类型指针printk(KERN_DEBUG "/dev/led%d opened.\n", minor);return 0;
}static int led_release(struct inode *inode, struct file *file)//"关闭led"函数
{printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));return 0;
}static void print_help(void)//打印帮助信息
{printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);//printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);printk("Turn LED on command : %u\n", LED_ON);printk("Turn LED off command : %u\n", LED_OFF);return;
}//ioctl(fd, LED_ON, 0)
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)//ioctl()函数会用到,其中参数为用户程序空间传过来的参数
{int which = (int)file->private_data;//获取次设备号switch (cmd)//判断并执行相应的命令{case LED_ON:turn_led(which, LED_ON);break;case LED_OFF:turn_led(which, LED_OFF);break;default:printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);print_help();break;}return 0;
}/*面向对象的思想*/
static struct file_operations led_fops = //定义fop结构体,针对该驱动提供的系统调用和操作
{.owner = THIS_MODULE,//.owener的值一般为THIS_MODULE.open = led_open,//open为指向led_open()的函数指针,调用open()函数时会用到.release = led_release,.unlocked_ioctl = led_ioctl,
};static int __init s3c_led_init(void)//led初始化函数
{int result;dev_t devno;/*如果硬件初始化失败,打印如下信息并返回-ENODEV*/if( 0 != s3c_hw_init() ){printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");return -ENODEV;}/* 如果硬件初始化成功,分配主次设备号 */if (0 != dev_major) /* 如果已经有了设备号 静态获取主次设备号 */{devno = MKDEV(dev_major, 0);result = register_chrdev_region (devno, dev_count, DEV_NAME);}else//动态获取主次设备号{result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);dev_major = MAJOR(devno);}/* 如果设备号申请失败,打印如下信息并返回-ENODEV */if (result < 0){printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);return -ENODEV;} printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);if(NULL == (led_cdev=cdev_alloc()) )//分配cdev结构体,如果分配失败,打印如下信息 {printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);unregister_chrdev_region(devno, dev_count);return -ENOMEM;}led_cdev->owner = THIS_MODULE;//如果分配成功,将cdev结构体添加进内核 cdev_init(led_cdev, &led_fops);//连接led_cdev和led_fops/*只有当cdev_add()函数执行之后,才能在 /dev/目录下看到设备节点*/result = cdev_add(led_cdev, devno, dev_count);//将其添加进内核,字符设备注册的最后一步if (0 != result){ printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); goto ERROR;}printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n", DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);return 0;ERROR:printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);cdev_del(led_cdev);unregister_chrdev_region(devno, dev_count);return result;
}static void __exit s3c_led_exit(void)//led卸载函数
{dev_t devno = MKDEV(dev_major, dev_minor);s3c_hw_term();//释放。。。cdev_del(led_cdev);//删掉cdevunregister_chrdev_region(devno, dev_count);//释放设备节点号printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);return ;
}/* These two functions defined in <linux/init.h> */
module_init(s3c_led_init);//Led驱动首先应该找到module_init函数
module_exit(s3c_led_exit);module_param(debug, int, S_IRUGO);//使能驱动的debug功能,debug为全局变量
module_param(dev_major, int, S_IRUGO);//函数传参,insmod时通过参数动态修改主设备号MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
3.Makefile
驱动程序通过Makefile编译,Makefile主要内容如下[lwn@localhost s3c-led]vim MakefileMakefile 1 LINUX_SRC?=../../kernel/linux-lwn-3.0.12 CROSS_COMPILE=/opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-3 4 obj-m := s3c_led.o5 6 modules:7 @make -C $(LINUX_SRC) M=`pwd` modules8 @make clean9 10 clean:11 rm -f *.ko.* *.o *mod.c *.order *.symvers12
[lwn@localhost s3c-led]$ ls
a.out ledtest.c Makefile s3c_led.c s3c_led.ko
4.开发板上面运行程序
在主机上面生成了a.out和s3c_led.ko之后,使用 tftp 工具将其下载到开发板。
现在开发板上只是有了s3c_led.ko文件,但是如果要使用驱动,还需要安装驱动,创建设备节点,运行应用程序。
使用insmod命令安装驱动
下面是在开发板上的操作
执行a.out之后在开发板上可以看到led已经跑起来了。到此,整个LED驱动和用户程序已经成功执行。
更多推荐
led驱动实例
发布评论