Parsing Byte via Serial

Has the OP tried the FastLed library?

Well, it's the library he's using in the code he posted in post#18.
But, he hasn't yet seemed to grasp the difference between eight bits of a single byte transmitted, and transmitting that eight bits as eight ASCII 1 and 0 characters, so there's some work to be done.
Seriously, I said it all in post#21, perhaps a tad too cryptically for most to gather in. He's got a problem, but it's solvable with the right technique. Barely. Now, whether he's up for it, and whether he can write the python end to send the data succinctly, I guess is an unknown.

1 Like

@jbillauer

Until you grasp that fact then your system is never going to work no matter what software you have.

400 connected LEDs all turned off is going to draw 400 * 1mA = 0.4 Amp no matter what you do with the software. This is very close to the limit of the current you can get from an Arduino Uno. Which only leaves a few 100mA to power your lit LEDs.

Another very important thing you have missed is that receiving the data and showing it are two very different things. When developing anything do not do two unknown things at the same time. My advice to you would be to forget about the serial data at the moment, and just set up an array with the RGB values you want and get that to display. See if the time it takes for the "show" function to work is sufficient to not miss any bytes from the serial port. My guess it won't be, but see. If you don't believe you have not got enough current to light all the LEDs and are refusing to fix that then try lighting the first and last LED in the strip.

When that works you can look into sending the data you want to change through software. At the moment you seem to be fixated in sending all the data for a refresh all the time. Ask yourself is it possible all the data is the not required all the time?

So rather than sending the whole string of RGB values every time you want to change them, what about sending only the values that need changing?

Of course that will require you to send a different sort of data package. One that requires an RGB triplet, or the value you want to send along with a two byte address of the place in the array you want to save this. This is a technique I have used when making a graphical LED matrix editor for animated displays with a language called Processing.

Your other alternative is to continue running round like a headless chicken ignoring facts you can't face up to. Your choice.

1 Like

Indeed and the duration for refreshing the Neopixels is almost a known data point.

According to Adafruit’s Uber guide

NeoPixels receive data from a fixed-frequency 800 KHz data stream. Each bit of data therefore requires 1/800,000 sec — 1.25 microseconds. One pixel requires 24 bits (8 bits each for red, green blue) — 30 microseconds. After the last pixel’s worth of data is issued, the stream must stop for at least 50 microseconds for the new colors to “latch.”

So a strip of 400 pixels will require (400 * 30) + 50, or 12,050 microseconds - about 12ms.

Then there is the unpacking time taken after receiving the full payload - which would need to be timed.

Then you would need to add a few more ms to be on the safe side.

The total will be the minimum delay in between two payloads that the PC / Python script should honor.

This might still be second guessing the timing and may be a better approach would be to have a communication protocol and a two way discussion between the PC and Arduino.

The Python scripts starts and opens the Arduino’s serial port. That will reboot the arduino. Then the Python script waits for a message from the Arduino saying it’s ready to run. The PC sends the first frame and pauses. The arduino receives the frame and updates the strip and then sends a message again to the PC stating it’s ready to get the next frame. And you cycle again.

The two sides then become synchronized and you don’t have a race between the serial port and the strip’s update.

I’ve written a small tutorial on interfacing with Python. May be it can help See Two ways communication between Python3 and Arduino

1 Like

Alright been continuing to play around with both my python and my arduino code and got something that is working decently.

To address some comments:
I'm using the FastLED library but the leds I'm using are Neopixels because that's what I already had so no purchasing of other hardware happening here.

Arduino Code

#include <FastLED.h>

#define LED_PIN     6
#define NUM_LEDS    210
#define BRIGHTNESS  64
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB

CRGB leds[NUM_LEDS];
char incomingData[NUM_LEDS * 3 + 1];  // Buffer for incoming RGB data (+1 for null terminator)
int dataIndex = 0;
bool newDataAvailable = false;
bool receivingData = false;

void setup() {
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
  FastLED.setMaxRefreshRate(100);  // Set the refresh rate to 100 Hz or higher
  Serial.begin(460800);  // Increase baud rate for faster data transmission
}

void loop() {
  // Read incoming bytes until start and end markers are detected
  while (Serial.available() > 0 && !newDataAvailable) {
    char incomingByte = Serial.read();

    if (incomingByte == '<') {
      receivingData = true;     // Start receiving new frame
      dataIndex = 0;            // Reset the index
    } else if (incomingByte == '>') {
      incomingData[dataIndex] = '\0';  // Null-terminate the string
      newDataAvailable = true;         // Full frame received
      receivingData = false;           // Stop receiving
    } else if (receivingData && dataIndex < NUM_LEDS * 3) {
      incomingData[dataIndex++] = incomingByte;  // Store byte if within bounds
    }
  }

  // Process the data once a full frame has been received
  if (newDataAvailable) {
    processIncomingData();
    FastLED.show();  // Update the LEDs with new colors
    newDataAvailable = false;  // Reset for next frame
  }
}

void processIncomingData() {
  // Loop through the incoming data and update each LED with its RGB values
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].r = incomingData[i * 3];       // Red
    leds[i].g = incomingData[i * 3 + 1];   // Green
    leds[i].b = incomingData[i * 3 + 2];   // Blue
  }
}

Python Code

# me - this DAT
# timerOp - the connected Timer CHOP
# cycle - the cycle index
# interrupt - True if the user initiated a premature
#             False if a result of a normal timeout
# fraction - the time in 0-1 fractional form
#
# segment - an object describing the segment:
#
#    can be automatically cast to its index: e.g.:  segment+3, segment==2, etc      
#    
#  members of segment object:
#      index     numeric order of the segment, from 0
#      owner    Timer CHOP it belongs to
#
#      lengthSeconds, lengthSamples, lengthFrames
#      delaySeconds, delaySamples, delayFrames
#      beginSeconds, beginSamples, beginFrames
#      speed
#      cycle, cycleLimit, maxCycles
#      cycleEndAlertSeconds, cycleEndAlertSamples, cycleEndAlertFrames
#      row - one validly named member per column:
#            column headings used to override parameters:
#                          length, delay, speed, cyclelimit, maxcycles, cycleendalert
#            special column headings:
#                          begin (time at which to start, independent of delays/lengths, overrides delay)
#
#    custom - dictionary of all columns that don't map to a built-in feature
#

# Global variables to control when data is sent to the Arduino
cycle_counter = 0
send_every_n_cycles = 4  # Increase this value to throttle sending further (experiment with values)
previous_rgb_string = ""  # Cache to store previous RGB string

# Function to convert RGB string to byte array
def rgb_string_to_bytes(rgb_string):
    rgb_values = rgb_string.split(',')
    return bytearray(int(value) for value in rgb_values)

# Function to send RGB data to Arduino via Serial DAT
def send_rgb_data(rgb_string):
    byte_data = bytearray()
    
    # Add start marker
    byte_data.append(ord('<'))
    
    # Convert the RGB string into raw bytes
    byte_data.extend(rgb_string_to_bytes(rgb_string))
    
    # Add end marker
    byte_data.append(ord('>'))
    
    # Access the Serial DAT named 'arduino'
    serial_op = op('arduino')
    
    # Send the raw byte data to the Arduino
    serial_op.sendBytes(byte_data)
    
    # Print the RGB values being sent to the Textport for debugging
    print(f"Sending RGB Data: {rgb_string}")

# Function to check if the RGB string has changed
def rgb_string_has_changed(new_rgb_string):
    global previous_rgb_string
    if new_rgb_string != previous_rgb_string:
        previous_rgb_string = new_rgb_string
        return True
    return False

# Called while the timer is active (every frame)
def whileTimerActive(timerOp, segment, cycle, fraction):
    global cycle_counter
    if cycle_counter % send_every_n_cycles == 0:
        # Fetch the RGB string from the 'rgb' DAT operator (assuming first cell holds the comma-separated string)
        rgb_string = op('rgb')[0, 0].val  # Access the value in the first cell of the 'rgb' DAT
        
        # Only send the data if the RGB string has changed
        if rgb_string_has_changed(rgb_string):
            send_rgb_data(rgb_string)
    
    cycle_counter += 1
    return

# Called when the timer is initialized
def onInitialize(timerOp, callCount):
    return 0

# Called when the timer is ready to start
def onReady(timerOp):
    return
	
# Called when the timer starts
def onStart(timerOp):
    return
	
# Called when a timer pulse occurs (end of a segment)
def onTimerPulse(timerOp, segment):
    return

# Called when a new segment is entered
def onSegmentEnter(timerOp, segment, interrupt):
    return
	
# Called when a segment is exited
def onSegmentExit(timerOp, segment, interrupt):
    return

# Called when a new cycle starts
def onCycleStart(timerOp, segment, cycle):
    global cycle_counter
    cycle_counter += 1  # Increment cycle counter on each cycle
    return

# Called to alert at the end of a cycle
def onCycleEndAlert(timerOp, segment, cycle, alertSegment, alertDone, interrupt):
    return
	
# Called when a cycle is completed
def onCycle(timerOp, segment, cycle):
    return

# Called when the timer is done (all cycles completed)
def onDone(timerOp, segment, interrupt):
    return

# Called when a subrange starts
def onSubrangeStart(timerOp):
    return

The refresh rate is fine and now all I'm fine tuning is the "jumpiness" of the leds but that is most likely due to the source video I'm using. Planning to look at lerping the leds but I know that will take up more space as I'm basically doubling my storage requirements.

I have successfully run all 210 leds off the 5v and gnd from the uno with the occasional voltage drop causing some random colors to pop in there. Adapting an old power supply to rectify this issue.

Isn't Neopixels just a brand name for WS2812 led strings?

Clue: Fastled Library is for WS28xx addressable led strings.

If you read hex chars on the fly (as Serial.available()) you can fill the LED[] array without buffering and need to keep no copies.

I have WS2811 "tree lights" with 12mm bulbs that use the same code. There are 3 wires, GND, VCC 5V external from a 60W supply and DATA from Arduino pin. The Arduino does not power the bulbs that can take up to 60 mA current each (all bright white) wuth 50 bulbs using up to 3 Amps from a supply capable of delivering 12 Amos. The Arduino pin only delivers data to the first led in the string, the board IO pin max is 200 mA.

I fill the array wuth new values and call the show() function. If I do that every 40 ms or faster, it appears to move smoothly. For Arduino at high serial speed, 40 ms is a long time!

1 Like

If you are sending large amounts of characters through serial where you need a complete line at once use 2 buffers
Setup 2 byte arrays
Byte arrayOne[numofcharacters]
Byte areayTwo[numofcharacters]
Read your serial stream into one array until you hit your delimiter.
When its full. Switch to reading to the other array

Once an array is full then convert the bytes from the completed array into your values and output to the leds.

If you have serial in on an interrrupt you can still be reading your serial in into an array while not overflowing the serial buffer

WS28xx uses shift registers, one per pixel built into the hardware.

The nodes are a buffer that the show() function loads. Once you run show(), you can change the array on the controller without changing the pixels at all. And when you run show(), AFAIK execution does not resume until it is finished, unlike how Serial output on Arduino works. Check that, double-buffering likely only wastes RAM with WS28xx leds. And Serial on Arduino, BTW, has a 64 byte output buffer that you don't want to overfill.

True, unless you're using a processor and CPixelLEDController class that supports blasting out the LED data via DMA. You can do this with Teensy, ESP32, and perhaps others. See Post #36 Above and Post #2 in this Thread.

But not an Uno or Nano that have no DMA or RAM to waste!
FastKed writes the string so fast.. but I have only run 50 leds at a time.

I use FastLeds to get the write timing right. When I can see well I should put time in to steal their code. I want to see if I can shift already loaded and shown pixels down the string, adding another to the start instead of re-loading the whole bunch. AFAIK that is possible with shift registers so maybe WS leds too. It would mean not having to buffer the lot. I need to read a few pages of doc thoroughly to find the hooks and holes to try it, but it's be worth it! In one ms slack space, Arduino can have 16000 cycles to generate a pixel, KWIM? A tenth of that is plenty!

Oh BTW, I'm not an EE. If DMA transfer is taking place, is the processor blocked from writing to that segment of RAM?

On processors sophisticated enough to have DMA, DMA transfers are typically interleaved with processor accesses. Much of the time the processor doesn't need to access memory as the compiler / optimizer causes data to be temporarily held in processor registers. Also, the memory is available for DMA when the processor is fetching instructions.

I doubt it. Take a look at the WS2812 datasheet. The entire string must be written during a continuous transfer. After a 50us reset period, the data is latched and displayed by each pixel's IC. You must then write the entire string again to change any pixels.

1 Like

The WS2812 chips aren't shift registers and they don't shift data. Once their reset condition has been met (50us with of logic LOW at their input), they latch the the first three bytes of data presented at their input (encoded per the datasheet) and hold it to set the levels of their 3 LEDS (RGB). Any subsequent data arriving at their put (after latching) is simply transferred to their output (and the input of the next WS2812 chip). After 50us of logic LOW at the input, the chip uses the latched data as PWM values, thus setting the 3 LEDs to their next brightness levels. The WS2812 then resets and waits for the cycle to start again.

1 Like

What you described right there work as a chain of 24 bit shift registers except that the latching is time based which saves a pin per node and the wire between nodes!
IIRC there is a certain minimal time between data bits and if I send 240 bits without a 50 usec pause it would fill 10 nodes.

And... it means I can do as I want with regard to adding a color at the start end just pushing the rest up +1 node on the string.

If that's alll there is to it, it's wonderful!
I have MSEQ7 7 band equalizer display chips that will let me read audio level of 7 frequency ranges 1 at a time and synthesize R, G, B output levels. Using 8 bit ADC, figure over 150 usecs then turn the values into R, G and B bytes and send the bits then wait perhaps 30 or 40 ms and do it again to make a flow, no buffer needed.

Without the NSEQ7 chip, high and low audio bypass filters could separate the sound into bass, midrange and treble, full wave rectiers and leveling capacitors turn those into levels and there would be 3 ADC pins to read to get R, G and B values. More wiring, same basic if not exact result.

Either way, an ATtiny25 could run a very long string with RAM to spare!

It doesn't matter to me if you belive me or not. Based on my experience doing low-level programming with these devices, I'm willing to bet your idea won't work. I'll be happy to take that back when you post some working code.

1 Like

My reasoning is that all the nodes are the same. Once the reset period is reached for one and maybe a bit longer, all of them are reset. A are waiting for data so when the 1st gets bits in it pushes bits to the next which pushes bits to the next etc all the way down with some short period between each. And then when bits are not sent to the first for 50 us or so and it doesn't push to the next, the reset time moves down the chain and latching occurs.

Of course it's not that simple because there are time windows to push bits into the first node. I saw the spec pre-COVID and didn't try it then but it's not screamingly tight, IIRC. There is a minimal delay between bits sent at the least. What I expect to have to do is read and understand the FastLed show() function and compare that to the doc spec to check my own ideas. If they look right, I might know what to do!

i don't have a string. UNO and PS set up but as a test I could try setting FastLed up as an array of 1 node with a longer string and run show() multiple times to see if I get a line of lit up nodes. That would show pass-along right there.

Your reasoning is fundamentally flawed because a string of WS2812 devices is simply not a shift register. This is how a shift register behaves on successive cycles:
image

This is how a string of WS2812 devices behaves on successive cycles:

image

It's totally different. Once a set of RGB values is latched in a WS2812, it is NEVER shifted up the string.

2 Likes

So after the pause/write/latch operation, it never reads the data back out of the latch. And also in each new cycle, since there isn't a shared clock line, the shifting doesn't happen until the shifting propagates up from the prior pixels.

(oops, I missed the reversal...)

The https://cdn-shop.adafruit.com/datasheets/WS2812.pdf datasheet tries to make it clear:

The data transfer protocol use single NZR communication mode. After the pixel power-on reset, the DIN port receive data from controller, the first pixel collect initial 24bit data then sent to the internal data latch, the other data which reshaping by the internal signal reshaping amplification circuit sent to the next cascade pixel through the DO port. After transmission for each pixel, the signal to reduce 24bit. pixel adopt auto reshaping transmit technology, making the pixel cascade number is not limited the signal transmission, only depend on the speed of signal transmission.

Oh.