From 1fd31563facc3bdf81c7b3e6847b046cb68edd10 Mon Sep 17 00:00:00 2001 From: suda-morris <362953310@qq.com> Date: Wed, 19 Apr 2023 17:06:36 +0800 Subject: [PATCH] 1-wire: add 1-wire bus driver --- .github/ISSUE_TEMPLATE/bug-report.yml | 1 + .github/workflows/upload_component.yml | 1 + onewire_bus/CHANGELOG.md | 3 + onewire_bus/CMakeLists.txt | 6 + onewire_bus/LICENSE | 202 +++++++ onewire_bus/README.md | 5 + onewire_bus/idf_component.yml | 6 + onewire_bus/include/onewire_bus.h | 90 ++++ onewire_bus/include/onewire_bus_impl_rmt.h | 42 ++ onewire_bus/include/onewire_cmd.h | 12 + onewire_bus/include/onewire_crc.h | 27 + onewire_bus/include/onewire_device.h | 63 +++ onewire_bus/include/onewire_types.h | 38 ++ onewire_bus/interface/onewire_bus_interface.h | 97 ++++ onewire_bus/src/onewire_bus_api.c | 47 ++ onewire_bus/src/onewire_bus_impl_rmt.c | 493 ++++++++++++++++++ onewire_bus/src/onewire_crc.c | 60 +++ onewire_bus/src/onewire_device.c | 124 +++++ test_app/CMakeLists.txt | 2 +- 19 files changed, 1318 insertions(+), 1 deletion(-) create mode 100644 onewire_bus/CHANGELOG.md create mode 100644 onewire_bus/CMakeLists.txt create mode 100644 onewire_bus/LICENSE create mode 100644 onewire_bus/README.md create mode 100644 onewire_bus/idf_component.yml create mode 100644 onewire_bus/include/onewire_bus.h create mode 100644 onewire_bus/include/onewire_bus_impl_rmt.h create mode 100644 onewire_bus/include/onewire_cmd.h create mode 100644 onewire_bus/include/onewire_crc.h create mode 100644 onewire_bus/include/onewire_device.h create mode 100644 onewire_bus/include/onewire_types.h create mode 100644 onewire_bus/interface/onewire_bus_interface.h create mode 100644 onewire_bus/src/onewire_bus_api.c create mode 100644 onewire_bus/src/onewire_bus_impl_rmt.c create mode 100644 onewire_bus/src/onewire_crc.c create mode 100644 onewire_bus/src/onewire_device.c diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 9e51995ace..39e52e8d61 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -35,6 +35,7 @@ body: - led_strip - libsodium - nghttp + - onewire_bus - pcap - pid_ctrl - qrcode diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 9c69a5a1e0..14287b5966 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -41,6 +41,7 @@ jobs: led_strip; libsodium; nghttp; + onewire_bus; pcap; pid_ctrl; qrcode; diff --git a/onewire_bus/CHANGELOG.md b/onewire_bus/CHANGELOG.md new file mode 100644 index 0000000000..4e7a21cb4f --- /dev/null +++ b/onewire_bus/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial driver version, with the RMT driver as backend controller diff --git a/onewire_bus/CMakeLists.txt b/onewire_bus/CMakeLists.txt new file mode 100644 index 0000000000..b1b729669a --- /dev/null +++ b/onewire_bus/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRCS "src/onewire_bus_api.c" + "src/onewire_bus_impl_rmt.c" + "src/onewire_crc.c" + "src/onewire_device.c" + INCLUDE_DIRS "include" "interface" + PRIV_REQUIRES driver) diff --git a/onewire_bus/LICENSE b/onewire_bus/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/onewire_bus/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/onewire_bus/README.md b/onewire_bus/README.md new file mode 100644 index 0000000000..97c3b37343 --- /dev/null +++ b/onewire_bus/README.md @@ -0,0 +1,5 @@ +# 1-Wire Bus Driver + +[![Component Registry](https://components.espressif.com/components/espressif/onewire_bus/badge.svg)](https://components.espressif.com/components/espressif/onewire_bus) + +This directory contains an implementation for 1-Wire bus by different peripherals. Currently only RMT is supported as the backend. diff --git a/onewire_bus/idf_component.yml b/onewire_bus/idf_component.yml new file mode 100644 index 0000000000..968a3291dc --- /dev/null +++ b/onewire_bus/idf_component.yml @@ -0,0 +1,6 @@ +version: "1.0.0" +description: Driver for Dalas 1-Wire bus +url: https://github.com/espressif/idf-extra-components/tree/master/onewire_bus +issues: "https://github.com/espressif/idf-extra-components/issues" +dependencies: + idf: ">=5.0" diff --git a/onewire_bus/include/onewire_bus.h b/onewire_bus/include/onewire_bus.h new file mode 100644 index 0000000000..51206da3c2 --- /dev/null +++ b/onewire_bus/include/onewire_bus.h @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "onewire_types.h" +#include "onewire_bus_impl_rmt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Write bytes to 1-wire bus + * + * @param[in] bus 1-Wire bus handle + * @param[in] tx_data pointer to data to be sent + * @param[in] tx_data_size size of data to be sent, in bytes + * @return + * - ESP_OK: Write bytes to 1-Wire bus successfully + * - ESP_ERR_INVALID_ARG: Write bytes to 1-Wire bus failed because of invalid argument + * - ESP_FAIL: Write bytes to 1-Wire bus failed because of other errors + */ +esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size); + +/** + * @brief Read bytes from 1-wire bus + * + * @param[in] bus 1-wire bus handle + * @param[out] rx_buf pointer to buffer to store received data + * @param[in] rx_buf_size size of buffer to store received data, in bytes + * @return + * - ESP_OK: Read bytes from 1-Wire bus successfully + * - ESP_ERR_INVALID_ARG: Read bytes from 1-Wire bus failed because of invalid argument + * - ESP_FAIL: Read bytes from 1-Wire bus failed because of other errors + */ +esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_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 bus, 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 bus, uint8_t *rx_bit); + +/** + * @brief Send reset pulse to the bus, and check if there are devices attached to the bus + * + * @param[in] bus 1-Wire bus handle + * + * @return + * - ESP_OK: Reset 1-Wire bus successfully and find device on the bus + * - ESP_ERR_NOT_FOUND: Reset 1-Wire bus successfully but no device found on the bus + * - ESP_FAIL: Reset 1-Wire bus failed because of other errors + */ +esp_err_t onewire_bus_reset(onewire_bus_handle_t bus); + +/** + * @brief Free 1-Wire bus resources + * + * @param[in] bus 1-Wire bus handle + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ +esp_err_t onewire_bus_del(onewire_bus_handle_t bus); + +#ifdef __cplusplus +} +#endif diff --git a/onewire_bus/include/onewire_bus_impl_rmt.h b/onewire_bus/include/onewire_bus_impl_rmt.h new file mode 100644 index 0000000000..6aaee46720 --- /dev/null +++ b/onewire_bus/include/onewire_bus_impl_rmt.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "onewire_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 1-Wire bus RMT specific configuration + */ +typedef struct { + uint32_t max_rx_bytes; /*!< Set the largest possible single receive size, + which determins the size of the internal buffer that used to save the receiving RMT symbols */ +} onewire_bus_rmt_config_t; + +/** + * @brief Create 1-Wire bus with RMT backend + * + * @note One 1-Wire bus utilizes a pair of RMT TX and RX channels + * + * @param[in] bus_config 1-Wire bus configuration + * @param[in] rmt_config RMT specific configuration + * @param[out] ret_bus Returned 1-Wire bus handle + * @return + * - ESP_OK: create 1-Wire bus handle successfully + * - ESP_ERR_INVALID_ARG: create 1-Wire bus handle failed because of invalid argument + * - ESP_ERR_NO_MEM: create 1-Wire bus handle failed because of out of memory + * - ESP_FAIL: create 1-Wire bus handle failed because some other error + */ +esp_err_t onewire_new_bus_rmt(const onewire_bus_config_t *bus_config, const onewire_bus_rmt_config_t *rmt_config, onewire_bus_handle_t *ret_bus); + +#ifdef __cplusplus +} +#endif diff --git a/onewire_bus/include/onewire_cmd.h b/onewire_bus/include/onewire_cmd.h new file mode 100644 index 0000000000..5a091b521b --- /dev/null +++ b/onewire_bus/include/onewire_cmd.h @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#define ONEWIRE_CMD_SEARCH_NORMAL 0xF0 +#define ONEWIRE_CMD_MATCH_ROM 0x55 +#define ONEWIRE_CMD_SKIP_ROM 0xCC +#define ONEWIRE_CMD_SEARCH_ALARM 0xEC +#define ONEWIRE_CMD_READ_POWER_SUPPLY 0xB4 diff --git a/onewire_bus/include/onewire_crc.h b/onewire_bus/include/onewire_crc.h new file mode 100644 index 0000000000..c754d1fd70 --- /dev/null +++ b/onewire_bus/include/onewire_crc.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Calculate Dallas CRC8 value of a given buffer + * + * @param[in] init_crc Initial CRC value + * @param[in] input Input buffer to calculate CRC value + * @param[in] input_size Size of input buffer, in bytes + * @return CRC8 result of the input buffer + */ +uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size); + +#ifdef __cplusplus +} +#endif diff --git a/onewire_bus/include/onewire_device.h b/onewire_bus/include/onewire_device.h new file mode 100644 index 0000000000..fee0c1fdaa --- /dev/null +++ b/onewire_bus/include/onewire_device.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "onewire_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 1-Wire device generic type + */ +typedef struct onewire_device_t { + onewire_bus_handle_t bus; /*!< Which bus the 1-Wire device is attached to */ + onewire_device_address_t address; /*!< Device address (represented by its internal ROM ID) */ +} onewire_device_t; + +/** + * @brief Create an iterator to enumerate the 1-Wire devices on the bus + * + * @param[in] bus 1-Wire bus handle + * @param[out] ret_iter Returned created device iterator + * @return + * - ESP_OK: Create device iterator successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NO_MEM: No memory to create device iterator + * - ESP_FAIL: Other errors + */ +esp_err_t onewire_new_device_iter(onewire_bus_handle_t bus, onewire_device_iter_handle_t *ret_iter); + +/** + * @brief Delete the device iterator + * + * @param[in] iter Device iterator handle + * @return + * - ESP_OK: Delete device iterator successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_FAIL: Other errors + */ +esp_err_t onewire_del_device_iter(onewire_device_iter_handle_t iter); + +/** + * @brief Get the next 1-Wire device from the iterator + * + * @param[in] iter Device iterator handle + * @param[out] dev Returned 1-Wire device handle + * @return + * - ESP_OK: Get next device successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NOT_FOUND: No more device to get + * - ESP_FAIL: Other errors + */ +esp_err_t onewire_device_iter_get_next(onewire_device_iter_handle_t iter, onewire_device_t *dev); + +#ifdef __cplusplus +} +#endif diff --git a/onewire_bus/include/onewire_types.h b/onewire_bus/include/onewire_types.h new file mode 100644 index 0000000000..6d7b12098c --- /dev/null +++ b/onewire_bus/include/onewire_types.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of 1-Wire bus handle + */ +typedef struct onewire_bus_t *onewire_bus_handle_t; + +/** + * @brief Type of the address for a 1-Wire compatible device + */ +typedef uint64_t onewire_device_address_t; + +/** + * @brief Type of 1-Wire device iterator handle + */ +typedef struct onewire_device_iter_t *onewire_device_iter_handle_t; + +/** + * @brief 1-Wire bus configuration + */ +typedef struct { + int bus_gpio_num; /*!< GPIO number that used by the 1-Wire bus */ +} onewire_bus_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/onewire_bus/interface/onewire_bus_interface.h b/onewire_bus/interface/onewire_bus_interface.h new file mode 100644 index 0000000000..9920c4e4d0 --- /dev/null +++ b/onewire_bus/interface/onewire_bus_interface.h @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct onewire_bus_t onewire_bus_t; /*!< Type of 1-Wire bus */ + +/** + * @brief 1-Wire bus interface definition + */ +struct onewire_bus_t { + /** + * @brief Write bytes to 1-wire bus + * + * @note This is a blocking function + * + * @param[in] bus 1-Wire bus handle + * @param[in] tx_data pointer to data to be sent + * @param[in] tx_data_size size of data to be sent, in bytes + * @return + * - ESP_OK: Write bytes to 1-Wire bus successfully + * - ESP_ERR_INVALID_ARG: Write bytes to 1-Wire bus failed because of invalid argument + * - ESP_FAIL: Write bytes to 1-Wire bus failed because of other errors + */ + esp_err_t (*write_bytes)(onewire_bus_t *bus, const uint8_t *tx_data, uint8_t tx_data_size); + + /** + * @brief Read bytes from 1-wire bus + * + * @param[in] bus 1-wire bus handle + * @param[out] rx_buf pointer to buffer to store received data + * @param[in] rx_buf_size size of buffer to store received data, in bytes + * @return + * - ESP_OK: Read bytes from 1-Wire bus successfully + * - ESP_ERR_INVALID_ARG: Read bytes from 1-Wire bus failed because of invalid argument + * - ESP_FAIL: Read bytes from 1-Wire bus failed because of other errors + */ + esp_err_t (*read_bytes)(onewire_bus_t *bus, uint8_t *rx_buf, size_t rx_buf_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 (*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 (*read_bit)(onewire_bus_handle_t handle, uint8_t *rx_bit); + + /** + * @brief Send reset pulse to the bus, and check if there are devices attached to the bus + * + * @param[in] bus 1-Wire bus handle + * + * @return + * - ESP_OK: Reset 1-Wire bus successfully and find device on the bus + * - ESP_ERR_NOT_FOUND: Reset 1-Wire bus successfully but no device found on the bus + * - ESP_FAIL: Reset 1-Wire bus failed because of other errors + */ + esp_err_t (*reset)(onewire_bus_t *bus); + + /** + * @brief Free 1-Wire bus resources + * + * @param[in] bus 1-Wire bus handle + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ + esp_err_t (*del)(onewire_bus_t *bus); +}; + +#ifdef __cplusplus +} +#endif diff --git a/onewire_bus/src/onewire_bus_api.c b/onewire_bus/src/onewire_bus_api.c new file mode 100644 index 0000000000..3cded8f3d5 --- /dev/null +++ b/onewire_bus/src/onewire_bus_api.c @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_log.h" +#include "esp_check.h" +#include "onewire_types.h" +#include "onewire_bus_interface.h" + +static const char *TAG = "1-wire"; + +esp_err_t onewire_bus_reset(onewire_bus_handle_t bus) +{ + ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return bus->reset(bus); +} + +esp_err_t onewire_bus_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size) +{ + ESP_RETURN_ON_FALSE(bus && tx_data && tx_data_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return bus->write_bytes(bus, tx_data, tx_data_size); +} + +esp_err_t onewire_bus_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size) +{ + ESP_RETURN_ON_FALSE(bus && rx_buf && rx_buf_size, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return bus->read_bytes(bus, rx_buf, rx_buf_size); +} + +esp_err_t onewire_bus_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit) +{ + ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return bus->write_bit(bus, tx_bit); +} + +esp_err_t onewire_bus_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit) +{ + ESP_RETURN_ON_FALSE(bus && rx_bit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return bus->read_bit(bus, rx_bit); +} + +esp_err_t onewire_bus_del(onewire_bus_handle_t bus) +{ + ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return bus->del(bus); +} diff --git a/onewire_bus/src/onewire_bus_impl_rmt.c b/onewire_bus/src/onewire_bus_impl_rmt.c new file mode 100644 index 0000000000..0938100bc0 --- /dev/null +++ b/onewire_bus/src/onewire_bus_impl_rmt.c @@ -0,0 +1,493 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 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 "freertos/semphr.h" +#include "esp_check.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "onewire_bus_impl_rmt.h" +#include "onewire_bus_interface.h" + +static const char *TAG = "1-wire.rmt"; + +#define ONEWIRE_RMT_RESOLUTION_HZ 1000000 // RMT channel default resolution for 1-wire bus, 1MHz, 1tick = 1us +#define ONEWIRE_RMT_DEFAULT_TRANS_QUEUE_SIZE 4 + +// the memory size of each RMT channel, in words (4 bytes) +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#define ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64 +#else +#define ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48 +#endif + +// for chips whose RMT RX channel doesn't support ping-pong, we need the user to tell the maximum number of bytes will be received +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +// one RMT symbol represents one bit, so x8 +#define ONEWIRE_RMT_RX_MEM_BLOCK_SIZE (rmt_config->max_rx_bytes * 8) +#else // otherwise, we just use one memory block, to save resources +#define ONEWIRE_RMT_RX_MEM_BLOCK_SIZE ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS +#endif + +/* +Reset Pulse: + + | RESET_PULSE | RESET_WAIT_DURATION | + | _DURATION | | + | | | | RESET | | + | | * | | _PRESENCE | | + | | | | _DURATION | | +----------+ +-----+ +-------------- + | | | | + | | | | + | | | | + +-------------+ +-----------+ +*: RESET_PRESENCE_WAIT_DURATION +*/ +#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_PRESENCE_WAIT_DURATION_MIN 15 // minimum duration for master to wait device to show its presence +#define ONEWIRE_RESET_PRESENCE_DURATION_MIN 60 // minimum duration for master to recognize device as present + +/* +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 | + +-----------------------------+ +*/ +#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 + +typedef struct { + onewire_bus_t base; /*!< base class */ + rmt_channel_handle_t tx_channel; /*!< rmt tx channel handler */ + rmt_channel_handle_t rx_channel; /*!< rmt rx 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_symbol_word_t *rx_symbols_buf; /*!< hold rmt raw symbols */ + + size_t max_rx_bytes; /*!< buffer size in byte for single receive transaction */ + + QueueHandle_t receive_queue; + SemaphoreHandle_t bus_mutex; +} onewire_bus_rmt_obj_t; + +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_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_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 esp_err_t onewire_bus_rmt_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit); +static esp_err_t onewire_bus_rmt_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit); +static esp_err_t onewire_bus_rmt_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size); +static esp_err_t onewire_bus_rmt_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size); +static esp_err_t onewire_bus_rmt_reset(onewire_bus_handle_t bus); +static esp_err_t onewire_bus_rmt_del(onewire_bus_handle_t bus); +static esp_err_t onewire_bus_rmt_destroy(onewire_bus_rmt_obj_t *bus_rmt); + +static bool onewire_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) +{ + BaseType_t task_woken = pdFALSE; + onewire_bus_rmt_obj_t *bus_rmt = (onewire_bus_rmt_obj_t *)user_data; + + xQueueSendFromISR(bus_rmt->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 | | Presence | + +------+ +----------- + | | | + | | | + | | | +-------------------+ +----------+ + 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 | | Presence | +------------+ +------+ +----------- + | | | | + | | | | + | | | | + +-------+ +----------+ + 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) +{ + bool ret = false; + 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_PRESENCE_WAIT_DURATION_MIN && + rmt_symbols[1].duration0 > ONEWIRE_RESET_PRESENCE_DURATION_MIN) { + ret = true; + } + } else { // bus is low before reset pulse(first pulse after rmt channel init) + if (rmt_symbols[0].duration0 > ONEWIRE_RESET_PRESENCE_WAIT_DURATION_MIN && + rmt_symbols[1].duration1 > ONEWIRE_RESET_PRESENCE_DURATION_MIN) { + ret = true; + } + } + } + return ret; +} + +static void onewire_rmt_decode_data(rmt_symbol_word_t *rmt_symbols, size_t symbol_num, uint8_t *rx_buf, size_t rx_buf_size) +{ + size_t byte_pos = 0; + size_t bit_pos = 0; + for (size_t i = 0; i < symbol_num; i ++) { + if (rmt_symbols[i].duration0 > ONEWIRE_SLOT_BIT_SAMPLE_TIME) { // 0 bit + rx_buf[byte_pos] &= ~(1 << bit_pos); // LSB first + } else { // 1 bit + rx_buf[byte_pos] |= 1 << bit_pos; + } + bit_pos ++; + if (bit_pos >= 8) { + bit_pos = 0; + byte_pos ++; + if (byte_pos >= rx_buf_size) { + break; + } + } + } +} + +esp_err_t onewire_new_bus_rmt(const onewire_bus_config_t *bus_config, const onewire_bus_rmt_config_t *rmt_config, onewire_bus_handle_t *ret_bus) +{ + esp_err_t ret = ESP_OK; + onewire_bus_rmt_obj_t *bus_rmt = NULL; + ESP_RETURN_ON_FALSE(bus_config && rmt_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + bus_rmt = calloc(1, sizeof(onewire_bus_rmt_obj_t)); + ESP_RETURN_ON_FALSE(bus_rmt, ESP_ERR_NO_MEM, TAG, "no mem for onewire_bus_rmt_obj_t"); + + // 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, &bus_rmt->tx_bytes_encoder), + err, TAG, "create bytes 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, &bus_rmt->tx_copy_encoder), + err, TAG, "create copy encoder failed"); + + // Note: must create rmt rx channel before tx channel + rmt_rx_channel_config_t onewire_rx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, + .gpio_num = bus_config->bus_gpio_num, + .mem_block_symbols = ONEWIRE_RMT_RX_MEM_BLOCK_SIZE, + }; + ESP_GOTO_ON_ERROR(rmt_new_rx_channel(&onewire_rx_channel_cfg, &bus_rmt->rx_channel), + err, TAG, "create rmt rx channel failed"); + + // create rmt tx channel + rmt_tx_channel_config_t onewire_tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = ONEWIRE_RMT_RESOLUTION_HZ, + .gpio_num = bus_config->bus_gpio_num, + .mem_block_symbols = ONEWIRE_RMT_DEFAULT_MEM_BLOCK_SYMBOLS, + .trans_queue_depth = ONEWIRE_RMT_DEFAULT_TRANS_QUEUE_SIZE, + .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, &bus_rmt->tx_channel), + err, TAG, "create rmt tx channel failed"); + + // allocate rmt rx symbol buffer, one RMT symbol represents one bit, so x8 + bus_rmt->rx_symbols_buf = malloc(rmt_config->max_rx_bytes * sizeof(rmt_symbol_word_t) * 8); + ESP_GOTO_ON_FALSE(bus_rmt->rx_symbols_buf, ESP_ERR_NO_MEM, err, TAG, "no mem to store received RMT symbols"); + bus_rmt->max_rx_bytes = rmt_config->max_rx_bytes; + + bus_rmt->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t)); + ESP_GOTO_ON_FALSE(bus_rmt->receive_queue, ESP_ERR_NO_MEM, err, TAG, "receive queue creation failed"); + + bus_rmt->bus_mutex = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(bus_rmt->bus_mutex, ESP_ERR_NO_MEM, err, TAG, "bus mutex 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(bus_rmt->rx_channel, &cbs, bus_rmt), + err, TAG, "enable rmt rx channel failed"); + + // enable rmt channels + ESP_GOTO_ON_ERROR(rmt_enable(bus_rmt->rx_channel), err, TAG, "enable rmt rx channel failed"); + ESP_GOTO_ON_ERROR(rmt_enable(bus_rmt->tx_channel), err, TAG, "enable rmt tx channel failed"); + + // release the bus by sending a special RMT symbol + static const rmt_symbol_word_t release_symbol = { + .level0 = 1, + .duration0 = 1, + .level1 = 1, + .duration1 = 0, + }; + ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &release_symbol, + sizeof(release_symbol), &onewire_rmt_tx_config), err, TAG, "release bus failed"); + + bus_rmt->base.del = onewire_bus_rmt_del; + bus_rmt->base.reset = onewire_bus_rmt_reset; + bus_rmt->base.write_bit = onewire_bus_rmt_write_bit; + bus_rmt->base.write_bytes = onewire_bus_rmt_write_bytes; + bus_rmt->base.read_bit = onewire_bus_rmt_read_bit; + bus_rmt->base.read_bytes = onewire_bus_rmt_read_bytes; + *ret_bus = &bus_rmt->base; + + return ret; + +err: + if (bus_rmt) { + onewire_bus_rmt_destroy(bus_rmt); + } + + return ret; +} + +static esp_err_t onewire_bus_rmt_destroy(onewire_bus_rmt_obj_t *bus_rmt) +{ + if (bus_rmt->tx_bytes_encoder) { + rmt_del_encoder(bus_rmt->tx_bytes_encoder); + } + if (bus_rmt->tx_copy_encoder) { + rmt_del_encoder(bus_rmt->tx_copy_encoder); + } + if (bus_rmt->rx_channel) { + rmt_disable(bus_rmt->rx_channel); + rmt_del_channel(bus_rmt->rx_channel); + } + if (bus_rmt->tx_channel) { + rmt_disable(bus_rmt->tx_channel); + rmt_del_channel(bus_rmt->tx_channel); + } + if (bus_rmt->receive_queue) { + vQueueDelete(bus_rmt->receive_queue); + } + if (bus_rmt->bus_mutex) { + vSemaphoreDelete(bus_rmt->bus_mutex); + } + if (bus_rmt->rx_symbols_buf) { + free(bus_rmt->rx_symbols_buf); + } + free(bus_rmt); + return ESP_OK; +} + +static esp_err_t onewire_bus_rmt_del(onewire_bus_handle_t bus) +{ + onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); + return onewire_bus_rmt_destroy(bus_rmt); +} + +static esp_err_t onewire_bus_rmt_reset(onewire_bus_handle_t bus) +{ + onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); + esp_err_t ret = ESP_OK; + + xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); + // send reset pulse while receive presence pulse + ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, sizeof(rmt_symbol_word_t) * 2, &onewire_rmt_rx_config), + err, TAG, "1-wire reset pulse receive failed"); + ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &onewire_reset_pulse_symbol, sizeof(onewire_reset_pulse_symbol), &onewire_rmt_tx_config), + err, TAG, "1-wire reset pulse transmit failed"); + + // wait and check presence pulse + rmt_rx_done_event_data_t rmt_rx_evt_data; + ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, + ESP_ERR_TIMEOUT, err, TAG, "1-wire reset pulse receive timeout"); + if (onewire_rmt_check_presence_pulse(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols) == false) { + ret = ESP_ERR_NOT_FOUND; + } + +err: + xSemaphoreGive(bus_rmt->bus_mutex); + return ret; +} + +static esp_err_t onewire_bus_rmt_write_bytes(onewire_bus_handle_t bus, const uint8_t *tx_data, uint8_t tx_data_size) +{ + onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); + esp_err_t ret = ESP_OK; + + xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); + // transmit data with the bytes encoder + ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_bytes_encoder, tx_data, tx_data_size, &onewire_rmt_tx_config), + err, TAG, "1-wire data transmit failed"); + // wait the transmission to complete + ESP_GOTO_ON_ERROR(rmt_tx_wait_all_done(bus_rmt->tx_channel, 50), err, TAG, "wait for 1-wire data transmit failed"); + +err: + xSemaphoreGive(bus_rmt->bus_mutex); + return ret; +} + +// 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. +static esp_err_t onewire_bus_rmt_read_bytes(onewire_bus_handle_t bus, uint8_t *rx_buf, size_t rx_buf_size) +{ + onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(rx_buf_size <= bus_rmt->max_rx_bytes, ESP_ERR_INVALID_ARG, TAG, "rx_buf_size too large for buffer to hold"); + memset(rx_buf, 0, rx_buf_size); + + xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); + + // transmit one bits to generate read clock + uint8_t tx_buffer[rx_buf_size]; + memset(tx_buffer, 0xFF, rx_buf_size); + // transmit 1 bits while receiving + ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, rx_buf_size * 8 * sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), + err, TAG, "1-wire data receive failed"); + ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_bytes_encoder, tx_buffer, sizeof(tx_buffer), &onewire_rmt_tx_config), + err, TAG, "1-wire data transmit failed"); + + // wait the transmission finishes and decode data + rmt_rx_done_event_data_t rmt_rx_evt_data; + ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT, + err, TAG, "1-wire data receive timeout"); + onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, rx_buf, rx_buf_size); + +err: + xSemaphoreGive(bus_rmt->bus_mutex); + return ret; +} + +static esp_err_t onewire_bus_rmt_write_bit(onewire_bus_handle_t bus, uint8_t tx_bit) +{ + onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); + const rmt_symbol_word_t *symbol_to_transmit = tx_bit ? &onewire_bit1_symbol : &onewire_bit0_symbol; + esp_err_t ret = ESP_OK; + + xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); + + // transmit bit + ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, symbol_to_transmit, sizeof(rmt_symbol_word_t), &onewire_rmt_tx_config), + err, TAG, "1-wire bit transmit failed"); + // wait the transmission to complete + ESP_GOTO_ON_ERROR(rmt_tx_wait_all_done(bus_rmt->tx_channel, 50), err, TAG, "wait for 1-wire bit transmit failed"); + +err: + xSemaphoreGive(bus_rmt->bus_mutex); + return ret; +} + +static esp_err_t onewire_bus_rmt_read_bit(onewire_bus_handle_t bus, uint8_t *rx_bit) +{ + onewire_bus_rmt_obj_t *bus_rmt = __containerof(bus, onewire_bus_rmt_obj_t, base); + esp_err_t ret = ESP_OK; + + xSemaphoreTake(bus_rmt->bus_mutex, portMAX_DELAY); + + // transmit 1 bit while receiving + ESP_GOTO_ON_ERROR(rmt_receive(bus_rmt->rx_channel, bus_rmt->rx_symbols_buf, sizeof(rmt_symbol_word_t), &onewire_rmt_rx_config), + err, TAG, "1-wire bit receive failed"); + ESP_GOTO_ON_ERROR(rmt_transmit(bus_rmt->tx_channel, bus_rmt->tx_copy_encoder, &onewire_bit1_symbol, sizeof(rmt_symbol_word_t), &onewire_rmt_tx_config), + err, TAG, "1-wire bit transmit failed"); + + // wait the transmission finishes and decode data + rmt_rx_done_event_data_t rmt_rx_evt_data; + ESP_GOTO_ON_FALSE(xQueueReceive(bus_rmt->receive_queue, &rmt_rx_evt_data, pdMS_TO_TICKS(1000)) == pdPASS, ESP_ERR_TIMEOUT, + err, TAG, "1-wire bit receive timeout"); + uint8_t rx_buffer = 0; + onewire_rmt_decode_data(rmt_rx_evt_data.received_symbols, rmt_rx_evt_data.num_symbols, &rx_buffer, sizeof(rx_buffer)); + *rx_bit = rx_buffer & 0x01; + +err: + xSemaphoreGive(bus_rmt->bus_mutex); + return ret; +} diff --git a/onewire_bus/src/onewire_crc.c b/onewire_bus/src/onewire_crc.c new file mode 100644 index 0000000000..81595b141b --- /dev/null +++ b/onewire_bus/src/onewire_crc.c @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "onewire_crc.h" + +#define FAST_CRC 1 // define this to use the fast CRC table + +#if FAST_CRC + +static const uint8_t dalas_crc8_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_crc8(uint8_t init_crc, uint8_t *input, size_t input_size) +{ + uint8_t crc = init_crc; + for (size_t i = 0; i < input_size; i ++) { + crc = dalas_crc8_table[crc ^ input[i]]; + } + return crc; +} + +#else // FAST_CRC + +uint8_t onewire_crc8(uint8_t init_crc, uint8_t *input, size_t input_size) +{ + uint8_t crc = init_crc; + for (size_t i = 0; i < input_size; i++) { + uint8_t byte = input[i]; + for (int j = 0; j < 8; j++) { + uint8_t x = (byte ^ crc) & 0x01; + crc >>= 1; + if (x != 0) { + crc ^= 0x8C; + } + byte >>= 1; + } + } + return crc; +} + +#endif // FAST_CRC diff --git a/onewire_bus/src/onewire_device.c b/onewire_bus/src/onewire_device.c new file mode 100644 index 0000000000..82a1e32edd --- /dev/null +++ b/onewire_bus/src/onewire_device.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "esp_check.h" +#include "esp_log.h" +#include "onewire_bus.h" +#include "onewire_device.h" +#include "onewire_crc.h" +#include "onewire_cmd.h" + +static const char *TAG = "1-wire.device"; + +typedef struct onewire_device_iter_t { + onewire_bus_handle_t bus; + uint16_t last_discrepancy; + bool is_last_device; + uint8_t rom_number[sizeof(onewire_device_address_t)]; +} onewire_device_iter_t; + +esp_err_t onewire_new_device_iter(onewire_bus_handle_t bus, onewire_device_iter_handle_t *ret_iter) +{ + ESP_RETURN_ON_FALSE(bus && ret_iter, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + onewire_device_iter_t *iter = calloc(1, sizeof(onewire_device_iter_t)); + ESP_RETURN_ON_FALSE(iter, ESP_ERR_NO_MEM, TAG, "no mem for device iterator"); + + iter->bus = bus; + *ret_iter = iter; + + return ESP_OK; +} + +esp_err_t onewire_del_device_iter(onewire_device_iter_handle_t iter) +{ + ESP_RETURN_ON_FALSE(iter, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + free(iter); + + return ESP_OK; +} + +// Search algorithm inspired by https://www.analog.com/en/app-notes/1wire-search-algorithm.html +esp_err_t onewire_device_iter_get_next(onewire_device_iter_handle_t iter, onewire_device_t *dev) +{ + ESP_RETURN_ON_FALSE(iter && dev, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + // we don't treat iterator ending and ESP_ERR_NOT_FOUND as an error condition, so just print debug message here + if (iter->is_last_device) { + ESP_LOGD(TAG, "1-wire rom search finished"); + return ESP_ERR_NOT_FOUND; + } + onewire_bus_handle_t bus = iter->bus; + esp_err_t reset_result = onewire_bus_reset(bus); + if (reset_result == ESP_ERR_NOT_FOUND) { + ESP_LOGW(TAG, "reset bus failed: no devices found"); + return ESP_ERR_NOT_FOUND; + } + ESP_RETURN_ON_ERROR(reset_result, TAG, "reset bus failed"); + + // send rom search command and start search algorithm + ESP_RETURN_ON_ERROR(onewire_bus_write_bytes(bus, (uint8_t[]) { + ONEWIRE_CMD_SEARCH_NORMAL + }, 1), TAG, "send ONEWIRE_CMD_SEARCH_NORMAL failed"); + + uint8_t last_zero = 0; + for (uint16_t rom_bit_index = 0; rom_bit_index < sizeof(onewire_device_address_t) * 8; 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 = 0; + uint8_t rom_bit_complement = 0; + ESP_RETURN_ON_ERROR(onewire_bus_read_bit(bus, &rom_bit), TAG, "read rom_bit error"); // write 1 bit to read from the bus + ESP_RETURN_ON_ERROR(onewire_bus_read_bit(bus, &rom_bit_complement), TAG, "read rom_bit_complement error"); // read a bit and its complement + + // No devices participating in search. + if (rom_bit && rom_bit_complement) { + ESP_LOGE(TAG, "no devices participating in search"); + return ESP_ERR_NOT_FOUND; + } + + uint8_t search_direction; + 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 < iter->last_discrepancy) { // current id bit is before the last discrepancy bit + search_direction = (iter->rom_number[rom_byte_index] & rom_bit_mask) ? 0x01 : 0x00; // follow previous way + } else { + search_direction = (rom_bit_index == iter->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 search direction + iter->rom_number[rom_byte_index] |= rom_bit_mask; + } else { + iter->rom_number[rom_byte_index] &= ~rom_bit_mask; + } + + // set search direction + ESP_RETURN_ON_ERROR(onewire_bus_write_bit(bus, search_direction), TAG, "write direction bit error"); + } + + // if the search was successful + iter->last_discrepancy = last_zero; + if (iter->last_discrepancy == 0) { // last zero loops back to the first bit + iter->is_last_device = true; + } + + // check crc + ESP_RETURN_ON_FALSE(onewire_crc8(0, iter->rom_number, 7) == iter->rom_number[7], ESP_ERR_INVALID_CRC, TAG, "bad device crc"); + + // save the ROM number as the device address + memcpy(&dev->address, iter->rom_number, sizeof(onewire_device_address_t)); + dev->bus = bus; + ESP_LOGD(TAG, "new 1-Wire device found, address: %016llX", dev->address); + + return ESP_OK; +} diff --git a/test_app/CMakeLists.txt b/test_app/CMakeLists.txt index bccf13dbbf..729360a1f3 100644 --- a/test_app/CMakeLists.txt +++ b/test_app/CMakeLists.txt @@ -14,7 +14,7 @@ endif() # 3. Add here if the component is compatible with IDF >= v5.0 if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") - list(APPEND EXTRA_COMPONENT_DIRS ../bdc_motor ../led_strip ../sh2lib ../nghttp ../esp_serial_slave_link) + list(APPEND EXTRA_COMPONENT_DIRS ../bdc_motor ../led_strip ../sh2lib ../nghttp ../esp_serial_slave_link ../onewire_bus) endif() # !This section should NOT be touched when adding new component!