6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
618 lines
17 KiB
C
618 lines
17 KiB
C
/****************************************************************************
|
|
* include/nuttx/sensors/dhtxx.c
|
|
*
|
|
* Copyright (C) 2018 Abdelatif GUETTOUCHE. All rights reserved.
|
|
* Author: Abdelatif GUETTOUCHE <abdelatif.guettouche@gmail.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/time.h>
|
|
#include <nuttx/clock.h>
|
|
|
|
#include <nuttx/sensors/dhtxx.h>
|
|
|
|
/*****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define DHTXX_START_SIGNAL_LOW_US 20000
|
|
#define DHTXX_START_SIGNAL_HIGH_US 100
|
|
#define DHTXX_RESPONSE_SIGNAL_US 85
|
|
#define DHTXX_TRANSMISSION_START_US 55
|
|
#define DHTXX_MIN_ZERO_DURATION 22
|
|
#define DHTXX_MAX_ZERO_DURATION 30
|
|
#define DHTXX_MIN_ONE_DURATION 40
|
|
#define DHTXX_MAX_ONE_DURATION 75
|
|
#define DHTXX_SAMPLING_PERIOD_S 2
|
|
|
|
#define DHTXX_RESPONSE_BITS 40U
|
|
|
|
#define DHT11_MIN_HUM 20.0F
|
|
#define DHT11_MAX_HUM 90.0F
|
|
#define DHT11_MIN_TEMP 0.0F
|
|
#define DHT11_MAX_TEMP 50.0F
|
|
|
|
#define DHT12_MIN_HUM 20.0F
|
|
#define DHT12_MAX_HUM 95.0F
|
|
#define DHT12_MIN_TEMP -20.0F
|
|
#define DHT12_MAX_TEMP 60.0F
|
|
|
|
#define DHT22_MIN_HUM 0.0F
|
|
#define DHT22_MAX_HUM 100.0F
|
|
#define DHT22_MIN_TEMP -40.0F
|
|
#define DHT22_MAX_TEMP 80.0F
|
|
|
|
/****************************************************************************
|
|
* Private Type Definitions
|
|
****************************************************************************/
|
|
|
|
struct dhtxx_dev_s
|
|
{
|
|
FAR struct dhtxx_config_s *config;
|
|
sem_t devsem;
|
|
uint8_t raw_data[5];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static void dht_standby_mode(FAR struct dhtxx_dev_s *priv);
|
|
static void dht_send_start_signal(FAR struct dhtxx_dev_s *priv);
|
|
static int dht_prepare_reading(FAR struct dhtxx_dev_s *priv);
|
|
static int dht_read_raw_data(FAR struct dhtxx_dev_s *priv);
|
|
static bool dht_verify_checksum(FAR struct dhtxx_dev_s *priv);
|
|
static bool dht_check_data(FAR struct dhtxx_sensor_data_s *data,
|
|
float min_hum, float max_hum,
|
|
float min_temp, float max_temp);
|
|
static int dht_parse_data(FAR struct dhtxx_dev_s *priv,
|
|
FAR struct dhtxx_sensor_data_s *data);
|
|
|
|
/* Character driver methods */
|
|
|
|
static int dhtxx_open(FAR struct file *filep);
|
|
static int dhtxx_close(FAR struct file *filep);
|
|
static ssize_t dhtxx_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t dhtxx_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int dhtxx_ioctl(FAR struct file *filep, int cmd,
|
|
unsigned long arg);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_dhtxxfops =
|
|
{
|
|
dhtxx_open, /* open */
|
|
dhtxx_close, /* close */
|
|
dhtxx_read, /* read */
|
|
dhtxx_write, /* write */
|
|
NULL, /* seek */
|
|
dhtxx_ioctl, /* ioctl */
|
|
NULL /* poll */
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
, NULL /* unlink */
|
|
#endif
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: dht_standby_mode
|
|
*
|
|
* Description:
|
|
* Put the sensor in low power consumption (standby) mode.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void dht_standby_mode(FAR struct dhtxx_dev_s *priv)
|
|
{
|
|
priv->config->config_data_pin(priv->config, false);
|
|
priv->config->set_data_pin(priv->config, true);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dht_send_start_signal
|
|
*
|
|
* Description:
|
|
* Send a start signal to the sensor.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void dht_send_start_signal(FAR struct dhtxx_dev_s *priv)
|
|
{
|
|
int64_t start_time;
|
|
int64_t current_time;
|
|
|
|
priv->config->config_data_pin(priv->config, false);
|
|
|
|
priv->config->set_data_pin(priv->config, false);
|
|
start_time = priv->config->get_clock(priv->config);
|
|
while (1)
|
|
{
|
|
current_time = priv->config->get_clock(priv->config);
|
|
if (current_time - start_time >= DHTXX_START_SIGNAL_LOW_US)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
priv->config->set_data_pin(priv->config, true);
|
|
start_time = priv->config->get_clock(priv->config);
|
|
while (1)
|
|
{
|
|
current_time = priv->config->get_clock(priv->config);
|
|
if (current_time - start_time >= DHTXX_START_SIGNAL_HIGH_US)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dht_prepare_reading
|
|
*
|
|
* Description:
|
|
* Gets the sensor ready for transmitting data.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dht_prepare_reading(FAR struct dhtxx_dev_s *priv)
|
|
{
|
|
int64_t start_time;
|
|
int64_t current_time;
|
|
|
|
/* Prepare for reading data. */
|
|
|
|
priv->config->config_data_pin(priv->config, true);
|
|
|
|
/* The DHT will clear the data pin for about 80uS. */
|
|
|
|
start_time = priv->config->get_clock(priv->config);
|
|
while (!priv->config->read_data_pin(priv->config))
|
|
{
|
|
current_time = priv->config->get_clock(priv->config);
|
|
if (current_time - start_time > DHTXX_RESPONSE_SIGNAL_US)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* The DHT will set the data pin for about 80uS. */
|
|
|
|
while (priv->config->read_data_pin(priv->config))
|
|
{
|
|
current_time = priv->config->get_clock(priv->config);
|
|
if (current_time - start_time > DHTXX_RESPONSE_SIGNAL_US)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dht_read_raw_data
|
|
*
|
|
* Description:
|
|
* Reads raw data from the sensor.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dht_read_raw_data(FAR struct dhtxx_dev_s *priv)
|
|
{
|
|
int64_t start_time;
|
|
int64_t end_time;
|
|
int64_t current_time;
|
|
uint8_t i;
|
|
uint8_t j;
|
|
|
|
j = 0u;
|
|
for (i = 0u; i < DHTXX_RESPONSE_BITS; i++)
|
|
{
|
|
/* Start of transmission begins with a ~50uS low. */
|
|
|
|
start_time = priv->config->get_clock(priv->config);
|
|
while (!priv->config->read_data_pin(priv->config))
|
|
{
|
|
current_time = priv->config->get_clock(priv->config);
|
|
if (current_time - start_time > DHTXX_TRANSMISSION_START_US)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Get start time. */
|
|
|
|
start_time = priv->config->get_clock(priv->config);
|
|
while (priv->config->read_data_pin(priv->config))
|
|
{
|
|
current_time = priv->config->get_clock(priv->config);
|
|
if (current_time - start_time > DHTXX_MAX_ONE_DURATION)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
end_time = priv->config->get_clock(priv->config);
|
|
if (end_time - start_time >= DHTXX_MIN_ONE_DURATION)
|
|
{
|
|
priv->raw_data[j] = (priv->raw_data[j] << 1U) | 1U;
|
|
}
|
|
else
|
|
{
|
|
priv->raw_data[j] = priv->raw_data[j] << 1U;
|
|
}
|
|
|
|
if (i % 8U == 7U)
|
|
{
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dht_verify_checksum
|
|
*
|
|
* Description:
|
|
* Verify the sent checksum with the calculated checksum.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool dht_verify_checksum(FAR struct dhtxx_dev_s *priv)
|
|
{
|
|
uint8_t sum;
|
|
|
|
sum = (priv->raw_data[0] + priv->raw_data[1] +
|
|
priv->raw_data[2] + priv->raw_data[3]) & 0xffu;
|
|
|
|
return (sum == priv->raw_data[4]);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dht_check_data
|
|
*
|
|
* Description:
|
|
* Check for data integrity in regards with the sensors range.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool dht_check_data(FAR struct dhtxx_sensor_data_s *data,
|
|
float min_hum, float max_hum,
|
|
float min_temp, float max_temp)
|
|
{
|
|
if (data->hum < min_hum || data->hum > max_hum)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (data->temp < min_temp || data->temp > max_temp)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dht_check_data
|
|
*
|
|
* Description:
|
|
* Get the humidity and temperature's values from raw data.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dht_parse_data(FAR struct dhtxx_dev_s *priv,
|
|
FAR struct dhtxx_sensor_data_s *data)
|
|
{
|
|
int ret = OK;
|
|
|
|
switch (priv->config->type)
|
|
{
|
|
case DHTXX_DHT11:
|
|
data->hum = priv->raw_data[0];
|
|
data->temp = priv->raw_data[2];
|
|
|
|
/* if data is not within sensor's measurement range,
|
|
* an error must have accured.
|
|
*/
|
|
|
|
if (!dht_check_data(data, DHT11_MIN_HUM, DHT11_MAX_HUM,
|
|
DHT11_MIN_TEMP, DHT11_MAX_TEMP))
|
|
{
|
|
ret = -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case DHTXX_DHT12:
|
|
data->hum = priv->raw_data[0] + priv->raw_data[1] * 0.1F;
|
|
|
|
data->temp = priv->raw_data[2] + (priv->raw_data[3] & 0x7fu) * 0.1F;
|
|
if (priv->raw_data[3] & 0x80u)
|
|
{
|
|
data->temp *= -1;
|
|
}
|
|
|
|
if (!dht_check_data(data, DHT12_MIN_HUM, DHT12_MAX_HUM,
|
|
DHT12_MIN_TEMP, DHT12_MAX_TEMP))
|
|
{
|
|
ret = -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case DHTXX_DHT21:
|
|
case DHTXX_DHT22:
|
|
case DHTXX_DHT33:
|
|
case DHTXX_DHT44:
|
|
data->hum = (priv->raw_data[0] << 8u | priv->raw_data[1]) * 0.1F;
|
|
|
|
data->temp = (((priv->raw_data[2] & 0x7fu) << 8u) |
|
|
priv->raw_data[3]) * 0.1F;
|
|
if (priv->raw_data[2] & 0x80u)
|
|
{
|
|
data->temp *= -1;
|
|
}
|
|
|
|
if (!dht_check_data(data, DHT22_MIN_HUM, DHT22_MAX_HUM,
|
|
DHT22_MIN_TEMP, DHT22_MAX_TEMP))
|
|
{
|
|
ret = -1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dhtxx_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever the Dhtxx device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dhtxx_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct dhtxx_dev_s *priv = inode->i_private;
|
|
|
|
/* Acquire the semaphore, wait the sampling time before sending anything to
|
|
* pass unstable state.
|
|
*/
|
|
|
|
nxsem_wait_uninterruptible(&priv->devsem);
|
|
|
|
dht_standby_mode(priv);
|
|
|
|
nxsig_sleep(DHTXX_SAMPLING_PERIOD_S);
|
|
|
|
/* Sensor ready. */
|
|
|
|
nxsem_post(&priv->devsem);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dhtxx_close
|
|
*
|
|
* Description:
|
|
* This routine is called when the Dhtxx device is closed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dhtxx_close(FAR struct file *filep)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dhtxx_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t dhtxx_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
int ret = OK;
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct dhtxx_dev_s *priv = inode->i_private;
|
|
FAR struct dhtxx_sensor_data_s *data =
|
|
(FAR struct dhtxx_sensor_data_s *)buffer;
|
|
|
|
if (!buffer)
|
|
{
|
|
snerr("ERROR: Buffer is null.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (buflen < sizeof(FAR struct dhtxx_sensor_data_s))
|
|
{
|
|
snerr("ERROR: Not enough memory for reading out a sensor data sample.\n");
|
|
return -ENOSYS;
|
|
}
|
|
|
|
memset(priv->raw_data, 0u, sizeof(priv->raw_data));
|
|
|
|
nxsem_wait_uninterruptible(&priv->devsem);
|
|
|
|
dht_send_start_signal(priv);
|
|
|
|
if (dht_prepare_reading(priv) != 0)
|
|
{
|
|
data->status = DHTXX_TIMEOUT;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (dht_read_raw_data(priv) != 0)
|
|
{
|
|
data->status = DHTXX_TIMEOUT;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!dht_verify_checksum(priv))
|
|
{
|
|
data->status = DHTXX_CHECKSUM_ERROR;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (dht_parse_data(priv, data) != 0)
|
|
{
|
|
data->status = DHTXX_READ_ERROR;
|
|
ret = -1;
|
|
}
|
|
else
|
|
{
|
|
data->status = DHTXX_SUCCESS;
|
|
}
|
|
|
|
out:
|
|
|
|
/* Done reading, set the sensor back to low mode. */
|
|
|
|
dht_standby_mode(priv);
|
|
|
|
/* Don't release the semaphore just yet. The sensor needs time between
|
|
* consecutive readings.
|
|
*/
|
|
|
|
nxsig_sleep(DHTXX_SAMPLING_PERIOD_S);
|
|
|
|
/* Sensor ready for new reading */
|
|
|
|
nxsem_post(&priv->devsem);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dhtxx_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t dhtxx_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dhtxx_ioctl
|
|
****************************************************************************/
|
|
|
|
static int dhtxx_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
int ret = OK;
|
|
|
|
switch (cmd)
|
|
{
|
|
/* Command was not recognized */
|
|
|
|
default:
|
|
snerr("ERROR: Unrecognized cmd: %d\n", cmd);
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: dhtxx_register
|
|
*
|
|
* Description:
|
|
* Register the Dhtxx character device as 'devpath'
|
|
*
|
|
* Input Parameters:
|
|
* devpath - The full path to the driver to register. E.g., "/dev/dht0"
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int dhtxx_register(FAR const char *devpath, FAR struct dhtxx_config_s *config)
|
|
{
|
|
FAR struct dhtxx_dev_s *priv;
|
|
int ret;
|
|
|
|
/* Initialize the Dhtxx device structure */
|
|
|
|
priv = (FAR struct dhtxx_dev_s *)kmm_malloc(sizeof(struct dhtxx_dev_s));
|
|
if (priv == NULL)
|
|
{
|
|
snerr("ERROR: Failed to allocate instance\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->config = config;
|
|
|
|
/* Register the character driver */
|
|
|
|
ret = register_driver(devpath, &g_dhtxxfops, 0666, priv);
|
|
if (ret < 0)
|
|
{
|
|
kmm_free(priv);
|
|
snerr("ERROR: Failed to register driver: %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|