历史上的今天
返回首页

历史上的今天

今天是:2025年02月15日(星期六)

2019年02月15日 | ARM开发之linux字符型驱动的编写----LED驱动为例

2019-02-15 来源:eefocus

相应头文件:


#include


#include

#include

#include

#include

#include

#include

#include

#include





开发步骤:


0.驱动开发的简单架构


1.定义设备结构体


2.申请设备号


3.定义文件操作集


4.设备初始化


5.注册设备


6.申请物理内存区


7.通过映射物理地址来获得相应虚拟地址


8.创建设备的类


9.创建设备文件结点


10.应用程序的编写


11.代码演示




0.驱动开发的简单架构


驱动代码虽然也是用C语言编写,但没有main函数。取而代之有起始宏( module_init() )和结束宏( module_exit() ),宏中指定了驱动开始嵌入内核时执行的函数和驱动被移出内核时所执行的函数,另外还有MODULE_LICENSE(“GPL”),也是必须的,指定了GPL授权。


当在开发板执行命令:insmod 驱动名 ;时,就把驱动作为模块嵌入到内核中,此时会自动执行module_init()指定的函数。


当在开发板执行命令:rmmod 驱动名 ;时,就把驱动模块从内核中移出,此时会自动执行module_exit()指定的函数。



static int __init  start(void)       //初始函数的格式,函数名可以自己定

{

 

}

 

static void __exit end(void)        //退出函数的格式,函数名可以自己定

{

 

}

 

module_init(start);

module_exit(end);

MODULE_LICENSE("GPL");




1.定义设备结构体


直接定义一个 cdev 结构体变量即可。cdev结构体在头文件 #include


如:

static struct cdev cdev;




2.申请设备号


0.设备号:一个设备号由主设备号和次设备号组成。主设备号代表该驱动属于什么种类的驱动,而次设备号代表驱动在当前种类驱动中的第几个。


1.设备号的用处是:用于标识这个设备,就如人的名字一样,电脑标识的一般是数字


2.注册设备号的函数有动态注册和静态注册的


动态:


alloc_chrdev_region( *设备号,起始次设备号,注册个数,名字 );



static dev_t dev_num;  //定义设备号变量

static const char dev_name[]="led";

int ret;

 

ret = alloc_chrdev_region(&dev_num,0,1,dev_name);

if(ret<0)

{

     printk("申请设备号失败\n");  //从内核打印信息用printk

}

动态申请设备号,会自动生成设备号并放到dev_num 变量中。



静态:


用MKDEV(主设备号,次设备号)生成指定的设备号,如:


static int major =10;

static int minor = 5;

static dev_t dev_num;

 

dev_num = MKDEV(major,minor);

静态申请要人工指定主设备号和次设备号,这样一来,设备号就确定了。

注销函数:unregister_chrdev_region( dev_num,1 );//用于注销申请的设备号


3.定义文件操作集

在linux内核源码的 include/linux 下的 fs.h 文件中,指定了一个文件操作集的结构体,里面包含驱动可以使用的系统调用函数。


有驱动程序绝对要有相对应的应用程序,因为驱动程序一般是用于被应用程序调用的。而应用程序与驱动程序间是如何联系的呢?


拿上图的 int (*open) (struct inode *, struct file *); 为例,是一个函数指针,用于指向一个函数(假设指向函数a)。意思是,但应用程序调用 open()函数时,驱动程序就自动执行函数a。注意的是,int (*open) (struct inode *, struct file *); 为例,自己写open对应的函数时,格式一定要与int (*open) (struct inode *, struct file *)一样,只允许函数名自己定义。最后会有代码会演示。




所以,我们可以根据自己要用什么函数来定义自己的文件操作集。把结构体上的函数指针赋值就行了。


还有.owner = THIS_MODULE 一定要写,用于初始化。记住就好


/*下面是指定当应用程序调用open函数时,驱动程序会调用device_open函数,同理还有write函数,这里特别的是release是指应用程序的close()函数*/

static const struct file_operations fops={

.owner = THIS_MODULE,

.open = device_open,

.write = device_write,

.release = device_release,

};

无注销函数



4.设备初始化


函数:cdev_init( *设备结构体变量,*文件操作集 );


作用:把 设备结构体变量 和 文件操作集 结合起来。相当于告诉内核这个驱动用哪些系统调用函数。



cdev_init(&cdev,&fops);

无注销函数。


5.注册设备


函数:cdev_add( *设备结构体变量,设备号变量,注册设备个数 );


作用:把 设备结构体变量 赋予 他 一个设备号。


失败会返回一个负值的错误码



ret = cdev_add(&cdev,dev_num,1);

if(ret<0)

{

printk("cdev add failed\n");

goto failed_cdev_add;

}

对应注销函数:cdev_del(*cdev结构体变量);


6.申请物理内存区


写驱动一定要看原理图和用户手册的,申请物理内存区就是获取相应虚拟地址的前一个过程,先要把 设备(例子LED灯)所在的物理地址作为资源申请到内核中。

我的开发板的操控LED灯的寄存器如下:




控制寄存器地址是:0XE0200280;数据寄存器地址是:0XE0200284(怎么找寄存器这里不详细说)




函数:request_mem_region( 起始物理地址,申请多大字节,给这段内存资源一个名字 );


返回:返回一个 struct resource 结构体的指针变量



static struct resource *res = NULL;

res = request_mem_region(0xe0200280,8,"GPJ2CON_MEM");

if(res==NULL)

{

printk("failed to request mem\n");


}

至于为什么第二个参数填8,因为我们ARM开发板,一个寄存器大小是32位。也就是4个字节,那我操作的是2个寄存器,所有填8.

对应注销函数:void release_mem_region(0XE0200280,8);






7.通过映射物理地址来获得相应虚拟地址


有操作系统的,操作的是虚拟地址。所以要通过相对的物理地址映射出虚拟地址给系统操作,从而实现操作系统操作物理地址。


函数:ioremap( 起始物理地址,多少个字节 );


返回虚拟地址起始地址。



unsigned int GPJ2CON_VA=NULL;

       GPJ2CON_VA = ioremap(0xe0200280,8);

if(GPJ2CON_VA==NULL)

{

printk("failed to ioremap\n");


}

对应的注销函数:void iounmap(GPJ2CON_VA);






8.创建设备的类class


应用程序调用驱动程序是通过打开 根目录/dev下的 设备文件结点来调用的,而文件结点需要分类。要先创建类class,才能创建设备文件结点。


创建类成功后,可以在路径 /sys/class 下看到,而且进入类后,可以看到属于该类的 设备文件结点。



static struct class *class = NULL; ---->这个要设成全局变量

class = class_create(THIS_MODULE,"class_name"); 

对应的注销函数: void class_destroy(class变量);



9.创建设备文件结点device

函数:device_create( class,NULL,设备号,NULL,“文件结点的名字” );

作用:通过设备号把 设备结构体变量 与 文件结点联系起来。换句话说就是给 设备结构体变量 弄一个文件结点。

参数:第一个,设备文件结点属于的类变量

第二个,该设备的父设备,一般为NULL

第三个,设备号变量

第四个,给 文件结点的数据,一般为NULL

第五个,该文件结点的名字

static struct device* dev_device = NULL;

dev_device = device_create(dev_class,NULL,dev_num,NULL,"led_device");

if(dev_device == NULL)

{

printk("failed to create device\n");

}

对应的注销函数:void device_destroy( class变量,设备号变量 );

注销时,一定要先注销文件结点,才能注销所在的class类变量



10.应用程序的编写:

1.用open打开 /dev下自己写的 设备文件结点

2.对其进行读写操作

3.copy_from_user()和copy_to_user()的使用


copy_from_user(wbuf,buf,len);用于内核空间(驱动程序)获取用户空间(应用程序)给的数据,如应用程序用write函数往驱动的设备文件结点写数据,

驱动程序就用copy_from_user获取。

参数:把第二个参数复制给第一个参数,len表示复制的字节大小。


copy_to_user(buf,wbuf,len)用于内核空间(驱动程序)发数据给用户空间(应用程序)。

参数:也是第二个参数复给第一个参数,第二个参数指内核空间buf,第一个指用户空间buf。


11.代码演示

LED驱动程序:实现4栈LED灯轮流闪烁

#include

#include

#include

#include

#include

#include

#include

#include

 

/*第一步:定义设备结构体cdev*/

static struct cdev cdev;

static dev_t dev_num;

static const char device_name[]="led_device";

static struct resource *py_mem = NULL;

static unsigned int *GPJ2CON_VA = NULL;

static unsigned int *GPJ2DAT_VA = NULL; 

static struct class *dev_class = NULL;

static struct device *dev_device = NULL;

char wbuf[1];

 

static int device_open(struct inode *inode, struct file *f)

{

printk("device open..\n");

*GPJ2CON_VA &=~0xffff;

*GPJ2CON_VA |=0x1111;

*GPJ2DAT_VA &=~0xf;

*GPJ2DAT_VA |=0xf;

return 0;

}

 

static int device_release(struct inode *inode, struct file *f)

{

printk("device close...\n");

*GPJ2DAT_VA|=0xf;

return 0;

}

 

static ssize_t device_write(struct file *f, const char __user*buf,

size_t len, loff_t *t)

{

int ret;

ret = copy_from_user(wbuf,buf,len);

if(ret!=0)

{

printk("failed to copy from user\n");

return -1;

}

 

if(wbuf[0]=='0')

{

*GPJ2DAT_VA &=~0xf;

*GPJ2DAT_VA |=0xe;

}

 

if(wbuf[0]=='1')

{

*GPJ2DAT_VA &=~0xf;

*GPJ2DAT_VA |=0xd;

}

 

if(wbuf[0]=='2')

{

*GPJ2DAT_VA &=~0xf;

*GPJ2DAT_VA |=0xb;

}

 

if(wbuf[0]=='3')

{

*GPJ2DAT_VA &=~0xf;

*GPJ2DAT_VA |=0x7;

}

return 0;

}

 

/*第三步:定义文件操作集*/

static const struct file_operations fops={

.owner = THIS_MODULE,

.open = device_open,

.write = device_write,

.release = device_release,

};

 

static int __init led_init(void)

{

int ret;

 

/*第二步:申请设备号*/

ret = alloc_chrdev_region(&dev_num,0,1,device_name);

if(ret<0)

{

printk("cannot register dev num\n");

return -1;

}

 

/*第四步:设备初始化*/

cdev_init(&cdev,&fops);

 

/*第五步:注册设备*/

ret = cdev_add(&cdev,dev_num,1);

if(ret<0)

{

printk("cdev add failed\n");

goto failed_cdev_add;

}

 

/*第六步:申请物理内存( 地址 )区:0xe0200280~0xe0200287*/

py_mem = request_mem_region(0xe0200280,8,"GPJ2CON_MEM");

if(py_mem==NULL)

{

printk("failed to request mem\n");

goto failed_request_mem;

}

 

/*第七步:通过有映射物理地址获得相应虚拟地址*/

GPJ2CON_VA = ioremap(0xe0200280,8);

if(GPJ2CON_VA==NULL)

{

printk("failed to ioremap\n");

goto failed_ioremap;

}

 

/*GPJ2CON_VA为int型,占4个字节,+1相当于移4个字节

(32个位)*/

GPJ2DAT_VA = GPJ2CON_VA+1;

 

/*第八步:创建设备的类*/

dev_class = class_create(THIS_MODULE,"led_class");

if(dev_class == NULL)

{

printk("failed to create class\n");

goto failed_create_class;

}

 

/*第九步:创建设备文件结点*/

dev_device = device_create(dev_class,NULL,dev_num,NULL,"led_device");

if(dev_device == NULL)

{

printk("failed to create device\n");

goto failed_create_device;

}

 

printk("init completed!\n");

return 0;

 

failed_create_device:

class_destroy(dev_class);

 

failed_create_class:

iounmap(GPJ2CON_VA);

 

failed_ioremap:

release_mem_region(0xe0200280,8);

 

failed_request_mem:

cdev_del(&cdev);

 

failed_cdev_add:

unregister_chrdev_region(dev_num,1);

 

return -1;

}

 

static void __exit led_exit(void)

{

class_destroy(dev_class);

iounmap(GPJ2CON_VA);

release_mem_region(0xe0200280,8);

cdev_del(&cdev);

unregister_chrdev_region(dev_num,1);

}

 

module_init(led_init);

module_exit(led_exit);

 

MODULE_LICENSE("GPL");



应用程序:

#include

#include

 

int main()

{

char buf[1];

char i;

 

int fd = open("/dev/led_device",O_WRONLY);

if(fd<0)

{

perror("failed to open");

return -1;

}

while(1)

{

for(i='0';i<'4';i++)

{

buf[0]=i;

write(fd,buf,1);

sleep(1);

}

}

return 0;

}



推荐阅读

史海拾趣

EOZ Secme公司的发展小趣事

随着国内市场的饱和,EOZ Secme开始积极寻求海外市场的拓展。公司制定了详细的国际化战略,通过参加国际展会、建立海外销售渠道等方式,逐步打开了国际市场的大门。同时,EOZ Secme还积极与国际知名企业开展合作,共同研发新产品,拓展业务领域。这些努力使得EOZ Secme在国际市场上的影响力不断增强。

ACT [Advanced Crystal Technology]公司的发展小趣事

EOZ Secme公司自创立之初,就致力于电子安全技术的研发。在2010年代初,随着物联网技术的兴起,EOZ Secme敏锐地捕捉到这一机遇,投入大量资源进行物联网安全技术的研发。经过数年的努力,公司成功开发出了一系列具有自主知识产权的物联网安全解决方案,并在市场上获得了广泛应用。这些技术的创新不仅提高了EOZ Secme的市场竞争力,也奠定了其在电子安全领域的领先地位。

3L Electronic Corporation公司的发展小趣事

面对日新月异的电子科技,3L Electronic Corporation始终坚持以技术创新为核心竞争力。公司投入大量研发资源,不断推出具有市场竞争力的新产品。从传统的电子零组件到智能电子设备,再到物联网解决方案,3L的产品线不断升级,满足了市场的多样化需求。

Global Specialties公司的发展小趣事

在电子技术的不断推动下,Global Specialties始终保持对新技术的高度敏感。公司不断投入研发资源,对现有产品进行升级换代,并推出了一系列具有创新性的新产品。例如,公司开发的智能测试仪器能够自动完成复杂的测试任务,大大提高了测试效率和准确性。这些技术创新不仅巩固了Global Specialties在业内的领先地位,还为公司带来了更多的商业机会。

Dongguan Jingyue Electronics Co Ltd公司的发展小趣事

随着公司业务的不断扩大,Dongguan Jingyue Electronics Co Ltd面临着越来越复杂的供应链管理问题。为了降低成本、提高效率,公司决定对供应链进行优化。通过引入先进的供应链管理软件和系统,加强与供应商和物流公司的合作,公司成功实现了供应链的数字化和智能化管理,提高了整体运营效率。

Electronic-Bauteile Goerlitz GmbH公司的发展小趣事

Electronic-Bauteile Goerlitz GmbH公司自创立之初,就以其独特的技术创新为核心竞争力。公司不断投入研发资源,开发出了一系列具有竞争力的电子产品部件。这些部件以其高性能、低功耗和可靠性,赢得了市场的广泛认可。公司通过与高校和研究机构的合作,不断引进新技术,推动产品升级换代,确保了其在行业中的领先地位。

问答坊 | AI 解惑

寻求小天线的区分范围

  各位高手,有谁能告诉我,具体的小天线是指哪些天线,它的范围包括哪些?谢谢大家的帮助!!…

查看全部问答>

哪位高人能帮我设计一个射基跟随器呀?

我需要一个放大电路,它的要求是把我现有的一个0.6V的开关,放大成3.0V的开关,最后是要控制发光二极管亮灭,可以提供12.0V电源,哪位高人可以帮忙设计或有成品电路可以用请提供一下信息啊?…

查看全部问答>

关于linux设备驱动的书

我是一个嵌入式linux开发的新手,现在想自学驱动开发,但是我一直有个疑问,请您指教: 我到底先读《linux设备驱动程序(第三版)》和宋老师编著的《linux设备驱动开发详解》哪一本书?我很苦恼。。。…

查看全部问答>

WM手机如何与单片机加USB_HOST芯片通信

如题 只考虑手机端。 随便问问,没希望得到答案 呵呵…

查看全部问答>

我给大家特别推荐的一款产品,非常棒!!!

c8051f 单片机(c2 ,jtag)和at89s5x单片机(isp)二合一串口编程下载线,支持3伏(c8051f 单片机),5伏(at89s5x单片机)电源, 目前通过实际验证可编程下载的芯片:c8051f310、c8051f320、c8051f330D、c8051f340、c8051f350、c8051f360、c8051f4 ...…

查看全部问答>

EVC表盘类?

现在evc上作界面开发,用到模拟压力表盘显示压力值,表针可以实时转动,刻度可以自己设置,不知谁用过,能否帮帮忙?…

查看全部问答>

关于STM32L151的几个问题

1. 何处可以得到 STM32L151 的片子; 2. STM32L151 的内部时钟可以支持 USB吗? 3. IAR5.3 +jilink 7 可以支持STM32L151? 谢谢!…

查看全部问答>

请教:我设置了开机密码但是无法弹出软键盘

我设置了开机密码但是无法弹出软键盘,这样没办法进入系统也没办法关机,请教如何关闭密码或者调出软键盘 是windows CE6.0的 谢谢各位大大了! [ 本帖最后由 qty0 于 2011-7-28 20:21 编辑 ]…

查看全部问答>

一个简单的DA应用程序

一个简单的DAC程序,输出正弦波。…

查看全部问答>