
imx6ull裸机开发手册;
博客园:https://www.cnblogs.com/foxclever/p/13836986.html
阿里云:https://developer.aliyun.com/article/1216810
DHT11使用手册:
DHT11 简介
产品描述:
传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选择。产品为4针单排引脚封装。
应用领域:
►暖通空调►测试及检测设备
►汽车►数据记录器
► 消费品►自动控制
►气象站►家电
►湿度调节器►医疗
►除湿器
量程精度:
DHT11 模块硬件设计
引脚:
DHT11的供电电压为 3-5.5V。传感器上电后,要等待 1s 以越过不稳定状态在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。
通讯接口:
主机通过一条数据线与 DH11 连接,主机通过这条线发命令给 DHT11,DHT11 再通过这条线把数据发送给主机。建议连接线长度短于20米时用5K上拉电阻,大于20米时根据实际情况使用合适的上拉电阻
DHT11 模块软件设计
DHT11 通信协议:
用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。:下图为一次完整的传输示例:
其中深黑色信号表示由主机驱动,即主机向 DHT11 发信号,浅灰色信号表示DHT11 驱动,即 DHT11 发向主机发信号。
图:延时时间
图:数字0信号
图:数字1信号
⚫ 当主机没有与 DHT11 通信时,总线处于空闲状态,此时总线电平由于上拉电阻的作用处于高电平。
⚫ 当主机与 DHT11 正在通信时,总线处于通信状态,一次完整的通信过程如下:
a) 主机将对应的 GPIO 管脚配置为输出,准备向 DHT11 发送数据;
b) 主机发送一个开始信号:
开始信号 = 一个低脉冲 + 一个高脉冲。(低脉冲至少持续 18ms,高脉冲持续 20-40us。)
c) 主机将对应的 GPIO 管脚配置为输入,准备接受 DHT11 传来的数据,这时信号由上拉电阻拉高;
d) DHT11 发出响应信号:
响应信号 = 一个低脉冲 + 一个高脉冲。(低脉冲持续 80us,高脉冲持续 80us。)
e) DHT11 发出数据信号:
◼ 数据为 0 的一位信号 = 一个低脉冲 + 一个高脉冲。(低脉冲持续50us,高脉冲持续 26~28us。)
◼ 数据为 1 的一位信号 = 一个低脉冲 + 一个高脉冲。(低脉冲持续50us,高脉冲持续 70us。)
f) DHT11 发出结束信号:
最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,然后释放总线,总线由上拉电阻拉高进入空闲状态。
数据格式:
8bit 湿度整数数据+8bit 湿度小数数据
+8bi 温度整数数据+8bit 温度小数数据
+8bit 校验和。
数据传送正确时,校验和等于“8bit 湿度整数数据+8bit 湿度小数数据+8bi温度整数数据+8bit 温度小数数据”所得结果的末 8 位。
测量分辨率:
测量分辨率分别为 8bit(温度)、8bit(湿度)。
修改设备树:
添加引脚节点
根节点
程序
dht11_drv.c
#include <linux/module.h>
#include <linux/poll.h>
#include "linux/jiffies.h"
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/kthread.h>
/* 主设备号 */
static int major = 0;
static struct class *dht11_class; //设备类
static struct gpio_desc *dht11_gpio_desc; // GPIO结构体操作指针
static void dht11_reset(void)
{
gpiod_direction_output(dht11_gpio_desc, 1);
}
static void dht11_start(void)
{
// 不能使用GPIOD_OUT_HIGH,有问题,字节用0,1
mdelay(30);
gpiod_set_value(dht11_gpio_desc, 0);
mdelay(20);
gpiod_set_value(dht11_gpio_desc, 1);
udelay(40);
gpiod_direction_input(dht11_gpio_desc);
udelay(2);
}
static int dht11_wait_ready(void)
{
int timeout_us = 20000;
/* 等待低电平 - 或者直到超时*/
timeout_us = 200;
while (gpiod_get_value(dht11_gpio_desc) && --timeout_us)
{
udelay(1);
}
// 超时
if (!timeout_us)
{
printk("----debug1-----\n");
return -1;
}
/* 等待高电平 - 或者直到超时 */
timeout_us = 200;
while (!gpiod_get_value(dht11_gpio_desc) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
printk("----debug2-----\n");
return -1;
}
/* 等待低电平 */
timeout_us = 200;
while (gpiod_get_value(dht11_gpio_desc) && --timeout_us)
{
udelay(1);
}
// 超时
if (!timeout_us)
{
printk("----debug3-----\n");
return -1;
}
return 0;
}
static int dht11_read_byte(unsigned char *data)
{
int i = 0;
unsigned char buffer = 0; //0-255
int timeout_us = 400;
for (i = 0; i < 8; i++)
{
/* 等待高电平 */
timeout_us = 400;
while (!gpiod_get_value(dht11_gpio_desc) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
udelay(40);
/* 延时在26-28us是0,70us是1,直接延时40us后,读到高电平则是1*/
if (gpiod_get_value(dht11_gpio_desc))
{
buffer = (buffer << 1) | 1; //初始:0左移1位还是0,|1则为1
/* 等待高电平结束 */
timeout_us = 400;
while (gpiod_get_value(dht11_gpio_desc) && --timeout_us)
{
udelay(1);
}
if (!timeout_us)
{
return -1;
}
}
else
{
buffer = (buffer << 1) | 0;
}
}
*data = buffer;
return 0;
}
/* 实现对应的open/read/write等函数,填入file_operations结构体 */
/**
* 从DHT11传感器读取数据
*
* 此函数用于从DHT11湿度和温度传感器读取数据。它首先发送一个高脉冲来启动DHT11,
* 然后等待DHT11准备好。接着,它读取5个字节的数据,并根据校验码验证数据的完整性。
* 最后,将数据返回给用户空间。
*
* @param file 文件结构指针,通常为NULL
* @param buf 用户空间的缓冲区指针,用于接收数据
* @param size 要读取的数据大小
* @param offset 当前文件位置指针,通常未使用
*
* @return 成功时返回读取的字节数,失败时返回负错误码
*/
static ssize_t dht11_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int i = 0;
unsigned char kernel_buf[5]; // 存储从DHT11读取的数据
unsigned long flags; // 保存中断状态
u64 pre, last;
// 检查输入的大小是否正确,DHT11返回的数据应为4字节
if(size != 4)
return -EINVAL;
printk("==== %s ====\n", __FUNCTION__);
local_irq_save(flags); //保存并禁用本地中断。
pre = ktime_get_ns();
udelay(40);
last = ktime_get_ns();
printk("udelay 40 times use ns: %lld\n", last - pre);
// 发送高脉冲启动DHT11
dht11_reset();
dht11_start();
// 等待DHT11就绪
if (0 != dht11_wait_ready())
{
printk("设备未就绪\n");
local_irq_restore(flags); // 恢复中断状态
return -1;
}
// 读取5个字节
for (i = 0; i < 5; i++)
{
if (dht11_read_byte(&kernel_buf[i]) != 0) //返回其他数据则错误
{
local_irq_restore(flags); // 恢复中断状态
return -1;
}
}
dht11_reset(); // 复位
local_irq_restore(flags); // 恢复中断状态
// 根据校验码验证数据
if (kernel_buf[4] != kernel_buf[0] + kernel_buf[1] + kernel_buf[2] + kernel_buf[3]) {
printk("验证错误\n");
local_irq_restore(flags); // 恢复中断状态
return -1;
}
// 将数据返回给用户空间
size = copy_to_user(buf, kernel_buf, size);
return size;
}
/* 定义自己的file_operations结构体 */
static struct file_operations dht11_drv = {
.owner = THIS_MODULE,
.read = dht11_read,
};
static int dht11_probe(struct platform_device *pdev)
{
printk("====%s====\n", __FUNCTION__);
// 获得硬件信息
dht11_gpio_desc = gpiod_get(&pdev->dev, "dht11", GPIOD_OUT_HIGH);
if (IS_ERR(dht11_gpio_desc))
{
dev_err(&pdev->dev, "Failed to get GPIO for dht11\n");
return PTR_ERR(dht11_gpio_desc);
}
/* 注册file_operations */
major = register_chrdev(0, "dht11_chrdev", &dht11_drv); /* /dev/gpio_desc */
dht11_class = class_create(THIS_MODULE, "dht11_class");
device_create(dht11_class, NULL, MKDEV(major, 0), NULL, "dht11"); /* /dev/mydht11 */
dev_info(&pdev->dev, "dht11 initialized successfully\n");
return 0;
}
static int dht11_remove(struct platform_device *pdev)
{
printk("======%s=======\n", __FUNCTION__);
device_destroy(dht11_class, MKDEV(major, 0));
class_destroy(dht11_class);
unregister_chrdev(major, "dht11_chrdev");
gpiod_put(dht11_gpio_desc); // 释放 GPIO
return 0;
}
/* 定义设备树匹配表,用于识别和支持特定的LED驱动器 */
static const struct of_device_id dht11_match_table[] = {
/* 匹配字符串 "fire,dht11" 用于标识 */
{.compatible = "fire,dht11"},
/* 空项作为匹配表的结束标志 */
{},
};
/* 定义platform_driver */
static struct platform_driver dht11_driver = {
.probe = dht11_probe, // 设置探测函数,当设备被探测到时调用
.remove = dht11_remove, // 设置移除函数,当设备被移除时调用
/* 设置<驱动程序的名称>和<设备树匹配表> */
.driver = {
.name = "dht11", // 字符设备名
.of_match_table = dht11_match_table, // 设置设备树匹配表,用于设备的匹配
},
};
/* 在入口函数 */
static int __init dht11_platform_driver_init(void)
{
int ret = 0;
printk("====%s====\n", __FUNCTION__);
ret = platform_driver_register(&dht11_driver); // 注册驱动程序
return ret;
}
/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
*/
static void __exit dht11_platform_driver_exit(void)
{
printk("====%s====\n", __FUNCTION__);
platform_driver_unregister(&dht11_driver); // 销毁设备信息
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(dht11_platform_driver_init);
module_exit(dht11_platform_driver_exit);
MODULE_LICENSE("GPL");
dht11_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
/*
* ./dht11_test /dev/dht11
*
*/
int main(int argc, char **argv)
{
int fd;
int ret = 0;
unsigned char data[4];
/* 1. 判断参数 */
if (argc != 2)
{
printf("Usage: %s /dev/xxx\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
while (1)
{
ret = read(fd, data, 4);
if (ret < 0)
{
perror("read error");
return -1;
}
printf("temperature: %d.%d\t", data[0], data[1]);
printf("humidity: %d.%d\n", data[2], data[3]);
sleep(1);
}
// close(fd);
return 0;
}
Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88 # 板子所用内核源码的目录
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o dht11_test dht11_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order dht11_test
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += dht11_drv.o
评论