Electronics > Projects, Designs, and Technical Stuff

WS2812B DMX project - Flickering LEDs - Help Needed

(1/7) > >>

paul_g_787:
Hi. I am working on a DMX to WS2812B decoder project and I am encountering a problem with the leds flickering and I am stuck and need some help.

The project requirement is that there is a master dimmer as the first DMX channel that dims the entire strip then 3 channels per individual WS2812B LED to control RGB supporting up to a max 170 LEDs in one DMX universe (if the DMX channel is set to 1). Then if the channel is set to a higher number simply ignore the later LEDs.

I have got all of this working great but the WS2812B LEDs flicker and flash randomly. If I unplug the DMX input from my arduino, the LEDs work just fine and don't flicker.

I believe that the Interrupt Service Routine is causing delays upsetting the timing of the WS2812B LEDs which causes the data to go in and out of sync.

I tried stopping the ISR when updating the LEDs using a variable ISR_Disable which is set to false when the LEDs are updated but this does not appear to help the situation.

This is where my knowledge of this ends and I am stuck.

I am using an arduino Uno with ATMega 328P-PU.
I am using the FASTLed library to control the LEDs.
I am using a MAX485 to convert DMX to TTL which is connected to the arduino Serial RX pin.
I am reading the DMX channel from a 9 way DIP switch (this part works fine)

My code:

--- Code: ---/* pinout
pin0 - serial in
pin1 - serial out (unused)
pin2 - DIP Switch 1 (1)
pin3 - DIP Switch 2 (2)
pin4 - DIP Switch 3 (4)
pin5 - DIP Switch 4 (8)
pin6 - DIP Switch 5 (16)
pin7 - DIP Switch 6 (32)
pin8 - DIP Switch 7 (64)
pin9 - DIP Switch 8 (128)
pin10 - DIP Switch 9 (256)
pin11 - LED error1 - too much time between frames.
pin12 - LED error2 - bad dmx (non dimmer data)
pin13 - LED error3 - last channel not recieved.
apin0 (pin14) - LED WS2812B Strip Data Pin
apin1 (pin15) -
apin2 (pin16) -
apin3 (pin17) -
apin4 (pin18) -
apin5 (pin19) -
pinout */

//== LED STRIP ===================================================/
//Define LED Parameters
#include <FastLED.h>
#define Led_Pin 14  //Data In pin for LED strip
#define Led_Num 170 //Max number of LEDs in strip
#define Led_Type WS2812B  //Type of LEDs
#define Led_ColourOrder GRB //Order of data for LEDs
CRGB Led_Arr[Led_Num]; //Define the mode and number of LEDs
byte Led_Dimmer = 0;

bool ISR_Disable = false;

//Define DMX Error LED indicator pin numbers
#define Pin_Error1 11
#define Pin_Error2 12
#define Pin_Error3 13


//= DMX =========================================================/
//Create DMX Variables
volatile uint8_t  DmxRxField[511]; //array of 8 bit int DMX vals (raw)
volatile uint16_t DmxAddress = 0; //start address (16 bit int)
volatile uint16_t DmxChannels = 511; //Max number of DMX channels

enum States {IDLE, BREAK, STARTCODE, MYADDRESSES};
States gDmxState; //create a global variable.
//idle - nothing interesting to be doing,
//break - the break has been recieved so expect some data after a startcode.
//startcode - the startcode wase recieved so we're now into the frames of data stage.
//myaddresses - we've started receieving data for my addresses.

//error1 is no new packet within time defined by the default timer.
boolean error1 = true;
int timer;
int defaulterror1time=500;
// i recomend at least 100 to avoid the LED flickering between packets, and not more than 2000, as 2 seconds is reasonably long.

//error2 for bad DMX (non dimmer data)
boolean error2 = true;
//error 2 is all to do with the valid startcode.... and it only appears in that code.

//error3 is for last channel not recieved.
//it only is true if a new packet is recieved before the end of the last one.
//its implimented in two parts.
//serror3 is within the ISR, it's set to true at the begining of a packet,
//serror3 is set to false once the last frame expected is recieved.
boolean error3 = false;
boolean serror3 = false;
//in the break section of code, if it gets to a break and is still an error then that means that error3 has occoured. 


//= DMX INTERRUPT SERVICE ROUTINE ================================/
ISR(USART_RX_vect) {
  //this code/function is called whenever the harware UART recieves a low startbit (well it's called after the byte is recieved.)

  if(ISR_Disable == false) {
    // two byte counter - needed to count upto all 512 channel frames
  static  uint16_t DmxCounter;

  uint8_t  USARTstate= UCSR0A;    //get state before data!
  uint8_t  DmxByte   = UDR0;     //get data
  uint8_t  DmxState  = gDmxState; //just load once from SRAM to increase speed

  if (USARTstate &(1<<FE0)) { //check for break
      //data has been recieved, the USART is enabled and it's low signal for more than 8 bits so an error has also been created - this is a Break.
      gDmxState= BREAK;
               
      //as this marks the start of a new packet we shall set a counter to the address we wish to use as our start address.
      DmxCounter =  DmxAddress;

      //dmx counter counts down untill it reaches the address
      if(serror3==true) {error3=true;}
      serror3 = true; //as we're at the start of a packet.
   
      //lets set the error1 timer back to it's default and set it to false as DMX is imcomming
      timer=defaulterror1time; error1=false;
  }
 
    //lets assume that the break has occoured - then to get this far in the code, another frame of data has been recieved. As the break has occoured then the next frame should be the start code. The start code for DMX data for dimmer channels is the 0 value. 
  else if (DmxState == BREAK) {
      if (DmxByte == 0) { //normal start code detected
        gDmxState= STARTCODE; error2=false;
      } 
  else { //non normal start code recieved eg: RDM packet.
        gDmxState= IDLE; error2=true; //ignore the rest of the packet.
      }
  }

  //to get to this point, we've had a break, then the start code. The next frame should be the the first channel of DMX data.
    else if (DmxState == STARTCODE) {
      //now we need to see if we are in the data before the start address or at the start address.
      //to do this lets decrement the counter and see if it's 0.
      //the counter initially was set to the start address, so when it gets to the start address's frame it should be zero.
  if (--DmxCounter == 0) { //start address reached?
        //wahoo - we're at the start address so lets grab this frame
  DmxRxField[DmxCounter]= DmxByte; //get 1st DMX channel of device
        DmxCounter= 1; //set up counter for required channels
        if(DmxChannels==1){gDmxState= IDLE;serror3 = false;}
        else{gDmxState= MYADDRESSES;}
        //
  }
  }

    //to get to here we've recieved a break, start code and frams upto and
    //including the first channel we want. 
  else if (DmxState == MYADDRESSES) {
      //lets incriment the counter,
      //is the new counter equal to the number of channels we require?
      //(remember here that the DMX counter is zero indexed and the Dmx channels is 1 indexed...
      //if so then we've gone past our last channel so go back to the ideal state.
      //if not then this data is for one of our channels so chuck it into the array. 
  DmxRxField[DmxCounter++]= DmxByte; //get channel
  if (DmxCounter >= DmxChannels) { //all ch received?
  gDmxState= IDLE; //wait for next break 
        serror3 = false; //as we're at the end of data expected.
  }
  }
  //end of ISR
  }
}


//= BOOT UP FUNCTION =============================================/
void setup()
{
  delay( 1000 ); // power-up delay/

  //Setup DMX error LED indicator output pins 
  pinMode(Pin_Error1,OUTPUT);
  pinMode(Pin_Error2,OUTPUT);
  pinMode(Pin_Error3,OUTPUT);

  // Set the pin IDs for the 9 DIP switch inputs
  // 1, 2, 4, 8, 16, 32, 64, 128, 256
  const byte Pin_Dips[9] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
 
  // Set each of the DIP switch inputs as an input with built in pull-up resistor
  for (byte i = 0; i < sizeof(Pin_Dips); i++) {
    pinMode(Pin_Dips[i], INPUT_PULLUP);
  }
 
  //Read each of the DIP switches to get the DMX channel ID in binary
  //This is stored as DmxAddress
  for (byte i = 0; i < sizeof(Pin_Dips); i++) {
    DmxAddress = DmxAddress + (!digitalRead(Pin_Dips[i]) << i);
  }
  //DmxAddress = 10; //Override DIP switch for testing purposes
 
  //Setup LED strip
  //  CRGB Led_Arr[Led_Num]; //Define the mode and number of LEDs/
  FastLED.addLeds<Led_Type, Led_Pin, Led_ColourOrder>(Led_Arr, Led_Num).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness( 0 ); //Set initial brightness to 0
  FastLED.clear(); //Clear all the LEDs of colour data to ensure they are all off at boot up
//  fill_solid(Led/_Arr, Led_Num, CRGB(255,255,255));  // fill white for testing purposes
  FastLED.show(); //Update the LEDs

  //Start listening for DMX data
  Serial.begin(250000); //Enable serial reception with a 250k rate
  gDmxState= IDLE; // initial state
  timer=defaulterror1time;

}

void loop()
{
  //lets output the error status to LED's
  if(error1==true){digitalWrite(Pin_Error1, HIGH);}else{digitalWrite(Pin_Error1, LOW);} //Too much time between frames (usually cable unplugged)
  if(error2==true){digitalWrite(Pin_Error2, HIGH);}else{digitalWrite(Pin_Error2, LOW);} //Bad (non-DMX) data (corrupted data or RF noise from unplugged cable)
  //error2 is kindof untested as i dont have any unvalid or DRM dmx devices
  if(error3==true){digitalWrite(Pin_Error3, HIGH);}else{digitalWrite(Pin_Error3, LOW);} //Did not receive a full DMX frame (generally lights up for desks that do not support a full DMX frame.

  //Check for errors and run the actions if there are none.
  if(error1==false & error2 == false) {
    action_loop();
  }
 
  //this is the count down part for the ''timer''
  if(timer==0)  {error1=true;}
  else  {  timer--;  }
  delay(10);
  //this delays the loop by 1ms... less than ideal but required for the timer counting down between successfull packets.
  //if your action function takes significant time to output stuff then you could remove the delay by making it a comment
  //as the action loop would be a time delay. in this case the timer is then more of a counter, with error 1 being a number
  //of action calls without a successfull DMX packet being recieved.
}

//This function does stuff
void action_loop()
{
  bool changed = false; //This variable will be used to determine if any DMX values have been updated since last loop

  //Master dimmer
  if(Led_Dimmer != DmxRxField[0]) {
    Led_Dimmer = DmxRxField[0]; //Store the new value
    FastLED.setBrightness( DmxRxField[0] ); //Set the LED strip brightness
    changed = true; //Update LED strip at end of loop
  }

  //Loop through DMX fields in groups of 3 starting with 1
  for(int i = 1; i<=511; i=i+3) {
    int LED = i/3; //The corresponding LED ID
    int VALR = Led_Arr[LED][0]; //Get the current LED red value
    int VALG = Led_Arr[LED][1]; //Get the current LED green value
    int VALB = Led_Arr[LED][2]; //Get the current LED blue value
    if(VALR != DmxRxField[i] || VALG != DmxRxField[i+1] || VALB != DmxRxField[i+2]) {
      Led_Arr[LED] = CRGB(DmxRxField[i],DmxRxField[i+1],DmxRxField[i+2]);
      changed = true; //Update LED strip at end of loop
    }
  }
 
  //Finally update the LEDs if any changes were made since last loop
  if ( changed == true) {
    //ISR_Disable = true;
    FastLED.show(); //Update the LEDs
    changed = false;
    //ISR_Disable = false;
  }
}
--- End code ---

ozcar:
I don't know what FASTLed does, but if it is an interrupt issue, then "disabling" the ISR with a flag tested in the ISR is not the way to go. What happens if you use cli() ... sei() instead? If that is necessary, I would expect FASTLed to do it, but who knows?

globoy:
You're right, it's most likely the UART ISR interfering with the FastLED show() method.

You might do some timing analysis to see how long each DMX transmission is and often they are initiated; and also how long it takes FastLED to output its buffer to the LEDs.  You'll most likely have to serialize the activities since each is so time consuming on the 328P - alternating between getting a DMX frame and then sending it to the LEDs.  Maybe you can slow down the transmission of frames.  At the minimum maybe get a frame and then disable the UART until you've sent it to the LEDs.  You might miss some frames but you'll be assuring FastLED won't be interrupted.

Years ago I did a series of DMX fixtures using the 328P.  I remember that receiving DMX was pretty tricky.  My code was based on Max Pierson's stuff (search for DMX on his blog: blog.wingedvictorydesign.com).  He used a timer constantly firing an interrupt to determine when the Break was over.  The the UART ISR tried to process incoming characters as quickly as it could.  Basically a counter looking for the start of interesting data, and the end, storing the data into an array and then disabling receiving data again until the end of the next Break.

paul_g_787:

--- Quote from: ozcar on November 22, 2022, 07:47:11 pm ---I don't know what FASTLed does, but if it is an interrupt issue, then "disabling" the ISR with a flag tested in the ISR is not the way to go. What happens if you use cli() ... sei() instead? If that is necessary, I would expect FASTLed to do it, but who knows?

--- End quote ---

FastLED is a library to control LED strips: https://github.com/FastLED

I have not heard of cli() or sei() before. Do you mean something like this:
https://forum.arduino.cc/t/cli-and-sei/94102

paul_g_787:

--- Quote from: globoy on November 22, 2022, 08:02:55 pm ---You're right, it's most likely the UART ISR interfering with the FastLED show() method.

You might do some timing analysis to see how long each DMX transmission is and often they are initiated; and also how long it takes FastLED to output its buffer to the LEDs.  You'll most likely have to serialize the activities since each is so time consuming on the 328P - alternating between getting a DMX frame and then sending it to the LEDs.  Maybe you can slow down the transmission of frames.  At the minimum maybe get a frame and then disable the UART until you've sent it to the LEDs.  You might miss some frames but you'll be assuring FastLED won't be interrupted.

Years ago I did a series of DMX fixtures using the 328P.  I remember that receiving DMX was pretty tricky.  My code was based on Max Pierson's stuff (search for DMX on his blog: blog.wingedvictorydesign.com).  He used a timer constantly firing an interrupt to determine when the Break was over.  The the UART ISR tried to process incoming characters as quickly as it could.  Basically a counter looking for the start of interesting data, and the end, storing the data into an array and then disabling receiving data again until the end of the next Break.

--- End quote ---
Well I am glad you agree with my hypothesis, I am at least thinking on the right lines. (Coding is not my strong point, I am much better at soldering  :P)

The DMX code I have is a slightly tidied up version of this chap's code from here:

https://www.crazy-logic.co.uk/projects/lighting/receiving-dmx-on-arduino-with-ardmx

It works by commenting out this code in HardwareSerial0.cpp

--- Code: ---#if defined(USART_RX_vect)
  ISR(USART_RX_vect)
#elif defined(USART0_RX_vect)
  ISR(USART0_RX_vect)
#elif defined(USART_RXC_vect)
  ISR(USART_RXC_vect) // ATmega8
#else
  #error "Don't know what the Data Received vector is called for Serial"
#endif
  {
    Serial._rx_complete_irq();
  }
--- End code ---

and replacing it with another function ISR(USART_RX_vect) { do_stuff_here; }

And as far as I understand the ISR function waits for the "break" in the serial code and then reads the DMX data into an array. Then it goes idle when there is no DMX (hence why my flickering issue stops when the DMX is disconnected).

I know that DMX is at 250kbps and is "idle high", it consists of a break, mark after break, then it sends out two low bits followed by 8 data bits for each channel 0-511, channel 0 is always 8 low bits.
This is looped continuously over and over from the lighting desk, so pausing listening for DMX for a short period would be acceptable as it would just pick up data again on the next DMX frame.

Have you got any examples of how to pause listening for DMX while sending data to the LEDs.

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod