diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 573ca39a..575c872a 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -60,6 +60,12 @@ config LUA_MODULE_BTHCI help Includes the simple BlueTooth HCI module. +config LUA_MODULE_DHT + bool "DHT11/21/22/AM2301/AM2302 module" + default "n" + help + Includes the dht module. + config LUA_MODULE_ENCODER bool "Encoder module" default "n" diff --git a/components/modules/dht.c b/components/modules/dht.c new file mode 100644 index 00000000..53e47da1 --- /dev/null +++ b/components/modules/dht.c @@ -0,0 +1,102 @@ + + +#include "module.h" +#include "lauxlib.h" +#include + +#include "platform.h" + + +typedef enum { + LDHT_OK = 0, + LDHT_ERROR_CHECKSUM = -1, + LDHT_ERROR_TIMEOUT = -2, + LDHT_INVALID_VALUE = -999 +} ldht_result_t; + +typedef enum { + LDHT11, + LDHT2X +} ldht_type_t; + +static int ldht_compute_data11( uint8_t *data, double *temp, double *humi ) +{ + *humi = data[0]; + *temp = data[2]; + + uint8_t sum = data[0] + data[1] + data[2] + data[3]; + return sum == data[4] ? LDHT_OK : LDHT_ERROR_CHECKSUM; +} + +static int ldht_compute_data2x( uint8_t *data, double *temp, double *humi ) +{ + *humi = (data[0] * 256 + data[1]) * 0.1; + *temp = ((data[2] & 0x7f) * 256 + data[3]) * 0.1; + + if (data[2] & 0x80) + *temp = - *temp; + + uint8_t sum = data[0] + data[1] + data[2] + data[3]; + return sum == data[4] ? LDHT_OK : LDHT_ERROR_CHECKSUM; +} + +static int ldht_read_generic( lua_State *L, ldht_type_t type ) +{ + int stack = 0; + + int gpio = luaL_checkint( L, ++stack ); + luaL_argcheck( L, platform_gpio_output_exists( gpio ), stack, "invalid gpio" ); + + uint8_t data[5]; + if (platform_dht_read( gpio, + type == LDHT11 ? PLATFORM_DHT11_WAKEUP_MS : PLATFORM_DHT2X_WAKEUP_MS, + data ) != PLATFORM_OK) { + lua_pushinteger( L, LDHT_ERROR_TIMEOUT ); + return 1; + } + + double temp, humi; + int res; + switch (type) { + case LDHT11: + res = ldht_compute_data11( data, &temp, &humi ); + break; + case LDHT2X: + res = ldht_compute_data2x( data, &temp, &humi ); + break; + default: + res = LDHT_INVALID_VALUE; + break; + } + lua_pushinteger( L, res ); + lua_pushnumber( L, temp ); + lua_pushnumber( L, humi ); + int tempdec = (int)((temp - (int)temp) * 1000); + int humidec = (int)((humi - (int)humi) * 1000); + lua_pushinteger( L, tempdec ); + lua_pushinteger( L, humidec ); + + return 5; +} + +static int ldht_read11( lua_State *L ) +{ + return ldht_read_generic( L, LDHT11 ); +} + +static int ldht_read2x( lua_State *L ) +{ + return ldht_read_generic( L, LDHT2X ); +} + + +static const LUA_REG_TYPE dht_map[] = { + { LSTRKEY( "read11" ), LFUNCVAL( ldht_read11 ) }, + { LSTRKEY( "read2x" ), LFUNCVAL( ldht_read2x ) }, + { LSTRKEY( "OK" ), LNUMVAL( LDHT_OK ) }, + { LSTRKEY( "ERROR_CHECKSUM" ), LNUMVAL( LDHT_ERROR_CHECKSUM ) }, + { LSTRKEY( "ERROR_TIMEOUT" ), LNUMVAL( LDHT_ERROR_TIMEOUT ) }, + { LNILKEY, LNILVAL } +}; + +NODEMCU_MODULE(DHT, "dht", dht_map, NULL); diff --git a/components/platform/dht.c b/components/platform/dht.c new file mode 100644 index 00000000..7bb21a34 --- /dev/null +++ b/components/platform/dht.c @@ -0,0 +1,205 @@ + +/* **************************************************************************** + * + * ESP32 platform interface for DHT temperature & humidity sensors + * + * Copyright (c) 2017, Arnim Laeuger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ****************************************************************************/ + +#include "platform.h" + +#include "driver/rmt.h" +#include "driver/gpio.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" + + +#undef DHT_DEBUG + +// RX idle threshold [us] +// needs to be larger than any duration occurring during bit slots +// datasheet specs up to 200us for "Bus master has released time" +#define DHT_DURATION_RX_IDLE (250) + +// zero bit duration threshold [us] +// a high phase +// * shorter than this is detected as a zero bit +// * longer than this is detected as a one bit +#define DHT_DURATION_ZERO (50) + + +// grouped information for RMT management +static struct { + int channel; + RingbufHandle_t rb; + int gpio; +} dht_rmt = {-1, NULL, -1}; + + +static void dht_deinit( void ) +{ + // drive idle level 1 + gpio_set_level( dht_rmt.gpio, 1 ); + + rmt_rx_stop( dht_rmt.channel ); + rmt_driver_uninstall( dht_rmt.channel ); + + platform_rmt_release( dht_rmt.channel ); + + // invalidate channel and gpio assignments + dht_rmt.channel = -1; + dht_rmt.gpio = -1; +} + +static int dht_init( uint8_t gpio_num ) +{ + // acquire an RMT module for RX + if ((dht_rmt.channel = platform_rmt_allocate( 1 )) >= 0) { + +#ifdef DHT_DEBUG + ESP_LOGI("dht", "RMT RX channel: %d", dht_rmt.channel); +#endif + + rmt_config_t rmt_rx; + rmt_rx.channel = dht_rmt.channel; + rmt_rx.gpio_num = gpio_num; + rmt_rx.clk_div = 80; // base period is 1us + rmt_rx.mem_block_num = 1; + rmt_rx.rmt_mode = RMT_MODE_RX; + rmt_rx.rx_config.filter_en = true; + rmt_rx.rx_config.filter_ticks_thresh = 30; + rmt_rx.rx_config.idle_threshold = DHT_DURATION_RX_IDLE; + if (rmt_config( &rmt_rx ) == ESP_OK) { + if (rmt_driver_install( rmt_rx.channel, 512, 0 ) == ESP_OK) { + + rmt_get_ringbuf_handler( dht_rmt.channel, &dht_rmt.rb ); + + dht_rmt.gpio = gpio_num; + + // use gpio for TX direction + // drive idle level 1 + gpio_set_level( gpio_num, 1 ); + gpio_pullup_dis( gpio_num ); + gpio_pulldown_dis( gpio_num ); + gpio_set_direction( gpio_num, GPIO_MODE_INPUT_OUTPUT_OD ); + gpio_set_intr_type( gpio_num, GPIO_INTR_DISABLE ); + + return PLATFORM_OK; + + } + } + + platform_rmt_release( dht_rmt.channel ); + } + + return PLATFORM_ERR; +} + + +int platform_dht_read( uint8_t gpio_num, uint8_t wakeup_ms, uint8_t *data ) +{ + if (dht_init( gpio_num ) != PLATFORM_OK) + return PLATFORM_ERR; + + // send start signal and arm RX channel + TickType_t xDelay = pdMS_TO_TICKS( wakeup_ms ) > 0 ? pdMS_TO_TICKS( wakeup_ms ) : 1; + gpio_set_level( gpio_num, 0 ); // pull wire low + vTaskDelay( xDelay ); // time low phase + rmt_rx_start( dht_rmt.channel, true ); // arm RX channel + gpio_set_level( gpio_num, 1 ); // release wire + + // wait for incoming bit stream + size_t rx_size; + rmt_item32_t* rx_items = (rmt_item32_t *)xRingbufferReceive( dht_rmt.rb, &rx_size, pdMS_TO_TICKS( 100 ) ); + + // default is "no error" + // error conditions have to overwrite this with PLATFORM_ERR + int res = PLATFORM_OK; + + if (rx_items) { + +#ifdef DHT_DEBUG + ESP_LOGI("dht", "rx_items received: %d", rx_size); + for (size_t i = 0; i < rx_size / 4; i++) { + ESP_LOGI("dht", "level: %d, duration %d", rx_items[i].level0, rx_items[i].duration0); + ESP_LOGI("dht", "level: %d, duration %d", rx_items[i].level1, rx_items[i].duration1); + } +#endif + + // we expect 40 bits of payload plus a response bit and two edges for start and stop signals + // each bit on the wire consumes 2 rmt samples (and 2 rmt samples stretch over 4 bytes) + if (rx_size >= (5*8 + 1+1) * 4) { + + // check framing + if (rx_items[ 0].level0 == 1 && // rising edge of the start signal + rx_items[ 0].level1 == 0 && rx_items[1].level0 == 1 && // response signal + rx_items[41].level1 == 0) { // falling edge of stop signal + + // run through the bytes + for (size_t byte = 0; byte < 5 && res == PLATFORM_OK; byte++) { + size_t bit_pos = 1 + byte*8; + data[byte] = 0; + + // decode the bits inside a byte + for (size_t bit = 0; bit < 8; bit++, bit_pos++) { + if (rx_items[bit_pos].level1 != 0) { + // not a falling edge, terminate decoding + res = PLATFORM_ERR; + break; + } + // ignore duration of low level + + // data is sent MSB first + data[byte] <<= 1; + if (rx_items[bit_pos + 1].level0 == 1 && rx_items[bit_pos + 1].duration0 > DHT_DURATION_ZERO) + data[byte] |= 1; + } + +#ifdef DHT_DEBUG + ESP_LOGI("dht", "data 0x%02x", data[byte]); +#endif + } + + // all done + + } else { + // framing mismatch on start, response, or stop signals + res = PLATFORM_ERR; + } + + } else { + // too few bits received + res = PLATFORM_ERR; + } + + vRingbufferReturnItem( dht_rmt.rb, (void *)rx_items ); + } else { + // time out occurred, this indicates an unconnected / misconfigured bus + res = PLATFORM_ERR; + } + + dht_deinit(); + + return res; +} diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index 85814143..cf5883fb 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -164,6 +164,14 @@ uint8_t platform_onewire_crc8( const uint8_t *addr, uint8_t len ); bool platform_onewire_check_crc16( const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc ); uint16_t platform_onewire_crc16( const uint8_t* input, uint16_t len, uint16_t crc ); +// ***************************************************************************** +// DHT platform interface +#define PLATFORM_DHT11_WAKEUP_MS 20 +#define PLATFORM_DHT2X_WAKEUP_MS 1 + +int platform_dht_read( uint8_t gpio_num, uint8_t wakeup_ms, uint8_t *data ); + + // Internal flash erase/write functions uint32_t platform_flash_get_sector_of_address( uint32_t addr ); diff --git a/docs/en/modules/dht.md b/docs/en/modules/dht.md new file mode 100644 index 00000000..2148a813 --- /dev/null +++ b/docs/en/modules/dht.md @@ -0,0 +1,82 @@ +# DHT Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-03-30 | [Arnim Läuger](https://github.com/devsaurus) | [Arnim Läuger](https://github.com/devsaurus) | [dht](../../../components/modules/dht.c)| + +## Constants +Constants for various functions. + +`dht.OK`, `dht.ERROR_CHECKSUM`, `dht.ERROR_TIMEOUT` represent the potential values for the DHT read status + +## dht.read11() +Read DHT11 humidity temperature combo sensor. + +#### Syntax +`dht.read11(pin)` + +#### Parameters +`pin` 0~33, IO index + +#### Returns +- `status` as defined in Constants +- `temp` temperature +- `humi` humidity +- `temp_dec` temperature decimal (always 0) +- `humi_dec` humidity decimal (always 0) + +#### Example +```lua +pin = 4 +status, temp, humi = dht.read11(pin) +if status == dht.OK then + print("DHT Temperature:"..temp..";".."Humidity:"..humi) + +elseif status == dht.ERROR_CHECKSUM then + print( "DHT Checksum error." ) +elseif status == dht.ERROR_TIMEOUT then + print( "DHT timed out." ) +end +``` + +## dht.read2x() +Read DHT21/22/33/43 and AM2301/2302/2303 humidity temperature combo sensors. + +#### Syntax +`dht.read2x(pin)` + +#### Parameters +`pin` 0~33, IO index + +#### Returns +- `status` as defined in Constants +- `temp` temperature (see note below) +- `humi` humidity (see note below) +- `temp_dec` temperature decimal +- `humi_dec` humidity decimal + +!!! note + + If using float firmware then `temp` and `humi` are floating point numbers. On an integer firmware, the final values have to be concatenated from `temp` and `temp_dec` / `humi` and `hum_dec`. + +#### Example +```lua +pin = 4 +status, temp, humi, temp_dec, humi_dec = dht.read2x(pin) +if status == dht.OK then + -- Integer firmware using this example + print(string.format("DHT Temperature:%d.%03d;Humidity:%d.%03d\r\n", + math.floor(temp), + temp_dec, + math.floor(humi), + humi_dec + )) + + -- Float firmware using this example + print("DHT Temperature:"..temp..";".."Humidity:"..humi) + +elseif status == dht.ERROR_CHECKSUM then + print( "DHT Checksum error." ) +elseif status == dht.ERROR_TIMEOUT then + print( "DHT timed out." ) +end +``` diff --git a/lua_modules/dht_lib/README.md b/lua_modules/dht_lib/README.md deleted file mode 100644 index af18eb82..00000000 --- a/lua_modules/dht_lib/README.md +++ /dev/null @@ -1,7 +0,0 @@ -Support for this Lua module has been discontinued. - -Equivalent functionality is available from the dht module in the NodeMCU -firmware code base. Refer to `docs/en/modules/dht.md` for API -documentation. - -The original Lua code can be found in the [git repository](https://github.com/nodemcu/nodemcu-firmware/tree/2fbd5ed509964a16057b22e00aa8469d6a522d73/lua_modules/dht_lib). diff --git a/mkdocs.yml b/mkdocs.yml index 7a1d622e..ba6dcb32 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,6 +33,7 @@ pages: - Modules: - 'bit': 'en/modules/bit.md' - 'bthci': 'en/modules/bthci.md' + - 'dht': 'en/modules/dht.md' - 'file': 'en/modules/file.md' - 'i2c': 'en/modules/i2c.md' - 'node': 'en/modules/node.md'