Linux LED驱动程序框架分析

简介: Linux LED驱动程序框架分析

前言

本篇文章我将为大家分析LED驱动程序的框架,驱动程序框架是来自于韦东山老师所讲。


一、如何编写一个字符设备驱动程序

1.确定主设备号,也可以让内核分配。

每一个设备都有自己的主设备号和次设备号用于区分不同的设备,这里我们暂时不使用次设备号只使用到了主设备号。

2.定义自己的file_operations结构体

编写字符设备驱动程序时需要提供一个file_operations结构体,这个结构体里面包含了read和write函数,在应用程序中调用read和write函数将会调用到驱动程序中的read和write函数。

static struct file_operations gpio_key_drv = {
  .owner   = THIS_MODULE,
  .read    = led_drv_read,
  .write   = led_drv_write,
};

3.实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体

static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
  return 0;
}
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
  return 0;
}

4.把file_operations结构体告诉内核:register_chrdev

  major = register_chrdev(0, "100ask_led", &gpio_key_drv);

5.谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数

static int __init led_drv_init(void)
{
  return 0;
} 

6.有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev

static void __exit led_drv_exit(void)
{
}

7.其他完善:提供设备信息,自动创建设备节点:class_create, device_create

led_class = class_create(THIS_MODULE, "100ask_led_class");
if (IS_ERR(led_class)) {
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  unregister_chrdev(major, "100ask_led");
  return PTR_ERR(led_class);
}
device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led");

二、具体LED驱动程序编写

1.确定LED引脚标号

由原理图可知LED标号为GPIO5_3,由于我们使用的开发板是imx6ull,所以我们这里需要转换一下。

870920b136c145e3b69a869d2508323e.png

7c925ae3b04749c09b0d58c0587587bb.png

imx6ull内部对GPIO进行了分组32个引脚为一组,从gpiochip0开始计数,所以GPIO5_3对应的编号为:32*4+3=131,这样我们就得到了GPIO5_3的编号了。


2.定义描述GPIO的结构体

这里我们定义一个结构体,这个结构体里面含有gpio引脚的各种信息,一个gpio引脚就分别对应一个结构体。

struct gpio_desc{
  int gpio;//gpio编号
  int irq;//中断号
    char *name;//引脚名字
    int key;
  struct timer_list key_timer;
} ;
static struct gpio_desc gpios[2] = {
    {131, 0, "led1", },
    //{132, 0, "led2", },
};

3.入口函数与出口函数

通过class_create和device_create这些辅助信息我们就能创建出一个名为/dev/100ask_led 的设备节点了

/* 入口函数 */
static int __init led_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);//计算共有几个LED灯
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  for (i = 0; i < count; i++)
  {   
    /* get gpio */
    err = gpio_request(gpios[i].gpio, gpios[i].name);//得到GPIO引脚
    if (err) {
      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
      return -1;
    }   
    /* set gpio as output */
    gpio_direction_output(gpios[i].gpio, 1);//设置GPIO引脚为输出模式
  }
  /* 注册file_operations  */
  major = register_chrdev(0, "100ask_led", &gpio_key_drv);  /* /dev/gpio_desc */
  led_class = class_create(THIS_MODULE, "100ask_led_class");
  if (IS_ERR(led_class)) {
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    unregister_chrdev(major, "100ask_led");
    return PTR_ERR(led_class);
  }
  device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /* /dev/100ask_gpio */
  return err;
}
/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit led_drv_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  device_destroy(led_class, MKDEV(major, 0));
  class_destroy(led_class);
  unregister_chrdev(major, "100ask_led");
  for (i = 0; i < count; i++)
  {
    gpio_free(gpios[i].gpio);
  }
}

4.读写函数

在read和write函数中我们一般都是通过copy_to_user和copy_from_user这两个函数和应用程序进行数据的交互。

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
  //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  unsigned char led_buf[2];
  int err;
  int count = sizeof(gpios)/sizeof(gpios[0]);
  if (size != 2)
    return -EINVAL;
  err = copy_from_user(led_buf, buf, 1);
  if(led_buf[0] > count)
    return -EINVAL;
  led_buf[1] = gpio_get_value(gpios[led_buf[0]].gpio);//读取引脚电平
  err = copy_to_user(buf, led_buf, 2);  
  return 2;
}
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;
    if (size != 2)
        return -EINVAL;
    err = copy_from_user(ker_buf, buf, size);
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;
    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);//设置引脚电平
    return 2;    
}

三、测试程序编写

应用程序比较简单大家自己就能看懂,这里就不多赘述。

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
static int fd;
/*
 * ./led_test <0|1|2...> on|off
 *
 */
int main(int argc, char **argv)
{
  int fd;
  char buf[2];
  int ret;
  if(argc < 2)
  {
    printf("Usage :%s <0|1|2...> [on|off]\n",argv[0]);
    return -1;
  }
  fd = open("/dev/100ask_led",O_RDWR);
  if(fd < 0)
  {
    printf("open /dev/100ask_led err\n"); 
    return -1;
  }
  if(argc == 3)
  {
    buf[0] = strtol(argv[1], NULL, 0);
    if(strcmp(argv[2],"on") == 0)
    {
      buf[1] = 0;
    }
    else
    {
      buf[1] = 1;
    }
    write(fd, buf, 2);
  }
  else
  {
    buf[0] = strtol(argv[1], NULL, 0);
    ret = read(fd, buf, 2);
    if (ret == 2)
    {
      printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off");
    }   
  }
  close(fd);
  return 0;
}

四、上机实验

1.挂载网络文件系统,打开内核打印信息

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

echo 7 4 1 7 > /proc/sys/kernel/printk


2.装载驱动程序

f9287ffdbdf44f3aa6d3b7ee7b29107e.png

3.查看设备节点和引脚信息

36e2c65fc95f4d72887a3919e5510387.png

8fcb1b763e8f4f65aecf31758c1565b0.png

4.运行测试程序

4f24c11a22ec40e285eb5bedd60176a9.png

总结

有了百问网韦老师讲的驱动程序框架一切都变得简单起来了。

相关文章
|
3月前
|
存储 IDE Unix
Linux 内核源代码情景分析(四)(上)
Linux 内核源代码情景分析(四)
31 1
Linux 内核源代码情景分析(四)(上)
|
2月前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】
|
3月前
|
Linux C语言
深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 编译过程分析
28 2
|
3月前
|
存储 Unix Linux
Linux 内核源代码情景分析(四)(下)
Linux 内核源代码情景分析(四)
23 2
|
2月前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
232 0
|
2月前
|
Linux API SoC
Linux电压和电流调节器框架 【ChatGPT】
Linux电压和电流调节器框架 【ChatGPT】
|
3月前
|
存储 算法 Unix
Linux 内核源代码情景分析(四)(中)
Linux 内核源代码情景分析(四)
43 0
|
存储 Unix Linux
浅入分析Linux
Linux 操作系统必须完成的两个主要目的 与硬件部分交互, 为包含在硬件平台上的所有底层可编程部件提供服务 为运行在计算机系统上的应用程序(即所谓的用户空间)提供执行环境 一些操作系统运行所有的用户程序都直接与硬件部分进行交互, 比如典型的MS-DOS。
1002 0
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
18 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2