diff --git a/docs/en/api-reference/peripherals/rmt.rst b/docs/en/api-reference/peripherals/rmt.rst index 9b0068b704..d3e4655671 100644 --- a/docs/en/api-reference/peripherals/rmt.rst +++ b/docs/en/api-reference/peripherals/rmt.rst @@ -518,6 +518,7 @@ Application Examples * RMT transactions in queue: :example:`peripherals/rmt/musical_buzzer` * RMT based stepper motor with S-curve algorithm: : :example:`peripherals/rmt/stepper_motor` * RMT infinite loop for driving DShot ESC: :example:`peripherals/rmt/dshot_esc` +* RMT simulate 1-wire protocol (take DS18B20 as example): :example:`peripherals/rmt/onewire_ds18b20` API Reference ------------- diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index b47031ffc1..2211b1f480 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -84,6 +84,10 @@ examples/peripherals/rmt/musical_buzzer: enable: - if: SOC_RMT_SUPPORT_TX_LOOP_COUNT == 1 +examples/peripherals/rmt/onewire_ds18b20: + disable: + - if: SOC_RMT_SUPPORTED != 1 + examples/peripherals/rmt/stepper_motor: enable: - if: SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP == 1 diff --git a/examples/peripherals/rmt/onewire_ds18b20/CMakeLists.txt b/examples/peripherals/rmt/onewire_ds18b20/CMakeLists.txt new file mode 100644 index 0000000000..cdd8daafe6 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(onewire_ds18b20) diff --git a/examples/peripherals/rmt/onewire_ds18b20/README.md b/examples/peripherals/rmt/onewire_ds18b20/README.md new file mode 100644 index 0000000000..cb9eb18c33 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/README.md @@ -0,0 +1,90 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# RMT Transmit & Receive Example -- 1-Wire bus + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +RMT peripheral has both transmit and receive channels. Connecting one transmit channel and one receive channel to the same GPIO and put the GPIO in open-drain mode can simulate bi-directional single wire protocols, such as [1-Wire protocol](https://www.maximintegrated.com/en/design/technical-documents/tutorials/1/1796.html). + +This example demonstrates how to use RMT to simulate 1-Wire bus and read temperatrue from [DS18B20](https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf). + +## How to Use Example + +### Hardware Required + +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) +* Several DS18B20 temperature sensors and a 4.7kohm pullup resistor + +Connection : + +``` +┌──────────────────────────┐ +│ 3.3V├───────┬─────────────┬──────────────────────┐ +│ │ ┌┴┐ │VDD │VDD +│ ESP32 Board │ 4.7k│ │ ┌──────┴──────┐ ┌──────┴──────┐ +│ │ └┬┘ DQ│ │ DQ│ │ +│ ONEWIRE_GPIO_PIN├───────┴──┬───┤ DS18B20 │ ┌───┤ DS18B20 │ ...... +│ │ └───│-------------│────┴───│-------------│── +│ │ └──────┬──────┘ └──────┬──────┘ +│ │ │GND │GND +│ GND├─────────────────────┴──────────────────────┘ +└──────────────────────────┘ +``` + +The GPIO number used in this example can be changed according to your board, by the macro `EXAMPLE_ONEWIRE_GPIO_PIN` defined in [onewire_ds18b20_example_main.c](main/onewire_ds18b20_example_main.c). + +*Note*: Parasite power mode is not supported currently by this example, you have to connect VDD pin to make DS18B20 functional. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Console Output + +If there are some DS18B20s on the bus: + +``` +I (327) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +I (338) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (348) onewire_rmt: RMT Tx channel created for 1-wire bus +I (358) gpio: GPIO[5]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 +I (358) onewire_rmt: RMT Rx channel created for 1-wire bus +I (368) example: 1-wire bus installed +I (418) example: found device with rom id 28FF30BE21170317 +I (458) example: found device with rom id 28FF297E211703A1 +I (498) example: found device with rom id 28FF6F7921170352 +I (508) example: 3 devices found on 1-wire bus +I (2518) example: temperature of device 28FF30BE21170317: 27.00C +I (2528) example: temperature of device 28FF297E211703A1: 26.81C +I (2538) example: temperature of device 28FF6F7921170352: 26.50C +I (3548) example: temperature of device 28FF30BE21170317: 26.94C +I (3558) example: temperature of device 28FF297E211703A1: 26.75C +I (3568) example: temperature of device 28FF6F7921170352: 26.44C +``` + +If there is no DS18B20 on the bus: + +``` +I (327) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +I (337) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (347) onewire_rmt: RMT Tx channel created for 1-wire bus +I (357) gpio: GPIO[5]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 +I (357) onewire_rmt: RMT Rx channel created for 1-wire bus +I (367) example: 1-wire bus installed +E (377) onewire_rmt: no device present on 1-wire bus +I (377) example: 0 device found on 1-wire bus +I (387) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (397) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (397) example: 1-wire bus deleted +``` + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/CMakeLists.txt b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/CMakeLists.txt new file mode 100644 index 0000000000..05df56811a --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "onewire_bus_rmt.c" "onewire_bus.c" + INCLUDE_DIRS "." + PRIV_REQUIRES driver) diff --git a/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus.c b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus.c new file mode 100644 index 0000000000..8bfa126510 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus.c @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_check.h" +#include "esp_log.h" +#include "onewire_bus.h" + +static const char *TAG = "onewire"; + +struct onewire_rom_search_context_t { + onewire_bus_handle_t bus_handle; + + uint8_t last_device_flag; + uint16_t last_discrepancy; + uint8_t rom_number[8]; +}; + +// Algorithm inspired by https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/187.html + +static const uint8_t dscrc_table[] = { + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53 +}; + +uint8_t onewire_check_crc8(uint8_t *input, size_t input_size) +{ + uint8_t crc8 = 0; + + for (size_t i = 0; i < input_size; i ++) { + crc8 = dscrc_table[crc8 ^ input[i]]; + } + + return crc8; +} + +esp_err_t onewire_rom_search_context_create(onewire_bus_handle_t handle, onewire_rom_search_context_handler_t *context_out) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(context_out, ESP_ERR_INVALID_ARG, TAG, "invalid context handler pointer"); + + struct onewire_rom_search_context_t *context = calloc(1, sizeof(struct onewire_rom_search_context_t)); + if (!context) { + return ESP_ERR_NO_MEM; + } + + context->bus_handle = handle; + *context_out = context; + + return ESP_OK; +} + +esp_err_t onewire_rom_search_context_delete(onewire_rom_search_context_handler_t context) +{ + ESP_RETURN_ON_FALSE(context, ESP_ERR_INVALID_ARG, TAG, "invalid context handler pointer"); + + free(context); + + return ESP_OK; +} + +esp_err_t onewire_rom_search(onewire_rom_search_context_handler_t context) +{ + ESP_RETURN_ON_FALSE(context, ESP_ERR_INVALID_ARG, TAG, "invalid context handler pointer"); + + uint8_t last_zero = 0; + + if (!context->last_device_flag) { + if (onewire_bus_reset(context->bus_handle) != ESP_OK) { // no device present + return ESP_ERR_NOT_FOUND; + } + + // send rom search command and start search algorithm + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(context->bus_handle, (uint8_t[]){ONEWIRE_CMD_SEARCH_ROM}, 1), + TAG, "error while sending search rom command"); + + for (uint16_t rom_bit_index = 0; rom_bit_index < 64; rom_bit_index ++) { + uint8_t rom_byte_index = rom_bit_index / 8; + uint8_t rom_bit_mask = 1 << (rom_bit_index % 8); // calculate byte index and bit mask in advance for convenience + + uint8_t rom_bit, rom_bit_complement; + ESP_RETURN_ON_ERROR(onewire_bus_read_bit(context->bus_handle, &rom_bit), TAG, "error while reading rom bit"); // write 1 bit to read from the bus + ESP_RETURN_ON_ERROR(onewire_bus_read_bit(context->bus_handle, &rom_bit_complement), + TAG, "error while reading rom bit"); // read a bit and its complement + + uint8_t search_direction; + if (rom_bit && rom_bit_complement) { // No devices participating in search. + ESP_LOGE(TAG, "no devices participating in search"); + return ESP_ERR_NOT_FOUND; + } else { + if (rom_bit != rom_bit_complement) { // There are only 0s or 1s in the bit of the participating ROM numbers. + search_direction = rom_bit; // just go ahead + } else { // There are both 0s and 1s in the current bit position of the participating ROM numbers. This is a discrepancy. + if (rom_bit_index < context->last_discrepancy) { // current id bit is before the last discrepancy bit + search_direction = (context->rom_number[rom_byte_index] & rom_bit_mask) ? 0x01 : 0x00; // follow previous way + } else { + search_direction = (rom_bit_index == context->last_discrepancy) ? 0x01 : 0x00; // search for 0 bit first + } + + if (search_direction == 0) { // record zero's position in last zero + last_zero = rom_bit_index; + } + } + + if (search_direction == 1) { // set corrsponding rom bit by serach direction + context->rom_number[rom_byte_index] |= rom_bit_mask; + } else { + context->rom_number[rom_byte_index] &= ~rom_bit_mask; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bit(context->bus_handle, search_direction), + TAG, "error while writing direction bit"); // set search direction + } + } + } else { + ESP_LOGD(TAG, "1-wire rom search finished"); + return ESP_FAIL; + } + + // if the search was successful + context->last_discrepancy = last_zero; + if (context->last_discrepancy == 0) { // last zero loops back to the first bit + context->last_device_flag = true; + } + + if (onewire_check_crc8(context->rom_number, 7) != context->rom_number[7]) { // check crc + ESP_LOGE(TAG, "bad crc checksum of device with id " ONEWIRE_ROM_ID_STR, ONEWIRE_ROM_ID(context->rom_number)); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +esp_err_t onewire_rom_get_number(onewire_rom_search_context_handler_t context, uint8_t *rom_number_out) +{ + ESP_RETURN_ON_FALSE(context, ESP_ERR_INVALID_ARG, TAG, "invalid context pointer"); + ESP_RETURN_ON_FALSE(rom_number_out, ESP_ERR_INVALID_ARG, TAG, "invalid rom_number pointer"); + + memcpy(rom_number_out, context->rom_number, sizeof(context->rom_number)); + + return ESP_OK; +} diff --git a/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus.h b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus.h new file mode 100644 index 0000000000..3cb0ea511c --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "onewire_bus_rmt.h" + +#define ONEWIRE_CMD_SEARCH_ROM 0xF0 +#define ONEWIRE_CMD_READ_ROM 0x33 +#define ONEWIRE_CMD_MATCH_ROM 0x55 +#define ONEWIRE_CMD_SKIP_ROM 0xCC +#define ONEWIRE_CMD_ALARM_SEARCH_ROM 0xEC + +#define ONEWIRE_ROM_ID(id) (id)[0], (id)[1], (id)[2], (id)[3], (id)[4], (id)[5], (id)[6], (id)[7] +#define ONEWIRE_ROM_ID_STR "%02X%02X%02X%02X%02X%02X%02X%02X" + +/** + * @brief Type of 1-wire ROM search algorithm context + * + */ +typedef struct onewire_rom_search_context_t *onewire_rom_search_context_handler_t; + +/** + * @brief Calculate Dallas CRC value of given buffer + * + * @param[in] input Input buffer to calculate CRC value + * @param[in] input_size Size of input buffer + * @return CRC result of input buffer + */ +uint8_t onewire_check_crc8(uint8_t *input, size_t input_size); + +/** + * @brief Create context for 1-wire ROM search algorithm + * + * @param[in] handle 1-wire handle used for ROM search + * @param[out] context_out Created context for ROM search algorithm + * @return + * - ESP_OK 1-wire ROM search context is created successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_rom_search_context_create(onewire_bus_handle_t handle, onewire_rom_search_context_handler_t *context_out); + +/** + * @brief Delete context for 1-wire ROM search algorithm + * + * @param[in] context Context for ROM search algorithm + * @return + * - ESP_OK 1-wire ROM search context is deleted successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_rom_search_context_delete(onewire_rom_search_context_handler_t context); + +/** + * @brief Search next device on 1-wire bus + * + * @param[in] context Context for ROM search algorithm + * @return + * - ESP_OK Successfully found a device + * - ESP_ERR_NOT_FOUND There are no device on the bus + * - ESP_ERR_INVALID_CRC Bad CRC value of found device + * - ESP_FAIL Reached last device on the bus, search algorighm finishes + */ +esp_err_t onewire_rom_search(onewire_rom_search_context_handler_t context); + +/** + * @brief Get device ROM number from ROM search context + * + * @param[in] context Context for ROM search algorithm + * @param[out] rom_number_out Device ROM number + * @return + * - ESP_OK Get ROM numbuer from 1-wire ROM search context success. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_rom_get_number(onewire_rom_search_context_handler_t context, uint8_t *rom_number_out); diff --git a/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus_rmt.c b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus_rmt.c new file mode 100644 index 0000000000..d422383349 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus_rmt.c @@ -0,0 +1,438 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_check.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "driver/rmt_types.h" +#include "driver/rmt_encoder.h" +#include "onewire_bus_rmt.h" + +static const char *TAG = "onewire_rmt"; + +/** + * @brief RMT resolution for 1-wire bus, in Hz + * + */ +#define ONEWIRE_RMT_RESOLUTION_HZ 1000000 + +/** + * @brief 1-wire bus timing parameters, in us (1/ONEWIRE_RMT_RESOLUTION_HZ) + * + */ +#define ONEWIRE_RESET_PULSE_DURATION 500 // duration of reset bit +#define ONEWIRE_RESET_WAIT_DURATION 200 // how long should master wait for device to show its presence +#define ONEWIRE_RESET_PRESENSE_WAIT_DURATION_MIN 15 // minimum duration for master to wait device to show its presence +#define ONEWIRE_RESET_PRESENSE_DURATION_MIN 60 // minimum duration for master to recognize device as present + +#define ONEWIRE_SLOT_START_DURATION 2 // bit start pulse duration +#define ONEWIRE_SLOT_BIT_DURATION 60 // duration for each bit to transmit +// refer to https://www.maximintegrated.com/en/design/technical-documents/app-notes/3/3829.html for more information +#define ONEWIRE_SLOT_RECOVERY_DURATION 2 // recovery time between each bit, should be longer in parasite power mode +#define ONEWIRE_SLOT_BIT_SAMPLE_TIME 15 // how long after bit start pulse should the master sample from the bus + +/* +Reset Pulse: + + | RESET_PULSE | RESET_WAIT_DURATION | + | _DURATION | | + | | | | RESET | | + | | * | | _PRESENSE | | + | | | | _DURATION | | +------────┐ ┌─────┐ ┌───────------- + │ │ │ │ + │ │ │ │ + │ │ │ │ + └─────────────┘ └───────────┘ +*: RESET_PRESENSE_WAIT_DURATION + +Write 1 bit: + + | SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT + | _DURATION | _DURATION | _DURATION | SLOT + | | | | +------────┐ ┌───────────────────────────────------ + │ │ + │ │ + │ │ + └────────────┘ + +Write 0 bit: + + | SLOT_START | SLOT_BIT | SLOT_RECOVERY | NEXT + | _DURATION | _DURATION | _DURATION | SLOT + | | | | +------────┐ ┌───────────────────------ + │ │ + │ │ + │ │ + └────────────────────────┘ + +Read 1 bit: + + + | SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT + | _DURATION | | _DURATION | SLOT + | | SLOT_BIT_ | | | + | | SAMPLE_TIME | | | +------────┐ ┌────────────────────────────────────────------ + │ │ + │ │ + │ │ + └────────────┘ + +Read 0 bit: + + | SLOT_START | SLOT_BIT_DURATION | SLOT_RECOVERY | NEXT + | _DURATION | | _DURATION | SLOT + | | SLOT_BIT_ | | | + | | SAMPLE_TIME | | | +------────┐ | | ┌───────────────────────------ + │ | │ + │ | PULLED DOWN │ + │ | BY DEVICE │ + └─────────────────────────────┘ +*/ + +struct onewire_bus_t { + rmt_channel_handle_t tx_channel; /*!< rmt tx channel handler */ + rmt_encoder_handle_t tx_bytes_encoder; /*!< used to encode commands and data */ + rmt_encoder_handle_t tx_copy_encoder; /*!< used to encode reset pulse and bits */ + + rmt_channel_handle_t rx_channel; /*!< rmt rx channel handler */ + rmt_symbol_word_t *rx_symbols; /*!< hold rmt raw symbols */ + + size_t max_rx_bytes; /*!< buffer size in byte for single receive transaction */ + + QueueHandle_t receive_queue; +}; + +const static rmt_symbol_word_t onewire_bit0_symbol = { + .level0 = 0, + .duration0 = ONEWIRE_SLOT_START_DURATION + ONEWIRE_SLOT_BIT_DURATION, + .level1 = 1, + .duration1 = ONEWIRE_SLOT_RECOVERY_DURATION +}; + +const static rmt_symbol_word_t onewire_bit1_symbol = { + .level0 = 0, + .duration0 = ONEWIRE_SLOT_START_DURATION, + .level1 = 1, + .duration1 = ONEWIRE_SLOT_BIT_DURATION + ONEWIRE_SLOT_RECOVERY_DURATION +}; + +const static rmt_symbol_word_t onewire_reset_pulse_symbol = { + .level0 = 0, + .duration0 = ONEWIRE_RESET_PULSE_DURATION, + .level1 = 1, + .duration1 = ONEWIRE_RESET_WAIT_DURATION +}; + +const static rmt_transmit_config_t onewire_rmt_tx_config = { + .loop_count = 0, // no transfer loop + .flags.eot_level = 1 // onewire bus should be released in IDLE +}; + +const static rmt_receive_config_t onewire_rmt_rx_config = { + .signal_range_min_ns = 1000000000 / ONEWIRE_RMT_RESOLUTION_HZ, + .signal_range_max_ns = (ONEWIRE_RESET_PULSE_DURATION + ONEWIRE_RESET_WAIT_DURATION) * 1000 +}; + +static bool onewire_rmt_rx_done_callback(rmt_channel_handle_t channel, rmt_rx_done_event_data_t *edata, void *user_data) +{ + BaseType_t task_woken = pdFALSE; + struct onewire_bus_t *handle = (struct onewire_bus_t *)user_data; + + xQueueSendFromISR(handle->receive_queue, edata, &task_woken); + + return task_woken; +} + +/* +[0].0 means symbol[0].duration0 + +First reset pulse after rmt channel init: + +Bus is low | Reset | Wait | Device | Bus Idle +after init | Pulse | | Presense | + ┌──────┐ ┌─────------ + │ │ │ + │ │ │ + │ │ │ +------─────────────┘ └──────────┘ + 1 2 3 + + [0].1 [0].0 [1].1 [1].0 + + +Following reset pulses: + +Bus is high | Reset | Wait | Device | Bus Idle +after init | Pulse | | Presense | +------──────┐ ┌──────┐ ┌─────------ + │ │ │ │ + │ │ │ │ + │ │ │ │ + └───────┘ └──────────┘ + 1 2 3 4 + + [0].0 [0].1 [1].0 [1].1 +*/ + +static bool onewire_rmt_check_presence_pulse(rmt_symbol_word_t *rmt_symbols, size_t symbol_num) +{ + if (symbol_num >= 2) { // there should be at lease 2 symbols(3 or 4 edges) + if (rmt_symbols[0].level1 == 1) { // bus is high before reset pulse + if (rmt_symbols[0].duration1 > ONEWIRE_RESET_PRESENSE_WAIT_DURATION_MIN && + rmt_symbols[1].duration0 > ONEWIRE_RESET_PRESENSE_DURATION_MIN) { + return true; + } + } else { // bus is low before reset pulse(first pulse after rmt channel init) + if (rmt_symbols[0].duration0 > ONEWIRE_RESET_PRESENSE_WAIT_DURATION_MIN && + rmt_symbols[1].duration1 > ONEWIRE_RESET_PRESENSE_DURATION_MIN) { + return true; + } + } + } + + ESP_LOGE(TAG, "no device present on 1-wire bus"); + return false; +} + +static void onewire_rmt_decode_data(rmt_symbol_word_t *rmt_symbols, size_t symbol_num, uint8_t *decoded_bytes) +{ + size_t byte_pos = 0, bit_pos = 0; + for (size_t i = 0; i < symbol_num; i ++) { + if (rmt_symbols[i].duration0 > ONEWIRE_SLOT_BIT_SAMPLE_TIME) { // 0 bit + decoded_bytes[byte_pos] &= ~(1 << bit_pos); // LSB first + } else { // 1 bit + decoded_bytes[byte_pos] |= 1 << bit_pos; + } + + bit_pos ++; + if (bit_pos >= 8) { + bit_pos = 0; + byte_pos ++; + } + } +} + +esp_err_t onewire_new_bus_rmt(onewire_rmt_config_t *config, onewire_bus_handle_t *handle_out) +{ + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "invalid config pointer"); + ESP_RETURN_ON_FALSE(handle_out, ESP_ERR_INVALID_ARG, TAG, "invalid handle pointer"); + + esp_err_t ret = ESP_OK; + + struct onewire_bus_t *handle = calloc(1, sizeof(struct onewire_bus_t)); + ESP_GOTO_ON_FALSE(handle, ESP_ERR_NO_MEM, err, TAG, "memory allocation for 1-wire bus handler failed"); + + // create rmt bytes encoder to transmit 1-wire commands and data + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = onewire_bit0_symbol, + .bit1 = onewire_bit1_symbol, + .flags.msb_first = 0 + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &handle->tx_bytes_encoder), + err, TAG, "create data tx encoder failed"); + + // create rmt copy encoder to transmit 1-wire reset pulse or bits + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &handle->tx_copy_encoder), + err, TAG, "create reset pulse tx encoder failed"); + + // create rmt rx channel + rmt_rx_channel_config_t onewire_rx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = config->gpio_pin, +#if SOC_RMT_SUPPORT_RX_PINGPONG + .mem_block_symbols = 64, // when the chip is ping-pong capable, we can use less rx memory blocks +#else + .mem_block_symbols = config->max_rx_bytes * 8, +#endif + .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, // in us + }; + ESP_GOTO_ON_ERROR(rmt_new_rx_channel(&onewire_rx_channel_cfg, &handle->rx_channel), + err, TAG, "create rmt rx channel failed"); + ESP_LOGI(TAG, "RMT Tx channel created for 1-wire bus"); + + // create rmt tx channel after rx channel + rmt_tx_channel_config_t onewire_tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = config->gpio_pin, + .mem_block_symbols = 64, // ping-pong is always avaliable on tx channel, save hardware memory blocks + .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, // in us + .trans_queue_depth = 4, + .flags.io_loop_back = true, // make tx channel coexist with rx channel on the same gpio pin + .flags.io_od_mode = true // enable open-drain mode for 1-wire bus + }; + ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&onewire_tx_channel_cfg, &handle->tx_channel), + err, TAG, "create rmt tx channel failed"); + ESP_LOGI(TAG, "RMT Rx channel created for 1-wire bus"); + + // allocate rmt rx symbol buffer + handle->rx_symbols = malloc(config->max_rx_bytes * sizeof(rmt_symbol_word_t) * 8); + ESP_GOTO_ON_FALSE(handle->rx_symbols, ESP_ERR_NO_MEM, err, TAG, "memory allocation for rx symbol buffer failed"); + handle->max_rx_bytes = config->max_rx_bytes; + + handle->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t)); + ESP_GOTO_ON_FALSE(handle->receive_queue, ESP_ERR_NO_MEM, err, TAG, "receive queue creation failed"); + + // register rmt rx done callback + rmt_rx_event_callbacks_t cbs = { + .on_recv_done = onewire_rmt_rx_done_callback + }; + ESP_GOTO_ON_ERROR(rmt_rx_register_event_callbacks(handle->rx_channel, &cbs, handle), + err, TAG, "enable rmt rx channel failed"); + + // enable rmt channels + ESP_GOTO_ON_ERROR(rmt_enable(handle->rx_channel), err, TAG, "enable rmt rx channel failed"); + ESP_GOTO_ON_ERROR(rmt_enable(handle->tx_channel), err, TAG, "enable rmt tx channel failed"); + + *handle_out = handle; + return ESP_OK; + +err: + if (handle) { + onewire_del_bus(handle); + } + + return ret; +} + +esp_err_t onewire_del_bus(onewire_bus_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + if (handle->tx_bytes_encoder) { + rmt_del_encoder(handle->tx_bytes_encoder); + } + if (handle->tx_copy_encoder) { + rmt_del_encoder(handle->tx_copy_encoder); + } + if (handle->rx_channel) { + rmt_disable(handle->rx_channel); + rmt_del_channel(handle->rx_channel); + } + if (handle->tx_channel) { + rmt_disable(handle->tx_channel); + rmt_del_channel(handle->tx_channel); + } + if(handle->receive_queue) { + vQueueDelete(handle->receive_queue); + } + if (handle->rx_symbols) { + free(handle->rx_symbols); + } + free(handle); + + return ESP_OK; +} + +esp_err_t onewire_bus_reset(onewire_bus_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + // send reset pulse while receive presence pulse + ESP_RETURN_ON_ERROR(rmt_receive(handle->rx_channel, handle->rx_symbols, sizeof(rmt_symbol_word_t)*2, &onewire_rmt_rx_config), + TAG, "1-wire reset pulse receive failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_copy_encoder, &onewire_reset_pulse_symbol, sizeof(onewire_reset_pulse_symbol), &onewire_rmt_tx_config), + TAG, "1-wire reset pulse transmit failed"); + + // wait and check presence pulse + bool is_present = false; + rmt_rx_done_event_data_t rmt_rx_evt_data; + if (xQueueReceive(handle->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS) { + is_present = onewire_rmt_check_presence_pulse(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols); + } + + return is_present ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t handle, const uint8_t *tx_data, uint8_t tx_data_size) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(tx_data && tx_data_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid tx buffer or buffer size"); + + // transmit data + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_bytes_encoder, tx_data, tx_data_size, &onewire_rmt_tx_config), + TAG, "1-wire data transmit failed"); + + // wait the transmission to complete + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(handle->tx_channel, 50), TAG, "wait for 1-wire data transmit failed"); + + return ESP_OK; +} + +esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t handle, uint8_t *rx_data, size_t rx_data_size) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(rx_data && rx_data_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid rx buffer or buffer size"); + ESP_RETURN_ON_FALSE(!(rx_data_size > handle->max_rx_bytes), ESP_ERR_INVALID_ARG, + TAG, "rx_data_size too large for buffer to hold"); + + uint8_t tx_buffer[rx_data_size]; + memset(tx_buffer, 0xFF, rx_data_size); // transmit one bits to generate read clock + + // transmit 1 bits while receiving + ESP_RETURN_ON_ERROR(rmt_receive(handle->rx_channel, handle->rx_symbols, rx_data_size * 8 * sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), + TAG, "1-wire data receive failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_bytes_encoder, tx_buffer, sizeof(tx_buffer), &onewire_rmt_tx_config), + TAG, "1-wire data transmit failed"); + + // wait the transmission finishes and decode data + rmt_rx_done_event_data_t rmt_rx_evt_data; + if (xQueueReceive(handle->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS) { + onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_data); + } else { + return ESP_ERR_TIMEOUT; + } + + return ESP_OK; +} + +esp_err_t onewire_bus_write_bit(onewire_bus_handle_t handle, uint8_t tx_bit) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + const rmt_symbol_word_t *symbol_to_transmit = tx_bit ? &onewire_bit1_symbol : &onewire_bit0_symbol; + + // transmit bit + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_copy_encoder, symbol_to_transmit, sizeof(onewire_bit1_symbol), &onewire_rmt_tx_config), + TAG, "1-wire bit transmit failed"); + + // wait the transmission to complete + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(handle->tx_channel, 50), TAG, "wait for 1-wire bit transmit failed"); + + return ESP_OK; +} + +esp_err_t onewire_bus_read_bit(onewire_bus_handle_t handle, uint8_t *rx_bit) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid rx_bit pointer"); + + // transmit 1 bit while receiving + ESP_RETURN_ON_ERROR(rmt_receive(handle->rx_channel, handle->rx_symbols, sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), + TAG, "1-wire bit receive failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(handle->tx_channel, handle->tx_copy_encoder, &onewire_bit1_symbol, sizeof(onewire_bit1_symbol), &onewire_rmt_tx_config), + TAG, "1-wire bit transmit failed"); + + // wait the transmission finishes and decode data + rmt_rx_done_event_data_t rmt_rx_evt_data; + if (xQueueReceive(handle->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS) { + uint8_t rx_buffer[1]; + onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_buffer); + *rx_bit = rx_buffer[0] & 0x01; + } else { + return ESP_ERR_TIMEOUT; + } + + return ESP_OK; +} diff --git a/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus_rmt.h b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus_rmt.h new file mode 100644 index 0000000000..5661364af3 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/components/onewire_bus/onewire_bus_rmt.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "driver/gpio.h" + +/** + * @brief 1-wire bus config + * + */ +typedef struct { + gpio_num_t gpio_pin; /*!< gpio used for 1-wire bus */ + uint8_t max_rx_bytes; /*!< should be larger than the largest possible single receive size */ +} onewire_rmt_config_t; + +/** + * @brief Type of 1-wire bus handle + * + */ +typedef struct onewire_bus_t *onewire_bus_handle_t; + +/** + * @brief Install new 1-wire bus + * + * @param[in] config 1-wire bus configurations + * @param[out] handle_out Installed new 1-wire bus' handle + * @return + * - ESP_OK 1-wire bus is installed successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NO_MEM Memory allocation failed. + */ +esp_err_t onewire_new_bus_rmt(onewire_rmt_config_t *config, onewire_bus_handle_t *handle_out); + +/** + * @brief Delete existing 1-wire bus + * + * @param[in] handle 1-wire bus handle to be deleted + * @return + * - ESP_OK 1-wire bus is deleted successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_del_bus(onewire_bus_handle_t handle); + +/** + * @brief Send reset pulse on 1-wire bus, and detect if there are devices on the bus + * + * @param[in] handle 1-wire bus handle + * @return + * - ESP_OK There are devices present on 1-wire bus. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + */ +esp_err_t onewire_bus_reset(onewire_bus_handle_t handle); + +/** + * @brief Write bytes to 1-wire bus, this is a blocking function + * + * @param[in] handle 1-wire bus handle + * @param[in] tx_data pointer to data to be sent + * @param[in] tx_data_count number of data to be sent + * @return + * - ESP_OK Write bytes to 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t handle, const uint8_t *tx_data, uint8_t tx_data_size); + +/** + * @brief Read bytes from 1-wire bus + * + * @note While receiving data, we use rmt transmit channel to send 0xFF to generate read pulse, + * at the same time, receive channel is used to record weather the bus is pulled down by device. + * + * @param[in] handle 1-wire bus handle + * @param[out] rx_data pointer to received data + * @param[in] rx_data_count number of received data + * @return + * - ESP_OK Read bytes from 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t handle, uint8_t *rx_data, size_t rx_data_size); + +/** + * @brief Write a bit to 1-wire bus, this is a blocking function + * + * @param[in] handle 1-wire bus handle + * @param[in] tx_bit bit to transmit, 0 for zero bit, other for one bit + * @return + * - ESP_OK Write bit to 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_write_bit(onewire_bus_handle_t handle, uint8_t tx_bit); + +/** + * @brief Read a bit from 1-wire bus + * + * @param[in] handle 1-wire bus handle + * @param[out] rx_bit received bit, 0 for zero bit, 1 for one bit + * @return + * - ESP_OK Read bit from 1-wire bus successfully. + * - ESP_ERR_INVALID_ARG Invalid argument. + */ +esp_err_t onewire_bus_read_bit(onewire_bus_handle_t handle, uint8_t *rx_bit); diff --git a/examples/peripherals/rmt/onewire_ds18b20/main/CMakeLists.txt b/examples/peripherals/rmt/onewire_ds18b20/main/CMakeLists.txt new file mode 100644 index 0000000000..080bcfe0d1 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "onewire_ds18b20_example_main.c" "ds18b20.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/onewire_ds18b20/main/ds18b20.c b/examples/peripherals/rmt/onewire_ds18b20/main/ds18b20.c new file mode 100644 index 0000000000..6801c4bae6 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/main/ds18b20.c @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "ds18b20.h" +#include "esp_check.h" + +static const char *TAG = "ds18b20"; + +esp_err_t ds18b20_trigger_temperature_conversion(onewire_bus_handle_t handle, const uint8_t *rom_number) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + ESP_RETURN_ON_ERROR(onewire_bus_reset(handle), TAG, "error while resetting bus"); // reset bus and check if the device is present + + uint8_t tx_buffer[10]; + uint8_t tx_buffer_size; + + if (rom_number) { // specify rom id + tx_buffer[0] = ONEWIRE_CMD_MATCH_ROM; + tx_buffer[9] = DS18B20_CMD_CONVERT_TEMP; + memcpy(&tx_buffer[1], rom_number, 8); + tx_buffer_size = 10; + } else { // skip rom id + tx_buffer[0] = ONEWIRE_CMD_SKIP_ROM; + tx_buffer[1] = DS18B20_CMD_CONVERT_TEMP; + tx_buffer_size = 2; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, tx_buffer_size), + TAG, "error while triggering temperature convert"); + + return ESP_OK; +} + +esp_err_t ds18b20_get_temperature(onewire_bus_handle_t handle, const uint8_t *rom_number, float *temperature) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + ESP_RETURN_ON_FALSE(temperature, ESP_ERR_INVALID_ARG, TAG, "invalid temperature pointer"); + + ESP_RETURN_ON_ERROR(onewire_bus_reset(handle), TAG, "error while resetting bus"); // reset bus and check if the device is present + + ds18b20_scratchpad_t scratchpad; + + uint8_t tx_buffer[10]; + uint8_t tx_buffer_size; + + if (rom_number) { // specify rom id + tx_buffer[0] = ONEWIRE_CMD_MATCH_ROM; + tx_buffer[9] = DS18B20_CMD_READ_SCRATCHPAD; + memcpy(&tx_buffer[1], rom_number, 8); + tx_buffer_size = 10; + } else { + tx_buffer[0] = ONEWIRE_CMD_SKIP_ROM; + tx_buffer[1] = DS18B20_CMD_READ_SCRATCHPAD; + tx_buffer_size = 2; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, tx_buffer_size), + TAG, "error while sending read scratchpad command"); + ESP_RETURN_ON_ERROR(onewire_bus_read_bytes(handle, (uint8_t *)&scratchpad, sizeof(scratchpad)), + TAG, "error while reading scratchpad command"); + + ESP_RETURN_ON_FALSE(onewire_check_crc8((uint8_t *)&scratchpad, 8) == scratchpad.crc_value, ESP_ERR_INVALID_CRC, + TAG, "crc error"); + + static const uint8_t lsb_mask[4] = { 0x07, 0x03, 0x01, 0x00 }; + uint8_t lsb_masked = scratchpad.temp_lsb & (~lsb_mask[scratchpad.configuration >> 5]); // mask bits not used in low resolution + *temperature = (((int16_t)scratchpad.temp_msb << 8) | lsb_masked) / 16.0f; + + return ESP_OK; +} + +esp_err_t ds18b20_set_resolution(onewire_bus_handle_t handle, const uint8_t *rom_number, ds18b20_resolution_t resolution) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid 1-wire handle"); + + ESP_RETURN_ON_ERROR(onewire_bus_reset(handle), TAG, "error while resetting bus"); // reset bus and check if the device is present + + uint8_t tx_buffer[10]; + uint8_t tx_buffer_size; + + if (rom_number) { // specify rom id + tx_buffer[0] = ONEWIRE_CMD_MATCH_ROM; + tx_buffer[9] = DS18B20_CMD_WRITE_SCRATCHPAD; + memcpy(&tx_buffer[1], rom_number, 8); + tx_buffer_size = 10; + } else { + tx_buffer[0] = ONEWIRE_CMD_SKIP_ROM; + tx_buffer[1] = DS18B20_CMD_WRITE_SCRATCHPAD; + tx_buffer_size = 2; + } + + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, tx_buffer_size), + TAG, "error while sending read scratchpad command"); + + tx_buffer[0] = 0; + tx_buffer[1] = 0; + tx_buffer[2] = resolution; + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(handle, tx_buffer, 3), + TAG, "error while sending write scratchpad command"); + + return ESP_OK; +} diff --git a/examples/peripherals/rmt/onewire_ds18b20/main/ds18b20.h b/examples/peripherals/rmt/onewire_ds18b20/main/ds18b20.h new file mode 100644 index 0000000000..7c106461e7 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/main/ds18b20.h @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#include "onewire_bus.h" + +#define DS18B20_CMD_CONVERT_TEMP 0x44 +#define DS18B20_CMD_WRITE_SCRATCHPAD 0x4E +#define DS18B20_CMD_READ_SCRATCHPAD 0xBE + +/** + * @brief Structure of DS18B20's scratchpad + * + */ +typedef struct { + uint8_t temp_lsb; /*!< lsb of temperature */ + uint8_t temp_msb; /*!< msb of temperature */ + uint8_t th_user1; /*!< th register or user byte 1 */ + uint8_t tl_user2; /*!< tl register or user byte 2 */ + uint8_t configuration; /*!< configuration register */ + uint8_t _reserved1; + uint8_t _reserved2; + uint8_t _reserved3; + uint8_t crc_value; /*!< crc value of scratchpad data */ +} ds18b20_scratchpad_t; + +/** + * @brief Enumeration of DS18B20's resolution config + * + */ +typedef enum { + DS18B20_RESOLUTION_12B = 0x7F, /*!< 750ms convert time */ + DS18B20_RESOLUTION_11B = 0x5F, /*!< 375ms convert time */ + DS18B20_RESOLUTION_10B = 0x3F, /*!< 187.5ms convert time */ + DS18B20_RESOLUTION_9B = 0x1F, /*!< 93.75ms convert time */ +} ds18b20_resolution_t; + +/** + * @brief Trigger temperature conversion of DS18B20 + * + * @param[in] handle 1-wire handle with DS18B20 on + * @param[in] rom_number ROM number to specify which DS18B20 to send command, NULL to skip ROM + * @return + * - ESP_OK Trigger tempreture convertsion success. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + */ +esp_err_t ds18b20_trigger_temperature_conversion(onewire_bus_handle_t handle, const uint8_t *rom_number); + +/** + * @brief Get temperature from DS18B20 + * + * @param[in] handle 1-wire handle with DS18B20 on + * @param[in] rom_number ROM number to specify which DS18B20 to read from, NULL to skip ROM + * @param[out] temperature result from DS18B20 + * @return + * - ESP_OK Get tempreture from DS18B20 success. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + * - ESP_ERR_INVALID_CRC CRC check failed. + */ +esp_err_t ds18b20_get_temperature(onewire_bus_handle_t handle, const uint8_t *rom_number, float *temperature); + +/** + * @brief Set DS18B20's temperation conversion resolution + * + * @param[in] handle 1-wire handle with DS18B20 on + * @param[in] rom_number ROM number to specify which DS18B20 to read from, NULL to skip ROM + * @param[in] resolution resolution of DS18B20's temperation conversion + * @return + * - ESP_OK Set DS18B20 resolution success. + * - ESP_ERR_INVALID_ARG Invalid argument. + * - ESP_ERR_NOT_FOUND There is no device present on 1-wire bus. + */ +esp_err_t ds18b20_set_resolution(onewire_bus_handle_t handle, const uint8_t *rom_number, ds18b20_resolution_t resolution); diff --git a/examples/peripherals/rmt/onewire_ds18b20/main/onewire_ds18b20_example_main.c b/examples/peripherals/rmt/onewire_ds18b20/main/onewire_ds18b20_example_main.c new file mode 100644 index 0000000000..ef723bf2be --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/main/onewire_ds18b20_example_main.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" +#include "esp_check.h" + +#include "onewire_bus.h" +#include "ds18b20.h" + +static const char *TAG = "example"; + +#define EXAMPLE_ONEWIRE_GPIO_PIN GPIO_NUM_5 +#define EXAMPLE_ONEWIRE_MAX_DEVICES 5 + +void app_main(void) +{ + onewire_rmt_config_t config = { + .gpio_pin = EXAMPLE_ONEWIRE_GPIO_PIN, + .max_rx_bytes = 10, // 10 tx bytes(1byte ROM command + 8byte ROM number + 1byte device command) + }; + + // install new 1-wire bus + onewire_bus_handle_t handle; + ESP_ERROR_CHECK(onewire_new_bus_rmt(&config, &handle)); + ESP_LOGI(TAG, "1-wire bus installed"); + + // create 1-wire rom search context + onewire_rom_search_context_handler_t context_handler; + ESP_ERROR_CHECK(onewire_rom_search_context_create(handle, &context_handler)); + + uint8_t device_num = 0; + uint8_t device_rom_id[EXAMPLE_ONEWIRE_MAX_DEVICES][8]; + + // search for devices on the bus + do { + esp_err_t search_result = onewire_rom_search(context_handler); + + if (search_result == ESP_ERR_INVALID_CRC) { + continue; // continue on crc error + } else if (search_result == ESP_FAIL || search_result == ESP_ERR_NOT_FOUND) { + break; // break on finish or no device + } + + ESP_ERROR_CHECK(onewire_rom_get_number(context_handler, device_rom_id[device_num])); + ESP_LOGI(TAG, "found device with rom id " ONEWIRE_ROM_ID_STR, ONEWIRE_ROM_ID(device_rom_id[device_num])); + device_num ++; + } while (device_num < EXAMPLE_ONEWIRE_MAX_DEVICES); + + // delete 1-wire rom search context + ESP_ERROR_CHECK(onewire_rom_search_context_delete(context_handler)); + ESP_LOGI(TAG, "%d device%s found on 1-wire bus", device_num, device_num > 1 ? "s" : ""); + + // convert and read temperature + while (device_num > 0) { + esp_err_t err; + vTaskDelay(pdMS_TO_TICKS(200)); + + // set all sensors' temperature conversion resolution + err = ds18b20_set_resolution(handle, NULL, DS18B20_RESOLUTION_12B); + if (err != ESP_OK) { + continue; + } + + // trigger all sensors to start temperature conversion + err = ds18b20_trigger_temperature_conversion(handle, NULL); // skip rom to send command to all devices on the bus + if (err != ESP_OK) { + continue; + } + + vTaskDelay(pdMS_TO_TICKS(800)); // 12-bit resolution needs 750ms to convert + + // get temperature from sensors + for (uint8_t i = 0; i < device_num; i ++) { + float temperature; + err = ds18b20_get_temperature(handle, device_rom_id[i], &temperature); // read scratchpad and get temperature + if (err != ESP_OK) { + continue; + } + ESP_LOGI(TAG, "temperature of device " ONEWIRE_ROM_ID_STR ": %.2fC", ONEWIRE_ROM_ID(device_rom_id[i]), temperature); + } + } + + ESP_ERROR_CHECK(onewire_del_bus(handle)); + ESP_LOGI(TAG, "1-wire bus deleted"); +} diff --git a/examples/peripherals/rmt/onewire_ds18b20/pytest_onewire_ds18b20.py b/examples/peripherals/rmt/onewire_ds18b20/pytest_onewire_ds18b20.py new file mode 100644 index 0000000000..4b189f3153 --- /dev/null +++ b/examples/peripherals/rmt/onewire_ds18b20/pytest_onewire_ds18b20.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_onewire_ds18b20_example(dut: Dut) -> None: + dut.expect_exact('onewire_rmt: RMT Tx channel created for 1-wire bus') + dut.expect_exact('onewire_rmt: RMT Rx channel created for 1-wire bus') + dut.expect_exact('example: 1-wire bus installed') + dut.expect_exact('example: 1-wire bus deleted')