Files
maoxiaochuan 9585ff23df update to e4af36c7270
1.add 2k300_gongkong board support
2.update dht11 driver
3.support EC200A-CN 4G model
4.goodix:reset: reset timing fixing
5.other bugs fix
2024-12-18 20:29:54 +08:00

343 lines
11 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*************************************************************************
> 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<linux/init.h>
#include<linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/delay.h> //内核延时 忙等
#include <linux/uaccess.h>
#include <linux/gpio.h> // 包含 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");