Aircoookie / WLED

Control WS2812B and many more types of digital RGB LEDs with an ESP8266 or ESP32 over WiFi!
https://kno.wled.ge
MIT License
14.58k stars 3.13k forks source link

two wire cc cw with swapped polarity #4112

Open florenso opened 3 weeks ago

florenso commented 3 weeks ago

Is your feature request related to a problem? Please describe. i have a led strip that contains cold and warm leds, but it only has two wires. image

Describe the solution you'd like I want two pwm channels (cc, cw) that are never high at the same time. so that i have a option to configure this type of led strip.

Describe alternatives you've considered Use the led strip with only warm or cold light.

Additional context I know a lot of chips cant handle phase shifting, but if you support it only for the easy ones (esp32 for example) i would be verry happy! (because i already solderd one..., would be awsome to add this functionality)

blazoncek commented 3 weeks ago

You can resolve this using additional circuitry and stock WLED.

florenso commented 3 weeks ago

what kind of circuitry? i could add a xor circuitry to one of the pwm signals, but even they would be in the same phase...

blazoncek commented 3 weeks ago

Differential output comes to mind. You do not need phase shifted output for that. Please use WLED forum or Discord for help and support question.

blazoncek commented 3 weeks ago

Correction. Using H-bridge (a mandatory circuit in this case) will require phase shifted PWM.

blazoncek commented 3 weeks ago

@jw2013 claimed he had a working solution for phase shifted PWM and exactly the same use case.

jw2013 commented 3 weeks ago

That's correct, I also needed a solution to run 'polarity change LED strings' via WLED. As @blazoncek mentioned, using a H-bridge is mandatory, and it needs to be controlled via phase shift PWM. That's the complicated part. Back then, I got it working on ESP8266 only, using an interrupt handler. The relevant working code looks like this:

unsigned char busPwmIntRoutine1Pin1 = 255;
unsigned char busPwmIntRoutine1Pin2 = 255;
unsigned char busPwmIntRoutine1Value1 = 255;
unsigned char busPwmIntRoutine1Value2 = 255;

void ICACHE_RAM_ATTR busPwmIntRoutine1() {
    if ( digitalRead(busPwmIntRoutine1Pin1) ) {
      GPOC = 1 << busPwmIntRoutine1Pin2;
    }
    else {
      GPOS = 1 << busPwmIntRoutine1Pin2;
    }
}

void analogWriteCombined(unsigned char gpio1, unsigned char gpio2, unsigned char value1, unsigned char value2) {

  if ( (gpio1 == 0xFF) || (gpio2 == 0xFF) ) return;

  if ( (busPwmIntRoutine1Pin1 == gpio1) && (busPwmIntRoutine1Pin2 == gpio2)
    && (busPwmIntRoutine1Value1 == value1) && (busPwmIntRoutine1Value1 == value2) ) return;

  busPwmIntRoutine1Pin1 = gpio1;
  busPwmIntRoutine1Pin2 = gpio2;
  busPwmIntRoutine1Value1 = value1;
  busPwmIntRoutine1Value2 = value2;

  if ( (value1 == 0) || (value1 == 255) || (value2 == 0) || (value2 == 255) ) {
    detachInterrupt(digitalPinToInterrupt(gpio1));
    analogWrite( gpio2, value2 );
    analogWrite( gpio1, value1 );
    return;
  }

  unsigned int value = value1 + value2;

  if ( value < 253 ) {        // !!!D*RTY WORKAROUND: use overlap instead of gap

    unsigned int overlap = 253 - value;

    analogWrite( gpio1, value1+overlap );
    analogWrite( gpio2, overlap );

    attachInterrupt(digitalPinToInterrupt(gpio1), &busPwmIntRoutine1, FALLING);
  }
  else if ( value > 255 ) {

  }
  else {

    analogWrite( gpio1, value1 );
    digitalWrite( gpio2, LOW );

    attachInterrupt(digitalPinToInterrupt(gpio1), &busPwmIntRoutine1, CHANGE);
  }
}

Later I found that there also exists a phase shift PWM API for ESP8266 only, but I never rewrote the WLED code (more below).

As motor drivers, depending on the voltage, I used either DRV8833 (3V to 10V) or DRV8871 (6.5V to 45V). Most polarity change LED strings use 31V, I assume that's also the case for you?

It would also be possible to use darlington drivers like L293D and L298D, but those are ancient and cause a big voltage drop.

It's funny that the question came up today, as right now I'm working on a completely different approach, that will be portable, and supports more channels:

https://www.wemos.cc/en/latest/d1_mini_shield/hr8833_motor.html (3V to 10V) https://www.wemos.cc/en/latest/d1_mini_shield/at8870_motor.html (6.5V to 38V)

OOTB, those motor shields are only usable via the LOLIN_I2C motor library, which does not expose the functionality required for phase shift PWM. So I started to develop my own firmware for the onboard microcontroller STC8H1K08, to make it behave similar to the PCA9685 :-)

I already got some working code. If others are interested, I'll consider making this open source. BTW: The same motor drivers, using that firmware, could also drive non-addressable RGBW LED strips, with phase shift functionality included. Just saying ;-)

jw2013 commented 3 weeks ago

The 'official' way to create a phase locked PWM can be found here, at least for ESP8266: https://github.com/esp8266/Arduino/blob/master/cores/esp8266/core_esp8266_waveform.h https://github.com/esp8266/Arduino/blob/master/cores/esp8266/core_esp8266_waveform_phase.cpp

IIRC, one needs to call enablePhaseLockedWaveform(), to make the linker use core_esp8266_waveform_phase instead of core_esp8266_waveform_pwm.

blazoncek commented 3 weeks ago

There are currently two people interested into this @paolotk and @dedehai.

blazoncek commented 3 weeks ago

@dedehai & I have a POC ready (untested). If you want a binary, contact me on Discord.

jw2013 commented 2 weeks ago

The video below shows an AT8870 motor shield (custom firmware), connected to a polarity change fairy light. Please excuse the quality, it's running at 240 Hz (240 Hz * 255 microsteps = 61200 interrupts per second), still my cellphone records a lot of flickering. Looks perfect for the human eye though :-)

https://github.com/user-attachments/assets/17e78ae5-0a72-4a4b-a9f8-fd31665a9705

jw2013 commented 2 weeks ago

Just tested the idea on a (analog) 12V RGBW strip, 5m with 108 LEDs/m. Works perfectly, too. Now going to read https://github.com/Aircoookie/WLED/pull/4107 regarding a proper implementation as a bus. IMG_20240824_230328114

blazoncek commented 2 weeks ago

@jw2013 you may want to check bus-config branch to use future implementation. @paolotk was also instructed to do so.

That branch already includes PWM phase shifting POC by @dedehai though it is currently flawed but does work.

DedeHai commented 2 weeks ago

working (but not final) implementation: https://github.com/Aircoookie/WLED/pull/4115

jw2013 commented 2 weeks ago

@blazoncek, referring to https://github.com/Aircoookie/WLED/blob/bus-config/wled00/bus_manager.cpp#L687

The existing Bus classes are hardcoded in the BusManager::add() method. How would I correctly add support for an I2C based PWM bus, which does not use any pins, but makes use of the global 'Wire' instance?

  1. by extending the implementation of BusPwm, to also cover I2C, based on the BusConfig? or
  2. by creating a new Bus class, e.g. BusWire?

Regarding the settings_leds.html and const.h, I'd like to use something like that:

<option value="75">I²C Polarity</option>
<option value="76">I²C White</option>
<option value="77">I²C CCT</option>
<option value="78">I²C RGB</option>
<option value="79">I²C RGBW</option>
blazoncek commented 2 weeks ago

@jw2013 that's something similar like @netmindz is doing with hub75 support. bus-config branch is supposed to provide that in the future. ATM it is still a POC waiting for enhancements.

Other parts of WLED currently require at least one GPIO to be allocated to function properly.

But this has nothing to do with this issue so please open a new one. Better yet, join discussion on Discord.

blazoncek commented 2 weeks ago

@florenso please use bus-config branch and report if the LEDs are working. You will need to enable "Phase shift" in settings. And make sure you have short-circuit protection (i.e. fuse) in place just in case.