Linux驱动

2023-11-07

HC-SR04超声波模块

工作原理参考: 超声波模块_star-air的博客-CSDN博客_超声波模块
https://blog.csdn.net/qq_41262681/article/details/95940707

在这里插入图片描述
使用超声波测距的操作步骤:

  • 触发:向T(脉冲触发引脚)发出一个大约10us的高电平。
    • 模块就自动发出8个40Khz的超声波,超声波遇到障碍物后反射回来,模块收到返回来的超声波。
  • 回响:模块接收到反射回来的超声波后,Echo引脚输出一个与检测距离成比例的高电平。
  • 计算距离原理: 时间 x 速度 = 距离,获取echo引脚维持高电平的时间就可以知道时间T,声速大概340m/s, 由此可计算出距离。

编程实现的两种方法:

  • 顺序执行,通过死等计算高电平时间

    伪代码:
    disable_irq();
    while(Echo == 0);      /* 等待变为高电平 */
    while (Echo)      /* 等待变为低电平和计算高电平时间 */
    {
    	udelay(1)
    	us++;
    }
    enable_irq();
    
  • 利用中断计算

    设置Echo引脚为双边沿触发,在上升沿触发中断时记录此刻时刻T0,在下降沿触发中断时记录时刻T1
    高电平时间 = T1 - T0
    
    内核中获取时间的API :
    ktime_get_ns();          // 获取内核启动到现在的时间,在挂起时会暂停
    ktime_get_boottime_ns(); // 获取内核启动到现在的时间,不受挂起影响,是绝对时间
    ktime_get_real_ns();     // 获取Unix时间(1970年)到现在的时间,可能涉及闰秒更新,用得比较少
    ktime_get_raw_ns();      // 类似ktime_get_ns(),不涉及闰秒更新,用得比较少
    

    参考文档:https://www.kernel.org/doc/html/latest/core-api/timekeeping.html#c.ktime_get_ns

驱动实现

1、设备树编写

imx6ull-mmc-npi.dts

根节点下添加HC-SR04的节点:

hc_sr04 {
		compatible = "hc_sr04";
		pinctrl-names = "default";
	 	pinctrl-0 = <&pinctrl_sr04_1
		             &pinctrl_sr04_2>;
		trig-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;   
		echo-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
		status = "okay"; 
	};
  • trig-gpios,触发引脚定义
  • echo-gpios,接收信号引脚
  • status ,可设置为"okay"或"disabled",表示启用或者禁用,当其他模块需要使用该gpio,可以直接将状态设置为disabled就可以取消占用,其他模块就可以使用该gpio

设备树编译:

ares@ubuntu:~/work/ebf_linux_kernel-ebf_4.19.35_imx6ul$ cat make_dtb.sh
#!/bin/sh

make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs

将设备树拷贝系统目录:

debian@npi:~/nfs_root/driver$ cat cp_dtb_to_linux.sh
#!/bin/sh

sudo cp imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-carp-imx6/
  • /usr/lib/linux-image-4.19.35-carp-imx6/ ,系统存放设备树的目录

重启系统设备树生效:

sudo reboot

2、驱动编写

头文件数据结构:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/timer.h>
// #include <asm/spinlock.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched/signal.h> 
#include <linux/poll.h>
// #include <asm/atomic.h>  
#include <linux/atomic.h>

#define HC_SR04_DTS_NAME    "hc_sr04"

#define DEV_NAME            "hc-sr04"

#define USE_GPIO_LIB        0

struct hc_sr04 {
    int irq;                        /* 中断号 */
    enum of_gpio_flags flag;
    struct gpio_desc *trig_gpio;    /* trig-gpio */
    struct gpio_desc *echo_gpio;    /* echo-gpio */
    dev_t dev_no;                   /* 设备号 */    
    struct cdev chrdev;             
    struct class *class;
    struct mutex  m_lock;           
    wait_queue_head_t  wq;          /* 等待队列 */
};

static struct hc_sr04  sr04;
static int sr04_trig_gpio;
static int sr04_echo_gpio;
static int sr04_data_ns = 0;

两种方式实现一致的地方:

static atomic_t sr04_atomic = ATOMIC_INIT(1);   /*  定义原子变量 */

/* 使设备只能被一个进程打开 */
static int _drv_open (struct inode *node, struct file *file)
{
    if (!atomic_dec_and_test(&sr04_atomic))  {
       atomic_inc(&sr04_atomic);
       return  -EBUSY;               /*  已经打开 */
    }

    gpio_direction_input(sr04_echo_gpio);   
    gpio_direction_output(sr04_trig_gpio, 0);
  
    return 0;
}

/* 使驱动支持多路复用IO */
static __poll_t _drv_poll(struct file *filp, struct poll_table_struct *wait)
{
    __poll_t mask = 0;

    // // wait_event_interruptible
    // mutex_lock(&sr04.m_lock);

    // poll_wait(filp, &sr04.wq, wait); 

    // if (sr04_val)
    // {
    //     mask |= POLLIN | POLLRDNORM;
    // }

    // mutex_unlock(&sr501.m_lock);

    return mask;
}

static int _drv_release(struct inode *node, struct file *file)
{
    atomic_set(&sr04_atomic, 1);      /* 释放时设置原子变量值为1 */
    printk("hc-sr04 release\n");
    return 0;
}


static struct file_operations sr04_drv_ops = { 
	.owner	= THIS_MODULE,
	.open   = _drv_open,
    .read   = _drv_read,
    .poll   = _drv_poll,
    .release = _drv_release,
};

/* 设备树的匹配列表 */
static struct of_device_id dts_match_table[] = {
    {.compatible = HC_SR04_DTS_NAME, },                     /* 通过设备树来匹配 */
};

static struct platform_driver _platform_driver = {
      .probe = _driver_probe,
      .remove = _driver_remove,
      .driver = {
        .name = HC_SR04_DTS_NAME,
        .owner = THIS_MODULE,
        .of_match_table = dts_match_table,         /* 通过设备树匹配 */
      },
};

/* 入口函数 */ 
static int __init _driver_init(void)
{
    int ret;
    printk("hc-sr04 %s\n", __FUNCTION__);
    
    ret = platform_driver_register(&_platform_driver);   //注册platform驱动
    return ret;
}

/*  出口函数 */
static void __exit _driver_exit(void)
{
    printk("hc-sr04  %s\n", __FUNCTION__);
    platform_driver_unregister(&_platform_driver);
}

module_init(_driver_init);
module_exit(_driver_exit);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");

方式1

static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    int time_us = 0;
    int timeout = 1000000;

    unsigned long flags;
    /* 中断屏蔽 */
    local_irq_save(flags);

    /* 启动触发信号 */
    gpio_set_value(sr04_trig_gpio, 1);  
    udelay(40);
    gpio_set_value(sr04_trig_gpio, 0); 

    /* 等接收信号GPIO变为高电平*/
    while (gpio_get_value(sr04_echo_gpio)==0 && timeout)   
    {
        udelay(1);
        timeout--;
    }   
    if (timeout == 0) 
    {
        local_irq_restore(flags);
        return -EAGAIN;
    }

    timeout = 1000000;
    while (gpio_get_value(sr04_echo_gpio)==1 && timeout)  
    {
        udelay(1);  
        time_us++;                                /* 计算信号高电平时间 */
        timeout--;                                /* 超时计算 */
    }

    if (timeout == 0) 
    {
        printk("timeout 2\r\n");
        local_irq_restore(flags);
        return -EAGAIN;   
    }    
    
    /* 恢复中断 */
    local_irq_restore(flags);

    size = size > 4 ? 4 : size;
	if (copy_to_user(buf, &time_us, size))     /* 将获取的时间拷贝到用户空间 */
    {
        ret = -EFAULT;
    } 
    else 
    {
        ret = size;
    }
    
    return ret;
}
  • 需要注意在死等的时候要加超时机制返回,否则等不信号时系统就会卡死

方式2

与普通死等获取的方式不同的是

  • 使用中断方式,在_drv_read中只会发起启动信号
  • 在中断函数hc_sr04_isr中获取超声波时间
  • _driver_probe申请中断:request_irq(sr04.irq, hc_sr04_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DEV_NAME, NULL);
  • _driver_remove中记得要释放中断,否则下次再insmod内核模块就使用不了该中断,必须要重启系统
static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    int timeout;

    unsigned long flags;
    /* 中断屏蔽 */
    local_irq_save(flags);
    /* 启动触发信号 */
    gpio_set_value(sr04_trig_gpio, 1);  //gpiod_set_value(sr04.trig_gpio, 1);
    udelay(40);
    gpio_set_value(sr04_trig_gpio, 0);//gpiod_set_value(sr04.trig_gpio, 0);    
    /* 恢复中断 */
    local_irq_restore(flags);

    timeout = wait_event_interruptible_timeout(sr04.wq, sr04_data_ns, HZ);	  /* wait 1 sec */
    if (!timeout) return -EAGAIN;

	if (copy_to_user(buf, &sr04_data_ns, size > 4 ? 4 : size)) 
    {
        ret = -EFAULT;
    } 
    else 
    {
        ret = size;
    }
    sr04_data_ns = 0;
    return ret;
}

static irqreturn_t hc_sr04_isr(int irq_num, void *dev)
{
    if (gpio_get_value(sr04_echo_gpio))
    {
        sr04_data_ns = ktime_get_ns();
    }
    else
    {
        sr04_data_ns = ktime_get_ns() - sr04_data_ns;
        wake_up(&sr04.wq);                             /* 唤醒等待队列中进入休眠的进程 */
    }
    
    return IRQ_RETVAL(IRQ_HANDLED);   
}

static int _driver_probe(struct platform_device *pdev)
{ 
    int err;
    struct device *sr04_dev;
    
    struct device_node *node = pdev->dev.of_node;

    if (!node) {          
        printk("hc-sr501 dts node can not found!\r\n");    
        return -EINVAL; 
    }

#if USE_GPIO_LIB
    sr04.trig_gpio = gpiod_get(&pdev->dev, "trig", GPIOD_OUT_LOW);
    if (IS_ERR(sr04.trig_gpio)) {              
        dev_err(&pdev->dev, "Failed to get trig-gpio for hc-sr04\n");             
        return PTR_ERR(sr04.trig_gpio);      
    }

    sr04.echo_gpio = gpiod_get(&pdev->dev, "echo", GPIOD_IN);
    if (IS_ERR(sr04.echo_gpio)) {              
        dev_err(&pdev->dev, "Failed to get trig-gpio for hc-sr04\n");     
        gpiod_put(sr04.trig_gpio);           /* 释放trig-gpio */        
        return PTR_ERR(sr04.echo_gpio);      
    }
#else
    struct device_node *dev_node = of_find_node_by_path("/hc_sr04");       /* 找到hc-sr04的设备树节点  */
    if (IS_ERR(dev_node)) {          
        printk("hc-sr04 DTS Node not found!\r\n"); 
        return PTR_ERR(dev_node); 
    }

    sr04_trig_gpio = of_get_named_gpio(dev_node, "trig-gpios", 0);   /* 获取trig-gpio的编号 */
    if (sr04_trig_gpio < 0) {
        printk("trig-gpio not found!\r\n"); 
        return -EINVAL;
    }

    err = gpio_request(sr04_trig_gpio, "trig-gpios");  
	if(err) 
    {
		printk("gpio_request trig-gpios is failed!\n");
        return -EINVAL;
	}

    sr04_echo_gpio = of_get_named_gpio(dev_node, "echo-gpios", 0);   /* 获取echo-gpio的编号 */
    if ( sr04_echo_gpio < 0) {
        printk("echo-gpio not found!\r\n"); 
        return -EINVAL;
    }
    err = gpio_request(sr04_echo_gpio, "echo-gpios");  
    if(err) 
    {
        gpio_free(sr04_trig_gpio);
		printk("gpio_request echo-gpios is failed!\n");
        return -EINVAL;
	}

    printk("trig-gpio %d  echo-gpio %d\n", sr04_trig_gpio, sr04_echo_gpio);

    sr04.irq = gpio_to_irq(sr04_echo_gpio);
#endif

    err = request_irq(sr04.irq, hc_sr04_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DEV_NAME, NULL);    /* 申请中断 */
    if (err) {
        printk(KERN_INFO"failed to request irq %d\r\n", sr04.irq);
        gpio_free(sr04_trig_gpio);
        gpio_free(sr04_echo_gpio);
        return err;
    }
    /* 内核自动分配设备号 */
    err = alloc_chrdev_region(&sr04.dev_no, 0, 1, DEV_NAME);        
	if (err < 0) {
		pr_err("Error: failed to register mbochs_dev, err: %d\n", err);
		return err;
	}

	cdev_init(&sr04.chrdev, &sr04_drv_ops);

	cdev_add(&sr04.chrdev, sr04.dev_no, 1);

    sr04.class = class_create(THIS_MODULE, DEV_NAME);
	if (IS_ERR(sr04.class)) { 
        err = PTR_ERR(sr04.class);
        goto failed1;
	}

    /* 创建设备节点 */
    sr04_dev = device_create(sr04.class , NULL, sr04.dev_no, NULL, DEV_NAME); 
    if (IS_ERR(sr04_dev)) {       /* 判断指针是否合法 */
        err = PTR_ERR(sr04_dev);
		goto failed2;
	}

    init_waitqueue_head(&sr04.wq);     /* 初始化等待队列头  */
    mutex_init(&sr04.m_lock);                 /* 初始化互斥锁  */   

    printk("hc-sr04 probe success\r\n");
    return 0;
failed2:
    device_destroy(sr04.class, sr04.dev_no);
    class_destroy(sr04.class);
failed1:
    unregister_chrdev_region(sr04.dev_no, 1);
    cdev_del(&sr04.chrdev);
#if USE_GPIO_LIB
    gpiod_put(sr04.echo_gpio);           /* 释放echo-gpio*/
    gpiod_put(sr04.trig_gpio);           /* 释放trig-gpio*/
#else
    gpio_free(sr04_trig_gpio);
    gpio_free(sr04_echo_gpio);
#endif
    return err;
}

static int _driver_remove(struct platform_device *pdev)
{
    device_destroy(sr04.class, sr04.dev_no);
	class_destroy(sr04.class);
	unregister_chrdev_region(sr04.dev_no, 1);
    cdev_del(&sr04.chrdev);
    free_irq(sr04.irq, NULL);             /* 释放中断*/
#if USE_GPIO_LIB
    gpiod_put(sr04.echo_gpio);           /* 释放echo-gpio*/
    gpiod_put(sr04.trig_gpio);           /* 释放trig-gpio*/
#else
    gpio_free(sr04_trig_gpio);
    gpio_free(sr04_echo_gpio);
#endif
    printk(KERN_INFO"hc-sr04 drv remove success\n");

    return 0;
}


3、测试程序编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>

#define DEV_NAME   "/dev/hc-sr04"

void sleep_ms(unsigned int ms)
{
    struct timeval delay;
	delay.tv_sec = 0;
	delay.tv_usec = ms * 1000; 
	select(0, NULL, NULL, NULL, &delay);
}

int main(int argc, char **argv)
{
	int fd;
    int ret;
  
    struct pollfd fds[1];
	
	/* 2. 打开文件 */
	fd = open(DEV_NAME, O_RDWR);   // | O_NONBLOCK

	if (fd < 0)
	{
		printf("can not open file %s, %d\n", DEV_NAME, fd);
		return -1;
	}

    int time_ns;
    while (1)
    {
        if ((ret = read(fd, &time_ns, 4)) == 4)
        {
            printf("time %d ns %d ms, distance %d mm %d cm\r\n", time_ns, time_ns/1000000, time_ns*340/2/1000000, time_ns*340/2/1000000/10);
        }
        else
        {
            printf("not get time, err %d\r\n", ret);
        }
        sleep_ms(500);
    }
}

通过逻辑分析仪测量获取时间是否准确

逻辑分析仪测量的启动信号:
在这里插入图片描述
逻辑分析仪测量的距离的高电平时间:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux驱动 的相关文章

随机推荐

  • 机器学习_深度学习毕设题目汇总——目标检测B

    下面是该类的一些题目 题目 典型恶劣天气条件下高铁周界入侵目标检测 图模型融合时空特征的视觉显著性目标检测算法研究 基于SAR图像的舰船目标检测方法研究 基于三维点云分析的智能汽车目标检测方法研究 基于传播模型的显著目标检测方法研究 基于全
  • 如何在Ubuntu服务器上安装 QEMU/KVM 以创建虚拟机

    KVM是 Kernel based Virtual Machine 的缩写 是集成到 Linux 内核中的开源类型 1 虚拟机管理程序 裸机虚拟机管理程序 它允许您创建和管理运行 Windows Linux 或 UNIX 变体 如 Free
  • 微信小程序云开发操作全解

    云开发三大基础能力 云函数 运行在微信服务器上的函数 处理微信相关操作有天然优势 如获得用户信息异常方便 以前服务端解析很麻烦 数据库 一个小程序可以直接操作的JSON数据库 可以直接操作数据库 不在需要服务端了 存储 用来存储文件和图片
  • Vue中获取接口的方法

    在vue小黑框中输入 cnpm install axios 下载安装 可以设置成全局 引入import axios from axios 把axios放进Vue对象的原型中 Vue prototype axios axios 设置默认的地址
  • 思科模拟器基础实验完整流程-初级

    文章目录 实验拓扑 第一部分 企业总部内网 第一层 接入层 第二层 汇聚层 解决 VLAN 间通信 第三层 网络层 第二部分 企业分部内网 第三部分 外部网络路由 第四部分 NAT 地址转换 第五步 VPN 实验拓扑 由图我们可以看到这张拓
  • 【SpringCloud】用【Idea】构建Maven父子工程图文(1)

    1 概述 之前老是出现idea建springCloud的maven父子工程失败 特此写下经验和教训 springboot版本 2 2 1 2 不多逼逼 图文教程直接来 新建maven空工程 事先准备好一个空文件夹 选择刚才建好的空文件夹 不
  • C# 企业微信:开启消息接受&接收消息&推送消息

    前言 微信吧 接触的人都会100 各种踩坑 就算同样东西去年做过 今年来一样踩坑 因为太多你稍微不记得一点点的细节就能让你研究N久 为此 我要把这个过程详细的记录下来 一 开启消息接受 1 拿到企业corpId 应用的Token Encod
  • proteus中如何加载C语言程序,Proteus运行Keil编写的51单片机C语言步骤

    1 在Keil 编写好程序以后 在左侧项目栏中单击鼠标右键 如下图 2 点击上图中Options for Target Target1 选项 弹出下列窗口 3 在Target标签中将Xtal MHz 中的晶振频率改为12 0 再在Outpu
  • 【周末闲谈】关于“数据库”你又知道多少?

    个人主页 个人主页 系列专栏 周末闲谈 系列目录 第一周 二进制VS三进制 第二周 文心一言 模仿还是超越 第二周 畅想AR 文章目录 系列目录 前言 数据库 数据库的五大特点 数据库介绍 数据库管理系统 DBMS 数据库的结构 数据库的操
  • 给定一个十进制正整数 n(0 < n < 1000000000),每个数位上数字均不为 0。n 的位数为 m。现在从 m位中删除 k位 (0<k < m),求生成的新整数最小为多少?例如: n = 9

    题目描述 给定一个十进制正整数 n 0 lt n lt 1000000000 每个数位上数字均不为 0 n 的位数为 m 现在从 m位中删除 k位 0
  • 如何搭建Tesla Occupancy Network的一个基线?

    作者 我叫斯蒂芬 编辑 汽车人 原文链接 https zhuanlan zhihu com p 575058907 点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 BEV感知 技术交流群 原文链
  • 前端音波绘制

    好久不曾写博客了 忙忙碌碌大半年 毕业就工作果然还是有点意思 本人贼懒 但是只要是研究了点东西的话 还是分享一下供其他感兴趣的小伙伴前车之鉴吧 回归正题 用过手机百度音乐的朋友们 这里算打个广告吧 估计会注意音乐播放后左下角那个音波绘制的看
  • 基于yolov3源码的训练过程

    基于yolov3源码的训练过程 在yolo官网上使用一下指令可以对模型的数据集进行训练 darknet detector train cfg voc data cfg yolov3 voc cfg darknet53 conv 74 但是训
  • fatfs移植和使用(在SPI_FLASH上建立文件系统)

    文件系统对于嵌入式系统的重要性是不言而喻的 有了文件系统管理数据和外设变得方便许多 同时简化了应用的开发 今天我们来以在SPI FLASH上建立文件系统为例 看看FATFS文件系统怎么移植和使用 需要准备的材料有 1 FATFS文件系统源码
  • VC项目中文件类型说明

    APS 存放二进制资源的中间文件 VC把当前资源文件转换成二进制格式 并存放在APS文件中 以加快资源装载速度 资源辅助文件 BMP 位图资源文件 BSC 浏览信息文件 由浏览信息维护工具 BSCMAKE 从原始浏览信息文件 SBR 中生成
  • Elasticsearch与Clickhouse数据存储对比

    1 背景 京喜达技术部在社区团购场景下采用JDQ Flink Elasticsearch架构来打造实时数据报表 随着业务的发展Elasticsearch开始暴露出一些弊端 不适合大批量的数据查询 高频次分页导出导致宕机 存储成本较高 Ela
  • 从键盘输入一个字符,若为小写,则改为大写再输出

    include
  • React学习笔记

    一 基础 1 概念 React是用于构建用户界面的JavaScript库 只关注视 2 特点 声明式编程 React 使创建交互式 UI 当数据变动时 React 能高效更新并渲染合适的组件 组件化 构建管理自身状态的封装组件 然后对其组合
  • 华为技术支持面试

    一面 技术面 不同面试官 面试内容差别较大 班里有些同学随便聊4 5分钟就通过了 我 大概面了15分钟 首先是三分钟的中文自我介绍 然后针对简历提问 被问到毕业课题 而且问得比较详细 叫我画出整个方案的框架图 I2C LCD的时序图 项目工
  • Linux驱动

    HC SR04超声波模块 工作原理参考 超声波模块 star air的博客 CSDN博客 超声波模块 https blog csdn net qq 41262681 article details 95940707 使用超声波测距的操作步骤