STM32MP157驱动开发——Linux下的单总线驱动

news2024/11/16 12:48:14

STM32MP157驱动开发——Linux下的单总线驱动

  • 0.前言
  • 一、DS18B20 及工作时序简介
    • 1.DS18B20 简介
    • 2.DS18B20 时序简介
    • 4.DS18B20温度读取流程
  • 二、DHT11 及工作时序简介
    • 1.DHT11 简介
    • 2.DHT11 工作时序简介
  • 三、驱动开发
    • 1.DS18B20驱动
      • 1)修改设备树
      • 2)驱动编写
    • 2.测试App
    • 3.DHT11驱动
      • 1)修改设备树
      • 2)驱动编写
    • 4.测试App
  • 五、运行测试


0.前言

  除了 IIC、SPI、uart 等通信方式以外,还有些传感器使用的是单总线通信方式,也就是主机和从机通过一根线进行通信。这种方式的优点在于单总线技术具有线路简单、硬件开销少、成本低等特点,本节就通过 DS18B20 和 DHT11 传感器学习一下 Linux 的单总线驱动。

一、DS18B20 及工作时序简介

1.DS18B20 简介

  DS18B20 为单总线接口的温度传感器,测试温度范围为-55 ~ +125℃,精度为±0.5℃。现场温度直接以单总线的数字方式传输,大大提高了系统的抗干扰性。该传感器工作在 3 ~ 5.5V 的电压范围,能直接读出被测温度,并且可根据实际要求通过简单的编程实现 9~12 位的数字值读数方式。设定分辨率以及用户设定的报警温度存储在 EEPROM 中,掉电后依然保存。其管脚排列如下:
在这里插入图片描述

2.DS18B20 时序简介

所有单总线器件要求采用严格的信号时序,以保证数据的完整性。DS18B20 共有 6 种信号类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。这些信号中,除了应答脉冲以外,都是由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。

  • 复位脉冲和应答脉冲
    单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少要在 480us,以产生复位脉冲。接着主机释放总线,4.7K 的上拉电阻将单总线拉高,延时时间在 15 ~ 60us,进入接收模式(Rx)。接着 DS18B20 拉低总线 60~240us,以产生低电平应答脉冲。
    在这里插入图片描述
  • 写时序
    写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在两次独立的写时序之间,至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线延时 2us。
    在这里插入图片描述
  • 读时序
    单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线 1us。主机在读时序期间必须释放总线,并且在时序起始后的 15us 之内采样总线的状态。
    典型的读时序过程为:主机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然后延时 50us。
    在这里插入图片描述

4.DS18B20温度读取流程

  DS18B20 的典型温度读取过程为:复位→发 SKIP ROM(0xCC)→发开始转换命令(0x44)→延时→复位→发送 SKIP ROM 命令(0xCC)→发送存储器命令(0xBE)→连续读取两个字节数据(即温度)→ 结束。

二、DHT11 及工作时序简介

1.DHT11 简介

  DHT11 是一款温湿度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个 NTC测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个 I/O 口。传感器内部湿度和温度数据 40Bit 的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。 其主要特性如下:

  • 工作电压范围: 3.3V ~ 5.5V
  • 工作电流:平均 0.5mA
  • 输出:单总线数字信号
  • 测量范围:湿度 20~90%RH,温度 0~50℃
  • 精度:湿度±5%,温度±2℃
  • 分辨率:湿度 1%,温度 1℃

管脚排列:
在这里插入图片描述

2.DHT11 工作时序简介

  DHT11 与 DS18B20 类似,都是单总线访问,但是 DHT11 的访问,相对 DS18B20 来说简单很多。
  DHT11 数字温湿度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由 5byte(40bit)组成。数据分小数部分和整数部分,一次完整的数据传输为 40bit,高位先出。DHT11 的数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit温度小数部分+8bit 校验和。其中校验和数据为前面四个字节相加。
  传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。
在这里插入图片描述
湿度 = byte4 . byte3 = 45.0(%RH)
温度 = byte2 . byte1 = 28.0(℃)
校验 = byte4 + byte3 + byte2 + byte1 = 73 (= 湿度 + 温度) (校验正确)

DHT11 和 MCU 的一次通信最大为 3ms 左右,建议主机连续读取时间间隔不要小于 100ms。

  • 数据发送流程
    主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后拉高数据线 t2(20 ~ 40us)时间,然后读取 DHT11 的响应,正常情况下,DHT11 会拉低数据线,保持 t3(40 ~ 50us)时间作为响应信号,然后 DHT11 拉高数据线,保持 t4(40~50us)时间后,开始输出数据。
    在这里插入图片描述
  • 输出数字 ‘0’ 时序
    在这里插入图片描述
  • 输出数字 ‘1’ 时序
    在这里插入图片描述

三、驱动开发

原理图:
在这里插入图片描述
JP9 为 DS18B20/DHT11 的接口,其中 DQ 为数据引脚,连接到了 STM32MP157 的 PF2 引脚上。JP2 是一个单排圆孔母座 4Pin 的座子,可用于连接 DHT11 或者 DS18B20,实物图如下:
在这里插入图片描述
DHT11 连接方法:正面(有孔面)朝向开发板的外侧。
DS18B20 的连接方法:半圆的一面,对准底板上丝印半圆的三个脚,半圆朝向开发板外侧。

1.DS18B20驱动

1)修改设备树

在 stm32mp157d-atk.dts 文件中,添加如下内容:

ds18b20 {
	compatible = "alientek,ds18b20";
	ds18b20-gpio = <&gpiof 2 GPIO_ACTIVE_LOW>;
	status = "okay";
};

2)驱动编写

单总线是依靠一根 GPIO 来实现收发功能的,需要设置 GPIO 的模式和控制输出高低电平。
ds18b20.c:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>

struct ds18b20_dev {
    struct miscdevice mdev;	/* MISC设备 */
    int gpio;				/* GPIO的编号 */
    unsigned char data[2]; /* 接收原始数据的BUFF */
	struct timer_list timer;	/* 定时器 */
    struct work_struct work;	/* 工作队列 */
};

#define HIGH 1	
#define LOW 0

struct ds18b20_dev ds18b20_device;

/*
 * @description	 : 	设置GPIO的输出值
 * @param - value: 	输出value的值 
 * @return 	     :  无
 */
static void ds18b20_set_output(int value)
{
    if(value)
        gpio_direction_output(ds18b20_device.gpio, 1);
    else
        gpio_direction_output(ds18b20_device.gpio, 0);
}

/*
 * @description	: 	设置GPIO为输入模式
 * @param 		:	无
 * @return 	  	:   无
 */
static void ds18b20_set_input(void)
{
    gpio_direction_input(ds18b20_device.gpio);
}

/*
 * @description	: 	获取GPIO的值
 * @param 		:	无 
 * @return 	  	:   GPIO的电平
 */
static int ds18b20_get_io(void)
{
    return gpio_get_value(ds18b20_device.gpio); 
}

/*
 * @description	: 	写一位数据
 * @param 	bit	: 	要写入的位数
 * @return 	  	:   无
 */
static void ds18b20_write_bit(int bit)
{
	local_irq_disable();		  /* 关闭处理器的所有中断 */
    if(bit) {
        ds18b20_set_output(LOW);  /* 把DQ拉低 */
        udelay(2);				  /* 拉低2us */
        ds18b20_set_output(HIGH); /* 把DQ拉高 */
        udelay(60); 			  /* 拉高60us */
    } else {
        ds18b20_set_output(LOW);  /* 把DQ拉低 */
        udelay(60);				  /* 拉低60us */
        ds18b20_set_output(HIGH); /* 把DQ拉高 */
        udelay(2);				  /* 拉高2us */
    }
	local_irq_enable();			  /* 开启处理器的所有中断 */
}

/*
 * @description	: 	读一位数据
 * @param 		: 	无
 * @return 	  	:   返回读取一位的数据
 */
static int ds18b20_read_bit(void)
{
    u8 bit = 0;
	local_irq_disable();
    ds18b20_set_output(LOW);	/* 把DQ拉低 */
    udelay(1);					/* 拉低1us */

    ds18b20_set_output(HIGH);	/* 把DQ拉高 */
    udelay(1);					/* 拉高1us */
    
    ds18b20_set_input();		/* 设置为输入模式,开始接收数据 */

    if(ds18b20_get_io())		/* 获取DQ的电平,高为1,低为0 */
        bit = 1;
    udelay(50);					/* 延时50us */
	 local_irq_enable();
    return bit;					/* 返回读取数据 */
}

/*
 * @description	: 	写一个字节到DS18B20
 * @param byte  : 	要写入的字节
 * @return 	  	:   无
 */
static void ds18b20_write_byte(u8 byte)
{
    int i;
    for(i = 0; i < 8; i++) {
        if(byte & 0x01)
            ds18b20_write_bit(1); /* write 1 */
        else
            ds18b20_write_bit(0); /* write 0 */
        byte >>= 1;	/* 右移一位获取高一位的数据 */
    }
}

/*
 * @description	: 	读取一个字节的数据
 * @param 		: 	无
 * @return 	  	:   读取到的数据
 */
static char ds18b20_read_byte(void)
{
    int i;
    u8 byte = 0;
    for(i = 0; i < 8; i++) {	/* DS18B20先输出低位数据 ,高位数据后输出 */
        if(ds18b20_read_bit())
            byte |= (1 << i);
        else
            byte &= ~(1 << i);
    }
    return byte;
}

/*
 * @description	: 	初始化DS18B20
 * @param 		: 	无
 * @return 	  	:   0,初始化成功,1,失败
 */
static int ds18b20_init(void)
{
    int ret = -1;
	ds18b20_set_output(HIGH);	/* 把DQ拉高 */
	udelay(1);					/* 拉高1us */
	ds18b20_set_output(LOW);	/* 把DQ拉低 */
    udelay(500);				/* 拉低500us */
	ds18b20_set_output(HIGH);	/* 把DQ拉高 */
	udelay(60);					/* 拉高60us */
	ds18b20_set_input();		/* 设置为输入模式 */
    ret = ds18b20_get_io();		/* 获取到低电平做响应 */
	udelay(240);				/* 延时240us */
	
	return ret;		
}
 
/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ds18b20_open(struct inode *inode, struct file *filp)
{
	return 0;
}
/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ds18b20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{
    int ret;
    ret = copy_to_user(buf, &ds18b20_device.data[0], 2);
	if(ret)
        return -ENOMEM;
	return ret;
}

static struct file_operations ds18b20_fops = {
	.owner	= THIS_MODULE,
	.open   = ds18b20_open,
	.read	= ds18b20_read,
};

/*
 * @description     : 使用内核的工作队列,获取温度的原始数据
 * @param - work 	: work的结构体
 * @return          : 无
 */
static void ds18b20_work_callback(struct work_struct *work)
{
	int ret = -1;
	 ret = ds18b20_init();	/* 初始化ds18b20 */
     if(ret != 0)
         goto out1;
	ds18b20_write_byte(0XCC);	/* 跳过ROM */
	ds18b20_write_byte(0X44);	/* 开启温度采集 */
	
     ret = ds18b20_init();		/* 初始化ds18b20 */
     if(ret != 0)
         goto out1;
	
	ds18b20_write_byte(0XCC);	/* 跳过ROM */
	ds18b20_write_byte(0XBE);	/* 开启读取温度 */
	
	ds18b20_device.data[0] = ds18b20_read_byte();	/* 获取低位数据 */
	ds18b20_device.data[1] = ds18b20_read_byte();	/* 获取高位数据 */
out1:
    return;
}

/*
 * @description     : 定时器的操作函数,每1s去获取一次数据
 * @param - asg 	: 定时器的结构体
 * @return          : 无
 */
static void ds18b20_timer_callback(struct timer_list *arg)
{
    schedule_work(&ds18b20_device.work);	/* 运行工作队列,去获取温度 */
    mod_timer(&ds18b20_device.timer, jiffies + (1000 * HZ/1000));	/* 定时1s */
}

/*
 * @description	: 	GPIO的初始化函数
 * @param pdev	:	platform设备 	
 * @return 	  	:   0表示转换成功,其它值表示转换失败
 */
static int ds18b20_request_gpio(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int ret;
	
    ds18b20_device.gpio = of_get_named_gpio(dev->of_node, "ds18b20-gpio", 0);
    if (!gpio_is_valid(ds18b20_device.gpio)) {
        dev_err(dev, "Failed to get gpio");
        return -EINVAL;
    }

    ret = devm_gpio_request(dev, ds18b20_device.gpio, "DS18B20 Gpio");
    if (ret) {
        dev_err(dev, "Failed to request gpio");
        return ret;
    }

    return 0;
}

/*
  * @description    : 驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - pdev  	: pdev设备
  * @return 		: 0表示转换成功,其它值表示转换失败
  */
static int ds18b20_probe(struct platform_device *pdev)
{
    struct miscdevice *mdev;
    int ret;

	dev_info(&pdev->dev, "ds18b20 device and driver matched successfully!\n");
	
	/* GPIO的初始化 */
    ret = ds18b20_request_gpio(pdev);
    if(ret)
        return ret;
	/* 初始化 MISG设备 */
    mdev = &ds18b20_device.mdev;
    mdev->name = "ds18b20";
    mdev->minor = MISC_DYNAMIC_MINOR;
    mdev->fops = &ds18b20_fops;
	
	/* 初始化定时器 */
	timer_setup(&ds18b20_device.timer, ds18b20_timer_callback, 0);
    ds18b20_device.timer.expires=jiffies + msecs_to_jiffies(1000);
    add_timer(&ds18b20_device.timer);
    
	/* 初始化工作队列 */
	INIT_WORK(&ds18b20_device.work, ds18b20_work_callback);
	
	/* MISG 设备注册 */
    return misc_register(mdev);
}

/*
 * @description     : 驱动的remove函数,移除驱动的时候此函数会执行
 * @param - pdev 	: pdev设备
 * @return          : 0,成功;其他负值,失败
 */
static int ds18b20_remove(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "DS18B20 driver has been removed!\n");
	
	/* 卸载MISG设备 */
	misc_deregister(&ds18b20_device.mdev);
	/* 卸载定时器 */
	del_timer(&ds18b20_device.timer);
	/* 卸载工作队列 */
    cancel_work_sync(&ds18b20_device.work);
    return 0;
}

static const struct of_device_id ds18b20_of_match[] = {
	{ .compatible = "alientek,ds18b20" },
	{ /* Sentinel */ }
};

static struct platform_driver ds18b20_driver = {
	.driver = {
		.name			= "ds18b20",
		.of_match_table	= ds18b20_of_match,
	},
	.probe		= ds18b20_probe,
	.remove		= ds18b20_remove,
};

module_platform_driver(ds18b20_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("amonter");
MODULE_INFO(intree, "Y");

①ds18b20_init 为设备初始化函数,是按照时序图进行编写的。
②传感器的读写函数为 ds18b20_read_bit、ds18b20_write_bit、ds18b20_read_byte、ds18b20_write_byte。都是按照之前的时序图进行编写,在读写操作之前使用 local_irq_disable 关闭 CPU 的中断,读写完成后再用 local_irq_enable 函数开启。如果不关闭,在有中断产生时可能造成错误数据。
③ds18b20_work_callback 和 ds18b20_timer_callback 分别是处理工作队列函数和定时器回调函数。定时器的作用是每一秒去获取温度,触发工作队列。在工作队列中处理 ds18b20 的初始化和获取温度。
在工作队列中用到了一些指令,其功能如下:

  • 跳过 ROM(0xCC)指令:该指令只适合总线只有一个节点的情况,允许总线上的主机不提供 64位 ROM 序列号而直接访问 RAM,节省操作时间。
  • 温度转换(0x44)指令:启动 DS18B20 进行温度转换,结果存入内部 RAM。
  • 读暂存器(0xBE)指令:读暂存器 9 个字节内容,该指令从 RAM 的第一个字节(字节 0)开始读取,直到九个字节(字节 8, CRC 值)全部被读出为止。如果不需要读出所有字节的内容,那么主机可以在任何时候发出复位信号来中止读操作。

④ds18b20_fops 为设备操作函数集,指定了 open 和 read 操作函数,使用了前面学习的 MISC 设备框架,因为单总线驱动主要设计一个 GPIO 引脚的操作,使用此框架开发方便。
⑤ ds18b20_driver 为 platform 平台驱动框架,包括 probe、remove 和 设备匹配表等信息。

2.测试App

ds18b20_App.c:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main()
{
    int fd, ret;
    unsigned char result[2];
    int TH, TL;
    short tmp = 0;
    float temperature;
    int flag = 0;

    fd = open("/dev/ds18b20", 0);

    if(fd < 0){
        perror("open device failed\n");
        exit(1);
    } else
        printf("Open success!\n");

    while(1)
    {
        ret = read(fd, &result, sizeof(result)); 
		if(ret == 0) {	/* 读取到数据 */
			TL = result[0];
			TH = result[1];
    
			if((TH == 0XFF) && (TL == 0XFF))/* 如果读取到数据为0XFFFF就跳出本次循序 */
				continue;
			if(TH > 7) {	/* 负数处理 */
				TH = ~TH;
				TL = ~TL;
				flag = 1;	/* 标记为负数 */
			}

			tmp = TH;
			tmp <<= 8;
			tmp += TL;
        
			if(flag == 1) {
				temperature = (float)(tmp+1)*0.0625; /* 计算负数的温度 */
				temperature = -temperature;
			} else {
				temperature = (float)tmp *0.0625;	/* 计算正数的温度 */
			}            

			if(temperature < 125 && temperature > -55) {	/* 温度范围 */
				printf("Current Temperature: %f\n", temperature);
			}
		}
	    flag = 0;
	    sleep(1);
    }
	close(fd);	/* 关闭文件 */
}

3.DHT11驱动

1)修改设备树

在 stm32mp157d-atk.dts 文件中,添加以下节点:

dht11 {
	compatible = "alientek,dht11";
	dht11-gpio = <&gpiof 2 GPIO_ACTIVE_LOW>;
	status = "okay";
};

2)驱动编写

DHT11 只有读函数,只需要实现读即可。

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>

struct dht11_dev {
	struct miscdevice mdev; 	/* MISC设备 */
	int gpio;					/* GPIO编号 */
	struct timer_list timer;	/* 定时器 */
    struct work_struct work;	/* 工作队列 */
    u8 data[5];					/* 数据BUFF */
};

#define  HIGH                   1
#define  LOW                    0

struct dht11_dev dht11_device;

/*
 * @description	 : 	设置GPIO的输出值
 * @param - value: 	输出value的值 
 * @return 	     :  无
 */
static void dht11_set_output(int val)
{
	if(val)
		gpio_direction_output(dht11_device.gpio, 1);
	else
		gpio_direction_output(dht11_device.gpio, 0);
}

/*
 * @description	: 	设置GPIO为输入模式
 * @param 		:	无
 * @return 	  	:   无
 */
static void dht11_set_input(void)
{
	gpio_direction_input(dht11_device.gpio);
}

/*
 * @description	: 	获取GPIO的值
 * @param 		:	无 
 * @return 	  	:   GPIO的电平
 */
static unsigned char dht11_get_io(void)
{
	return gpio_get_value(dht11_device.gpio);
}

/*
 * @description	: 	读取一个字节的数据
 * @param 		: 	无
 * @return 	  	:   读取到的数据
 */
static unsigned char dht11_read_byte(void)
{
	unsigned char i, time = 0, data = 0;
	local_irq_disable();
	
	for(i = 0; i < 8; i++) {
		time = 0;

		while(dht11_get_io() == 0) {
			udelay(1);
			time++;
			if (time > 100) {
				return -EINVAL;
			}
		}
		udelay(45);		/* 延时45us */
		if (dht11_get_io() == 1) {	/* 获取到高电平,数据就为1,否则就是0 */
			data |= 1<<(7 - i);		
			time = 0;
			while(dht11_get_io() == 1) {
				udelay(1);
				time++;
				if (time > 100)
					return -EINVAL;
			}
		}
	}
	local_irq_enable();
	
    return data;
}

/*
 * @description	: 	DHT11的初始化
 * @param 		: 	无
 * @return 	  	:   0,初始化成功;	其它表示失败
 */
static int dht11_init(void)
{
	dht11_set_output(HIGH);	/* 把拉高Duot */
	udelay(30);				/* 拉高30us */

	dht11_set_output(LOW);	/* 把拉低Duot */
	mdelay(20);				/* 拉低20us */

	dht11_set_output(HIGH);	/* 把拉高Duot */
	udelay(30);				/* 拉高30us */

	dht11_set_input();		/* 设置Duot为输入模式 */
    udelay(200);			/* 延时200us */
    if(!dht11_get_io()) {	/* 不是高电平,DHT11就没有响应 */
        return -ENODEV;
    }
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int dht11_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t dht11_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{
    int ret = 0;
	ret = copy_to_user(buf, &dht11_device.data[0], 5);

	return ret;
}

static struct file_operations dht11_fops = {
	.owner	= THIS_MODULE,
	.open	= dht11_open,
	.read   = dht11_read,
};

/*
 * @description     : 使用内核的工作队列,获取温度的原始数据
 * @param - work 	: work的结构体
 * @return          : 无
 */
static void dht11_work_callback(struct work_struct *work)
{
    int i = 0;
	unsigned char buff[5];
	
    if(!dht11_init()) {
        for(i = 0; i < 5; i++) {
            buff[i] = dht11_read_byte();	/* 获取数据 */
        }
			/* 校验数据是否正确 */
	    if((buff[0] + buff[1] + buff[2] + buff[3]) == buff[4]) {
		    memcpy(&dht11_device.data[0], &buff[0], 5);
        }
    }
}

/*
 * @description     : 定时器的操作函数,每1s去获取一次数据
 * @param - asg 	: 定时器的结构体
 * @return          : 无
 */
static void dht11_timer_callback(struct timer_list *arg)
{
    schedule_work(&dht11_device.work);
    mod_timer(&dht11_device.timer, jiffies + (1500 * HZ/1000));
}

/*
 * @description	: 	GPIO的初始化函数
 * @param pdev	:	platform设备 	
 * @return 	  	:   0表示转换成功,其它值表示转换失败
 */
static int dht11_request_gpio(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	int ret;

	dht11_device.gpio = of_get_named_gpio(dev->of_node, "dht11-gpio", 0);
	if (!gpio_is_valid(dht11_device.gpio)) {
		dev_err(dev, "Failed to get gpio");
		return -EINVAL;
	}

	ret = devm_gpio_request(dev, dht11_device.gpio, "DHT11 Gpio");
	if (ret) {
		dev_err(dev, "Failed to request gpio");
		return ret;
	}

    return 0;
}

/*
  * @description    : 驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - pdev  	: pdev设备
  * @return 		: 0表示转换成功,其它值表示转换失败
  */
static int dht11_probe(struct platform_device *pdev)
{
	struct miscdevice *mdev;
	int ret;

	dev_info(&pdev->dev, "dht11 device and driver matched successfully!\n");

	ret = dht11_request_gpio(pdev);
	if (ret)
		return ret;

	/* 初始化 MISG设备 */
	mdev = &dht11_device.mdev;
	mdev->name	= "dht11";
	mdev->minor	= MISC_DYNAMIC_MINOR;
	mdev->fops	= &dht11_fops;
	
	/* 初始化定时器 */
	timer_setup(&dht11_device.timer, dht11_timer_callback, 0);
    dht11_device.timer.expires=jiffies + msecs_to_jiffies(1500);
    add_timer(&dht11_device.timer);
	
	/* 初始化工作队列 */
    INIT_WORK(&dht11_device.work, dht11_work_callback);
	
	/* MISG 设备注册 */
	return misc_register(mdev);
}

/*
 * @description     : 驱动的remove函数,移除驱动的时候此函数会执行
 * @param - pdev 	: pdev设备
 * @return          : 0,成功;其他负值,失败
 */
static int dht11_remove(struct platform_device *pdev)
{
	gpio_set_value(dht11_device.gpio, 0);

	/* 卸载MISG设备 */
	misc_deregister(&dht11_device.mdev);
	/* 卸载定时器 */
    del_timer(&dht11_device.timer);
	/* 卸载工作队列 */
    cancel_work_sync(&dht11_device.work);

	dev_info(&pdev->dev, "DHT11 driver has been removed!\n");
	return 0;
}

static const struct of_device_id dht11_of_match[] = {
	{ .compatible = "alientek,dht11" },
	{ /* Sentinel */ }
};

static struct platform_driver dht11_driver = {
	.driver = {
		.owner			= THIS_MODULE,
		.name			= "dht11",
		.of_match_table	= dht11_of_match,
	},
	.probe		= dht11_probe,
	.remove		= dht11_remove,
};

module_platform_driver(dht11_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("amonter");
MODULE_INFO(intree, "Y");

同样使用 MISC 框架和 platform 框架,只不过在设备操作函数集 fops 中,只需要实现 open 和 read 即可,这里就不再详细叙述。

4.测试App

dht11_App.c:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main()
{
    int fd, ret, i = 0;
    unsigned char data[5];


    fd = open("/dev/dht11", 0);

    if(fd < 0)
    {
        perror("open device failed\n");
        exit(1);
    }
    else
        printf("Open success!\n");

    while(1)
    {
        ret = read(fd, &data, sizeof(data));
        if(ret == 0) {	/* 读取到数据 */
			if(data[4] == data[0] + data[1] + data[2] + data[3]) {
				printf("Temp:%d.%d℃,Humi:%d.%d%\r\n", data[2],data[3], data[0],data[1]);
			}
		}
    sleep(1);
    }
}

五、运行测试

驱动和测试App编写完成后,分别编译出 .ko 文件和测试App,放入开发板启动。笔者手头没有 dht11 传感器,ds18b20 管脚不适配,所以仅贴出相关的测试命令。

depmod 				#第一次加载驱动是时候需要运行此命令
modprobe ds18b20.ko #加载驱动
./ds18b20App /dev/ds18b20

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150206.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IB生物:干细胞与生命的各种功能

国际学校生物老师解读IB生物&#xff0c;感兴趣的同学记得收藏哦~IB生物分为SL(standard level)和HL(higher level)SL有6个topic∶细胞生物&#xff0c;分子生物&#xff0c;遗传学&#xff0c;生态学&#xff0c;物种进化以及多样性和人体生理。HL除了上述6个topic外还要加上∶…

Python入门基础实例讲解——两个数字比大小,并输出最大值

嗨害大家好鸭&#xff01; 我是小熊猫~ 今天也是给大家带来干货的一天~ pycharm永久激活码可以从这里找到我&#xff1a; 输出&#xff1a;print&#xff08;&#xff09; print() 方法用于打印输出&#xff0c;最常见的一个函数。 比较运算符 >&#xff1a; 大于&#…

关于校园网的各种连接问题

校园网网络使用异常&#xff0c;掉线、卡顿以及无法连接网络&#xff0c;经网络上收据的信息&#xff0c;大致分为五类&#xff1a;1.能获取到校园网地址如10.*.*.*&#xff0c;但无法跳出认证界面。2.物理链路故障&#xff1b;3.IP配置故障&#xff1b;4. 网络正常&#xff0c…

SpringCloud高级应用-1(SpringCloud技术栈概览)

1、SpringCloud技术栈 开发分布式系统可能具有挑战性&#xff0c;复杂性已从应用程序层转移到网络层&#xff0c;并要求服务之间进行更多的交互。将代码设为“cloud-native”就需要解决12-factor&#xff0c;例如外部配置&#xff0c;服务无状态&#xff0c;日志记录以及连接…

【矩阵论】8. 常用矩阵总结——单阵,正规阵,幂0阵,幂等阵,循环阵

矩阵论 1. 准备知识——复数域上矩阵,Hermite变换) 1.准备知识——复数域上的内积域正交阵 1.准备知识——Hermite阵&#xff0c;二次型&#xff0c;矩阵合同&#xff0c;正定阵&#xff0c;幂0阵&#xff0c;幂等阵&#xff0c;矩阵的秩 2. 矩阵分解——SVD准备知识——奇异值…

基于Promethus+Grafana搭建监控系统

简介 ● 监测数据类型&#xff1a;JVM数据、在线人数、消息时延等 ● 接入Prometheus性能监测工具&#xff0c;暴露服务器性能监测数据 ○ 模式&#xff1a;pull/push ● 接入Grafana可视化数据 搭建流程 准备环境 Java客户端配置 <!-- The client --> <depende…

IDEA 的Http接口调试工具

简介 这个工具我们可以完成绝大部分http请求&#xff0c;是一个不错的post-man替代工具 插件安装 在使用之前先确保 HTTP Client插件的安装&#xff0c;如果已经安装请无视这一步 > 安装后记得重启IDE编辑器确保插件可以正常使用 插件使用 描述&#xff1a; 所有HTTP请求…

2023年1月6日星期五-PPP/BPP相关学习-旧版重写

1. 独立均匀分布 matlab的rand指令可以帮助我们生成[0,1]的均匀分布的数据&#xff0c;这样&#xff0c;如果我们想要[a,b]的分布数据&#xff0c;只需要a(b-a)*rand就可以了。 [a,b] 均值μ&#xff0c;标准差 均值 标准差 matlab代码&#xff1a; %% 生成一个n行p列的矩…

Excel 中合并单元格的快捷键(ALT+H+M+M)

要在 excel 中合并单元格,首先选择要合并到一个单元格中的单元格,然后可以使用从ALT开始的快捷键,然后同时按H+M+M。 一旦我们使用快捷键执行该函数,它将弹出一条警告消息“合并单元格时,仅保留左上角的值,而放弃其他值“。如果我们继续,单元格将被合并,只保留一个值。…

Nature chemistry|机器学习可以克服自组装肽发现中的人类偏见

题目&#xff1a;Machine learning overcomes human bias in the discovery of self-assembling peptides 文献来源&#xff1a;Nature Chemistry | Volume 14 | December 2022 | 1427–1435 代码&#xff1a;https://doi.org/10.5281/zenodo.6564202&#xff08;非商业化证书…

Java安全:SecurityManager与AccessController

前言 什么是安全&#xff1f; 程序不能恶意破坏用户计算机的环境&#xff0c;比如特洛伊木马等可自我进行复制的恶意程序。程序不可获取主机及其所在网络的私密信息。程序的提供者和使用者的身份需要通过特殊验证。程序所涉及的数据在传输、持久化后都应是被加密的。程序的操…

Hudi的核心概念 —— 文件布局(File Layout)

文章目录文件布局&#xff08;File Layout&#xff09;Hudi 存储分为两个部分文件布局&#xff08;File Layout&#xff09; Hudi 将一个表映射为如下文件结构 Hudi 存储分为两个部分 &#xff08;1&#xff09;元数据&#xff1a;.hoodie 目录对应着表的元数据信息&#xff…

Vivado综合设置之-no_lc

本文详细讨论了当勾选或者不勾选-no_lc时的差异&#xff0c;也详细介绍了using O5 and O6以及using O6 output only的具体含义。 -no_lc表示NO LUT Combining&#xff0c;即无LUT整合&#xff0c;默认不勾选&#xff0c;即默认有LUT整合。LUT整合可以减少对LUT的使用量&#x…

架构设计---数据库的存储优化

前言&#xff1a; 互联网系统架构中&#xff0c;承受着最大出力压力&#xff0c;最难以被伸缩的&#xff0c;就是数据存储部分&#xff0c;原因主要有两方面&#xff0c;一方面&#xff0c;数据存储需要使用硬盘&#xff0c;而硬盘的处理速度要比其他几种计算资源都要慢&#…

6、Servlet——网络协议、HTTP协议、HTTP报文格式

目录 一、网络协议 1、网络协议三要素 2、层次结构 3、层次划分 二、HTTP协议 1、HTTP工作原理 2、HTTP协议特点 3、 三次握手 4、四次挥手 三、HTTP报文格式 1、请求报文 2、响应报文 一、网络协议 网络协议&#xff0c;简称协议 &#xff0c;网络协议是通信计算…

【Python百日进阶-数据分析】Day150 - plotly使用日期类型轴的时间序列 1

文章目录一、使用轴类型的时间序列date1.1 使用 plotly.express1.2 使用 graph_objects二、Dash 中的时间序列三、日期轴上的不同图表类型3.1 相对股票代码值的条形图3.2 多面区域图四、配置刻度标签五、将刻度标签移动到期间的中间六、用直方图总结时间序列数据七、显示期间数…

macOS 上安装和配置 Flutter 开发环境

本文基于此&#xff1a; Flutter中文网 一、安装和运行Flutter的系统环境要求 想要安装并运行 Flutter&#xff0c;你的开发环境需要最低满足以下要求&#xff1a; 操作系统:macOS磁盘空间:2.8 GB(不包括IDE/tools的磁盘空间)。工具:Flutter使用git进行安装和升级。我们建议安…

vue3中的写法以及,一些语法糖

vue3新增setup&#xff0c;属性执行比 created更早,同时setup中this不会指向实例&#xff09;这个方法在onBeforeMounted之前被调用。定义数据需要在数据前面加ref&#xff0c;ref在vue3中是将数据转换成响应式数据结构的一种,因为vue3中没有了data(){ },那么就没办法劫持数据做…

PDF拆分成多个页面怎么办?这三个方法让你实现将文件拆分成多页

PDF是我们常见的文件格式之一&#xff0c;在日常办公中&#xff0c;我们经常会将WORD、PPT、EXCEL等文档转换成PDF的格式后再进行传输&#xff0c;这样不仅传输速度快&#xff0c;格式也不会出现乱码的情况&#xff0c;但在一些特殊的场景下&#xff0c;我们也需要将一份完整的…

Allegro174版本新功能介绍之锁定菜单栏设置

Allegro174版本新功能介绍之锁定菜单栏设置 用Allegro设计的时候,经常因为切换了Symphony team design模式导致菜单栏变化,使用的时候又需要重新去调用一次,十分麻烦 但是在Allegro升级到了174的时候,有一个锁定菜单栏的功能 具体操作如下 选择Setup选择User Prefrences