AM335X——1-Wire和IrDA驱动

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指令
指令名称指令代码指令功能
读ROM33H读ROM中64Bits的只读数据
ROM匹配55H发出此命令后接着发64Bits的ROM编码,使单总线上与编码匹配的DS18B3做出响应
搜索ROMF0H用于确定挂在总线上的DS18B20的个数和识别64Bits的ROM地址
跳过ROMCCH忽略64BitsROM只读数据,接着发出功能指令,进行温度转换或读取温度
警报搜索ECH只有温度超过设定上限或下限的DS18B20才做出响应
功能指令
指令名称指令代码指令功能
温度转换44H启动温度转换,结果将保存在内部RAM中
读取温度BEH读取内部RAM中的温度数据
设置报警温度4EH设置上或下限报警温度指令,接着应发送两字节数据
保存报警温度48H将RAM中的报警温度数据,复制到EEPROM中保存
恢复RAMB8H将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 huangcheng.job@foxmail.com”);
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 harald@ccbib.org
  • 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 harald@ccbib.org”);
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 huangcheng.job@foxmail.com”);
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 %}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值