components/esp32-owb/owb_rmt.c

changeset 0
88d965579617
child 72
acc1904cd70d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/esp32-owb/owb_rmt.c	Tue Oct 08 12:00:31 2019 +0200
@@ -0,0 +1,463 @@
+/*
+Created by Chris Morgan based on the nodemcu project driver.
+Copyright 2017 Chris Morgan <chmorgan@gmail.com>
+
+Ported to ESP32 RMT peripheral for low-level signal generation by Arnim Laeuger.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Much of the code was inspired by Derek Yerger's code, though I don't
+think much of that remains.  In any event that was..
+    (copyleft) 2006 by Derek Yerger - Free to distribute freely.
+
+The CRC code was excerpted and inspired by the Dallas Semiconductor
+sample code bearing this copyright.
+//---------------------------------------------------------------------------
+// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//--------------------------------------------------------------------------
+*/
+
+#include "owb.h"
+
+#include "driver/rmt.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+
+#undef OW_DEBUG
+
+
+// bus reset: duration of low phase [us]
+#define OW_DURATION_RESET 480
+// overall slot duration
+#define OW_DURATION_SLOT 75
+// write 1 slot and read slot durations [us]
+#define OW_DURATION_1_LOW    2
+#define OW_DURATION_1_HIGH (OW_DURATION_SLOT - OW_DURATION_1_LOW)
+// write 0 slot durations [us]
+#define OW_DURATION_0_LOW   65
+#define OW_DURATION_0_HIGH (OW_DURATION_SLOT - OW_DURATION_0_LOW)
+// sample time for read slot
+#define OW_DURATION_SAMPLE  (15-2)
+// RX idle threshold
+// needs to be larger than any duration occurring during write slots
+#define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2)
+
+
+static const char * TAG = "owb_rmt";
+
+#define info_of_driver(owb) container_of(owb, owb_rmt_driver_info, bus)
+
+// flush any pending/spurious traces from the RX channel
+static void onewire_flush_rmt_rx_buf(const OneWireBus * bus)
+{
+    void *p;
+    size_t s;
+
+    owb_rmt_driver_info *i = info_of_driver(bus);
+
+    while ((p = xRingbufferReceive(i->rb, &s, 0)))
+    {
+        ESP_LOGD(TAG, "flushing entry");
+        vRingbufferReturnItem(i->rb, p);
+    }
+}
+
+static owb_status _reset(const OneWireBus *bus, bool *is_present)
+{
+    rmt_item32_t tx_items[1];
+    bool _is_present = false;
+    int res = OWB_STATUS_OK;
+
+    owb_rmt_driver_info *i = info_of_driver(bus);
+
+    tx_items[0].duration0 = OW_DURATION_RESET;
+    tx_items[0].level0 = 0;
+    tx_items[0].duration1 = 0;
+    tx_items[0].level1 = 1;
+
+    uint16_t old_rx_thresh;
+    rmt_get_rx_idle_thresh(i->rx_channel, &old_rx_thresh);
+    rmt_set_rx_idle_thresh(i->rx_channel, OW_DURATION_RESET+60);
+
+    onewire_flush_rmt_rx_buf(bus);
+    rmt_rx_start(i->rx_channel, true);
+    if (rmt_write_items(i->tx_channel, tx_items, 1, true) == ESP_OK)
+    {
+        size_t rx_size;
+        rmt_item32_t* rx_items = (rmt_item32_t *)xRingbufferReceive(i->rb, &rx_size, 100 / portTICK_PERIOD_MS);
+
+        if (rx_items)
+        {
+            if (rx_size >= (1 * sizeof(rmt_item32_t)))
+            {
+#ifdef OW_DEBUG
+                ESP_LOGI(TAG, "rx_size: %d", rx_size);
+
+                for (int i = 0; i < (rx_size / sizeof(rmt_item32_t)); i++)
+                {
+                    ESP_LOGI(TAG, "i: %d, level0: %d, duration %d", i, rx_items[i].level0, rx_items[i].duration0);
+                    ESP_LOGI(TAG, "i: %d, level1: %d, duration %d", i, rx_items[i].level1, rx_items[i].duration1);
+                }
+#endif
+
+                // parse signal and search for presence pulse
+                if ((rx_items[0].level0 == 0) && (rx_items[0].duration0 >= OW_DURATION_RESET - 2))
+                {
+                    if ((rx_items[0].level1 == 1) && (rx_items[0].duration1 > 0))
+                    {
+                        if (rx_items[1].level0 == 0)
+                        {
+                            _is_present = true;
+                        }
+                    }
+                }
+            }
+
+            vRingbufferReturnItem(i->rb, (void *)rx_items);
+        }
+        else
+        {
+            // time out occurred, this indicates an unconnected / misconfigured bus
+            ESP_LOGE(TAG, "rx_items == 0");
+            res = OWB_STATUS_HW_ERROR;
+        }
+    }
+    else
+    {
+        // error in tx channel
+        ESP_LOGE(TAG, "Error tx");
+        res = OWB_STATUS_HW_ERROR;
+    }
+
+    rmt_rx_stop(i->rx_channel);
+    rmt_set_rx_idle_thresh(i->rx_channel, old_rx_thresh);
+
+    *is_present = _is_present;
+
+    ESP_LOGD(TAG, "_is_present %d", _is_present);
+
+    return res;
+}
+
+static rmt_item32_t _encode_write_slot(uint8_t val)
+{
+    rmt_item32_t item;
+
+    item.level0 = 0;
+    item.level1 = 1;
+    if (val)
+    {
+        // write "1" slot
+        item.duration0 = OW_DURATION_1_LOW;
+        item.duration1 = OW_DURATION_1_HIGH;
+    }
+    else
+    {
+        // write "0" slot
+        item.duration0 = OW_DURATION_0_LOW;
+        item.duration1 = OW_DURATION_0_HIGH;
+    }
+
+    return item;
+}
+
+/** NOTE: The data is shifted out of the low bits, eg. it is written in the order of lsb to msb */
+static owb_status _write_bits(const OneWireBus * bus, uint8_t out, int number_of_bits_to_write)
+{
+    rmt_item32_t tx_items[number_of_bits_to_write + 1];
+    owb_rmt_driver_info *info = info_of_driver(bus);
+
+    if (number_of_bits_to_write > 8)
+    {
+        return OWB_STATUS_TOO_MANY_BITS;
+    }
+
+    // write requested bits as pattern to TX buffer
+    for (int i = 0; i < number_of_bits_to_write; i++)
+    {
+        tx_items[i] = _encode_write_slot(out & 0x01);
+        out >>= 1;
+    }
+
+    // end marker
+    tx_items[number_of_bits_to_write].level0 = 1;
+    tx_items[number_of_bits_to_write].duration0 = 0;
+
+    owb_status status;
+
+    if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_write+1, true) == ESP_OK)
+    {
+        status = OWB_STATUS_OK;
+    }
+    else
+    {
+        status = OWB_STATUS_HW_ERROR;
+        ESP_LOGE(TAG, "rmt_write_items() failed");
+    }
+
+    return status;
+}
+
+static rmt_item32_t _encode_read_slot(void)
+{
+    rmt_item32_t item;
+
+    // construct pattern for a single read time slot
+    item.level0    = 0;
+    item.duration0 = OW_DURATION_1_LOW;   // shortly force 0
+    item.level1    = 1;
+    item.duration1 = OW_DURATION_1_HIGH;  // release high and finish slot
+    return item;
+}
+
+/** NOTE: Data is read into the high bits, eg. each bit read is shifted down before the next bit is read */
+static owb_status _read_bits(const OneWireBus * bus, uint8_t *in, int number_of_bits_to_read)
+{
+    rmt_item32_t tx_items[number_of_bits_to_read + 1];
+    uint8_t read_data = 0;
+    int res = OWB_STATUS_OK;
+
+    owb_rmt_driver_info *info = info_of_driver(bus);
+
+    if (number_of_bits_to_read > 8)
+    {
+        ESP_LOGE(TAG, "_read_bits() OWB_STATUS_TOO_MANY_BITS");
+        return OWB_STATUS_TOO_MANY_BITS;
+    }
+
+    // generate requested read slots
+    for (int i = 0; i < number_of_bits_to_read; i++)
+    {
+        tx_items[i] = _encode_read_slot();
+    }
+
+    // end marker
+    tx_items[number_of_bits_to_read].level0 = 1;
+    tx_items[number_of_bits_to_read].duration0 = 0;
+
+    onewire_flush_rmt_rx_buf(bus);
+    rmt_rx_start(info->rx_channel, true);
+    if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_read+1, true) == ESP_OK)
+    {
+        size_t rx_size;
+        rmt_item32_t* rx_items = (rmt_item32_t *)xRingbufferReceive(info->rb, &rx_size, portMAX_DELAY);
+
+        if (rx_items)
+        {
+#ifdef OW_DEBUG
+            for (int i = 0; i < rx_size / 4; i++)
+            {
+                ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level0, rx_items[i].duration0);
+                ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level1, rx_items[i].duration1);
+            }
+#endif
+
+            if (rx_size >= number_of_bits_to_read * sizeof(rmt_item32_t))
+            {
+                for (int i = 0; i < number_of_bits_to_read; i++)
+                {
+                    read_data >>= 1;
+                    // parse signal and identify logical bit
+                    if (rx_items[i].level1 == 1)
+                    {
+                        if ((rx_items[i].level0 == 0) && (rx_items[i].duration0 < OW_DURATION_SAMPLE))
+                        {
+                            // rising edge occured before 15us -> bit 1
+                            read_data |= 0x80;
+                        }
+                    }
+                }
+                read_data >>= 8 - number_of_bits_to_read;
+            }
+
+            vRingbufferReturnItem(info->rb, (void *)rx_items);
+        }
+        else
+        {
+            // time out occurred, this indicates an unconnected / misconfigured bus
+            ESP_LOGE(TAG, "rx_items == 0");
+            res = OWB_STATUS_HW_ERROR;
+        }
+    }
+    else
+    {
+        // error in tx channel
+        ESP_LOGE(TAG, "Error tx");
+        res = OWB_STATUS_HW_ERROR;
+    }
+
+    rmt_rx_stop(info->rx_channel);
+
+    *in = read_data;
+    return res;
+}
+
+static owb_status _uninitialize(const OneWireBus *bus)
+{
+    owb_rmt_driver_info * info = info_of_driver(bus);
+
+    rmt_driver_uninstall(info->tx_channel);
+    rmt_driver_uninstall(info->rx_channel);
+
+    return OWB_STATUS_OK;
+}
+
+static struct owb_driver rmt_function_table =
+{
+    .name = "owb_rmt",
+    .uninitialize = _uninitialize,
+    .reset = _reset,
+    .write_bits = _write_bits,
+    .read_bits = _read_bits
+};
+
+static owb_status _init(owb_rmt_driver_info *info, uint8_t gpio_num,
+                        rmt_channel_t tx_channel, rmt_channel_t rx_channel)
+{
+    owb_status status = OWB_STATUS_HW_ERROR;
+
+    // Ensure the RMT peripheral is not already running
+    // Note: if using RMT elsewhere, don't call this here, call it at the start of your prgoram instead.
+    //periph_module_disable(PERIPH_RMT_MODULE);
+    //periph_module_enable(PERIPH_RMT_MODULE);
+
+    info->bus.driver = &rmt_function_table;
+    info->tx_channel = tx_channel;
+    info->rx_channel = rx_channel;
+    info->gpio = gpio_num;
+
+#ifdef OW_DEBUG
+    ESP_LOGI(TAG, "RMT TX channel: %d", info->tx_channel);
+    ESP_LOGI(TAG, "RMT RX channel: %d", info->rx_channel);
+#endif
+
+    rmt_config_t rmt_tx;
+    rmt_tx.channel = info->tx_channel;
+    rmt_tx.gpio_num = gpio_num;
+    rmt_tx.mem_block_num = 1;
+    rmt_tx.clk_div = 80;
+    rmt_tx.tx_config.loop_en = false;
+    rmt_tx.tx_config.carrier_en = false;
+    rmt_tx.tx_config.idle_level = 1;
+    rmt_tx.tx_config.idle_output_en = true;
+    rmt_tx.rmt_mode = RMT_MODE_TX;
+    if (rmt_config(&rmt_tx) == ESP_OK)
+    {
+        if (rmt_driver_install(rmt_tx.channel, 0, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK)
+        {
+            rmt_config_t rmt_rx;
+            rmt_rx.channel = info->rx_channel;
+            rmt_rx.gpio_num = gpio_num;
+            rmt_rx.clk_div = 80;
+            rmt_rx.mem_block_num = 1;
+            rmt_rx.rmt_mode = RMT_MODE_RX;
+            rmt_rx.rx_config.filter_en = true;
+            rmt_rx.rx_config.filter_ticks_thresh = 30;
+            rmt_rx.rx_config.idle_threshold = OW_DURATION_RX_IDLE;
+            if (rmt_config(&rmt_rx) == ESP_OK)
+            {
+                if (rmt_driver_install(rmt_rx.channel, 512, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK)
+                {
+                    rmt_get_ringbuf_handle(info->rx_channel, &info->rb);
+                    status = OWB_STATUS_OK;
+                }
+                else
+                {
+                    ESP_LOGE(TAG, "failed to install rx driver");
+                }
+            }
+            else
+            {
+                status = OWB_STATUS_HW_ERROR;
+                ESP_LOGE(TAG, "failed to configure rx, uninstalling rmt driver on tx channel");
+                rmt_driver_uninstall(rmt_tx.channel);
+            }
+        }
+        else
+        {
+            ESP_LOGE(TAG, "failed to install tx driver");
+        }
+    }
+    else
+    {
+        ESP_LOGE(TAG, "failed to configure tx");
+    }
+
+    // attach GPIO to previous pin
+    if (gpio_num < 32)
+    {
+        GPIO.enable_w1ts = (0x1 << gpio_num);
+    }
+    else
+    {
+        GPIO.enable1_w1ts.data = (0x1 << (gpio_num - 32));
+    }
+
+    // attach RMT channels to new gpio pin
+    // ATTENTION: set pin for rx first since gpio_output_disable() will
+    //            remove rmt output signal in matrix!
+    rmt_set_pin(info->rx_channel, RMT_MODE_RX, gpio_num);
+    rmt_set_pin(info->tx_channel, RMT_MODE_TX, gpio_num);
+
+    // force pin direction to input to enable path to RX channel
+    PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[gpio_num]);
+
+    // enable open drain
+    GPIO.pin[gpio_num].pad_driver = 1;
+
+    return status;
+}
+
+OneWireBus * owb_rmt_initialize(owb_rmt_driver_info *info, uint8_t gpio_num,
+                                rmt_channel_t tx_channel, rmt_channel_t rx_channel)
+{
+    ESP_LOGD(TAG, "%s: gpio_num: %d, tx_channel: %d, rx_channel: %d",
+             __func__, gpio_num, tx_channel, rx_channel);
+
+    owb_status status = _init(info, gpio_num, tx_channel, rx_channel);
+    if(status != OWB_STATUS_OK)
+    {
+        ESP_LOGE(TAG, "_init() failed with status %d", status);
+    }
+
+    return &(info->bus);
+}

mercurial