Serial Issues with Neopixel

488 Views Asked by At

I was looking for some help with; understanding DMA, Serial, managing interrupts and blocking code.

I have an ESP32 DEVKITC v4 that I'm using to handle user Input both from hardware such as rotary encoders and from wifi. The ESP32 is also responsible for driving an 20x4 LCD to display a menu for the user to interact with.

I then have a Teensy 3.5 handling the pixel driving. It's handles generating the pixels frame, loading it to the buffer and outputting the signal. I'm running a modified version of the Adafruit Neopixel Library to control my TM1814 LED's

The trouble I'm having at the moment is the communication between the ESP32 and Teensy. The neopixel's code requires blocking in order to get right the timings for the LED driver IC's. while the ESP has interrupts used for the rotary encoder to keep an accurate count, both of these mess with the Serial communication. Here is my test code so far, it is a stripped back version of the final project's code to make it simple to identify problems and help build complexity slowly.

ESP_Transmitter

    #include <Rotary.h>

#define RTS_PIN 5

int previousArray;
int previousRGBW;

#define inPinA  35

//rotary acceleration variables
int rotaryTime;
volatile int counterA;
volatile int counterB;
byte enableAcceleration;
bool lockFlag = false;

Rotary rotaryA = Rotary(32, 33);

//teensy is expecting data <rgbwArrayToTeensy,rgbwToTeensy>
typedef struct ESPtransmit_t {
  char startMarker;
  int rgbwArrayToTeensy;
  char comma;
  int rgbwToTeensy;
  char endMarker;
};

typedef union Channel_Packet_t {
  ESPtransmit_t rgbwLED;
  byte ChannelPacket[sizeof(ESPtransmit_t)];
};

Channel_Packet_t blueOn;

void setup() {
  Serial.begin(9600);
  Serial2.begin(115200, SERIAL_8N1, 16, 17);

  while (!Serial);
  while (!Serial2);

  pinMode(RTS_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(RTS_PIN), Transmit_Data, RISING);
  attachInterrupt(digitalPinToInterrupt(32), rotateA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(33), rotateA, CHANGE);

}

void loop() {
  blueOn.rgbwLED = {'<', 2, ',', counterA, '>'};
}


void Transmit_Data() {
  noInterrupts();
  if (previousRGBW != blueOn.rgbwLED.rgbwToTeensy) {
    Serial2.write(blueOn.ChannelPacket, sizeof(ESPtransmit_t));
    Serial.println("send");
    previousRGBW = blueOn.rgbwLED.rgbwToTeensy;
  }
  interrupts();
}

void rotateA() {
  int speedMultiplier = 1;
  unsigned char result = rotaryA.process();
  if (lockFlag == false) {
    if (result == DIR_CW) {
      if (millis() - rotaryTime <= 10 && enableAcceleration == 0x01) {
        speedMultiplier = 7;

      }
      else if (digitalRead(inPinA) == HIGH) {
        speedMultiplier = 700;
      }
      counterA += speedMultiplier;
      rotaryTime = millis();
    }
    else if (result == DIR_CCW) {
      if (millis() - rotaryTime <= 10 && enableAcceleration == 0x01) {
        speedMultiplier = 7;

      }
      else if (digitalRead(inPinA) == HIGH) {
        speedMultiplier = 700;
      }
      counterA -= speedMultiplier;
      rotaryTime = millis();
    }
  }
}

TEENSY3.5_RECEIVER

// include the library code:
#include <Adafruit_NeoPixel.h>

//number of LEDs in Strip
int NUM_LEDS = 52;

//data and clock pin RGB
#define DATA_PINA 11

#define RTR_PIN 28

uint32_t amp = ((uint32_t)63 << 24) | ((uint32_t)63 << 16) | ((uint32_t)63 <<  8) | 63;

Adafruit_NeoPixel pixelsA(NUM_LEDS, DATA_PINA, NEO_WRGB + NEO_KHZ800);

struct Received_Data_t {
  char startMarker;
  int rgbwArrayFromESP;
  char comma;
  int rgbwFromESP;
  char endMarker;
};

union Channel_Packet_t {
  Received_Data_t rgbwLED;
  byte ChannelPacket[sizeof(Received_Data_t)];
};

Channel_Packet_t LEDon;

//apeture controls
int apeture = NUM_LEDS;
int apeturePosition = NUM_LEDS / 2;

//RGB Sub Menu Variables
int rgbArraySelector;
uint8_t subRed;
uint8_t subGreen;
uint8_t subBlue;
uint8_t subWhite;

uint8_t rgbwArray [] = {subRed, subGreen, subBlue, subWhite};

const byte numChars = sizeof(Received_Data_t);
char receivedChars[numChars];

int rgbwFromESP = 0;

boolean newData = false;

void setup() {
  Serial1.setTX(26);
  Serial1.setRX(27);
  Serial1.begin(115200);
  Serial.begin(9600);

  while (!Serial);
  while (!Serial1);

  pixelsA.begin(); // INITIALIZE NeoPixel strip object

  //clear the LEDS
  pixelsA.fill(amp, ~amp, pixelsA.Color(0, 0, 0, 0), 0, NUM_LEDS);
  pixelsA.show();

  pinMode(RTR_PIN, OUTPUT);
  digitalWrite(RTR_PIN, LOW);

}

void loop() {
  Read_to_Receive(); //activate transmission 
  recvWithStartEndMarkers(); //read buffer
  if (newData == true) {
    parseData();
    showParsedData();
    newData = false;
  }
}

void LED_clear() {

  pixelsA.fill(amp, ~amp, pixelsA.Color(0, 0, 0, 0), 0, NUM_LEDS);
  pixelsA.show();

}

void LED_RGBW() {
  pixelsA.fill(amp, ~amp, pixelsA.Color(0, 0, 0, 0), 0, NUM_LEDS);
  pixelsA.fill(amp, ~amp, pixelsA.Color(rgbwArray[0], rgbwArray[1], rgbwArray[2], rgbwArray[3]), apeturePosition, apeture / 2);
  pixelsA.fill(amp, ~amp, pixelsA.Color(rgbwArray[0], rgbwArray[1], rgbwArray[2], rgbwArray[3]), apeturePosition - (apeture / 2), apeture / 2);
  pixelsA.show();
}

void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;
  while (Serial1.available() > 0 && newData == false) {
    rc = Serial1.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = rc; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }

    else if (rc == startMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      recvInProgress = true;
    }
  }
}
//============

void Read_to_Receive() {

  pixelsA.fill(amp, ~amp, pixelsA.Color(0, 0, 0, 0), 0, NUM_LEDS);
  pixelsA.fill(amp, ~amp, pixelsA.Color(rgbwArray[0], rgbwArray[1], rgbwArray[2], rgbwArray[3]), apeturePosition, apeture / 2);
  pixelsA.fill(amp, ~amp, pixelsA.Color(rgbwArray[0], rgbwArray[1], rgbwArray[2], rgbwArray[3]), apeturePosition - (apeture / 2), apeture / 2);
  digitalWrite(RTR_PIN, LOW);
  pixelsA.show();
  digitalWrite(RTR_PIN, HIGH);
  //wait for ESP to transmit
  delay(1);
}

//============

void parseData() {      // split the data into its parts
  for (uint8_t k = 0; k < sizeof(Received_Data_t); k++) {
    LEDon.ChannelPacket[k] = receivedChars[k];
  }
  rgbArraySelector = LEDon.rgbwLED.rgbwArrayFromESP;
  rgbwFromESP = LEDon.rgbwLED.rgbwFromESP;

  rgbwArray[rgbArraySelector] = rgbwFromESP;
}

//============
void showParsedData() {
  Serial.print("Array ");
  Serial.println(rgbArraySelector);
  Serial.print("Intensity ");
  Serial.println(rgbwFromESP);

}

Although this code mostly works I still get errors in transmission while turning the encoder quickly. This is where I'm hoping DMA might be a solution. If I understand DMA correctly I can use it and uart, Serial, to send data between the two MCU's and ignore the blocking code and interrupts. Then in the main loop poll DMA buffer and parse the received data but I'm unable to find a solid example of using DMA and Uart. Does anyone know if this will work and if so are there some examples you can link for me to check out?

I would prefer to find a software solution but As a hardware solution I was also looking at using this or an external SRAM that both MCU's have access to. To act as a buffer to store the user generated variables to be polled when appropriate.

I'm still fairly new to all this so any further questions are welcome and I want to know people's thoughts on this.

1

There are 1 best solutions below

1
On

The trouble I'm having at the moment is the communication between the ESP32 and Teensy.

If you have issues on both ends of the serial link, then you should try to simplify the test setup to reduce the number of variables/unknowns.
Either use the ESP32 to transmit data, and then try to verify that data using a terminal or capture program (on a PC?).
Or transmit canned data from a PC to the Teensy 3.5, and then analyze the response.


Although this code mostly works I still get errors in transmission while turning the encoder quickly.

I see an issue in your ESP_Transmitter code, the Transmit_Data() procedure:

void Transmit_Data() {
  noInterrupts();
  if (previousRGBW != blueOn.rgbwLED.rgbwToTeensy) {
    Serial2.write(blueOn.ChannelPacket, sizeof(ESPtransmit_t));
    Serial.println("send");
    previousRGBW = blueOn.rgbwLED.rgbwToTeensy;
  }
  interrupts();
}

Performing I/O requests with interrupts disabled is problematic.
In general you always want to minimize the time spent with interrupts disabled.

Actually I know nothing about Arduino, but ...
It looks like previousRGBW and/or blueOn are the critical regions that have to be protected, and the serial stuff can be performed outside this code section.
To protect blueOn simply make a copy of blueOn.ChannelPacket just for serial transmission.
Something like the following should be an improvement:

byte xmitPacket[sizeof(ESPtransmit_t)];

void Transmit_Data() {
  noInterrupts();
  if (previousRGBW != blueOn.rgbwLED.rgbwToTeensy) {
    memcpy(xmitPacket, blueOn.ChannelPacket, sizeof(ESPtransmit_t));
    previousRGBW = blueOn.rgbwLED.rgbwToTeensy;
    interrupts();

    Serial2.write(xmitPacket, sizeof(ESPtransmit_t));
    Serial.println("send");
  } else {
    interrupts();
  }
}