linux嵌入式学习阶段总结-学完LED驱动的编写

原创文章,转载请注明出处:http://www.mcuart.cn/forum/embedded/linux/2024-04-17/1.html

 

我买的是正点原子的imx6ull开发板,在B站上看的视频教程,今天看完了蜂鸣器实验

 

终于算学会点灯了。

 

刚开始学单片机的时候,大概三两个小时,最多不到半天就能学会点亮led

而linux开发真正学会点亮led(确切的说是跟着视频教程去点的),足足学了182节课,每节课平均半个小时左右,也就是点个灯学透的话至少我已经看了90个小时,还不算自己跟着打代码的时间

现在先做一下总结吧,本章总结主要是针对第182课里面的源码的各功能进行一个总结

首先

module_init(dtsled_init);        (函数名称定义还没有改彻底,但是不影响正常跑)

module_exit(dtsled_exit);

 

这两个定义了模块入口和出口

入口函数就是加载驱动的时候执行的函数,出口函数就卸载驱动时候执行的函数

1、加载驱动先是通过  gpioled.nd = of_find_node_by_path("/gpioled");  从设备树中查找节点,成功的话相当于这个入口函数找到了设备树中对应的节点

 

2、 gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpios",0); /*获取LED所对应的gpio*/   这句是从节点的属性值里面找到对应的引脚,只有正确找到引脚驱动文件能够准确驱动芯片的引脚

 

3、ret =  gpio_request(gpioled.led_gpio,"led-gpio");    /*申请IO*/    这句是向内核申请IO,据老师讲不申请也可以控制,申请的话是可以知道是否有其他程序在占用这个引脚。按说的话应该写上,一个是知道是否有其他程序在占用这个引脚,另一个是告诉其他程序我这个程序在占用这个引脚,可以防止程序间抢引脚资源导致冲突。

 

4、ret = gpio_direction_output(gpioled.led_gpio,0);    /*使用IO,设置为输出*/  这句主要的目的是设置为输出,并设置一个默认值,就类似STM32的引脚初始化函数。

 

5、gpio_set_value(gpioled.led_gpio,0); 将引脚设置为低电平,这句我认为没啥意义,因为上一个函数已经给引脚初始化电平了,应该是包含这句的工作了

 

 

------至此,完成了初始化引脚的工作。看上去过程比stm32复杂一些,因为stm32引脚定义是库文件给定好的,初始化可以直接通过给定的定义和函数操作底层,而linux则是把引脚参数放在设备树里,驱动文件再通过内核拿到对应的引脚信息,这样实现分层。驱动编写是在和内核打交道,而硬件是和设备树打交道。

 

 

 

6、

 

/*注册字符设备驱动*/

    gpioled.major = 0;

    if(gpioled.major){

        ///自己给定设备号

        gpioled.devid = MKDEV(gpioled.major,0);

        register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME);

    }else{

        ///系统自动分配设备号

        alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME);

        gpioled.major = MAJOR(gpioled.devid);

        gpioled.minor = MINOR(gpioled.devid);

    }

这段的主要功能是得到一个设备ID,并向内核注册这个设备ID,对应的是 cat /proc/devices

 

7、

gpioled.cdev.owner = THIS_MODULE;

    cdev_init(&gpioled.cdev,&gpioled_fops);

    /*添加cdev*/

    cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);

这个cdev就是字符设备,这句的作用不太清楚,网上查的说在Linux内核中,cdev_add() 函数用于将一个字符设备结构(struct cdev)添加到内核的字符设备列表中,使其能够接收设备文件系统(如/dev下的文件)的请求,在注册前要实现设备操作函数集(file_operations结构)。大概和步骤6的区别是,6是注册一个设备号,而此步是注册一个设备实例。

8、

    /*创建类*/

    gpioled.class = class_create(THIS_MODULE,GPIOLED_NAME);

    if(IS_ERR(gpioled.class)){

        return PTR_ERR(gpioled.class);

    }

    /*创建设备*/

    gpioled.device = device_create(gpioled.class,NULL,gpioled.devid,NULL,GPIOLED_NAME);

    if(IS_ERR(gpioled.device)){

        return PTR_ERR(gpioled.device);

}

创建类和创建设备的目的最终应该是创建设备,生成一个/dev/gpioled的文件

-----至此入口函数就算结束了,其中678步也可以放到1-5步前面,因为1-5步是初始化引脚,是在和硬件打交道(向下),678步是在给应用层提供一个接口(向上)。但是个人觉得还是先初始化引脚的会好一些。

 

9、驱动文件里面led_open,led_release,led_write这些函数是给应用层提用一些方法,用来让应用层控制硬件

10、ledAPP.c这个文件有fd = open(filename,O_RDWR);ret = write(fd,databuf,sizeof(databuf));close(fd);这些方法,这些方法都是针对/dev/gpioled进行操作,其中write 的目的是向/dev/gpioled这个文件内写入数据。也就是应用层只跟/dev/gpioled打交道,不会跟硬件或驱动产生任何的直接联系。

11、当内核发现/dev/gpioled这个文件被写入数据的时候,会调用驱动文件的led_write函数,从而通过gpio_set_value实现对硬件的操作。

 

以上就是个人的理解

再来总结一下视频教程中总看的几个地方

ls /proc/device-tree    :设备树定义了哪些节点

cat /proc/devices :在内核注册了哪些设备

ls /dev :给应用程序提供的可操作设备文件列表

 

 

FOLLOW US ON INSTAGRAM