首页 > 编程语言 >led驱动程序进阶-基于面向对象思想的led驱动模板

led驱动程序进阶-基于面向对象思想的led驱动模板

时间:2024-04-16 21:23:05浏览次数:15  
标签:__ led 驱动程序 int 函数 LED include 进阶

在上一篇文章中编写led驱动程序采用的是最传统的编写方式,这里回顾一下流程就是:给file_operations结构体添加具体的open、read、write、release函数,并实现这些函数的定义,然后定义入口函数并在里面使用这个结构体变量注册驱动、寄存器地址映射、创建设备,然后定义出口函数并进行撤销驱动、取消地址映射、销毁设备。
分析上述过程,我们在init、exit、open、write函数中直接对寄存器进行操作,并没有体现封装的思想,并且代码看上去思路还不够清晰,如果led的GPIO引脚改变位置,还需要去修改其中的引脚,并不适合于所有板子的LED。所以我们能否在驱动程序里写出一个适用于所有GPIO引脚的LED驱动程序框架,对于接在不同的GPIO引脚的LED,只需要修改少量的代码即可?
这是可行的,思路就是我们把有关寄存器操作的部分进行封装成为函数,这些具体的函数根据不同的板子来实现,那么在led驱动程序里只需要包含具体板子的头文件定义,并提供对应头文件中函数实现的.c文件即可,从而实现适配不同的板子。

梳理一下思路:(1)使用的GPIO引脚不同,那么对应的寄存器地址不一样,因此可以将寄存器映射部分封装;
(2)控制LED需要配置GPIO,配置GPIO部分也可以进行封装;
(3)需要设置GPIO输出的高低电平,因此控制这部分也可以进行封装
因此总结一下,可以把(1)和(2)合并作为具体led驱动的初始化函数,(3)单独作为led的控制函数,因此可以定义如下结构体:

点击查看代码
struct led_operations {
	int (*init) (int which); /* 初始化LED, which-哪个LED */       
	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
};

同时我们应该提供一个接口,以获取这个结构体,因此最终可以得到如下头文件

点击查看代码
//led_opr.h

#ifndef _LED_OPR_H
#define _LED_OPR_H

struct led_operations {
	int (*init) (int which); /* 初始化LED, which-哪个LED */       
	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
};

struct led_operations *get_board_led_opr(void);


#endif


针对任何一个单板的led或者任何一个GPIO引脚控制的LED,在写驱动程序时,我们只需要写出对应的实现上述头文件led_orp.h中函数的具体.c文件,然后在驱动框架里调用这些函数即可
一般情况下,寄存器地址映射可以放在init函数也可以放在open函数中,但是考虑到多个GPIO控制的多个LED可能共用一个驱动程序,因此把寄存器地址映射放在open函数更加合理,并且只有open了才是真的要使用对应设备。对应的,close函数在就取消地址映射,也可以不取消。

针对具体单板,只需要包含头文件led_orp.h实现对应函数既可以,下面给出一个通用的模板board.c

点击查看代码
board.c

#include "led_orp.h"
#include "asm/memory.h"
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <asm/io.h>

int led_init(int which)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

int led_ctl(int which, char status)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

static struct led_operations led_orp = {
    .init = led_init,
    .ctl = led_ctl,
};

struct led_operations* get_led_operations()
{
    return &led_orp;
}

基于上述led_orp.h和board.c可以写出led驱动模板

点击查看代码
led_driver.c

#include "asm/memory.h"
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <asm/io.h>

#include "led_orp.h"

// (1)确定主设备号,也可以让内核分配
static int major = 0;
int led_nums = 2;
static struct class *led_class;
struct led_operations *led_orp;

#define MIN(a, b)   (a < b ? a : b)

// (3)实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
//为了不用声明,将函数定义放到前面,但是逻辑顺序应该是在后面
static ssize_t led_drv_write (struct file * file, const char __user * buf, size_t size, loff_t * offset)
{
    char val;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
   /*
   读取从用户空间传入的数据,给DR寄存器写0/1控制GPIO1_IO03输出高/低电平以控制LED亮灭
   */
    copy_from_user(&val, buf, 1);
    struct inode *node;
    int which;
    node = file_inode(file);
    which = iminor(node);
    led_orp->ctl(which, val);
    
    return 1;   //返回1,表示写入1个数据

}


static int led_drv_open (struct inode * node, struct file * file)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    int which;
    which = iminor(node);
    led_orp->init(which);   //初始化led,包括地址映射、寄存器配置
    return 0;
}
static int led_drv_close (struct inode * node, struct file *file)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    /*
    关闭设备,撤销寄存器地址映射
    */

    return 0;
}

// (2)定义自己的 file_operations
static struct file_operations led_drv = {
    .owner  = THIS_MODULE,
    .open   = led_drv_open,
    .write  = led_drv_write,
    .release    = led_drv_close,
};



// (4)把 file_operations 结构体告诉内核:register_chrdev 注册驱动程序,因此先跳到步骤5


// (5)谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    major = register_chrdev(0, "led_driver", &led_drv); //注册驱动
   

    led_class = class_create(THIS_MODULE, "led_class"); //这个class对象与驱动绑定了吗?为什么需要用它来创建设备?
    err = PTR_ERR(led_class);
    if (IS_ERR(led_class))
    {
        unregister_chrdev(major, "led_driver");
        return -1;
    }

    //创建设备节点,有几个led就创建几个,使用次设备号作为编号
    int i;
    for(i = 0; i < led_nums; i++)
        device_create(led_class, NULL, MKDEV(major, i), NULL, "led_%d", i);
    
    //获取具体板子的led操作函数结构体
    led_orp = get_led_operations();

    return 0;

}

// (6)有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    int i;
    for(i = 0; i < led_nums; i++)
        device_destroy(led_class, MKDEV(major, i));
    class_destroy(led_class);


    unregister_chrdev(major, "led_driver");



    
}

// (7)其他完善:提供设备信息,自动创建设备节点:class_create,device_create
module_init(led_init);    //凭什么说上面的Init函数就是入口函数?这里这个宏的作用就是告诉内核这是入口函数
module_exit(led_exit);
MODULE_LICENSE("GPL");

下一篇文章再基于上述模板和imx6ull开发板实现led驱动

标签:__,led,驱动程序,int,函数,LED,include,进阶
From: https://www.cnblogs.com/starstxg/p/18135013

相关文章

  • 进阶数据结构
    学到哪写到哪说是既然打ACM可以用板子,我就不用再隔几天敲一遍板子了只能说赢麻了线段树线段树是一种利用二分思想的数据结构,主要用于区间修改以及查询问题。它的基本思想是可以用一下一个图来表示,其中最底层的是原数组简单来说,对于每个区间的修改或者查询操作,我们都会将它......
  • XPath和CSS选择器的进阶
    记录一下关于selenium下xpath的进阶技术XPath轴(axes)和CSS选择器的伪类(pseudo-classes)与伪元素(pseudo-elements)是高级定位技术,可以在复杂的HTML结构中帮助你更精确地定位元素。1.XPath轴(Axes)XPath轴提供了一种方式来选择与当前节点有特定关系的节点。以下是一些常用的XPath轴:......
  • 【进阶篇】Java 实际开发中积累的几个小技巧(二)
    目录前言六、自定义注解6.1定义注解6.2切面实现6.3业务使用七、抽象类和接口7.1隔离业务层与ORM层7.2隔离子系统的业务实现7.3选择对比文章小结前言笔者目前从事一线Java开发今年是第3个年头了,从0-1的SaaS、PaaS的项目做过,基于多租户的标准化开发项目也做过,项目的PM......
  • day10_01_我的Java学习笔记 (JavaSE进阶课程预备)
    JavaSE进阶课程预备1.JavaSE加强课程简介2.IDEA开发模式统一工程,相当于一个小区的院子;模块,是小区的哪一栋;包,是这栋楼的那一单元类,是这个单元的哪一层楼;对象,是这层楼具体的某一户房间。eg:滢水山庄二区--工程9栋--模块4单元--包8楼--类......
  • 搭建 Windows GPU 服务器需要考虑多个方面,包括硬件选择、操作系统安装、驱动程序安装
    搭建WindowsGPU服务器需要考虑多个方面,包括硬件选择、操作系统安装、驱动程序安装、软件配置等。以下是一个简单的指南,介绍了搭建WindowsGPU服务器的基本步骤:1.硬件选择选择适合您需求的硬件配置,包括GPU、CPU、内存和存储。GPU是关键的组件,应根据您的应用需求选择......
  • java连接ssmsSqlserver数据库 报错信息:com.microsoft.sqlserver.jdbc.SQLServerExce
    解决办法:将官网下载的驱动文件打开,找到如下路径,并复制,粘贴放到jdk的bin目录下......
  • nginx报错:bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a soc
    问题:1.nginx启动失败2.在logs/error.log文件下,出现报错信息:bind()to0.0.0.0:80failed(10013:Anattemptwasmadetoaccessasocketinawayforbiddenbyitsaccesspermissions) 目录:1、cmd输入命令netstat-aon|findstr"80"2.、查看80端口7532对应的任务3、......
  • 内置上电复位电路/数显控制电路VK1651 SOP16/DIP16数显LED驱动电路
    产品品牌:永嘉微电/VINKA产品型号:VK1651封装形式:SOP16/DIP16概述VK1651是一种带键盘扫描电路接口的LED驱动控制专用芯片,内部集成有数据锁存器、LED驱动、键盘扫描等电路。SEG脚接LED阴极,GRID脚接LED阳极,可支持7SEGx4GRID的点阵LED显示。最大支持7x1按键。本芯片性能优良,适用......
  • Command PhaseScriptExecution failed with a nonzero exit code 错误解决记录
    xCode报这个错误,首先看是哪个文件报错,进入错误日志/Users/fanvil/Library/Developer/Xcode/DerivedData。因为我这边是FBReactNativeSpec这个文件下的错误,所以很容易找到错误日志:/Users/fanvil/Desktop/iOS/Demo/ios/Pods/../../node_modules/react-native/React/FBReactNativeSp......
  • 详解K8s 镜像缓存管理kube-fledged
    本文分享自华为云社区《K8s镜像缓存管理kube-fledged认知》,作者:山河已无恙。我们知道 k8s 上的容器调度需要在调度的节点行拉取当前容器的镜像,在一些特殊场景中,需要快速启动和/或扩展的应用程序。例如,由于数据量激增,执行实时数据处理的应用程序需要快速扩展。镜像比......