• Linux驱动开发(八)---树莓派SR04驱动开发


    前文回顾

    《Linux驱动开发(一)—环境搭建与hello world》
    《Linux驱动开发(二)—驱动与设备的分离设计》
    《Linux驱动开发(三)—设备树》
    《Linux驱动开发(四)—树莓派内核编译》
    《Linux驱动开发(五)—树莓派设备树配合驱动开发》
    《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
    《Linux驱动开发(七)—树莓派按键驱动开发》

    继续宣传一下韦老师的视频

    70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

    在这里插入图片描述
    后面的内容,就开始以实际设备进行驱动学习,学习为各种传感器,总线设备等进行驱动编写,熟悉驱动开发过程。

    SR04

    卡姿兰大眼睛
    在这里插入图片描述
    这是一款超声波测距传感器,共有四个引脚,VCC和GND就不说了,Trip是触发信号,Echo是回响信号
    在这里插入图片描述
    1、Trig引脚接收至少10us的高电平信号,用于触发超声波模块工作;
    2、模块会自动发送8个40KHz的方波信号,自动检测是否有信号返回;
    3、有信号返回,通过Echo引脚连接单片机的I/O口输出一高电平,高电平持续时间T就是超声波从发射到返回的时间;
    4、声音在空气中的传播速度为340米/秒,即可计算出所测的距离:D = 340*T/2。

    原理是很简单的。
    在这里插入图片描述

    驱动原理

    这里就需要用到两个GPIO,一个负责Trip,发出触发信号,一个负责Echo,接收Echo高电平信号,并计算高电平时间。

    这里的Trip简单,持续一个大于10us的高电平即可,Echo引脚,我们需要监听上下边沿,然后计算出中间的时间,这里就需要用到中断。通过中断得到两个时间点,然后计算差值,传给用户。

    其实前面两个大眼睛,其实一个是嘴巴,一个是耳朵。一个喊一个听。
    在这里插入图片描述

    设备树

    设备树的编写如下
    在这里插入图片描述
    这里定义了两个引脚,17和是18,用来分别接Trip和Echo。
    读取方法就是通过gpiod_get_index,可以读取多个引脚的描述信息(handle)。

    struct gpio_desc *trip, *echo;
    trip= gpiod_get_index(dev, "sr04", 0, GPIOD_OUT_HIGH);
    echo= gpiod_get_index(dev, "sr04", 1, GPIOD_OUT_HIGH);
    
    • 1
    • 2
    • 3

    也可以分开定义不同的名字,看个人喜好罢了。

    在这里插入图片描述

    驱动编写第一步—测试硬件及中断

    模块加载卸载部分就不说了,没什么好注意的,从probe函数开始说起吧。
    首先定一个结构,用来存储gpio的信息,描述信息,终端信息。

    struct sr04_gpios{
    	struct gpio_desc *trip;
    	struct gpio_desc *echo;
    	int echo_irq;
    } ;
    
    struct sr04_gpios my_sr04_gpios;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后probe函数中。获取引脚描述和中断,并且注册中断,采用上下边沿触发

    static int mysr04_probe(struct platform_device *pdev)
    {
    
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	
    
    	//读取关键引脚描述信息
    	my_sr04_gpios.trip= gpiod_get_index(&pdev->dev, "sr04", 0, GPIOD_OUT_HIGH);
    	my_sr04_gpios.echo= gpiod_get_index(&pdev->dev, "sr04", 1, GPIOD_OUT_HIGH);
    
    
    	//配置方向
    	gpiod_direction_output(my_sr04_gpios.trip,0);
    	gpiod_direction_input(my_sr04_gpios.echo);
    
    	//获取中断
    	my_sr04_gpios.echo_irq = gpiod_to_irq(my_sr04_gpios.echo);
    
    	//注册中断
    	request_irq(my_sr04_gpios.echo_irq, my_sr04_echo_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "mysr04_irq", NULL);//IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
    
    
    	/* 注册file_operations 	*/
    	major = register_chrdev(0, "pgg_sr04", &gpio_button_drv);  
    
    	mysr04_class = class_create(THIS_MODULE, "mysr04_class");
    	if (IS_ERR(mysr04_class)) 
    	{
    		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    		unregister_chrdev(major, "pgg_sr04");
    		return PTR_ERR(mysr04_class);
    	}
    
    	device_create(mysr04_class, NULL, MKDEV(major, 0), NULL, "pgg_sr04"); /* /dev/pgg_sr04 */
        return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述

    同理,remove函数中反向操作

    static int mysr04_remove(struct platform_device *pdev)
    {
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	device_destroy(mysr04_class, MKDEV(major, 0));
    	class_destroy(mysr04_class);
    	unregister_chrdev(major, "pgg_sr04");
    	gpiod_put(my_sr04_gpios.trip);
    	gpiod_put(my_sr04_gpios.echo);
    	free_irq(my_sr04_gpios.echo_irq,NULL);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    中断函数中,我们先调试一下,看看是否能收到上下边沿的终端信息。所以简单的添加一个打印

    static irqreturn_t my_sr04_echo_isr(int irq, void *dev_id)
    {
    	printk("revice irq %d\n",  irq);
    	return IRQ_HANDLED;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后,我们在read函数中,将trip引脚拉高100us。随后等待看看能否收到两次中断。

    static ssize_t gpio_button_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
    {
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	gpiod_set_value(my_sr04_gpios.trip,1);
    	udelay(100);
    	gpiod_set_value(my_sr04_gpios.trip,0);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    用户侧程序

    int main(int argc, char **argv)
    {
    	int fd;
    	char buf[1024];
    	int len;
    	int ret;
    	
    	/* 1. 判断参数 */
    	if (argc < 2) 
    	{
    		printf("Usage: %s -w \n", argv[0]);
    		printf("       %s -r\n", argv[0]);
    		return -1;
    	}
    
    	/* 2. 打开文件 */
    	fd = open("/dev/pgg_sr04", O_RDWR);
    	if (fd == -1)
    	{
    		printf("can not open file /dev/pgg_sr04\n");
    		return -1;
    	}
    	
    	printf("open file /dev/pgg_sr04 ok\n");
    
    	/* 3. 写文件或读文件 */
    	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
    	{
    		len = strlen(argv[2]) + 1;
    		len = len < 1024 ? len : 1024;
    		ret = write(fd, argv[2], len);
    		printf("write driver: %d\n", ret);
    	}
    	else
    	{
    		len = read(fd, buf, 1024);		
    		printf("read driver: %d\n", len);
    		buf[1023] = '\0';
    		printf("APP read : %s\n", buf);
    	}
    	
    	close(fd);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    开始试验,更新DTD,上传ko,用户程序编译

    pgg@raspberrypi:~/work/dirver $ sudo insmod mysr04.ko 
    pgg@raspberrypi:~/work/dirver $ gcc -o mysr04_user mysr04_user.c 
    pgg@raspberrypi:~/work/dirver $ sudo ./mysr04_user -r
    open file /dev/pgg_sr04 ok
    read driver: 0
    APP read : 
    pgg@raspberrypi:~/work/dirver $ dmesg 
    [  477.269477] drivers/char/mysr04.c gpio_button_drv_read line 49
    [  477.271846] revice irq 200
    [  477.271888] revice irq 200
    pgg@raspberrypi:~/work/dirver $ dmesg -d -T
    [727 09:15:17 2022 <    0.000000>] drivers/char/mysr04.c gpio_button_drv_read line 49
    [727 09:15:17 2022 <    0.002369>] revice irq 200
    [727 09:15:17 2022 <    0.000042>] revice irq 200
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    果然是收到了两次中断。看来思路差不多。
    在这里插入图片描述

    驱动编写第二步—计算时间差值

    继续优化。来计算一下两次中断的时间间隔。我们在中断中计算下时间差

    static irqreturn_t my_sr04_echo_isr(int irq, void *dev_id)
    {
    
    	int val = gpiod_get_value(my_sr04_gpios.echo);
    
    	if (val) /* 上升沿 */
    	{
    		/* 1. 记录数据 */
    		a = ktime_get_ns();
    		printk("revice irq up a=%llu\n",a);
    	}
    	else /* 下降沿 */
    	{
    		b = ktime_get_ns();
    		printk("revice irq down b=%llu\n",b);
    
    		printk("revice irq %llu\n",b-a);
    
    	}
    	return IRQ_HANDLED;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    pgg@raspberrypi:~/work/dirver $ sudo ./mysr04_user -r
    open file /dev/pgg_sr04 ok
    read driver: 0
    APP read : 
    pgg@raspberrypi:~/work/dirver $ sudo dmesg
    [ 4987.578948] drivers/char/mysr04.c gpio_button_drv_read line 51
    [ 4987.581316] revice irq up a=4987648598062
    [ 4987.581979] revice irq down b=4987649263185
    [ 4987.581994] revice irq 665123
    [ 5067.627410] drivers/char/mysr04.c gpio_button_drv_read line 51
    [ 5067.629782] revice irq up a=5067699231916
    [ 5067.630045] revice irq down b=5067699497079
    [ 5067.630059] revice irq 265163
    [ 5126.754375] drivers/char/mysr04.c gpio_button_drv_read line 51
    [ 5126.756742] revice irq up a=5126827662096
    [ 5126.767534] revice irq down b=5126838453813
    [ 5126.767564] revice irq 10791717
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    [ 4987.581994] revice irq 665123这里是一个挡板,计算距离大概是10几厘米
    [ 5067.630059] revice irq 265163这里是用手遮挡,大概三厘米左右
    [ 5126.767564] revice irq 10791717 这里是计算到房顶的距离,大概1.8米。
    感觉误差还是有的。暂时不管他。
    在这里插入图片描述

    驱动编写第三步—休眠唤醒

    没事,先把整体完成,在用户侧直接返回时间差。还是通过休眠唤醒方式,传出数据。
    中断函数,仅保留获得时间戳,然后唤醒read.

    static irqreturn_t my_sr04_echo_isr(int irq, void *dev_id)
    {
    
    	int val = gpiod_get_value(my_sr04_gpios.echo);
    
    	if (val) /* 上升沿 */
    	{
    		a = ktime_get_ns();
    	}
    	else /* 下降沿 */
    	{
    		b = ktime_get_ns();
    		dataready = 1;
    		wake_up_interruptible(&mysr04_wait);
    	}
    	return IRQ_HANDLED;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    读取函数中,传出计算出来的时间差

    static ssize_t mysr0_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
    {
    	char result[64]={0};
    	int reslen=0;
    	static u64 c=0;
    	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    	
    	a=0;
    	b=0;
    	dataready = 0;
    	
    	gpiod_set_value(my_sr04_gpios.trip,1);
    	udelay(100);
    	gpiod_set_value(my_sr04_gpios.trip,0);
    	
    	wait_event_interruptible(mysr04_wait, dataready);
    	dataready = 0;
    	c = b-a;
    	sprintf(result,"%llu",c);
    	
    	reslen=strlen(result)+1;
    	copy_to_user(buf, result, reslen);
    	
    	return reslen;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注意这里的时间返回是纳秒。
    在这里插入图片描述

    驱动测试

    用户侧函数,读取时间差,转化为long,然后再计算出距离。

    int main(int argc, char **argv)
    {
    	int fd;
    	char buf[1024];
    	int len;
    	int ret;
    	
    	/* 1. 判断参数 */
    	if (argc < 2) 
    	{
    		printf("       %s -r\n", argv[0]);
    		return -1;
    	}
    
    	/* 2. 打开文件 */
    	fd = open("/dev/pgg_sr04", O_RDWR);
    	if (fd == -1)
    	{
    		printf("can not open file /dev/pgg_sr04\n");
    		return -1;
    	}
    	
    	//printf("open file /dev/pgg_sr04 ok\n");
    
    	/* 3. 写文件或读文件 */
    	if ((0 == strcmp(argv[1], "-r")) && (argc == 2))
    	{
    		len = read(fd, buf, 1024);		
    		buf[1023] = '\0';
    		printf("driver read : %s\n", buf);
    
    		long res= strtol(buf, NULL, 0);
    		double resm=0.00000017*((double)res);
    		printf("sr04 距离 : %lf 米\n", resm);
    
    	}
    	else
    	{
    		printf("	   %s -r\n", argv[0]);
    	}
    	
    	close(fd);
    	
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    来吧展示
    在这里插入图片描述

    pgg@raspberrypi:~/work/dirver $ sudo ./mysr04_user -r
    driver read : 647500
    sr04 距离 : 0.110075
    • 1
    • 2
    • 3

    知识点

    今天的知识点比较复杂,也比较多。
    在这里插入图片描述

    时间延迟

    时间延迟分为了忙等待或者睡眠等待。前者死等,后者让出CPU,根据情况不同,分开使用。
    可参考Leon_George《Linux内核中的延时函数详解》

    时间获取

    内核中获取时间的函数如下

    函数功能
    ktime_get_ns()获取内核启动到现在的时间,在挂起时会暂停
    ktime_get_boottime_ns()获取内核启动到现在的时间,不受挂起影响,是绝对时间
    ktime_get_real_ns()获取Unix时间(1970年)到现在的时间,可能涉及闰秒更新,用得比较少
    ktime_get_raw_ns()类似ktime_get_ns(),不涉及闰秒更新,用得比较少

    注意时间的类型,可能是u64,所以输出的时候,用%llu。
    在这里插入图片描述

    内核可用的字符操作函数

    用户侧和内核用的字符串操作函数有所区别,详细可以参考墨染锦年syx的《linux 内核库函数》
    可不要傻乎乎的直接调用用户侧之前的那些函数啊。
    在这里插入图片描述

    关键函数返回值判断

    内核中有一些函数调用的时候,必须检查返回值,否则会报错,例如

    static inline int __must_check
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    	    const char *name, void *dev)
    {
    	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果你没有检查,编译的时候,会提醒

    warning: ignoring return value of ‘request_irq’, declared with attribute warn_unused_result [-Wunused-result]
    
    • 1

    在这里插入图片描述

    函数返回值

    在error-base.h中定义了如下基础错误,在开发过程中,可以适当使用。

    #ifndef _ASM_GENERIC_ERRNO_BASE_H
    #define _ASM_GENERIC_ERRNO_BASE_H
    
    #define	EPERM		 1	/* Operation not permitted */
    #define	ENOENT		 2	/* No such file or directory */
    #define	ESRCH		 3	/* No such process */
    #define	EINTR		 4	/* Interrupted system call */
    #define	EIO		 5	/* I/O error */
    #define	ENXIO		 6	/* No such device or address */
    #define	E2BIG		 7	/* Argument list too long */
    #define	ENOEXEC		 8	/* Exec format error */
    #define	EBADF		 9	/* Bad file number */
    #define	ECHILD		10	/* No child processes */
    #define	EAGAIN		11	/* Try again */
    #define	ENOMEM		12	/* Out of memory */
    #define	EACCES		13	/* Permission denied */
    #define	EFAULT		14	/* Bad address */
    #define	ENOTBLK		15	/* Block device required */
    #define	EBUSY		16	/* Device or resource busy */
    #define	EEXIST		17	/* File exists */
    #define	EXDEV		18	/* Cross-device link */
    #define	ENODEV		19	/* No such device */
    #define	ENOTDIR		20	/* Not a directory */
    #define	EISDIR		21	/* Is a directory */
    #define	EINVAL		22	/* Invalid argument */
    #define	ENFILE		23	/* File table overflow */
    #define	EMFILE		24	/* Too many open files */
    #define	ENOTTY		25	/* Not a typewriter */
    #define	ETXTBSY		26	/* Text file busy */
    #define	EFBIG		27	/* File too large */
    #define	ENOSPC		28	/* No space left on device */
    #define	ESPIPE		29	/* Illegal seek */
    #define	EROFS		30	/* Read-only file system */
    #define	EMLINK		31	/* Too many links */
    #define	EPIPE		32	/* Broken pipe */
    #define	EDOM		33	/* Math argument out of domain of func */
    #define	ERANGE		34	/* Math result not representable */
    
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    查看中断信息

    通过命令

    cat /proc/interrupts 
    
    • 1

    可以查看注册过的中断信息。
    在这里插入图片描述

    查看gpio信息

    在root用户下,通过命令

    cat /sys/kernel/debug/gpio 
    
    • 1

    可以查看当前各个GPIO的状态,电平,以及是否注册中断
    在这里插入图片描述

    最后注意

    最后要注意的就是这个模块,VCC要用5V,开始接的3.3,怎么算时间都不对
    在这里插入图片描述

    结束语

    男生的快乐真的就是很简单,能有自己的地方玩会游戏就好啊。
    在这里插入图片描述
    这马上就到七夕了,男生都喜欢电子产品,可不要直接送南孚电池啊。
    在这里插入图片描述

  • 相关阅读:
    Live800:避开客服雷区,提升客服转化
    推进数据要素化,数据云为何是“加速器”?
    377. 组合总和 Ⅳ【完全背包】求排列数:外层for背包,内层for物品;求组合数:外层for物品,内层for背包;
    AP5101C 高压线性恒流 LED电源驱动IC 3D打印机显示灯驱动器
    springcloud整合nacos实现服务注册
    【21天算法挑战赛】排序算法——希尔排序
    1224. 最大相等频率(数组)
    电脑键盘功能基础知识汇总
    基于LSM树的存储机制简述
    百度Mysql面试题总结
  • 原文地址:https://blog.csdn.net/baidu_19348579/article/details/126006690