[Linux字符驱动] LED基于gpio系统点灯

首先聊一下linux中的软件工作岗位,有专门负责BSP的,负责把uboot、kernel、文件系统都搞定,这些都是比较复杂的;打包好了之后,基本万年不动,除非有问题暴露需要解决;内核驱动里面有基于总线架构和一些子系统实现,比如input子系统、pinctrl子系统、i2c、spi总线模型驱动;还有一些块驱动,比如,Nand、EMMC驱动;网络驱动,有网卡芯片、PHY芯片,和spi接口的网卡芯片驱动都算是网络驱动;最后介绍一下字符驱动,这是最常更改的一类驱动,也通常是自己实现的驱动。
驱动的范围包含:LED、DO控制、DI读取、按键读取等;

网上教学的LED字符驱动有很多,大多数从iormap,或者调用底层的地址去点灯,那是想让大家了解与单片机操作地址的区别;那些博主都非常的棒,我现在直接上干货,项目和工作中直接的用法。

关注微信公众号,回复“led字符驱动”,下载源代码。

1、模块驱动的入口函数

module_init(at91_led_init);//加载模块先运行init函数
module_exit(at91_led_cleanup);//卸载模块进入cleanup函数


//符合GPL规范,开源,否则无法加载进内核运行
MODULE_DESCRIPTION ("AT91 led DRIVER"); 
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR ("Jack"); 
MODULE_LICENSE ("GPL");


//驱动模块的描述信息,可帮助使用者了解这个模块的功能
#define DRV_VERSION "1.0.0"

static int led_major;
module_param(led_major, int, 0);
MODULE_PARM_DESC(led_major, "led Major device number");

2、创建init和cleanup函数

定义初始化信息,4个LED灯

struct led_pin
{
    ulong led_pin_run;          /* 运行灯 */
    ulong led_pin_fault;        /* 故障灯 */
    ulong led_pin_can5;     /* CAN5 */
    ulong led_pin_can6;     /* CAN6 */
};

struct St_led
{
    dev_t dev_major;
    struct led_pin st_pin;
    atomic_t open_count;
    struct mutex led_mudex;//互斥锁
    struct cdev led_dev;//
    struct class *led_class;
    struct device *led_device;
};

注册驱动流程

static int __init at91_led_init(void)
{ 
    int retval = -1;
    dev_t dev_id;

    // 分配一个结构
    pled = (struct St_led *)kzalloc(sizeof (struct St_led),GFP_KERNEL);
    if(!pled)
    {
        printk("%s:kzalloc failded from:%s\n", __func__,LED_NAME);
        return -ENOMEM;
    }

    //初始化锁,指定具体的PIN脚,根据数据手册定义的地址自定义宏
    pled->st_pin.led_pin_run = AT91_PIN_PD26;   /* 运行灯 */
    pled->st_pin.led_pin_fault= AT91_PIN_PD27;  /* 故障灯 */
    pled->st_pin.led_pin_can5= AT91_PIN_PA29;   /* CAN5灯 */
    pled->st_pin.led_pin_can6= AT91_PIN_PA6;    /* CAN6灯 */

    //加了互斥锁,只能同时被一个应用程序调用
    //printk("led_pin:green:%d\n",pwdt->wdg_pin);
    mutex_init(&(pled->led_mudex));
    //pwdt->open_count = ATOMIC_INIT(0);//初始化为0

    //分配主设备号,注册字符驱动
    if (led_major) {
        dev_id = MKDEV(led_major, 0);
        retval = register_chrdev_region(dev_id, MAX_LED_NB,
                        LED_NAME);
    } else {
        retval = alloc_chrdev_region(&dev_id, 0, MAX_LED_NB,
                         LED_NAME);
        led_major = MAJOR(dev_id);
    }
    if(retval<0)
    {
        printk("led: register error!\n");
        goto error_malloc;
    }
    else
        printk("led:led_major:%d\n",led_major); 

        //绑定操作方法at91_led_fops
    cdev_init(&(pled->led_dev), &at91_led_fops);
    retval = cdev_add(&(pled->led_dev), dev_id,MAX_LED_NB);
    if(retval<0)
    {
        printk("led:cdev_add failed!!!\n");
        goto error_reg;
    }

    //创建类
    pled->led_class = class_create(THIS_MODULE,LED_NAME);
    if(!pled->led_class)
    {
        printk("led:class_create error!\n");
        goto error_cdev;
    }
        //驱动和设备匹配,使用设备号匹配,MKDEV(led_major, 0),驱动中唯一的
    pled->led_device= device_create(pled->led_class, NULL,
                   MKDEV(led_major, 0),
                   NULL, LED_NAME);
    if(!pled->led_device)
    {
        printk("led:device_create error!\n");
        goto error_class;
    }

    return 0;

error_class:
    class_destroy(pled->led_class);
error_cdev:
    cdev_del(&(pled->led_dev));
error_reg:
    unregister_chrdev_region(dev_id,MAX_LED_NB);
error_malloc:
    kfree(pled);

    return retval;
} 

注销驱动流程

注意观察,这个顺序和注册流程刚好是反的;这是个多层嵌套;

static void __exit at91_led_cleanup(void)
{ 
    dev_t dev_id;
    gpio_set_value(pled->st_pin.led_pin_run, 1);        /* 关闭指示灯 */
    gpio_set_value(pled->st_pin.led_pin_fault, 1);      
    gpio_set_value(pled->st_pin.led_pin_can5, 1);
    gpio_set_value(pled->st_pin.led_pin_can6, 1);   

    dev_id = MKDEV(led_major,0);
    if(pled->led_device)
    {
        device_destroy(pled->led_class,MKDEV(led_major, 0));
    }
    if(pled->led_class)
    {
        class_destroy(pled->led_class);
    }
    // del cdev
    cdev_del(&(pled->led_dev));
    if(led_major)
    {
        unregister_chrdev_region(dev_id,MAX_LED_NB);
    }
    //mutex destory
    mutex_destroy(&pled->led_mudex);

    //释放内存
    kfree(pled);

    printk("led:mod is cleanup.\n"); 
} 

2、驱动的操作方法

绑定次驱动的操作方法

static struct file_operations at91_led_fops={ 
    .owner      = THIS_MODULE, 
    .llseek     = NULL,
    .read       = NULL,
    .write      = NULL,
    .unlocked_ioctl      = at91_led_ioctl, //点灯和灭灯操作
    .open       = at91_led_open,    //申请io资源,io初始化操作
    .release    = at91_led_close,   //释放io资源
};
我们需要实现三个操作方法,所以需要写三个函数at91_led_open、at91_led_close、at91_led_ioctl;

static int at91_led_open(struct inode *inode, struct file *filp)
{
    int ret = 0;
    struct St_led *pled;  

    pled = container_of(inode->i_cdev, struct St_led,led_dev);  
    filp->private_data = (void *)pled;
        //原子操作,防止有多个应用打开
    if(atomic_read(&(pled->open_count))>0)
    {
        printk("at91_led_open:busy!!!\n");
        return -EAGAIN;
    }
    else
    {
        atomic_add(1,&(pled->open_count));
    }
/*
    at91_set_GPIO_periph(pled->st_pin.led_pin_run, 1);
    at91_set_GPIO_periph(pled->st_pin.led_pin_fault, 1);
    at91_set_GPIO_periph(pled->st_pin.led_pin_can5, 1);
    at91_set_GPIO_periph(pled->st_pin.led_pin_can6, 1);
*/

    ret = gpio_request(pled->st_pin.led_pin_run, "led_pin_run");
    if (ret < 0) {
          printk("led led_pin_run %ld\n", pled->st_pin.led_pin_run);

    }

    ret = gpio_request(pled->st_pin.led_pin_fault, "led_pin_fault");
    if (ret < 0) {
          printk("led GPIO %ld\n", pled->st_pin.led_pin_fault);

    }

    ret = gpio_request(pled->st_pin.led_pin_can5, "led_pin_can5");
    if (ret < 0) {
          printk("led GPIO %ld\n", pled->st_pin.led_pin_can5);

    }

    ret = gpio_request(pled->st_pin.led_pin_can6, "led_pin_can6");
    if (ret < 0) {
          printk("led GPIO %ld ret=%d\n", pled->st_pin.led_pin_can6,ret);

    }

    /*最基本的硬件初始化,gpio为标准输入输出设备*/
    //printk("led:gpio_g value:%d gpio_r value:%d\n",at91_get_gpio_value(pled->st_pin.led_pin_green),at91_get_gpio_value(pled->st_pin.led_pin_red));
    gpio_direction_output(pled->st_pin.led_pin_run, 1);     /* 关闭指示灯 */
    gpio_direction_output(pled->st_pin.led_pin_fault, 1);       
    gpio_direction_output(pled->st_pin.led_pin_can5, 1);
    gpio_direction_output(pled->st_pin.led_pin_can6, 1);

    gpio_set_value(pled->st_pin.led_pin_run, 1);        /* 关闭指示灯 */
    gpio_set_value(pled->st_pin.led_pin_fault, 1);      
    gpio_set_value(pled->st_pin.led_pin_can5, 1);
    gpio_set_value(pled->st_pin.led_pin_can6, 1);

    return nonseekable_open(inode,filp);

}

static int at91_led_close(struct inode *inode, struct file *filp)
{
       struct St_led *pled;  

    pled = container_of(inode->i_cdev, struct St_led, led_dev);  

    gpio_set_value(pled->st_pin.led_pin_run, 1);        /* 关闭指示灯 */
    gpio_set_value(pled->st_pin.led_pin_fault, 1);      
    gpio_set_value(pled->st_pin.led_pin_can5, 1);
    gpio_set_value(pled->st_pin.led_pin_can6, 1);   

    atomic_sub(1,&(pled->open_count));
    gpio_free(pled->st_pin.led_pin_run);
    gpio_free(pled->st_pin.led_pin_fault);
    gpio_free(pled->st_pin.led_pin_can5);
    gpio_free(pled->st_pin.led_pin_can6);

    return 0;
}
    gpio_set_value(pled->st_pin.led_pin_run, 1);        /* 关闭指示灯 */
    gpio_set_value(pled->st_pin.led_pin_fault, 1);      
    gpio_set_value(pled->st_pin.led_pin_can5, 1);
    gpio_set_value(pled->st_pin.led_pin_can6, 1);   

    atomic_sub(1,&(pled->open_count));
    gpio_free(pled->st_pin.led_pin_run);
    gpio_free(pled->st_pin.led_pin_fault);
    gpio_free(pled->st_pin.led_pin_can5);
    gpio_free(pled->st_pin.led_pin_can6);

    return 0;
}
//驱动名字,应用程序调用的时候使用
#define LED_NAME     "at91_led"

#define LED_MAGIC_NB    'l'
#define LED_IOC_MAXNR    2
//定义应用调用的灯的功能编码
#define LED_RUN            0   /* 运行灯 */
#define LED_FAULT        1   /* 故障灯 */
#define LED_CAN5        2   /* CAN5 */
#define LED_CAN6        3   /* CAN6 */

//定义命令功能
#define LED_LIGHTS_ON        _IOW(LED_MAGIC_NB,1,char)       /* 亮 */
#define LED_LIGHTS_OFF        _IOW(LED_MAGIC_NB,2,char)       /* 灭 */


static long at91_led_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{ 
    int ret = 0;
    unsigned char r_or_g;
    struct St_led *pled;
    pled = (struct St_led *)filp->private_data;

    //检查命令的有效性
     if ((_IOC_TYPE(cmd) != LED_MAGIC_NB) || (_IOC_NR(cmd) > LED_IOC_MAXNR))
     {
        printk("%s:CMD error\n",__func__);
        return -EINVAL;
     }  

     //判断空间是否可访问
      if(_IOC_DIR(cmd) & _IOC_READ)
        ret = !access_ok(VERIFY_WRITE,(void *)args,_IOC_SIZE(cmd));
    else if(_IOC_DIR(cmd) & _IOC_WRITE)
        ret = !access_ok(VERIFY_READ,(void *)args,_IOC_SIZE(cmd));
    if(ret)
        return -EFAULT;
    ret = __get_user(r_or_g, (__u8 __user *)args);
    if(ret)
    {
        printk("led:__get_user error!!!\n");
        return -EFAULT;
    }
    else
    {
        //printk("r_or_g:%d\n",r_or_g);
    }

    //互斥锁
    if(mutex_lock_interruptible(&(pled->led_mudex)))
    {
        return -ERESTARTSYS;
    }
    //命令处理
    switch(cmd) 
    {
        case LED_LIGHTS_ON://亮灯
            if( LED_RUN == r_or_g )
                gpio_set_value(pled->st_pin.led_pin_run,0);
            else if( LED_FAULT== r_or_g )
                gpio_set_value(pled->st_pin.led_pin_fault,0);
            else if( LED_CAN5 == r_or_g )
                gpio_set_value(pled->st_pin.led_pin_can5,0);
            else if( LED_CAN6 == r_or_g )
                gpio_set_value(pled->st_pin.led_pin_can6,0);

            break;
        case LED_LIGHTS_OFF://灭灯
            if( LED_RUN == r_or_g )
                gpio_set_value(pled->st_pin.led_pin_run,1);
            else if( LED_FAULT== r_or_g )
                gpio_set_value(pled->st_pin.led_pin_fault,1);
            else if( LED_CAN5 == r_or_g )
                gpio_set_value(pled->st_pin.led_pin_can5,1);
            else if( LED_CAN6 == r_or_g )
                gpio_set_value(pled->st_pin.led_pin_can6,1);

            break;
        default:
            printk("led:error cmd %d.\n", cmd);
            mutex_unlock(&(pled->led_mudex));
            return -EINVAL;
    }
    mutex_unlock(&(pled->led_mudex));
    return ret;
}

3、应用程序编写

#include 
#include 
#include 
#include 
#include 
#include 
#include "led.h"
int main()
{    
    int iCnt=0;
    int value =0;
    int fd = 0;
    int i;

    while(1)
    {
            //open函数会调用驱动层的at91_led_open
        fd = open("/dev/"LED_NAME, 0);

        if (fd < 0)
        {
            printf("led:failed to open led.\n");
            return 0;       
        }
        else
        {
            printf("led:open led  sucess!!!\n");
        }
        //red off
         value = 0;
                 //会调用驱动层的at91_led_ioctl
         ioctl(fd,LED_LIGHTS_OFF,&value);

        for(i=0;i<4;i++)
        ioctl(fd,LED_LIGHTS_OFF,&i);
        sleep(1);
        for(i=0;i<4;i++)
            ioctl(fd,LED_LIGHTS_ON,&i);
            sleep(1);

            close(fd);
    }
    //会调用驱动层的at91_led_close,可以通过打印查看是否对应    
    close(fd);

}

4、Makefile文件中,编译模块指定的内核路径,必须是已经编译成功的内核,因为要依赖于已经编译的文件调用里面的库函数;

更多linux知识点推荐:

[Linux 驱动]模块加载RTX8025驱动

[linux kernel] 内核下RX8025对接系统时钟

[linux kernel]内核启动阶段控制IO口时序输出

[职场吐槽]如何缓解焦虑

[linux kernel] 内核下ksz8081驱动调试

[linux kernel] 内核下ksz9031驱动调试

[linux kernel]内核图形化裁剪配置

[linux kernel]内核移植过程记录

[linux kernel] 内核启动流程梳理

[linux 底层]u-boot EMMC驱动

[linux 底层]u-boot图形化裁剪配置

[Linux 底层]U-boot ksz9031网络驱动调试

[Linux 底层]U-boot调试命令使用技巧

[Linux 底层]U-boot编译移植

[Linux 底层]U-boot烧录脚本介绍SecureCRT

[Linux 底层]bootstrap移植裁剪及编译

[Linux 底层] 平台软件分层介绍

[Linux 驱动] RS485测试程序编写

[Linux 驱动] CAN测试程序编写

推荐阅读:

芯片手册解读 | Linux底层 | 职场吐槽 | C语言视频

关注微信公众号,回复“led字符驱动”,下载源代码。
[Linux字符驱动] LED基于gpio系统点灯_第1张图片

你可能感兴趣的:(Linux,嵌入式,linux)