~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
《常识》
¥应用程序----->系统内核----->设备驱动----->硬件设备
¥设备驱动既是系统内核的下属,又是硬件设备的老大。
¥在inux系统中用一个文件来代表一个设备。这个文件就叫设备文件。设备驱动的责任是将应用程序对设备文件
的打开、读、写、定位等操作转化为对硬件设备的打开、读、写、定位等操作。而对于任何硬件设备,应用程序
只需利用这些基本操作就可以完全控制它!
¥编写linux设备驱动需要的知识结构:
1、40%的设计模式相关知识。设计模式是系统内核限定的,做别人的下属就得按照别人的规矩办事。
2、30%的内核工作原理相关知识。内核是你领导,领会领导意图才能把事情办好。
3、30%的硬件相关知识。控制好硬件是你的的本质工作,你得把你的小弟管理好.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在学习了字符设备驱动相关的基础知识加上对s3c6410 GPIO驱动总结的基础上,可以开始写一个简单的gpio的驱动了,led控制就是最基本的一个实验。
pc机开发机环境:
操作系统:ubuntu 9.10
交叉编译环境:arm-linux-gcc 4.2.2
6410板子所用的内核源码在pc上linux中的路径:/kernelsourse/linux-2.6.36.2-v1.05/
目标板环境:OK6410-A linux2.6.36文件系统类型是yaffs
实验原理:
控制LED是最简单的一件事情,我们学习LED驱动程序,就相当于学习其他编程语言是的“hello world”程序一样,是一个入门的程序。
学习驱动程序,必须要对硬件有所了解,接下来看几个与硬件相关的材料。
OK6410 LED原理图
从上面的原理图可以得知,LED与CPU引脚的连接方法如下,低电平点亮。
LED0 -GPM0
LED1 -GPM1
LED2 -GPM2
LED3 -GPM3
从数据手册可以找到相应的寄存器描述,这里以LED0为例:
控制gpio的实质是控制相应的寄存器,而控制寄存器的实质又是向它对应的地址(查阅此表可知GPMPUD、GPMCON、GPMDAT各自对应的地址)中写数。
起始不仅是gpio,大多数的硬件都是如此。因此对于写驱动的程序员来说,硬件手册中最重要的就是硬件“寄存器描述”相关部分。
![](http://hi.csdn.net/attachment/201111/10/0_1320928245WMWO.gif)
根据以上的寄存器描述,总结LED0的操作如下:
1、配置GPM上拉使能:将GPMPUD寄存器的[0-1]位设置成10。
2、配置GPM0为输出方式:将GPMCON寄存器的[0-3]位设置成0001。
3、配置LED1的初始状态:将GPMDAT寄存的第零位设置成0,则LED0亮;设置成1,则LED0灭。
实验步骤:
1、编写驱动程序:
my_leds.c:
#include <linux/module.h>//这些头文件,在编写驱动模块的时候需要的相关头文件
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h> //这些是编写字符设备驱动和混杂设备驱动需要包含的
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include<mach/gpio.h> //这些是gpio相关的头文件,关于这些头文件的分析见我的相关博客
#include <plat/gpio-cfg.h>
#include "myleds.h"
static unsigned long led[] = //这些事定义在<mach/gpio.h>头文件中的,是s3c6410gpio的编
{
S3C64XX_GPM(0),
S3C64XX_GPM(1),
S3C64XX_GPM(2),
S3C64XX_GPM(3),
};
static long s3c6410_leds_ioctl(
struct file *filp,
unsigned int cmd,
unsigned long arg)
{
//check the cmd
if(_IOC_TYPE(cmd) != LED_CMD_MAGIC)
{
return -EINVAL;
}
if((_IOC_NR(cmd) + 1) > LED_CMD_MAX)
{
return -EINVAL;
}
//check the arg
if (arg > 4)
{
return -EINVAL;
}
switch(cmd)
{
case LED_CMD_ON:
gpio_set_value(led[arg],LED_ON);
printk("LED%ld is ON\n",arg);
return 0;
case LED_CMD_OFF:
gpio_set_value(led[arg],LED_OFF);
printk("LED%ld is OFF\n",arg);
return 0;
default:
return -EINVAL;
}
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = s3c6410_leds_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init s3c6410_leds_init(void)
{
int ret;
int i;
//gpio init
for(i = 0;i < 4; i ++)
{
//led[0-3]pull up
s3c_gpio_setpull(led[i],S3C_GPIO_PULL_UP);
//led[0-3]config output and turn off all lights
gpio_direction_output(led[i],LED_OFF);
}
//register micdevice
ret = misc_register(&misc);
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
static void __exit s3c6410_leds_exit(void)
{
misc_deregister(&misc);
printk (DEVICE_NAME"\tremoved\n");
}
module_init(s3c6410_leds_init);
module_exit(s3c6410_leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("QINGYU2431");
程序中用到的一些函数的的相关分析见s3c6410 GPIO驱动总结。在这里直接使用的是gpio相关的头文件提供的相关函数来控制gpio的(s3c_gpio_setpull、gpio_direction_output、gpio_set_value),实际上这些函数是通过向相关寄存器的对应的地址中写特定数来实现的。这些函数的具体实现方法,有兴趣的可以去阅读相应的源码,这里不赘述。
my_leds.h:
#ifndef MYLEDS_H_
#define MYLEDS_H_
#include <linux/ioctl.h>
#define DEVICE_NAME "myleds"
#define LED_ON 0
#define LED_OFF 1
//ioctl命令的定义
#define LED_CMD_MAGIC 'n'
#define LED_CMD_ON _IOW(LED_CMD_MAGIC,0,int)
#define LED_CMD_OFF _IOW(LED_CMD_MAGIC,1,int)
#define LED_CMD_MAX 2
#endif
此头文件存放的是相关的宏。之所以需要用一个单独的图文件来定义这些宏是因为,不仅驱动程序需要使用这些宏,下面的测试程序也需要使用到这些宏。
Makefile文件:
ifneq ($(KERNELRELEASE),)
obj-m := my_leds.o
else
KDIR:= /kernelsourse/linux-2.6.36.2-v1.05
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c .symvers
endif
2、编写测试程序
ledtest.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "myleds.h"
int main(int argc, char **argv)
{
int on;
int led_no;
int fd;
//check the ags number
if (argc != 3)
{
fprintf(stderr, "please input two args\n");
exit(1);
}
//check the first arg
if (sscanf(argv[1],"%d", &on) != 1 || on < 0 || on > 1)
{
fprintf(stderr, "The first arg can only be 0|1\n");
exit(1);
}
//check the second arg
if (sscanf(argv[2],"%d", &led_no) != 1 || led_no < 0 || led_no > 3)
{
fprintf(stderr, "The second arg can only be 0|1|2|3\n");
exit(1);
}
fd = open("/dev/myleds", 0);
if (fd < 0)
{
perror("can not open device leds");
exit(1);
}
//exe cmd
if(on == 0)
{
ioctl(fd,LED_CMD_ON,led_no);
}
else
{
ioctl(fd,LED_CMD_OFF,led_no);
}
close(fd);
return 0;
}
3、编译驱动程序与测试程序
编译驱动程序:
#make
生成my_leds.ko文件,make命令依赖是Makefile文件。将生成的my_leds.ko文件复制到nfs共享目录下。
编译测试程序:
#arm-linux-gcc ledtest.c -o ledtest
生成ledtest.o文件,将这个文件放到nfs共享目录下。
使用这个命令的前提是安装了交叉工具链(安装交叉工具链的步骤见《OK6410-A开发板LINUX2.6.36用户手册.pdf》5-5小节。),并且交叉编译器的路径已经添加到环境变量中了。如果没有,请参考arm-linux-gcc交叉工具链的使用
4、将程序下载到开发板
这里采用的是nfs 的方式,如果不知道这种方式,请参考如何使用nfs下载程序到开发板
首先在板子上挂载nfs后,将挂载目录中的my_leds.ko文件和ledstest.o文件复制到 /lib/modules/2.6.36.2 目录下(必须把程序放到这个目录下,如果没有此目录,则需要手动创建。不然,在卸载驱动的时候会出现问题)。这里需要注意的是板子上操作系统的类型必须为yaffs,而不能是cramfs。这是因为前者是可读可写系统,而后者是只读系统。也就是说前者可以手动创建文件夹,而后者不能够。
5、测试
进入到板子的/lib/modules/2.6.36.2目录下加载驱动 #insmod my_leds.ko
测试 命令解释如下:
./ledtest 0 0 开启第0盏灯
./ledtest 0 1 开启第1盏灯
./ledtest 0 2 开启第2盏灯
./ledtest 0 3 开启第3盏灯
./ledtest 1 0 关闭第0盏灯
./ledtest 1 1 关闭第1盏灯
./ledtest 1 2 关闭第2盏灯
./ledtest 1 3 关闭第3盏灯