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


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

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45682654/article/details/128602037
今日推荐