• 字符设备驱动开发

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

Linux应用程序对驱动的调用顺序如下图所示:

驱动程序主要任务就是“打通”内核与硬件设备之间的通道,最终形成统一的接口(open、write、read...)供内核调用,编写LED驱动程序实际上就是填充这些接口,下面就开始一步一步编写一个LED的驱动程序。

1.查看硬件电路,确定LED对应的GPIO口以及工作条件

引脚为LCD_D23  低电平点亮

2.编写open、write、read、release函数

  • 首先需要引用相关头文件、定义相关宏
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/bcd.h>
#include <linux/capability.h>
#include <linux/rtc.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <../arch/arm/mach-mx28/mx28_pins.h>#define DEVICE_NAME   "imx283_led"//驱动名称
#define major  200  //主设备号  仅静态分配时需要#define LED_GPIO    MXS_PIN_TO_GPIO(PINID_LCD_D23)      //for 283 287A/B
  • open调用实现
static int led_open(struct inode *inode ,struct file *flip)
{int ret = -1;gpio_free(LED_GPIO);ret = gpio_request(LED_GPIO, "LED1");printk("gpio_request = %d\r\n",ret);return 0;
}

该函数主要实现了向内核申请这个 GPIO 端口,同时把该引脚配置成 GPIO 工作模式。

  • write调用实现
static int led_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{int ret = -1;unsigned char databuf[1];ret = copy_from_user(databuf,buf,1);if(ret < 0 ) {printk("kernel write error \n");return ret;}gpio_direction_output(LED_GPIO, databuf[0]);return ret;
}

在该函数的传入参数中有 buf 和 count 参数。buf 表示应用程序调用 write 时,要写入数
据的缓冲区;count 表示缓冲区中有效数据的长度。

gpio_direction_output函数作用是让指定的IO口输出0或者1.
注意:buf 缓冲区是在用户空间的,而 LED 驱动程序是运行在内核空间的,并且在内核空间的代码是不能直接访问用户空间的缓冲区的,所以需要使用 copy_from_user 宏把用户空间 buf 中要写入的数据复制到 data 缓冲区中。

  • read调用实现
static int led_read(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{return 0;
}

对于一个LED灯我们只需要操作它输出0或1即可,所以read操作直接返回0即可。

  • release调用实现
static int led_release(struct inode *inode ,struct file *flip)
{gpio_free(LED_GPIO);return 0;
}

该函数的作用是释放GPIO。

3.填充file_operations结构体

file_operations结构体定义在Linux内核源码include/linux/fs.h中

/** NOTE:* read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl* can be called without the big kernel lock held in all filesystems.*/
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);
};

在上面的成员函数指针的形参中,struct file 表示一个打开的文件。Struct inode表示一个磁盘上的具体文件。

对于一个简单的LED驱动只需要实现基本的open、read、write、release接口即可,实际需要实现哪些接口需要根据驱动的需求来决定。

static struct file_operations led_fops=
{.owner     = THIS_MODULE,.open        = led_open,.write      = led_write,.read       = led_read,.release   = led_release,
};

owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。

4.注册与注销字符设备

  • 静态分配主设备号:

上面已经做好了驱动的准备工作,但是内核还不知道这个设备,所以接下来需要“告诉”内核我们编写的这个设备驱动,也就是向内核注册设备。

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

对于字符设备,我们一般调用register_chrdev函数注册,major是需要注册设备的主设备号,name是设备名称,fops就是上面第3步填充的file_operations结构体。

为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。

应用程序对设备文件进行读写操作时,都是通过主设备号找到这个设备的驱动文件,然后在驱动文件里取得具体操作的函数,最后再进行相关操作。

我们可以使用cat /proc/devices 看我们系统中哪些设备号已经被使用了。

上图就是已经被使用的设备号,我们最好不要再使用。

对于LED驱动,我们再一开始就定义了主设备号为200 设备名称为imx283_led,下面就需要实现设备的初始化函数。

static int __init led_init(void)
{int ret = -1;ret = register_chrdev(major,DEVICE_NAME, &led_fops );if(ret < 0) {printk("register chrdev failed!\n");return ret;}printk("module init ok \n"); return ret;
}

注意:内核中只能用printk打印,不可使用printf

led_init函数将在驱动模块被初始化(insmod)时调用。

同样的,有注册设备,就一定有注销设备,函数unregister_chrdev就是注销(卸载)一个字符设备,它的原型如下:

static inline void unregister_chrdev(unsigned int major, const char *name)

 它只需要提供设备的主设备号和设备名称即可。

我们还需要实现设备的注销函数:

static void __exit led_exit(void)
{unregister_chrdev(major,DEVICE_NAME);printk("module exit ok\n");
}
  • 动态分配主设备号:

register_chrdev函数第一个参数为0,则表示需要内核动态分配主设备号,其合法返回值(大于0)就是分配的主设备号。

因此我们可以通过如下方式让内核动态分配主设备号,而不需要我们手动设置。

static int MAJOR = 0; static int __init led_init(void)
{MAJOR = register_chrdev(0,DEVICE_NAME, &led_fops );printk("major=%d\n",MAJOR);printk("module init ok \n"); return 0;
}static void __exit led_exit(void)
{unregister_chrdev(MAJOR,DEVICE_NAME);printk("module exit ok\n");
}

由于我们是以模块的形式加载或者卸载设备驱动,我们还需要向内核注册模块的加载和卸载函数,就是说上面实现的led_init和led_exit两个函数也需要向内核注册,不然执行insmod或rmmod时,是不会调用led_init或led_exit函数的。

模块的加载和卸载注册函数如下:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

参数 xxx_init 就是需要注册的具体函数,这里就是刚刚说的led_init和led_exit函数。

module_init(led_init);
module_exit(led_exit);

5.添加作者和LICENSE信息

LICENSE信息必不可少,否则编译会报错。

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("imx283 first driver");//描述信息
MODULE_AUTHOR("xzx2020");//作者信息

到此,一个LED驱动就完成了,接下来就是编译和编写测试程序(应用程序)。

6.驱动程序Makefile编译脚本

/*obj-m:内核模块文件,指将myleds.o编译成myleds.ko*/
obj-m:=led_driver.o
PWD:=$(shell pwd)
KDIR:=/ZLG_linux/linux-2.6.35.3
all:$(MAKE) -C $(KDIR) M=$(PWD)
/*M=pwd :指定当前目录*/
/*make -C $(KERN_DIR) 表示将进入(KERN_DIR)目录,执行该目录下的Makefile*/
clean:rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions

led_driver是驱动文件名(我这里命名为led_driiver.c)

KDIR 是内核源码的主目录

保证驱动文件(.c文件)和MakeFile在同一目录下,执行make就可以生成驱动的.ko文件。

最终生成的led_driiver.ko就是我们需要的文件。

7.编写测试程序

测试程序很简单,就是打开led设备文件,然后控制led间隔200ms闪烁一段时间,最后再关闭该设备文件。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>int main(void)
{int fd = -1,i;char buf[1]={0};fd = open("/dev/imx283_led",O_RDWR);if(fd < 0){printf("open /dev/imx283_led fail fd=%d\n",fd); }for(i=0;i<50;i++) {buf[0] = 0;write(fd,buf,1);  usleep(200000);buf[0] = 1;write(fd,buf,1);usleep(200000);}fd = close(fd);if(fd < 0){printf("led test error\n");}return 0;
}

同样的,我们也可以写一个测试程序的MakeFile编译测试程序,或者直接用命令

arm-fsl-linux-gnueabi-gcc led_test.c -o led_test

即可编译生成测试程序的可执行文件。

测试程序makefile:

EXEC = ./led_test
OBJS    = led_test.oCROSS  = arm-fsl-linux-gnueabi-
CC       = $(CROSS)gcc
STRIP   = $(CROSS)strip
CFLAGS  = -Wall -g -O2all:  clean $(EXEC)$(EXEC):$(OBJS)$(CC) $(CFLAGS) -o $@ $(OBJS)$(STRIP) $@clean:-rm -f $(EXEC) *.o

这时候,把上面生成的.ko文件和led_test文件想办法弄到开发板上就可以测试了(我这里用的是U盘拷贝)。

8.测试

首先执行insmod加载驱动。

可以看到,模块加载成功,此时执行cat /proc/devices 应该能看到我们刚刚加载的设备:

接着我们需要手动创建设备节点,因为此时/dev目录下是没有我们这个imx283_led设备的。

//格式 mknod /dev/xxx 设备类型  主设备号  次设备号
//主设备号是cat /proc/devices里看到的  次设备号需要我们手动填写这里设置为0 最大255
mknod /dev/imx283_led c 200 0

接着再查看/dev下有没有生成LED设备节点。

ls -l /dev|grep led

可以看到/dev下已经生成了imx283_led设备节点,主设备号200,次设备号0,名称imx283_led。

这时候就可以执行测试程序了。

不出意外的话,开发板上的led灯应该已经开始闪烁了。

最后,再执行rmmod卸载设备驱动

卸载也没有什么问题。

9.总结

鉴于笔者水平有限,同时也是Linux驱动初学者,以上只是个人学习的总结,难免会有错误纰漏之处,望各位网友多多批评指教。

由于现在较新的Linux内核(2.6以上)的字符设备驱动开发已经不提倡这种注册方式,所以下一篇博客已对此驱动作了一些改进:i.MX283开发板第一个Linux驱动-LED驱动改进

本文参考:

1.《嵌入式Linux应用完全开发手册》 

2.《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0》

3.《EasyARM-iMX28xx Linux开发指南 20150901 V1.03》

i.MX283开发板第一个Linux驱动-LED驱动相关推荐

  1. firefly-rk3288j开发板--设备树下的 LED 驱动

    firefly-rk3288j开发板–设备树下的 LED 驱动 1 准备工作 开发板:aio-rk3288j SDK版本:rk3288_linux_release_20210304 下载工具:Linu ...

  2. 构建一个适合stm32mp157系列开发板的嵌入式Linux系统

    在线课堂:https://www.100ask.net/index(课程观看) 论  坛:http://bbs.100ask.net/(学术答疑) 开 发 板:https://100ask.taoba ...

  3. 对国产板子有阴影这些软硬件开源的ARM开发板可以学习Linux驱动

    对国产板子有阴影?这些软硬件开源的ARM开发板可以学习Linux驱动开发 为了点亮一块MIPI屏幕,我们除了要了解MIPI DSI的工作原理之外,大前提要了解整个MIPI DSI图显系统的组成,更需要 ...

  4. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(八)IMX6ULL开发板编译第一个程序以及驱动

    文章目录 1. IMX6ULL开发板初次操作 1.1 100ask_imx6ull开发板硬件资源简介 1.1.1 100ask imx6ull mini开发板 1.2 100ask_imx6ull开发 ...

  5. 在Android开发板跑一个LED驱动的历程(个人笔记)

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) 在Android开发板跑一个LED驱动的历程(个人笔记) 1. 板子端, 放置驱动程序 1.1 编写一个驱动程序 驱动程序的编写上, ...

  6. i.MX283开发板SPI驱动——RC522

    一.Linux下SPI驱动介绍 内核版本:2.6.35 Linux下SPI驱动和I2C驱动很类似,他们都是一种总线,且都不支持热拔插,因为一般情况下spi或者i2c设备都是直接焊接在板子上的,不像US ...

  7. linux内核led驱动开发,从Linux内核LED驱动来理解字符设备驱动开发流程

    目录 博客说明 开发环境 1. Linux字符设备驱动的组成 1.1 字符设备驱动模块加载与卸载函数 1.2 字符设备驱动的file_operations 结构体中的成员函数 2. 字符设备驱动--设 ...

  8. STM32MP157 Linux系统移植开发篇17:Linux内核摄像头驱动移植

    本文章为<STM32MP157 Linux系统移植开发篇>系列中的一篇,笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板).stm32mp157是ARM双核,2 ...

  9. AM335 嵌入式 linux,am335x开发板建立嵌入式 Linux NFS 开发环境

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 am335x开发板嵌入式开发板系统 NFS客户端的配置 首先运行在嵌入式开发板系统的 Linux 内核支持 NFS 客 户端, 运行 #make menu ...

最新文章

  1. 基于TensorRT车辆实时推理优化
  2. HTTP 协议的通用头域via 的意义以及作用
  3. hibernate开发错误及解决办法
  4. PySpark安装和测试
  5. 2.2.3 动量梯度下降法
  6. boost::pfr::detail::fields_count相关的测试程序
  7. 工作项跟踪管理系统需求
  8. 机器学习中用到的概率知识_山顶洞人学机器学习之——几种常见的概率分布
  9. 【kafka】kafka 启动 Version `123123` is not a valid version
  10. MATLAB 读取文件及数据
  11. Vue 数组封装和组件data定义为函数一些猜测
  12. c# 导出Excel
  13. 元宇宙的时代来不及解释了快上车
  14. 双目标定(三)标定流程(含矫正)
  15. PIE框架基本接口使用方法
  16. SPAMS稀疏建模工具箱
  17. 题目连接:http://acm.zznu.edu.cn/problem.php?id=1329
  18. 如何用Word制作流程图(一)
  19. 天下无贼--郭德纲相声
  20. Mysql主从复制,级联复制(重点,重点,重点)命令,参数

热门文章

  1. Android淘宝三方登陆
  2. utils.data的使用
  3. GPU 编程 CPU 异同点_分析师:英特尔(INTC.US)GPU不会构成威胁
  4. 天使的分裂【NOIP2016提高A组模拟9.24】
  5. 网络技术领域专业术语解释大全-170个术语
  6. Chrome浏览器中清除特定网站的Cookie数据
  7. 上证50指数基金定投三年亏2000
  8. C#读写Excel的4种方案(OpenXml、NPOI、EPPlus、Spire.Office)
  9. H3C HCL模拟器 MPLS ipv4/ipv6双栈实验
  10. 公羊优链蜘蛛池【高权重】