Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Neopixel and Onewire conflict on esp32-c3 #1126

Open
tve opened this issue May 28, 2023 · 8 comments
Open

Neopixel and Onewire conflict on esp32-c3 #1126

tve opened this issue May 28, 2023 · 8 comments

Comments

@tve
Copy link
Contributor

tve commented May 28, 2023

Build environment: Linux
Target device: Lolin pico-c3 (similar to Adafruit qtpyc3)
Description
The first use of the onewire bus causes the neopixel to cease functioning. Sometimes XS also hangs or crashes.

Steps to Reproduce

  1. Optionally attach a one-wire sensor to pin 2 (it will happily malfunction without it, hence the "optional")
  2. Build and run the attached main.js
  3. Observe the neopixel starting its rainbow effect ramping up green
  4. The XSbug log will show when onewire is initialized after 2 seconds, at that point the rainbow freezes and the xsbug connection is lost (can't reset, for example)

Other information
The manifest sets the RMT channels to be used by OneWire to tx=1 and rx=2 to accomodate the fact that on the C3 channels 0&1 are for tx and 2&3 are for rx. Channel 0 is used by Neopixel. On the onewire pin I see some pulses that don't actually look like a proper reset pulse. Doing a bus.search instead of reset has similar effects.

Editing modonewire.c line 44 to switch the ESP32 to use GPIO makes the Neopixel work normally and I see a reasonable reset pulse and search issues reasonable looking pulses.

main.js:

import Timer from "timer"
import config from "mc/config"
import OneWire from "onewire"
import DS18X20 from "DS18X20"

trace("\n==== main starting ====\n")

Timer.set(() => {
  trace("initializing OneWire\n")
  const bus = new OneWire({
    pin: config.onewire.pin,
  })

  const any = bus.reset()
  trace("reset: ", any, "\n")
}, 2000)

// let devices = bus.search() // search returns an array of device IDs
// trace("found ", devices.length, "\n")
// trace(devices.map(x => OneWire.hex(x) + "\n"))

manifest.json (adapted from the onewire example manifest):

{
  "include": ["$(MODDABLE)/examples/manifest_base.json"],
  "modules": {
    "*": ["$(MODULES)/drivers/onewire/*", "./main"]
  },
  "preload": ["onewire", "DS18X20"],
  "platforms": {
    "esp": {
      "include": ["$(MODULES)/pins/digital/manifest.json"],
      "modules": {
        "*": ["$(MODULES)/drivers/onewire/esp/*"]
      },
      "config": {
        "onewire": {
          "pin": "4"
        }
      },
      "defines": {
        "onewire": {
          "driver_gpio": "GPIO"
        }
      }
    },
    "esp32": {
      "include": ["$(MODULES)/pins/digital/manifest.json"],
      "modules": {
        "*": "$(MODULES)/drivers/onewire/esp/*"
      },
      "config": {
        "onewire": {
          "pin": "2"
        }
      },
      "defines": {
        "onewire": {
          "driver_rmt": "RMT",
          "rmt_rx_channel": "RMT_CHANNEL_3",
          "rmt_tx_channel": "RMT_CHANNEL_1"
        }
      }
    },
    "win": {
      "modules": {
        "*": "$(MODULES)/drivers/onewire/sim/*"
      },
      "config": {
        "onewire": {
          "pin": "1"
        }
      }
    },
    "...": {
      "error": "unsupported platform"
    }
  }
}

@phoddie
Copy link
Collaborator

phoddie commented May 29, 2023

It looks like a conflict in the RMT support - since both Neopixels and OneWire use that by default. If sounds like you got those using separate RMT channels, so they should operate independently. At least that's my understanding of the ESP-IDF APIs. Is that consistent with your study of this behavior?

@tve
Copy link
Contributor Author

tve commented May 29, 2023

Exactly. I haven't done much with the RMT devices, but I believe I configured things so Neopixel and OW use disjoint hardware. Either there's a bug in one of the C modules or ESP-IDF (or the HW).
The OW RMT driver is a bit hacky because it configures one pin as in-out using two RMT channels. It's possible that the RISC-V hardware has some difference in the I/O muxing that causes problems. I don't have an ESP32 with a neopixel I can easily test on to see whether that fails too.

Update:
Disabling the neopixel makes onewire work well. So either driver works in isolation, they just collide somehow when used at the same time.

      "config": {
        "led": {
          "rainbow": false
        },

@tve
Copy link
Contributor Author

tve commented May 29, 2023

I spent a couple of hours debugging the RMT issue and have gotten nowhere. The code all looks good and the values I've printed also look good. Next step might be to write an esp-idf C program to try to reproduce the issue. I don't think I can do that now.

I also tried to use the OneWire GPIO driver and that doesn't work at all. The way modGPIO is used does not work. For example, to write a '1' the OW driver calls modGPIOWrite(config, 0) followed by modGPIOSetMode(config, kModGPIOOutputOpenDrain) and well, the second call resets the GPIO which briefly sets it to input-with-pullup. This causes spikes on the pin, disrupting the protocol. This doesn't affect the esp8266 because it goes direct to esp-idf and doesn't use modGPIO.

The OW driver also has a bug where the timings are mixed up. Specifically:

This does affect the esp8266 and I remember some issues a few weeks ago...

I also noted that none of the OW drivers support reliable operation with parasitically powered devices. For that, when asking devices to "operate", for example, for DS18B20 the convert and copy-scratchpad commands, the master has to actively drive the bus high and not expect that the pull-up resistor will suffice. Whether it works without the active drive depends on voltage level, number of devices on the bus, length of the bus, etc.

Update: I 'fixed' modGPIO not to mess with the pin when switching mode and now the OW gpio driver works. (Messing with the pin value when switching modes is a bad feature of modGPIO, but that's a different topic). For reference, the diff for modGPIO is:

diff --git a/modules/pins/digital/esp32/modGPIO.c b/modules/pins/digital/esp32/modGPIO.c
index 7e1c0f01..12bd9be1 100644
--- a/modules/pins/digital/esp32/modGPIO.c
+++ b/modules/pins/digital/esp32/modGPIO.c
@@ -40,6 +40,7 @@ int modGPIOInit(modGPIOConfiguration config, const char *port, uint8_t pin, uint
        }
 
        config->pin = pin;
+       gpio_reset_pin(config->pin); // TvE
        result = modGPIOSetMode(config, mode);
        if (result) {
                config->pin = kUninitializedPin;
@@ -56,7 +57,7 @@ void modGPIOUninit(modGPIOConfiguration config)
 
 int modGPIOSetMode(modGPIOConfiguration config, uint32_t mode)
 {
-       gpio_reset_pin(config->pin);
+       //gpio_reset_pin(config->pin); // TvE
 
        switch (mode) {
                case kModGPIOOutput:
@@ -68,7 +69,7 @@ int modGPIOSetMode(modGPIOConfiguration config, uint32_t mode)
 
                        gpio_pad_select_gpio(config->pin);
                        gpio_set_direction(config->pin, (kModGPIOOutputOpenDrain == mode) ? GPIO_MODE_OUTPUT_OD : GPIO_MODE_OUTPUT);
-                       gpio_set_level(config->pin, 0);
+                       //gpio_set_level(config->pin, 0); // TvE
                        break;
 
                case kModGPIOInput:

@tve
Copy link
Contributor Author

tve commented May 30, 2023

On support for the parasitically powered devices... I connected 3 parasitically powered DS18B20 to a pin and implemented a "convert all" feature which causes all of them to acquire the temperature at the same time. With a 2.2K pull-up this caused the bus voltage to drop to 1.5, which the devices didn't really like (3.3V is already a bit marginal). I implemented a _drive_high() method in the OW GPIO driver and inserted calls at the end of _read_bits and _write_bits(), this way the bus ends up idle with a driven high level instead of just a pulled-up level. The sensors are now working reliably and the bus voltage has the expected 3.3V during conversion. For reference, this is the diff, although it's not directly useful 'cause other changes are needed to even use the GPIO driver with esp32 and more changes would be needed to make it work with esp8266...

+static void _drive_high(const OneWireBus * bus)
+{
+    struct modGPIOConfigurationRecord *config = bus->config;
+
+    modGPIOWrite(config, 1);  // Drive DQ high
+#if MODDEF_ONEWIRE_DIRECTGPIO
+    DIRECT_MODE_OUTPUT(REG, PIN_TO_BITMASK(config->pin)); // FIXME
+#else
+    modGPIOSetMode(config, kModGPIOOutput);
+#endif
+}
+

@@ -220,6 +230,7 @@ static owb_status _write_bits(const OneWireBus * bus, uint8_t data, int number_o
         _write_bit(bus, data & 0x01);
         data >>= 1;
     }
+    _drive_high(bus);
 
     return OWB_STATUS_OK;
 }
@@ -242,6 +253,7 @@ static owb_status _read_bits(const OneWireBus * bus, uint8_t *out, int number_of
         }
     }
     *out = result;
+    _drive_high(bus);
 
     return OWB_STATUS_OK;
 }

@phoddie
Copy link
Collaborator

phoddie commented May 31, 2023

The support for parasitic power is great. I had looked at that a little while back but obviously didn't get to it. Integrating the changes shouldn't be too bad.

I don't see a problem with the ESP8266 support. Is that just noted because you haven't tested it or are you aware of an issue?

@colinl was still seeing occasionally failures. I wonder if this would help in his case. I don't think he's using parasitic power, so maybe not, but there is still some degree of magic here so anything seems possible.

@phoddie
Copy link
Collaborator

phoddie commented May 31, 2023

The OW driver also has a bug where the timings are mixed up. Specifically:

in https://github.com/Moddable-OpenSource/moddable/blob/public/modules/drivers/onewire/esp/owb_gpio.c#L72-L76 the values A&C are to be used for writing a '1' and B&D for writing a '0'
in https://github.com/Moddable-OpenSource/moddable/blob/public/modules/drivers/onewire/esp/owb_gpio.c#L159-L170 uses A&B to write a '1' and C&D to write a '0'

This does affect the esp8266 and I remember some issues a few weeks ago...

This is a little tangled. The comments in the source code for the constants agrees with you:

6, // A - read/write "1" master pull DQ low duration
64, // B - write "0" master pull DQ low duration
60, // C - write "1" master pull DQ high duration
10, // D - write "0" master pull DQ high duration

But, Application Note 126 from Maxim disagrees. Here's the timing diagram -- A&B are used for "1" and C&D are used for "0"

image

The source code in the Application Note 126 is consistent with the image (see page 5).

A + B == C + D == 70, which makes the read and writing durations the same, FWIW. That's the not the case is using A&C and B&D.

@colinl
Copy link

colinl commented Jun 1, 2023

@colinl was still seeing occasionally failures.

Not so much 'occasional', as 'under certain circumstances'. On a D1 Mini, with one of my sensors, I can make it work reliably but if make an insignificant change to the node red flow (I am using node-red-mcu), such as adding a debug node, then solidly gets null temperature values on that sensor. Going back to the working flow makes it work again.
I am not using parasitic mode. I have mostly used Pis previously for this, and found parasitic to be marginal on 3.3V, so now always use three wire.

@tve
Copy link
Contributor Author

tve commented Jun 1, 2023

This is a little tangled.

LOL! You're correct. I don't think this matters much WRT functioning but would be nice to straighten out.

tve added a commit to tve/moddable that referenced this issue Jun 14, 2023
See discussion in Moddable-OpenSource#1126 - onewire and neopixel's use of RMT conflict on
esp32-c3 even though "it should work". Needs resolving but for now
switching to GPIO with a little hack in the GPIO module works too.
tve added a commit to tve/moddable that referenced this issue Nov 17, 2023
See discussion in Moddable-OpenSource#1126 - onewire and neopixel's use of RMT conflict on
esp32-c3 even though "it should work". Needs resolving but for now
switching to GPIO with a little hack in the GPIO module works too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants