Arduino Bluetooth Control FastLED

Hello,

I am trying to create a Bluetooth controlled lamp (via android mobile app) with ws2812B led strip using Arduino Nano and HC-05 module. I created the below code by following some tutorials on Bluetooth.
The code does its job of setting the desired color of led strip on receiving of a RGB value in form 255.255.255 (value sent using serial Bluetooth terminal app from play store).

My question is why is it that BLU.available() returns zero if I send anything other than the desired format of RGB values. For example: if I send 255 alone or 255.123 or any text the BLU.available() returns zero. Why is it that it returns non-zero value when I send the full value i.e. something like this 255.123.12?
My objective is to gain some understanding on how this transmission/interpretation of values is going on, so I may be able to further tweak this code.
You may observe several Serial.println() in the code below, those are because I was trying to see what exactly is happening over here.
It would also be helpful if someone can direct me towards some tutorials or resources where this type of info might be available.
Thanks in advance.
Rijul

Code:

#include <SoftwareSerial.h>
#include <FastLED.h>

#define NUM_LEDS 60
#define LED_PIN 2

SoftwareSerial BLU(9,10); //Rx,Tx
CRGB leds[NUM_LEDS];

void setup()
{
  //Serial setup
  Serial.begin(9600);
  Serial.println("-= HC-05 Bluetooth RGB LED =-");
  BLU.begin(9600);
  BLU.println("-= HC-05 Bluetooth RGB LED =-");
  
  //initializing fastled
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);

  fill_solid(leds, NUM_LEDS, CRGB::Red);
  FastLED.show();
}

void loop()
{
  while (BLU.available() > 0)
  {
    int redInt = BLU.parseInt();
    int greenInt = BLU.parseInt();
    int blueInt = BLU.parseInt();

    Serial.println(BLU.available());
    Serial.println("----");
    Serial.println(redInt);
    Serial.println(greenInt);
    Serial.println(blueInt);
    Serial.println("----");

    redInt = constrain(redInt, 0, 255);
    greenInt = constrain(greenInt, 0, 255);
    blueInt = constrain(blueInt, 0, 255);

    Serial.println("----");
    Serial.println(redInt);
    Serial.println(greenInt);
    Serial.println(blueInt);
    Serial.println("----");
    Serial.println(BLU.available());

    if (BLU.available() > 0)
    {
      Serial.println("entered loop");
      fill_solid(leds, NUM_LEDS, CRGB(redInt, greenInt, blueInt));
      FastLED.show();

      Serial.print("Red: ");
      Serial.print(redInt);
      Serial.print(" Green: ");
      Serial.print(greenInt);
      Serial.print(" Blue: ");
      Serial.print(blueInt);
      Serial.println();

      BLU.flush();
    }
  }
}

Hi, welcome to the forum.

The FastLED and SoftwareSerial both use almost the complete Arduino Nano. I doubt that they can run together.

The function .parseInt() waits until a integer is received with a timeout. The timeout is default 1 second.
You print BLU.available() after retrieving the data. The .parseInt() functions read the data from the stream, so there might be no more data.

If you are serious to make this always work, then you can read every received byte and put it in a buffer. Once the data is complete, then extract the data from the buffer.
There are also good tutorials to read serial data.

1 Like

Well they can work together, but alternatingly. FastLED.show() disables interrupts temporarily, and swSerial heavily relies upon them.
So keep that in mind. Also as Koepel pointed out already, using the parseInt() function is not helping. You should be parsing the incoming data (which due to the combination of .show() & swSerial is going to be a tad unreliable) yourself.
You should wait until you have received a complete message before you start parsing. Every byte will take about 1ms to be transferred (with interrupts triggered twice per bit) , so you will need to make sure that you have not emptied the buffer while there are still bytes on the way as well.

1 Like

Hello Koepel,

Thank you, this is in-fact my first Arduino project :slight_smile: .
Functionality wise FastLED and SoftwareSerial seem to be working together on my Nano so far (testing on breadboard).

Ohh so this .parseInt() function is consuming the data. Yes I did read about this that it times out if it does not receive any data or if it encounters some non-integer data, just missed to notice it here. When I print BLU.available() before parseInt(), it does give a non zero value.
This does answer my initial question, Thanks a lot!, however I ended up with another question now.
I modified the loop function as shown below in code section. When I sent 1.1.1 via android BT terminal, I got the below output in serial terminal (screenshot). Why is output of BLE.available() changing throughout before entering the loop argument?

Also, if possible, could you please direct me to some known good tutorial on reading serial data, storing it and then processing it for use as you suggested in your reply

Thanks Again!
Screenshot:

Code:

void loop()
{
  while (BLU.available() > 0)
  {
    Serial.println("before parseint");
    Serial.println(BLU.available());
    
    int redInt = BLU.parseInt();
    Serial.println("after red");
    Serial.println(BLU.available());
    
    int greenInt = BLU.parseInt();
    Serial.println("after green");
    Serial.println(BLU.available());
    
    int blueInt = BLU.parseInt();
    Serial.println("after blue");
    Serial.println(BLU.available());
    
    Serial.println("----");
    Serial.println(redInt);
    Serial.println(greenInt);
    Serial.println(blueInt);
    Serial.println("----");

    redInt = constrain(redInt, 0, 255);
    greenInt = constrain(greenInt, 0, 255);
    blueInt = constrain(blueInt, 0, 255);

    Serial.println("----");
    Serial.println(redInt);
    Serial.println(greenInt);
    Serial.println(blueInt);
    Serial.println("----");
    Serial.println(BLU.available());

    if (BLU.available() > 0)
    {
      Serial.println("entered loop");
      fill_solid(leds, NUM_LEDS, CRGB(redInt, greenInt, blueInt));
      FastLED.show();

      Serial.print("Red: ");
      Serial.print(redInt);
      Serial.print(" Green: ");
      Serial.print(greenInt);
      Serial.print(" Blue: ");
      Serial.print(blueInt);
      Serial.println();

      BLU.flush();
    }
  }
}

I recently completed a project using FastLED and a Bluetooth module. Like has been stated, SoftwareSerial and FastLED.show() can't bye used at the same time because the show() function disables interrupts. So what I do is when the btSerial.available() function returns true I stop issuing the show() function, go off and read the complete serial message, parse the message and do what need to be done before starting the to show() again. To read the serial data I use methods from the serial input basics tutorial.

1 Like

It doesn't; it returns the number of characters available :wink: But I'm sure you know that.

1 Like

Quite simply because there is data coming in still, and parseInt() contains .read(0 statements which is taking data out. So once there is 'something' in the buffer, the condition is true, but there is still more coming, then you start reading.

That is a good way to do this, but sometimes the first (few) characters get lost or damaged.

1 Like

I do not care what the first character is. It is only sent by the app to signal the Arduino that data is coming its way. When the first character comes in the program enters the serial receive mode, clears the serial receive buffer of any garbage and looks for the start marker. The data is received until the end marker, parsed, acted upon and then the mode is set back to running the show().

void loop()
{
   if (mode == 0)  // show mode
   {
      showMiles();  
      showDP(dpColor);      
      if (bt.available())
      {
         while (bt.available())
         {
            bt.read();  // clear bt buffer
         }
         mode = 1;
      }
   }
   else if (mode == 1)  // serial receive mode
   {
      if (setupMsg == 0)
      {
         Serial.println("Entered set up mode");
         bt.println("Entered set up mode");
         setupMsg = 1;
      }
      recvWithStartEndMarkers();
      if (newData == true)
      {
        parseData();
      }
   }
}
1 Like

Depending on the amount of LEDs that get data sent to them, it might be more than just the first character,.
Considering how much we want people to post full sketches, isn't this a little ambiguous ?

     if (newData == true)
     {
       parseData();
     }

I suspect that within parseData() the flags get reset, but at least 'mode' belongs in the main function

      if (newData == true)
      {
        parseData();
        mode = 0;
      }

I see your point. I should have written a more complete example instead of posting part of a program. The intention of posting that code was only to show how I separated the displaying and the serial reception.

And I agree that putting the mode in the main function is cleaner.

Is there an alternative way of lighting up ws2812b strip without using FastLED or may be some other library that does not cause this interrupt issues??

And in the way @groundFungus suggested, do we need to keep some sacrificial characters every time we transmit a command?

@groundFungus thanks for directing to tutorial on reading serial data.

May be I am too ambitious but i was thinking to have a universal lamp that could be BT controlled to switch between static colors (set via a color wheel in android app), a brightness control (mapped to a slider in app), few modes like a moving rainbow or fire kind of mode and then also a music reactive mode (with just 2-3 pattern types, sensitivity control, all settable via app). What do you think guys? Is it doable on nano? I dont want too many patterns to choose from just need it all to be controlled via app.

The issue when talking to the WS2812 strips is that the timing is so critical that interrupts must be disabled lest an interrupt occur and foul up the timing. So, I think, any library will have to be the same as FastLED with respect to interrupts.

As to sacrificial characters, that is just how I did it. There may well be a better way. I am no pro.

Two other possibilities:

  • Switch to a strip that uses APA102-type (aka Adafruit DotStar) LEDs. These include both Clock and Data lines and do not have the precise timing specification that require interrupts be disabled.

  • Switch to a processor that supports updating WS2812B LEDs via DMA. This eliminates the need to disabled interrupts. Various members of the Teensy family have the hardware and library support for this technique.

2 Likes

Yes that can be done.
You will simply have to work around the interrupt thing. Actually you can reduce the problem significantly by connecting the bluetooth device to hardware Serial. hwSerial does depend on interrupts as well, but a lot less than swSerial. hwSerial triggers an interrupt once there is a complete byte in the hw input buffer, that needs to be transferred to the (ring) buffer in RAM. At 9600kbps that is only about once every 1 ms. And there is no chance of the byte being 'broken', all that can happen is that you can lose a byte. Of course then you can not use hwSerial for anything else (like debugging)

It should be possible to use the UART for sending the signal to WS2812's but i don't think that there is a library that does that on an AVR (on an ESP there is, but then you can also use DMA mode)

1 Like