AM335X——1-Wire和IrDA驱动

版权声明:本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可,欢迎转载,但转载请注明来自hceng blog(www.hceng.cn),并保持转载后文章内容的完整。本人保留所有版权相关权利。 https://blog.csdn.net/hceng_linux/article/details/89944877

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/02/15/AM335X——1-Wire和IrDA驱动/#more

记录DS18B20温度传感器、DH11温湿度传感器、红外遥控驱动。

1-wire(单总线协议)就是只使用一条线(GPIO)实现时钟/数据的双向传输。
DS18B20是比较标准的1-wire协议,可以通过逻辑分析仪显示出含义,DH11不是很标准(专利原因?),需要自己参考芯片手册理解含义。
但它们原理都差不多,且都对时序要求比较高(us级的延时)。

IrDA也是一根线,原理也差不多,因此也把它放在一起记录。
AM335X没有1-wire的控制器,因此使用GPIO模拟。

1. DS18B20

1.1 基础知识

1.1.1 性能参数

分辨率:9~12位可编程(上电默认12位)
精度:±0.5℃(在-10~+85℃)
量程:-55°C ~ 125°C
转换时间:750ms(12位分辨率)

1.1.2 温度数据格式

一次返回的温度数据为16位,前五位表示正负,中间七位表示整数部分,最低四位为小数部分;
温度传感器的分辨率为用户可编程的9、10、11或12位, 分别对应0.5℃、0.25℃、0.125℃和 0.0625℃。
因此温度计算结果为:(正负)整数部分+小数部分*分辨率

0000 0000 1010 0010为例,前五位为0,即温度为零上;中间七位0001010,即温度整数部分为10;最后四位0010,即温度小数部分为2*0.625=0.125,因此温度为+10.125

1.1.3 64Bits只读数据

低八位用于CRC校验,中间48位是DS18B20唯一序列号,高八位是产品系列号(为28h)

1.1.4 操作步骤

每次对DS18B20操作,都必须严格按照以下步骤:
1、初始化;
2、ROM指令;
3、功能指令;

1.1.5 ROM指令和功能指令

ROM指令
指令名称 指令代码 指令功能
读ROM 33H 读ROM中64Bits的只读数据
ROM匹配 55H 发出此命令后接着发64Bits的ROM编码,使单总线上与编码匹配的DS18B3做出响应
搜索ROM F0H 用于确定挂在总线上的DS18B20的个数和识别64Bits的ROM地址
跳过ROM CCH 忽略64BitsROM只读数据,接着发出功能指令,进行温度转换或读取温度
警报搜索 ECH 只有温度超过设定上限或下限的DS18B20才做出响应
功能指令
指令名称 指令代码 指令功能
温度转换 44H 启动温度转换,结果将保存在内部RAM中
读取温度 BEH 读取内部RAM中的温度数据
设置报警温度 4EH 设置上或下限报警温度指令,接着应发送两字节数据
保存报警温度 48H 将RAM中的报警温度数据,复制到EEPROM中保存
恢复RAM B8H 将EEPROM中的报警温度数据恢复到RAM
读供电方式 B4H 寄生供电返回0,外界电源供电返回1

1.1.5 初始化时序

初始化DS18B20的时序如上,先拉低480us,然后拉高释放总线,随后在60-240us内,DS18B20将会拉低总线进行响应。
此时检测总线释放被拉低既可判断出DS18B20是否初始化成功。

1.1.6 读写时序

上面的图是写0或1的时序:
如果写0,拉低至少60us(写周期为60-120us)即可;如果写1,先拉低至少1us,然后拉高,整个写周期至少为60us即可。

下面的图是读0或1的时序:
先拉低至少1us,随后读取电平,如果为0,即读到的数据是0,如果为1,即可读到的数据是1。
整个过程必须在15us内完成,15us后引脚都会被拉高。

1.2 内核驱动

内核中自带1-Wire和DS18B20的驱动。
drivers/w1/masters/w1-gpio.c是单总线的IO操作方法,用于模拟单总线时序;
drivers/w1/slaves/w1_therm.c是DS18B20的寄存器操作方法,和IO时序无关;

1.2.1 加入内核

{% codeblock lang:Makefile %}
Device Drivers —>
<> Dallas’s 1-wire support —>
[
] Userspace communication over connector
1-wire Bus Masters —>
<> GPIO 1-wire busmaster
1-wire Slaves —>
<
> Thermal family implementation
{% endcodeblock %}

1.2.2 修改设备树

{% codeblock lang:dts %}
onewire@0 {
compatible = “w1-gpio”;
gpios = <&gpio0 13 0>;
//pinctrl-0 = <&ds18b20_dq_pin>;
};
{% endcodeblock %}

1.2.3 应用测试

cat /sys/bus/w1/drivers/w1_slave_driver/28-01d58c07010c/w1_slave

1.3 自己驱动

这次驱动,吸取了前面AM335X——hwmon和input子系统的经验。

1.3.1 完整代码

{% codeblock lang:c %}
//cat /sys/class/hwmon/hwmon0/device/temperature

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/irqflags.h>

struct ds18b20 {
struct device *hwmon_dev;
struct mutex lock;
int dq_pin;
u8 value[2];
u8 family_code;
u8 serial_num[6];
u8 crc;
};
static struct ds18b20 ds;

static int ds18b20_init(void)
{
int ret = 1;

mutex_lock(&ds.lock);

gpio_direction_output(ds.dq_pin, 1);
udelay(2);
gpio_direction_output(ds.dq_pin, 0); //Low level 480us for reset
udelay(480);                      
gpio_direction_output(ds.dq_pin, 1); //Pull high release bus
udelay(60);

gpio_direction_input(ds.dq_pin); //Read response pulse
ret = gpio_get_value(ds.dq_pin);
udelay(240);  //Waiting for the corresponding end

mutex_unlock(&ds.lock);

return ret;

}

static void write_byte(unsigned char data)
{
int i = 0;
unsigned long flags;

mutex_lock(&ds.lock);

local_irq_save(flags); //Save interrupt
//local_irq_disable(); //Close all interrupts

gpio_direction_output(ds.dq_pin, 1); 

for (i = 0; i < 8; i ++)
{
    gpio_direction_output(ds.dq_pin, 1); 
    udelay(2);    
    gpio_direction_output(ds.dq_pin, 0); //Start at a low level greater than 1us
    udelay(5);
    
    gpio_direction_output(ds.dq_pin, data & 0x01);  
    udelay(60); //Write cycle is greater than 60us
    
    data >>= 1;   
}
local_irq_restore(flags); //Recovery interrupt
//local_irq_enable(); //Open all interrupts

mutex_unlock(&ds.lock); 

}

static unsigned char read_byte(void)
{
int i;
unsigned long flags;
unsigned char data = 0;

mutex_lock(&ds.lock);

local_irq_save(flags);
//local_irq_disable();

for (i = 0; i < 8; i++)    
{    
    gpio_direction_output(ds.dq_pin, 1);    
    udelay(2);    
    gpio_direction_output(ds.dq_pin, 0); //Start at a low level greater than 1us
    udelay(5);    
    
    gpio_direction_output(ds.dq_pin, 1); //Pull high release bus 
    udelay(1); 
  
    data >>= 1;   
    gpio_direction_input(ds.dq_pin);
    if (gpio_get_value(ds.dq_pin)) //Must be read within 15us after being pulled low
        data |= 0x80;  
  
    udelay(60); //Read cycle is greater than 60us;   
}    

local_irq_restore(flags); 
//local_irq_enable();

mutex_unlock(&ds.lock);

return data;    

}

static ssize_t ds18b20_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
int ret = -1;
unsigned int m, n;

ret = ds18b20_init(); //Reset initialization of the DS18B20 before each read and write
if(0 != ret)
{
    printk(KERN_ERR"%s ds18b20_init error.\n",__func__);
    return -1;    
}
write_byte(0xCC); //Skip commands for ROM operations
write_byte(0x44); //Start the DS18B20 acquisition temperature


ret = ds18b20_init(); 
if(0 != ret)
{
    printk(KERN_ERR"%s ds18b20_init error.\n",__func__);
    return -1;    
}
write_byte(0xCC); //Skip commands for ROM operations    
write_byte(0xBE); //Read the data in the DS18B20 register

ds.value[0] = read_byte(); //Low byte
ds.value[1] = read_byte(); //High byte

m = ((ds.value[1] & 0x07)<<4) + ((ds.value[0] & 0xF0)>>4); //Integer part(7 bits in the middle)
n = ds.value[0] & 0x0F; //Fractional part(lower 4 bits)

if(ds.value[1] & 0xF8)//The high 5 bits indicate positive and negative
    ret = sprintf(buf, "TEMP: -%d.%02d\n", m, n*625);
else
    ret = sprintf(buf, "TEMP: %d.%02d\n", m, n*625);;   

return ret;

}

static ssize_t ds18b20_get_sensor_info(struct device *dev, struct device_attribute *devattr, char *buf)
{
int i, ret = -1;
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);

ret = ds18b20_init();  
if(0 != ret)
{
    printk(KERN_ERR"%s ds18b20_init error.\n",__func__);
    return -1;    
}

write_byte(0x33); //Read ROM command

ds.family_code = read_byte(); //The lower 8 bits is the family code(28h)

for (i=0; i<6; i++) //The middle 48 bits are the unique serial number
    ds.serial_num[i] = read_byte();


ds.crc = read_byte(); //The high 8 bits are the CRC check data.

switch(attr->index)
{
    case 1:
        ret = sprintf(buf, "0x%x\n", ds.family_code);
        break;
    case 2:
        ret = sprintf(buf, "%02d%02d%02d%02d%02d%02d\n", ds.serial_num[0],ds.serial_num[1], \
                      ds.serial_num[2], ds.serial_num[3],ds.serial_num[4],ds.serial_num[5]);
        break;
    case 3:
        ret = sprintf(buf, "%d\n", ds.crc);
        break;
    default:
        break;
}

return ret;

}

static struct sensor_device_attribute ds18b20_temp_attr[] = {
SENSOR_ATTR(temperature, S_IRUGO, ds18b20_get_sensor_value, NULL, 0),
SENSOR_ATTR(family_code, S_IRUGO, ds18b20_get_sensor_info, NULL, 1),
SENSOR_ATTR(serial_num, S_IRUGO, ds18b20_get_sensor_info, NULL, 2),
SENSOR_ATTR(crc, S_IRUGO, ds18b20_get_sensor_info, NULL, 3),
};

static int ds18b20_probe(struct platform_device *pdev)
{
int status, i;

//printk(KERN_INFO"%s OK.\n",__func__);

ds.dq_pin = of_get_named_gpio(pdev->dev.of_node, "dq-gpio", 0);
status = gpio_request(ds.dq_pin, "ds18b20_dq_pin");   
if (status)
{
    dev_err(&pdev->dev, "gpio_request() fail.\n");
    return -EBUSY;
}   

mutex_init(&ds.lock);
mutex_lock(&ds.lock);

dev_set_drvdata(&pdev->dev, &ds);

for (i=0; i<(sizeof(ds18b20_temp_attr)/sizeof(struct sensor_device_attribute)); i++)
{
    status = device_create_file(&pdev->dev, &ds18b20_temp_attr[i].dev_attr);
    if (status)
    {
        dev_err(&pdev->dev, "device_create_file() failed.\n");
        goto fail_crete_file;
    }
}

ds.hwmon_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(ds.hwmon_dev))
{
    dev_err(&pdev->dev, "hwmon_device_register() fail.\n");
    status = PTR_ERR(ds.hwmon_dev);
    goto fail_device_register;
}

mutex_unlock(&ds.lock);
return 0;

fail_device_register:
hwmon_device_unregister(ds.hwmon_dev);
fail_crete_file:
for (i–; i>=0; i–)
device_remove_file(&pdev->dev, &ds18b20_temp_attr[i].dev_attr);

dev_set_drvdata(&pdev->dev, NULL);
gpio_free(ds.dq_pin);
mutex_unlock(&ds.lock);

return status;

}

static int ds18b20_remove(struct platform_device *pdev)
{
int i;

mutex_lock(&ds.lock);

hwmon_device_unregister(ds.hwmon_dev);
for (i=0; i<(sizeof(ds18b20_temp_attr)/sizeof(struct sensor_device_attribute)); i++)
    device_remove_file(&pdev->dev, &ds18b20_temp_attr[i].dev_attr);
dev_set_drvdata(&pdev->dev, NULL);
gpio_free(ds.dq_pin);

mutex_unlock(&ds.lock);

return 0;

}

static const struct of_device_id ds18b20_of_match[] = {
{ .compatible = “maxim,ds18b20”, .data = NULL },
{ /* sentinel */ }

};
static struct platform_driver ds18b20_drv = {
.probe = ds18b20_probe,
.remove = ds18b20_remove,
.driver = {
.name = “ds18b20_drv”,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(ds18b20_of_match),
},
};

static int ds18b20_drv_init(void)
{
//printk(KERN_INFO"%s OK.\n",func);
return platform_driver_register(&ds18b20_drv);
}

static void ds18b20_drv_exit(void)
{
//printk(KERN_INFO"%s OK.\n",func);
platform_driver_unregister(&ds18b20_drv);
}

module_init(ds18b20_drv_init);
module_exit(ds18b20_drv_exit);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng [email protected]”);
MODULE_DESCRIPTION(“TI am335x board ds18b20 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}

驱动内容比较简单,严格按照前面的时序操作即可。
值得一提的是,因为时序是us级的,如果在发送时序过程中,产生中断就可能导致时序出错,因此在读写函数里加入local_irq_save(flags);local_irq_restore(flags);临时开/关中断。

1.3.2 设备树

{% codeblock lang:dts %}
ds18b20 {
compatible = “maxim,ds18b20”;
dq-gpio = <&gpio0 13 0>;
pinctrl-0 = <&ds18b20_dq_pin>;
};

……

&am33xx_pinmux {
ds18b20_dq_pin: ds18b20_dq_pin0 {
pinctrl-single,pins = <
0x17C (PIN_INPUT_PULLDOWN | MUX_MODE7) /* conf_uart1_rtsn.gpio0_13 */
>;
};
};
{% endcodeblock %}

1.3.3 应用测试

2. DH11

2.1 基础知识

2.1.1 性能参数

  • 温度
    分辨率:1°C
    精度:±2℃
    检测范围:-20°C ~ 60°C

  • 湿度
    分辨率:1%RH
    精度:±5%RH (0~50°C)
    检测范围:5%RH ~ 95%RH (25°C)

采样周期间隔不得低于1秒钟。

可以看到无论是测量温度的精度还是范围、采样周期,都比较烂。。

2.1.2 数据格式

一次返回的数据长度为40Bits,高位在前。
8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和
可以看到数据分小数部分和整数部分,当前小数部分用于以后扩展,现读出为零。

另外还有数据校验,如果"8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据"所得结果的末8位,等于"8bit校验和"即数据正确。

2.1.3 开始时序

开始时,主机拉低至少18ms,随后拉高20-40us,然后释放总线,完成开始信号。
DH11随后拉低80us,再拉高80us,最后发送40Bits数据。

2.1.4 读时序

先读到50us的低电平,随后如果是26-28us的高电平即收到的是数据0,如果是70us的高电平即收到的数据是1。

2.2 内核驱动

本来以为内核不含DH11驱动,后面又看到了,在iio子系统里面,路径为:linux-4.1.18/drivers/iio/humidity/dht11.c

2.2.1 内核代码

{% codeblock lang:c %}
/*

  • DHT11/DHT22 bit banging GPIO driver
  • Copyright © Harald Geyer [email protected]
  • This program is free software; you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation; either version 2 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
    */

#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/sysfs.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/wait.h>
#include <linux/bitops.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#include <linux/iio/iio.h>

#define DRIVER_NAME “dht11”

#define DHT11_DATA_VALID_TIME 2000000000 /* 2s in ns */

#define DHT11_EDGES_PREAMBLE 2
#define DHT11_BITS_PER_READ 40
/*

  • Note that when reading the sensor actually 84 edges are detected, but
  • since the last edge is not significant, we only store 83:
    /
    #define DHT11_EDGES_PER_READ (2
    DHT11_BITS_PER_READ + DHT11_EDGES_PREAMBLE + 1)

/* Data transmission timing (nano seconds) /
#define DHT11_START_TRANSMISSION 18 /
ms */
#define DHT11_SENSOR_RESPONSE 80000
#define DHT11_START_BIT 50000
#define DHT11_DATA_BIT_LOW 27000
#define DHT11_DATA_BIT_HIGH 70000

struct dht11 {
struct device *dev;

int             gpio;
int             irq;

struct completion       completion;
struct mutex            lock;

s64             timestamp;
int             temperature;
int             humidity;

/* num_edges: -1 means "no transmission in progress" */
int             num_edges;
struct {s64 ts; int value; }    edges[DHT11_EDGES_PER_READ];

};

static unsigned char dht11_decode_byte(int *timing, int threshold)
{
unsigned char ret = 0;
int i;

for (i = 0; i < 8; ++i) {
    ret <<= 1;
    if (timing[i] >= threshold)
        ++ret;
}

return ret;

}

static int dht11_decode(struct dht11 *dht11, int offset)
{
int i, t, timing[DHT11_BITS_PER_READ], threshold,
timeres = DHT11_SENSOR_RESPONSE;
unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum;

/* Calculate timestamp resolution */
for (i = 1; i < dht11->num_edges; ++i) {
    t = dht11->edges[i].ts - dht11->edges[i-1].ts;
    if (t > 0 && t < timeres)
        timeres = t;
}
if (2*timeres > DHT11_DATA_BIT_HIGH) {
    pr_err("dht11: timeresolution %d too bad for decoding\n",
        timeres);
    return -EIO;
}
threshold = DHT11_DATA_BIT_HIGH / timeres;
if (DHT11_DATA_BIT_LOW/timeres + 1 >= threshold)
    pr_err("dht11: WARNING: decoding ambiguous\n");

/* scale down with timeres and check validity */
for (i = 0; i < DHT11_BITS_PER_READ; ++i) {
    t = dht11->edges[offset + 2*i + 2].ts -
        dht11->edges[offset + 2*i + 1].ts;
    if (!dht11->edges[offset + 2*i + 1].value)
        return -EIO;  /* lost synchronisation */
    timing[i] = t / timeres;
}

hum_int = dht11_decode_byte(timing, threshold);
hum_dec = dht11_decode_byte(&timing[8], threshold);
temp_int = dht11_decode_byte(&timing[16], threshold);
temp_dec = dht11_decode_byte(&timing[24], threshold);
checksum = dht11_decode_byte(&timing[32], threshold);

if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum)
    return -EIO;

dht11->timestamp = iio_get_time_ns();
if (hum_int < 20) {  /* DHT22 */
    dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) *
                ((temp_int & 0x80) ? -100 : 100);
    dht11->humidity = ((hum_int << 8) + hum_dec) * 100;
} else if (temp_dec == 0 && hum_dec == 0) {  /* DHT11 */
    dht11->temperature = temp_int * 1000;
    dht11->humidity = hum_int * 1000;
} else {
    dev_err(dht11->dev,
        "Don't know how to decode data: %d %d %d %d\n",
        hum_int, hum_dec, temp_int, temp_dec);
    return -EIO;
}

return 0;

}

/*

  • IRQ handler called on GPIO edges
    */
    static irqreturn_t dht11_handle_irq(int irq, void *data)
    {
    struct iio_dev *iio = data;
    struct dht11 *dht11 = iio_priv(iio);

    /* TODO: Consider making the handler safe for IRQ sharing */
    if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) {
    dht11->edges[dht11->num_edges].ts = iio_get_time_ns();
    dht11->edges[dht11->num_edges++].value =
    gpio_get_value(dht11->gpio);

     if (dht11->num_edges >= DHT11_EDGES_PER_READ)
         complete(&dht11->completion);
    

    }

    return IRQ_HANDLED;
    }

static int dht11_read_raw(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long m)
{
struct dht11 *dht11 = iio_priv(iio_dev);
int ret;

mutex_lock(&dht11->lock);
if (dht11->timestamp + DHT11_DATA_VALID_TIME < iio_get_time_ns()) {
    reinit_completion(&dht11->completion);

    dht11->num_edges = 0;
    ret = gpio_direction_output(dht11->gpio, 0);
    if (ret)
        goto err;
    msleep(DHT11_START_TRANSMISSION);
    ret = gpio_direction_input(dht11->gpio);
    if (ret)
        goto err;

    ret = request_irq(dht11->irq, dht11_handle_irq,
              IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
              iio_dev->name, iio_dev);
    if (ret)
        goto err;

    ret = wait_for_completion_killable_timeout(&dht11->completion,
                             HZ);

    free_irq(dht11->irq, iio_dev);

    if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) {
        dev_err(&iio_dev->dev,
                "Only %d signal edges detected\n",
                dht11->num_edges);
        ret = -ETIMEDOUT;
    }
    if (ret < 0)
        goto err;

    ret = dht11_decode(dht11,
            dht11->num_edges == DHT11_EDGES_PER_READ ?
                DHT11_EDGES_PREAMBLE :
                DHT11_EDGES_PREAMBLE - 2);
    if (ret)
        goto err;
}

ret = IIO_VAL_INT;
if (chan->type == IIO_TEMP)
    *val = dht11->temperature;
else if (chan->type == IIO_HUMIDITYRELATIVE)
    *val = dht11->humidity;
else
    ret = -EINVAL;

err:
dht11->num_edges = -1;
mutex_unlock(&dht11->lock);
return ret;
}

static const struct iio_info dht11_iio_info = {
.driver_module = THIS_MODULE,
.read_raw = dht11_read_raw,
};

static const struct iio_chan_spec dht11_chan_spec[] = {
{ .type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), },
{ .type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), }
};

static const struct of_device_id dht11_dt_ids[] = {
{ .compatible = “dht11”, },
{ }
};
MODULE_DEVICE_TABLE(of, dht11_dt_ids);

static int dht11_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct dht11 *dht11;
struct iio_dev *iio;
int ret;

iio = devm_iio_device_alloc(dev, sizeof(*dht11));
if (!iio) {
    dev_err(dev, "Failed to allocate IIO device\n");
    return -ENOMEM;
}

dht11 = iio_priv(iio);
dht11->dev = dev;

dht11->gpio = ret = of_get_gpio(node, 0);
if (ret < 0)
    return ret;
ret = devm_gpio_request_one(dev, dht11->gpio, GPIOF_IN, pdev->name);
if (ret)
    return ret;

dht11->irq = gpio_to_irq(dht11->gpio);
if (dht11->irq < 0) {
    dev_err(dev, "GPIO %d has no interrupt\n", dht11->gpio);
    return -EINVAL;
}

dht11->timestamp = iio_get_time_ns() - DHT11_DATA_VALID_TIME - 1;
dht11->num_edges = -1;

platform_set_drvdata(pdev, iio);

init_completion(&dht11->completion);
mutex_init(&dht11->lock);
iio->name = pdev->name;
iio->dev.parent = &pdev->dev;
iio->info = &dht11_iio_info;
iio->modes = INDIO_DIRECT_MODE;
iio->channels = dht11_chan_spec;
iio->num_channels = ARRAY_SIZE(dht11_chan_spec);

return devm_iio_device_register(dev, iio);

}

static struct platform_driver dht11_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = dht11_dt_ids,
},
.probe = dht11_probe,
};

module_platform_driver(dht11_driver);

MODULE_AUTHOR(“Harald Geyer [email protected]”);
MODULE_DESCRIPTION(“DHT11 humidity/temperature sensor driver”);
MODULE_LICENSE(“GPL v2”);
{% endcodeblock %}

2.3 自己驱动

2.3.1 完整代码

{% codeblock lang:c %}
//cat /sys/class/hwmon/hwmon0/device/temperature

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>

#include <linux/irqflags.h>
#include <linux/wait.h>
#include <linux/sched.h>

struct dh11 {
struct device *hwmon_dev;
struct mutex lock;
int da_pin;
u8 value[5];
u8 humdity;
u8 temperature;

struct timer_list timer;
struct work_struct work;

};
static struct dh11 dh;

static unsigned char read_byte(void)
{
unsigned char i, count, dat = 0;
unsigned long flags;

local_irq_save(flags); //Save interrupt

for(i=0; i<8; i++)          
{      
    count = 0;
    while(0 == gpio_get_value(dh.da_pin)) //Waiting for 50us low level end
    {
        udelay(5);
        count ++;
        if (count > 20)
            goto time_out;
    }
  
    udelay(30); //Delay 30us, if it is still high, the data is 1, otherwise it is 0.
  
    dat <<= 1;                   
  
    if(gpio_get_value(dh.da_pin))    
        dat += 1;
     
    while(gpio_get_value(dh.da_pin)) //Waiting low level end
    {
        udelay(5);
        count ++;
        if (count > 20)
            goto time_out;
    }
}  

local_irq_restore(flags); 
return dat;

time_out:
local_irq_restore(flags);
return 0;
}

//Work queue callback function for read DH11
static void dh11_work_callback(struct work_struct *work)
{
int i, count;

mutex_lock(&dh.lock);

//Start signal
gpio_direction_output(dh.da_pin, 1);
udelay(2);
gpio_direction_output(dh.da_pin, 0);
mdelay(20);  //Low level hold time cannot be less than 18ms
gpio_direction_output(dh.da_pin, 1); //Pull up 20-40us
udelay(40);    

gpio_direction_input(dh.da_pin);
if (0 == gpio_get_value(dh.da_pin)) //Read response signal
{
    count = 0;
    while(0 == gpio_get_value(dh.da_pin)) //Waiting for the response signal to end(80us)   
    {
        udelay(5);
        count ++;
        if (count > 20)
            goto time_out;
    }
    
    count = 0;
    while(1 == gpio_get_value(dh.da_pin)) //Waiting for DH11 to pull up end(80us)
    {
        udelay(5);
        count ++;
        if (count > 20)
            goto time_out;
    }

    for (i=0; i<5; i++) //Start reading 40 bits of data
        dh.value[i] = read_byte();
}
else
    printk(KERN_WARNING"DH11 response error.\n"); 

//checksum
if ((dh.value[0] + dh.value[1] + dh.value[2] + dh.value[3]) == dh.value[4]) 
{
    dh.humdity = dh.value[0];
    dh.temperature = dh.value[2];
}
else
    printk(KERN_WARNING"DHT11 checksum error.\n"); 

mutex_unlock(&dh.lock); 
return;

time_out:
printk(KERN_WARNING"DH11 timeout error.\n");
mutex_unlock(&dh.lock);
return;
}

//Timercallback function for callback work queue
static void dh11_timer_callback(unsigned long data)
{
schedule_work(&dh.work);
mod_timer(&dh.timer, jiffies + (1200 * HZ/1000)); //Modify a timer’s timeout
}

static ssize_t dh11_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
int ret = -1;
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);

switch(attr->index)
{
    case 0:
        ret = sprintf(buf, "%d\n", dh.humdity);
        break;
    case 1:
        ret = sprintf(buf, "%d\n", dh.temperature);
        break;
    default:
        break;
}

return ret;

}

static struct sensor_device_attribute dh11_temp_attr[] = {
SENSOR_ATTR(humdity, S_IRUGO, dh11_get_sensor_value, NULL, 0),
SENSOR_ATTR(temperature, S_IRUGO, dh11_get_sensor_value, NULL, 1),
};

static int dh11_probe(struct platform_device *pdev)
{
int status, i;

//printk(KERN_INFO"%s OK.\n",__func__);

dh.da_pin = of_get_named_gpio(pdev->dev.of_node, "da-gpio", 0);
status = gpio_request(dh.da_pin, "dh11_da_pin");   
if (status)
{
    dev_err(&pdev->dev, "gpio_request() fail.\n");
    return -EBUSY;
}   

mutex_init(&dh.lock);
mutex_lock(&dh.lock);

dev_set_drvdata(&pdev->dev, &dh);

for (i=0; i<(sizeof(dh11_temp_attr)/sizeof(struct sensor_device_attribute)); i++)
{
    status = device_create_file(&pdev->dev, &dh11_temp_attr[i].dev_attr);
    if (status)
    {
        dev_err(&pdev->dev, "device_create_file() failed.\n");
        goto fail_crete_file;
    }
}

dh.hwmon_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(dh.hwmon_dev))
{
    dev_err(&pdev->dev, "hwmon_device_register() fail.\n");
    status = PTR_ERR(dh.hwmon_dev);
    goto fail_device_register;
}

//Timer
init_timer(&dh.timer);
dh.timer.function = dh11_timer_callback;
dh.timer.expires = jiffies + (1200 * HZ/1000); //1.2s (must > 1s)
dh.timer.data = ((unsigned long)0);
add_timer(&dh.timer);
//Workqueue
INIT_WORK(&dh.work, dh11_work_callback);

mutex_unlock(&dh.lock);

return 0;

fail_device_register:
hwmon_device_unregister(dh.hwmon_dev);

fail_crete_file:
for (i–; i>=0; i–)
device_remove_file(&pdev->dev, &dh11_temp_attr[i].dev_attr);
dev_set_drvdata(&pdev->dev, NULL);
gpio_free(dh.da_pin);
del_timer(&dh.timer);
cancel_work_sync(&dh.work);

mutex_unlock(&dh.lock);

return status;

}

static int dh11_remove(struct platform_device *pdev)
{
int i;

mutex_lock(&dh.lock);

hwmon_device_unregister(dh.hwmon_dev);
for (i=0; i<(sizeof(dh11_temp_attr)/sizeof(struct sensor_device_attribute)); i++)
    device_remove_file(&pdev->dev, &dh11_temp_attr[i].dev_attr);
dev_set_drvdata(&pdev->dev, NULL);
gpio_free(dh.da_pin);
del_timer(&dh.timer);
cancel_work_sync(&dh.work);

mutex_unlock(&dh.lock);

return 0;

}

static const struct of_device_id dh11_of_match[] = {
{ .compatible = “aosong,dh11”, .data = NULL },
{ /* sentinel */ }

};
static struct platform_driver dh11_drv = {
.probe = dh11_probe,
.remove = dh11_remove,
.driver = {
.name = “dh11_drv”,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(dh11_of_match),
},
};

module_platform_driver(dh11_drv);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng [email protected]”);
MODULE_DESCRIPTION(“TI am335x board dh11 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}

因为DH11的采样周期长达1S,如果应用层稍微读快一点,就会报错比较明显。
因此,这里采取的方案是,驱动设置个定时器每隔一定时间,不断读取传感器,保存数据。应用层读取的是不久前驱动才缓存下的数据。

因此在probe()函数里,先设置了一个定时器:
{% codeblock lang:c %}
//Timer
init_timer(&dh.timer);
dh.timer.function = dh11_timer_callback;
dh.timer.expires = jiffies + (1200 * HZ/1000); //1.2s (must > 1s)
dh.timer.data = ((unsigned long)0);
add_timer(&dh.timer);
{% endcodeblock %}
定时器每隔1.2s调用dh11_timer_callback()函数。

再设置了一个工作队列:
{% codeblock lang:c %}
INIT_WORK(&dh.work, dh11_work_callback);
{% endcodeblock %}
dh11_work_callback()函数加入到工作队列里。

dh11_timer_callback()里使用schedule_work()读取DH11数据并重新设置定时器周期反复。

定时器、工作队列的补充知识参考博客

2.3.2 设备树

{% codeblock lang:dts %}
dh11 {
compatible = “aosong,dh11”;
da-gpio = <&gpio0 12 0>;
pinctrl-0 = <&dh11_da_pin>;
};

……

&am33xx_pinmux {
dh11_da_pin: dh11_da_pin0 {
pinctrl-single,pins = <
0x178 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* conf_uart1_ctsn.gpio0_12 */
>;
};
};

{% endcodeblock %}

2.3.3 应用测试

3. IrDA

3.1 基础知识

3.1.1 红外原理

当遥控器按下不同的按键时,遥控器上的红外发射头,会发出人眼看不到(可通过手机摄像头看到)的光波给模块上的红外接收端。
红外接收端收到光波后,会在IRD引脚上产生相应的电平,通过对电平解析,就知道是按的遥控器哪一个键。

3.1.2 数据格式

按键一次的接收到数据结构如下,可以分解成五部分:引导码/连发码、系统码1、系统码2、数据码、数据反码。

3.1.3 引导码/连发码

最开始的一部分是用来判断是否是连按操作表示信号开始,分为引导码和连发码。
如果是第一按下或非连按,就是先9ms低电平,再4.5ms的高电平,后面接32Bits数据;
如果一直按着该键不放,下一个周期就发送的是连发码,先9ms低电平,再只有2.25ms的高电平,后面接32Bits数据;

3.1.4 数据电平

数据0和1前面都是0.56ms的低电平,那么就是后面的高电平持续时间不同,0为0.56ms,1为1.685ms

3.2 内核驱动

内核中自带红外遥控器的驱动,但没有我使用的遥控器布局文件。
drivers/medi/rc/gpio-ir-recv.c是GPIO模拟红外遥控驱动。
drivers/media/rc/keymaps/下是遥控器键盘布局文件。

3.2.1 加入内核

{% codeblock lang:makefile %}
Device Drivers —>
<> Multimedia support —>
[
] Remote Controller support
[] Remote controller decoders (NEW) —>
[
] Remote Controller devices —>
<*> GPIO IR remote control
{% endcodeblock %}

3.2.2 添加键盘布局

{% codeblock lang:c [rc-hceng-nec.c] %}
#include <media/rc-map.h>
#include <linux/module.h>

static struct rc_map_table hceng_nec[] = {
{ 0x45, KEY_CHANNELDOWN},
{ 0x46, KEY_CHANNEL},
{ 0x47, KEY_CHANNELUP},
{ 0x44, KEY_PREVIOUS},
{ 0x40, KEY_NEXT},
{ 0x43, KEY_PLAYPAUSE}, //
{ 0x07, KEY_VOLUMEDOWN},
{ 0x15, KEY_VOLUMEUP},
{ 0x09, KEY_EQUAL},

{ 0x16, KEY_0},
{ 0x19, KEY_F1},
{ 0x0d, KEY_F2},

{ 0x0c, KEY_1},
{ 0x18, KEY_2},
{ 0x5e, KEY_3},
{ 0x08, KEY_4},
{ 0x1c, KEY_5},
{ 0x5a, KEY_6},
{ 0x42, KEY_7},
{ 0x52, KEY_8}, //
{ 0x4a, KEY_9},

};

static struct rc_map_list hceng_nec_map = {
.map = {
.scan = hceng_nec,
.size = ARRAY_SIZE(hceng_nec),
.rc_type = RC_TYPE_NEC, //RC_TYPE_UNKNOWN //echo nec > /sys/class/rc/rc0/protocols
.name = “rc-hceng-nec”,
}
};

static int __init init_rc_map_hceng_nec(void)
{
return rc_map_register(&hceng_nec_map);
}

static void __exit exit_rc_map_hceng_nec(void)
{
rc_map_unregister(&hceng_nec_map);
}

module_init(init_rc_map_hceng_nec)
module_exit(exit_rc_map_hceng_nec)

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng”);
{% endcodeblock %}

rc-hceng-nec.c放在drivers/media/rc/keymaps/下,并修改Makefile,加入rc-hceng-nec.o

3.2.3 修改设备树

{% codeblock lang:dts %}
ir: ir-receiver {
compatible = “gpio-ir-receiver”;
gpios = <&gpio0 12 1>;
linux,rc-map-name = “rc-hceng-nec”;
};
{% endcodeblock %}

3.2.4 应用测试

测试程序:
{% codeblock lang:c %}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <linux/input.h>

int main()
{
int fd;
int version;
int ret;
struct input_event ev;

fd = open("/dev/input/event0", O_RDONLY);  
if (fd < 0) {  
    printf("open file failed\n");  
    exit(1);  
}  

ioctl(fd, EVIOCGVERSION, &version);  
printf("evdev driver version is 0x%x: %d.%d.%d\n",  
                version, version>>16, (version>>8) & 0xff, version & 0xff);  

while (1) {  
    ret = read(fd, &ev, sizeof(struct input_event));  
    if (ret < 0) {  
        printf("read event error!\n");  
        exit(1);  
    }  
      
    if (ev.type == EV_KEY)  
        printf("type %d,code %d, value %d\n", ev.type, ev.code, ev.value);  
}  
  
return 0;  

}
{% endcodeblock %}

3.2.5 其它

  • 码值关系
    遥控器产生一个原始数据码,
    rc-hceng-nec里,将原始数据码和输入子系统中的按键编号进行对应,
    最后用户态读到的code是输入子系统中的按键编号值。
    比如:
原始数据码  -------> 按键编号 -------->用户层读取
0x16                KEY_0/11         11
  • 打印原始数据
    修改drivers/media/rc/ir-nec-decoder.c,添加打印:
    {% codeblock lang:c %}
    183 } else {
    184 /* Normal NEC */
    185 scancode = address << 8 | command;
    186 //IR_dprintk(1, “NEC scancode 0x%04x\n”, scancode);
    187 printk(“NEC scancode 0x%04x\n”, scancode);
    188 }
    {% endcodeblock %}

猜你喜欢

转载自blog.csdn.net/hceng_linux/article/details/89944877