/************************************************************************* > File Name: DHT11.c > 作者:YJK > Mail: 745506980@qq.com > Created Time: 2021年05月20日 星期四 21时33分51秒 ************************************************************************/ /************************************************************************* > File Name: DHT11.c > 作者:OJT > Created Time: 2024-06-14 ************************************************************************/ #include #include #include #include #include #include #include #include //内核延时 忙等 #include #include // 包含 GPIO 支持 #define ERROR_PRINT(x) do{ \ printk(KERN_ERR x " is error!\n"); \ return -1; \ }while(0) #define DEVICE_NAME "dht11" #define HIGH 1 // 代表高电平 #define LOW 0 // 代表低电平 #define DHT_MAXCOUNT 32000 // 计数超时的最大值 // dht11响应信号 + 数据信号,一共 1 + 40 次低高电平变化 #define DHT_PULSES 41 // 低高电平变化中,低和高各变化一次,所以电平维持计数记录需要 (1 + 40) * 2 次 #define DHT_PULSES_SAMPLING_COUNT (DHT_PULSES * 2) struct gpio_desc* dht11_gpiod; //DHT11_GPIO编号 struct dht11_info { dev_t devt; // 实际上就是 u32 类型 用于记录主设备号 struct miscdevice dht11_device; // misc设备 用于创建 /dev/ 下的节点 }; struct dht11_info dht11; /* * dht11 采样时序 * 1. 起始信号 * 主机发送采集信号,此时 GPIO 由 CPU 控制,设置为输出状态 * 主机由高电平-->低电平(至少18ms)--->高电平(20-40us) * * 2. DHT响应信号,此时 GPIO 由 dht11 控制,所以应该设置为输入状态 * 低电平(80us)--->高电平(80us) * * 3. DHT数据传输信号,共40bit,即5字节数据开始传输,先发送的bit是字节里的高位(MSB) * 数据开始 低电平(50us) ---> 数据0 高电平 26us-28us * 数据开始 低电平(50us) ---> 数据1 高电平 70us */ static void end(void) { // 建议结束时,GPIO输出高,下次采样成功率高点 gpiod_direction_output(dht11_gpiod, HIGH); } ssize_t dht11_read(struct file *file, char __user * user_buf, size_t size, loff_t * loff) { int err; unsigned char dht11_data[5]; unsigned int pulse_counts[DHT_PULSES_SAMPLING_COUNT]; volatile unsigned int i; unsigned int pulse_low_average; int data_index; /* * 下列算法解析: * * DHT_PULSES_SAMPLING_COUNT 为 82,因为从DHT11的响应信号开始,低高电平一共有82次 * 低 80us 高 80us 为2次,这是一开始的响应信号 * 低 50us 高 28us 或 70us,表示bit为0或1,这个有40次信号 * 所以 (1 + 40) * 2 = 82 * 数组输出化 0,因为这个数组是将电平维持时间通过计数形式表达 * 如果电平维持时间为 80us,那么 pulse_counts[i] 就会比较大 * 如果电平维持时间为 28us,那么 pulse_counts[i] 就会相比于80us时比较小 * * 对于代表 低电平维持了 50us 的那些pulse_counts[i]而言(从i=2,i += 2) * 由于维持的时间都相同,所以计数值都差不多,把这40个低电平50us的计数求平均数 * 那么这个平均数就代表 50us 的计数 * 当分辨bit是0还是1时,就需要看低电平50us后的高电平维持的时间,也就是计数值。 * 当计数值小于上面求的平均数,代表着高电平维持的时间少于50us,也就是在了 28us 左右,那么当前bit为0。 * 当计数值大于上面求的平均数,代表着低电平维持的时间多于50us,也就是在了 70us 左右,那么当前bit为1。 * * 这样就能得到5个字节的数据。 */ for (i = 0; i < DHT_PULSES_SAMPLING_COUNT; ++i) pulse_counts[i] = 0; for (i = 0; i < 5; ++i) dht11_data[i] = 0; // 实现 1. 起始信号 的时序 gpiod_direction_output(dht11_gpiod, LOW); mdelay(20); //拉低20ms 要求是(最小18ms) gpiod_direction_output(dht11_gpiod, HIGH); udelay(40); //拉高40us 要求是(20-40us) // 之后是DHT11响应信号的数据传输信号,所以 GPIO 要设置为输入状态 gpiod_direction_input(dht11_gpiod); err = 0; for (i = 0; i < DHT_PULSES_SAMPLING_COUNT; i += 2) { // 根据前面所说,响应信号和数据传输信号,都是以低电平开始,高电平结束 // 这里就是前面说的计数,将电平维持时间转化为计数值 while(gpiod_get_value(dht11_gpiod) == LOW) { if (++pulse_counts[i] >= DHT_MAXCOUNT) { // failed err = 1; break; } } if (err) break; while(gpiod_get_value(dht11_gpiod) == HIGH) { if (++pulse_counts[i + 1] >= DHT_MAXCOUNT) { // failed err = 1; break; } } if (err) break; } // 计数过大(DHT_MAXCOUNT),代表DHT11数据信号采样(计数)失败 if (err) { // 计数失败会很常见,所以这个打印可以注释掉 pr_info("dht11 lost response! (GPIO : %d)\n", desc_to_gpio(dht11_gpiod)); end(); return -16; } /* * 开始统计收集到的计数值,此时已经和DHT11没关系了 */ // 跳过一开始的 80us 低 80us 高的响应信号 所以 i 不会等于0和1 // 算出低电平的维持50us时的平均值 pulse_low_average = 0; for (i = 2; i < DHT_PULSES_SAMPLING_COUNT; i += 2) pulse_low_average += pulse_counts[i]; pulse_low_average /= DHT_PULSES; //平均值 // 根据平均值,区分 bit 0 还是 bit 1 for (i = 3; i < DHT_PULSES_SAMPLING_COUNT; i += 2) { /* * 3 5 7 9 11 13 15 17 * 19 21 23 25 27 29 31 33 * 35 37 39 41 43 45 47 49 * 51 53 55 57 59 61 63 65 * 67 69 71 73 75 77 79 81 * * 要算当前的 pulse_counts[i] 属于那个字节,也就是 data_index * i从3开始,所以先减去3 * 8bit 一个字节,但是 pulse_counts[i - 1] pulse_counts[i] 构成一个bit * 所以 8 * 2 = 16,所以要除以16,这样就算到了 data_index */ data_index = (i - 3) / 16; dht11_data[data_index] <<= 1; // 前面说过,先来的bit属于高位,所以先左移一位,将高位向前移动 // 高于平均值代表是 bit1 if (pulse_counts[i] >= pulse_low_average) dht11_data[data_index] |= 1; // 由于前面已经左移一位,所以第 0 位一定是 0,所以遇到bit0,无需再置位 } if (copy_to_user(user_buf, dht11_data, size) != 0) ERROR_PRINT("dht11 date copy_to_user failed"); end(); return 0; } ssize_t dht11_write(struct file *file, const char __user *user_buf, size_t size, loff_t *loff) { int ret; unsigned int gpio; struct gpio_desc* new_gpiod_desc; // 检查用户空间的buffer是否足够大以存储一个无符号整型也就是4字节 if (size < sizeof(gpio)) { printk(KERN_WARNING "dht11_write: buffer too small\n"); return -EINVAL; } // 从用户空间复制数据到内核空间 不能直接 memcpy ret = copy_from_user(&gpio, user_buf, sizeof(gpio)); if (ret != 0) { printk(KERN_ERR "dht11_write: failed to copy data from user space\n"); return -EFAULT; } // 根据 GPIO 编号获取 gpiod_desc new_gpiod_desc = gpio_to_desc(gpio); if (new_gpiod_desc < 0) return -1; dht11_gpiod = new_gpiod_desc; // 更新 gpiod_desc 之后 read 调用时就会操作新的gpio /* 返回写入的字节数 */ return sizeof(gpio); } int dht11_open(struct inode * inode, struct file * file) { return 0; } int dht11_release(struct inode * inode, struct file *file) { return 0; } /* * 因为最终目的是为了对 /dev/dht11 节点进行io * 所以需要实现这些内核接口,实现了才能进行操作 * open 和 release 对应 open 和 close 函数,但没有什么初始化和关闭操作,所以函数里面无需实现代码 * * read 对应 read 函数,是为了读取5个字节的采样数据,也就是触发一次采样 * * write 对应 write 函数,对于dht11而言,没什么要写入的,但是本驱动实现的是改变操作dht11的gpio的效果 * 比如一开始设备树里面注册的是gpio 88去控制的,但是实际使用时,改成了gpio 61去控制 * 那么write写入61这个数字所在int的指针,即可修改gpiod_desc(dht11_gpiod)为 gpio 61 的gpiod_desc * 后续的read函数也是操作gpio 61这个引脚。 */ struct file_operations fops = { .open = dht11_open, .release = dht11_release, .read = dht11_read, .write = dht11_write, .owner = THIS_MODULE }; /* * 字符设备管理代码 * 下面的代码是为了向内核注册驱动和设备 * * 这两个是为了注册驱动和注销驱动 * dht11_init * dht11_exit * * 这两个是为了声明设备和删除设备 * dht11_probe * dht11_remove * * 有了驱动,才会去找设备 * 而找到设备靠的是设备树里面有没有一个设备的 compatible 字段和 * of_match_table 里面 compatible 字段匹配 * 如果匹配了,就会调用 dht11_probe */ int dht11_probe(struct platform_device *pdev) { int ret; /* * dht11-gpios = <&gpio 61 GPIO_ACTIVE_HIGH>; * 注意 GPIO_ACTIVE_HIGH 这个也要关注 * 如果 GPIO的高电平对应逻辑值 1,那么填 GPIO_ACTIVE_HIGH * 否则 填GPIO_ACTIVE_LOW */ dht11_gpiod = devm_gpiod_get_optional(&pdev->dev, DEVICE_NAME, 0); if (dht11_gpiod <= 0) ERROR_PRINT("devm_gpiod_get_optional"); /*注册字符设备驱动*/ // 分配设备号 ret = alloc_chrdev_region(&dht11.devt, 0, 1, DEVICE_NAME); if (ret != 0) ERROR_PRINT("alloc_chrdev_region"); // 注册 misc 设备,在/dev/下生成 dht11 节点(文件) dht11.dht11_device.minor = MISC_DYNAMIC_MINOR; // 动态分配次设备号 dht11.dht11_device.name = "dht11"; dht11.dht11_device.fops = &fops; ret = misc_register(&dht11.dht11_device); // 这样 /dev 下就会生成对应节点 if (ret < 0) { unregister_chrdev_region(dht11.devt, 1); return ret; } printk(KERN_INFO "dht11 probe success! (GPIO: %d)\n", desc_to_gpio(dht11_gpiod)); return 0; } int dht11_remove(struct platform_device *pdev) { // 注销 misc 设备 misc_deregister(&dht11.dht11_device); // 注销设备号 unregister_chrdev_region(dht11.devt, 1); // 释放 GPIO 描述符 gpiod_put(dht11_gpiod); return 0; } struct of_device_id of_match_table[] = { {.compatible = "dht11-sensor"}, {} }; // 设备树匹配 struct platform_driver pdrv = { .probe = dht11_probe, .remove = dht11_remove, .driver = { .name = DEVICE_NAME, .owner = THIS_MODULE, .of_match_table = of_match_table }, }; static int dht11_init(void) { // 注册platform_driver int ret = platform_driver_register(&pdrv); if (ret != 0) ERROR_PRINT("platform driver register"); return 0; } static void dht11_exit(void) { /*注销platform_driver*/ platform_driver_unregister(&pdrv); } module_init(dht11_init); module_exit(dht11_exit); MODULE_LICENSE("GPL");