嵌入式Linux字符设备入门之--LED驱动详解
2019-07-13 02:37发布
生成海报
一、实验环境
PC机:Redhat9.0 内核版本:Linux2.4
开发板:FL2440 内核版本:Linux2.6 CPU:s3c2440
二、实验步骤
1、硬件连接。
FL2440有四个可编程控制的共阳极LED,分别与CPU的GPB5,GPB6,GPB8,GPB10相连。
2、驱动程序
由于是刚刚接触驱动,实在是不知道怎么写,只好用开发板自带的led驱动了(手动分配设备号),我还写了一个自动分配设备号的程序。我所做的就是分析这个程序,争取把它给读懂。下面是开发板led驱动的源程序。
程序里的注释是我参考网上的资料自己加的,主要参考的是mini2440的led驱动程序的注释内容,不知道对不对!
a:手动分配设备号
/*-----------------s3c2440_leds.c--------------------------------*/
#include
#include
#include
#include
#include
#include //?????????????????
#include //???????????????
#include
#include
#include
#include
#define DEVICE_NAME "leds" //设备名
#define LED_MAJOR 231 //主设备号
/*----------------------------------------------------------------------------------*/
//定义引脚的寄存器数组(无符号长整形,对应于引脚的地址)
static unsigned long led_table [] = {
S3C2410_GPB5,
S3C2410_GPB6,
S3C2410_GPB8,
S3C2410_GPB10,
};
/*---------------------------------------------------------------------------------*/
//定义引脚功能,为输出(无符号整形)
static unsigned int led_cfg_table [] = {
S3C2410_GPB5_OUTP, //0x01<<10 defined in refg-gpio.h
S3C2410_GPB6_OUTP,
S3C2410_GPB8_OUTP,
S3C2410_GPB10_OUTP,
};
/*-------------------------------------------------------------------------------*/
//测试程序调用此函数,实现对led的控制
//设备节点,文件描述符,LED灯状态,LED灯编号四个命令参数
static int s3c2440_leds_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
switch(cmd) {
case 0:
case 1:
if (arg > 4)
{
return -EINVAL; /* Invalid argument,非法参数 */
}
//低电平有效,用户程序传来的cmd取反
s3c2410_gpio_setpin(led_table[arg], !cmd);//设置数据寄存器gpbdat,cmd的非
return 0;
default:
return -EINVAL;
}
}
/*----------------------------------------------------------------------------*/
static struct file_operations s3c2440_leds_fops = {
.owner = THIS_MODULE,
.ioctl = s3c2440_leds_ioctl,
};
/*---------------------------------------------------------------------------*/
//初始化函数
static int __init s3c2440_leds_init(void)
{
int ret;
int i;
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c2440_leds_fops);//注册设备
if (ret < 0)
{
printk(DEVICE_NAME " can't register major number/n");
return ret;
}
devfs_mk_cdev(MKDEV(LED_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);
//上句的作用是干甚??????
for (i = 0; i < 4 ; i++)
{
/*-----------------------------------------------------------------------*/
//设置GPIO对应的配置寄存器GPIOCON为输出状态,在头文件
//linux-2.6.12/include/asm-arm/arch-s3c2410/hardware.h中的函数原型为:
//extern void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function);
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
/*-----------------------------------------------------------------------*/
//设置GPIO对应的数据寄存器GPIODAT为高电平
//在模块加载结束后,四个LED应该是全部都是不发光状态
s3c2410_gpio_setpin(led_table[i], 1);
/*-----------------------------------------------------------------------*/
}
printk(DEVICE_NAME " initialized/n");
return 0;
}
/*------------------------------------------------------------------------------------*/
//注销设备
static void __exit s3c2440_leds_exit(void)
{
devfs_remove(DEVICE_NAME);
unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}
/*------------------------------------------------------------------------------------*/
module_init(s3c2440_leds_init);//执行insmod是调用该函数
module_exit(s3c2440_leds_exit);//执行rmmod是调用该函数
/*-----------------------------------------------------------------------------------*/
b:自动分配设备号
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "leds"
static unsigned long led_table [] = {
S3C2410_GPB5,
S3C2410_GPB6,
S3C2410_GPB8,
S3C2410_GPB10,
};
static unsigned int led_cfg_table [] = {
S3C2410_GPB5_OUTP,
S3C2410_GPB6_OUTP,
S3C2410_GPB8_OUTP,
S3C2410_GPB10_OUTP,
};
static int s3c2440_leds_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
switch(cmd) {
case 0:
case 1:
if (arg > 4)
{
return -EINVAL;
}
s3c2410_gpio_setpin(led_table[arg], !cmd);
return 0;
default:
return -EINVAL;
}
}
static struct file_operations s3c2440_leds_fops = {
.owner = THIS_MODULE,
.ioctl = s3c2440_leds_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &s3c2440_leds_fops,
};
static int __init s3c2440_leds_init(void)
{
int ret;
int i;
for (i = 0; i < 4; i++)
{
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i], 1);
}
ret = misc_register(&misc);
printk (DEVICE_NAME"/tinitialized/n");
return ret;
}
static void __exit s3c2440_leds_exit(void)
{
misc_deregister(&misc);
}
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);
这个程序我没有做注释,参考上一个程序吧!不过这里有些需要说明的,用insmod命令能加载到内核,用lsmod命令也可以查到,但是运行测试程序时却出不来该有的现象,并且提说找不到文件。(下面做了解释)
3、编译驱动程序
写个Makefile文件编译,2.6版本的内核好像只能这么编译驱动,不能在命令行里一步步的通过命令编译,不过2.4内核的可以。(不知道是不是这么回事?)
当然Makefile我也不会写,用的板子带的那个!
#Makefile for s3c2440_leds.c
obj-m :=s3c2440_leds.o
KERNELDIR ?=/usr/linux-2.6.12
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
经make后生成了四个文件
我只知道s3c2440_leds.ko是我想要的,其他三个不道是干嘛的?
4、将驱动加载到内核
有两种方式:
一是把驱动程序直接编译进内核;细节问题我没有怎么研究,大致上就是写好驱动程序之后,放到内核代码的目录里,当然还得修改几个文件,与内核一同编译。这样内核启动后会自动加载,免去很多麻烦,但要占用资源!
二是动态的将驱动加载进内核;这种方式比较适合实验开发,挺方便的,就是每次用到驱动是都要手动加载,我采用的就是这种方式!不过有这种方式有关前题,就是你的内核必须支持动态的加载模块,这个在你配置内核的时候要选择此项支持!
到你的内核的目录下,make menuconfig,第三项“Loadable module support”,选上“Enable loadable module support”,其他的随便,我就多选了个“Module unloading”。然后,重新编译内核。(参考的别人的,我没弄过!!!)
下面是加载的过程:
将PC机的根目录挂到开发板的tmp目录下
#mount 192.168.2.32:/ /tmp
加载驱动模块
#insmod s3c2440_led.ko
可以看看有没有加载上
#lsmod
Module size usedby tainted:P
S3c2440_leds 1664 0
Ok了,加载成功,现在可以写个测试程序测试一下了!
顺便说一下如何卸载驱动模块吧!
#rmmod s3c2440_leds.ko
注意:如果你加载的是a驱动程序,可以到/dev目录下查看一下,发现在字符设备中有关叫leds的设备,设备号231。但是如果你加载的是b驱动程序,你在/dev目录下是找不到leds的,不过你可以进的一个叫misc的目录,原来leds在这里,我试验内核分给它的设备号是63,不道为啥没有想a程序那样直接出现在/dev目录下。这也带来了麻烦,就是写测试程序时,针对两种不同的驱动程序,测试程序也略有不同!(后面会说,见测试程序)
5、测试程序
/*-----------------------led--------------------------------------------*/
/*这个程序实现的是跑马灯*/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/ioctl.h"
#include "stdlib.h"
#include "termios.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "sys/time.h"
int main(void)
{
int on=1;
int led;
int fd;
fd=open("/dev/leds",0);
/*如果有加载的是b驱动程序模块,这里要写出:fp=open(“/dev/misc/leds”,0)*/
if(fd<0)
{
perror("open device leds");
exit(1);
}
printf("leds test show,press ctrl+c to exit /n");
while(1)
{
for(led=0;led<4;led++)
{
ioctl(fd,on,led);
usleep(50000);
}
on=!on;
}
close(fd);
return 0;
}
6、编译测试程序,完成测试
将测试程序交叉编译,在板子上执行:
#./led
看效果吧,漂亮!!!!!
三、实验总结:
这个实验其实做了好多天,在做之前也做了很多准备,因为以前也没接触过这些,也没有什么基础,确实挺费劲的!而且我之前看的都是2.4内核的驱动,2.4的驱动与2.6<
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮