I am working on a project where I want to use Serial communication to send Analog Inputs from Arduino to Pure Data and send back Values from (0-255) that are supposed to control the brightness of Leds in a Neopixel string. Those values correspond to the amplitude of a sound. The Leds are supposed to 'fade out' together with the sound.
When I only implement the communication part it seems to be working fine. As soon as I insert the brightness value to the Neopixels and call the pixel.show() function from the adafruit library there are problems with the serial communication, meaning that the values are not transferred correctly, probably because the pixel.show() is disturbing the serial communication process.
I pasted the code below.
I already found a similar problem description in another post: https://forum.arduino.cc/t/neopixel-show-interrupting-serial-communication/470766
But it seems way more complicated than what I am trying to do and since I am not really experienced in Coding, it is hard for me to follow the code from the post.
What I already tried is to add a 'timer' (elapsedMillis.h) and only write the values to the Neopixels when 100ms have passed. This made the code work a lot better, but did not solve the problem completey (so I left it out in the code I pasted below).
Any help would be really appreciated.
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
// Released under the GPLv3 license to match the rest of the
// Adafruit NeoPixel library
const int num_of_analog_pins = 4; ////////////////////////////////////////////////////////////insert constellation number
const int const_low[] = {0, 4, 8, 12};
const int const_high[]= {3, 7, 11, 15};
//analog pins für kommunikation mit pure data (by Alexandros Drymonits Patch serial_print
int analog_values[num_of_analog_pins];
int constellation_brightness[num_of_analog_pins];
//initialize Neopixel
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// Which pin on the Arduino is connected to the NeoPixels?
#define PIN 6 // On Trinket or Gemma, suggest changing this to 1//////////////////////////////////////Arduino Pin
// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 15 // Popular NeoPixel ring size///////////////////////////////////////////////////////////NUM PIXELS
// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_RGB + NEO_KHZ800);
void setup() {
Serial.begin(115200);
//neopixel:
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.clear();
}
void loop() {
//PD Kommunikation Photoresistoren - send values to Pure Data
for(int i = 0; i < num_of_analog_pins; i++) analog_values[i] = analogRead(i);
Serial.print("Analog_values: ");
for(int i = 0; i < (num_of_analog_pins - 1); i++){
Serial.print(analog_values[i]);
Serial.print(" ");
}
Serial.println(analog_values[num_of_analog_pins - 1]);
//book alex _ digital electronic for musicians p. 91f. - 0-255 amplitude value from Pure Data
if(Serial.available()){
static int temp_val;
byte in_byte = Serial.read();
if((in_byte >= '0') && (in_byte <= '9'))
temp_val = 10 * temp_val + in_byte - '0';
else if((in_byte >= 'a') && (in_byte <= 'z')){
int which_constellation = in_byte - 'a';
constellation_brightness[which_constellation] = temp_val;
temp_val = 0;
}
}
//write values to LEDs
for (int i = 0; i < num_of_analog_pins; i++){
for (int j = const_low[i]; j < (const_high[i] + 1); j++) {
pixels.setPixelColor(j, pixels.Color(constellation_brightness[i], constellation_brightness[i], 0));
}
}
pixels.show();
}
The show() function disables interrupts. Serial reception depends on interrupts. If a byte arrives while interrupts are off, data will be lost or corrupted. When I want to receive serial data in my WS2812 project, I suspend calling the show() function till serial reception completes.
You also call show() every time through loop(), even if nothing has changed so it is wasting time.
Also, you for() loop with the j index overruns your pixel array since the upper bound will be 16 and you only have 15 LEDS with an index of 0..14 (I believe the adafruit library checks this and prevents you from writing out of bounds, but you shouldn't rely on others)
@groundFungus I do not really know how I can achieve this, how can I arrange that the show() function is only called when there is no serial?
@blh64 Would you use a timer for this (like the one I mentioned above)? Or is there a way to only call show() when something actually has changed? And thanks for pointing out the wrong upper bound.
Here is my loop() function. The sender (a Bluetooth app on my tablet) sends a single character (any character) to tell the Arduino that serial data is to follow. That character and any others that happen to be in the serial buffer are discarded and the mode set to serial receive mode. The serial data is read, parsed and acted upon then the mode is set back to 0 to enable the show function.
void loop()
{
if (mode == 0) // show mode
{
showMiles();
showDP(dpColor);
if (bt.available()) // if a character has come in
{
// clear serial input buffer
while (bt.available())
{
// read and throw away the alert character(s)
bt.read();
}
mode = 1; // set the serial input mode
}
}
else if (mode == 1) // read serial mode
{
if (setupMsg == 0) // send setup message if it hasn't been sent
{
Serial.println("Entered set up mode");
bt.println("Entered set up mode");
setupMsg = 1; // setup message sent
}
recvWithStartEndMarkers(); // get the serial data
if (newData == true)
{
parseData(); // parse and act upon serial data
// the parseData function will set mode back to 0
// when all serial is received and processed
}
}
}
//==================== loop
@alto777
No it's not:). I have adapted the code from the NeoPixel library and I am using a string of 15 Neopixels for testing. The default value (that I changed to 15) was 16.
@groundFungus
I read the tutorial and tried to get it to work, but it seems to take a lot more time for me to be able to do this. But maybe you can tell me if I understood it right, so I do not get lost:
The patch starts in mode (0) and calls the show function for the NeoPixels and then looks for Data that came in. Here I do not know what bt.available is, but is it correct that in my case this would be Serial.available()?
If Data is available I just call the Serial.read() function that reads the data (storing it nowhere) so the data is acutally deleted.
If any Data was received it means that the program should go to the Data Transfer mode.
So basically, in Pure Data I have to to send any character, signifying I want to send some values. At this point Pure Data stores the values that need to be send and 'waits' for a Message from the Arduino. Then when the Arduino is ready it sends a distinct message to Pure Data and Pure Data starts to transfer the actual values.
The first character alerts the Arduino that serial data is coming and is discarded. The Arduino does send a message, but the sender (Pure Data) does not have to wait for the message. it can send data right away. in fact my Bluetooth app (Serial Bluetooth Terminal) sends the whole line at once. For example, "s<c,0,0,255> where the s is the alert character (not saved), < is the data packet start marker, c is for color, 0,0,255 is red = 0, green = 0, blue = 255 and > is the data packet end marker. That data is parsed and acted upon, then the mode is set to 0 and showing resumes.
I updated my code with the help of your code and with the serial basic tutorial (see below). This seems to work way better, as long as I don't send serial data too fast.
What I do not really get is the delete part of the serial buffer. If it (maybe) deletes the complete message, am I supposed to send it two times? Or would it be possible to not delete it and just read it in the next step?
First I tried to use the serial data conversion as described in the Tutorial, but the problem was that Pure Datas Serial communication was not able to send a message with "<" and "," so I tried to adapt the code I already had and I am using "y" and "z" as begin and stop message. From Pure Data I am sending messages like "ayVALUEaz" for the first brightness value, "ayVALUEbz" for the second value and so forth. So each value gets a separate message. In the recvWithStartEndMarkers() function I am doing the "conversion" and directly write the value in the constellation_brightness[constellation_number] array.
Is this a good approach like this?
So does the message have any purpose? In the code below I just left it out.
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
// Released under the GPLv3 license to match the rest of the
// Adafruit NeoPixel library
//Time Elapsed counter
#include <elapsedMillis.h>
elapsedMillis timeElapsed; //declare global if you don't want it reset every time loop runs
const int num_of_analog_pins = 4; ////////////////////////////////////////////////////////////insert constellation number
const int const_low[] = {0, 4, 8, 12}; ////////////////// Lower and Upper Number of earch constellation (Number LED - 1, starting from zero)
const int const_high[]= {3, 7, 11, 14};
boolean newData = false; /////////////////////////////////to only activate the Neopixel Updates when new Data has been received
boolean mode = 0;
//analog pins für kommunikation mit pure data (by Alexandros Drymonits Patch serial_print
int analog_values[num_of_analog_pins];
int constellation_brightness[num_of_analog_pins];
//initialize Neopixel
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// Which pin on the Arduino is connected to the NeoPixels?
#define PIN 6 // On Trinket or Gemma, suggest changing this to 1//////////////////////////////////////Arduino Pin
// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 15 // Popular NeoPixel ring size (default value is 16)////////////////////////////////////////////////////////NUM PIXELS
// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_RGB + NEO_KHZ800);
//#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels
void setup() {
timeElapsed = 0; //time counter
Serial.begin(115200);
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.clear();
}
void loop() {
//sendAnalogValues();
//PD Kommunikation Photoresistoren schicken///////////////////////////////////////////hier muss multiplex eingefügt werden
if (mode == 0) //show mode
{
if (timeElapsed > 20)
{
sendAnalogValues();
timeElapsed = 0;
}
if (newData == 1)
{
// printToPd(); //for debugging
updateNeoPixels();
pixels.show();
}
if (Serial.available())
{
while (Serial.available())
{
// read and throw away the alert character(s)
Serial.read();
}
mode = 1;
}
}
else if (mode == 1) //serial input mode
{
recvWithStartEndMarkers();
}
}
////////////////end of loop
//////////////////////////////////////////////////////////////////////////////////////////////////Funktionen die im Loop aufgerufen werden
void sendAnalogValues() { ////////////////////////////////////////////////////////////Sendet Analoge Werte zu PD, PD Objekte serial_print13 von Alexandros Drymonitris
for(int i = 0; i < num_of_analog_pins; i++) analog_values[i] = analogRead(i);
Serial.print("Analog_values: ");
for(int i = 0; i < (num_of_analog_pins - 1); i++){
Serial.print(analog_values[i]);
Serial.print(" ");
}
Serial.println(analog_values[num_of_analog_pins - 1]);
}
void recvWithStartEndMarkers(){
static boolean recvInProgress = false;
char startMarker = 'y';
char endMarker = 'z';
while (Serial.available() > 0 && newData == false)//////////Serial ist da
{
static int temp_val;
byte in_byte = Serial.read();
if (recvInProgress == true)////////////////////////////////zuerst muss Startwert geschickt werden
{
if((in_byte >= '0') && (in_byte <= '9'))
temp_val = 10 * temp_val + in_byte - '0';
else if((in_byte >= 'a') && (in_byte < 'y'))
{
int which_constellation = in_byte - 'a';
constellation_brightness[which_constellation] = temp_val;
temp_val = 0;
}
else if (in_byte == endMarker)
{
recvInProgress = false;
newData = true;
mode = 0;
}
}
else if (in_byte == startMarker)
{
recvInProgress = true;
}
}
}
void updateNeoPixels() { ///////////////////////////////////////////////////////////updates the Neopixels with received Brightness values
for (int i = 0; i < num_of_analog_pins; i++){
for (int j = const_low[i]; j < (const_high[i] + 1); j++) {
pixels.setPixelColor(j, pixels.Color(constellation_brightness[i], constellation_brightness[i], 0));
newData = false;
}
}
}
void printToPd(){
if (newData == true){
Serial.print("Received_values: ");
for(int i = 0; i < 3; i++){
Serial.print(constellation_brightness[i]);
Serial.print(" ");
}
Serial.println(constellation_brightness[3]);
newData = false;
}
}
All that I want to do there is get rid of the useless alerting character and any other superfluous garbage that may be in the serial input buffer to make way for the actual data packet. I am preparing the buffer for the real serial data that is to follow, immediately.
That is fine. It serves no purpose except to alert a human on the Bluetooth app end that the mode is set to "setup".
It matters not what the alerting character is. It is discarded. The start and end characters (<>) and the delimiter (,) can be any characters, but they must never appear in the data. To receive data without the start marker and with newline (\n) as the end marker see example 2 of the linked tutorial.
I still don't understand completely how it is supposed to work. How can I be shure that a line that contains the complete message (alert character, start character, values, end character) ...
does not get deleted completely in mode == 0 in the while loop, but only the first character is deleted? Or does the while loop only 'look' at the first character from the line and then it continutes to mode==1 directly?
I looked up the documentation that I wrote for the program and it turns out that you are right that the alert character is sent and the user must wait for the “Entered set up mode” message before sending the commands. I should not have trusted my memory and I apologize for the wasted time.
From my documentation:
a transaction would look like this:
Send s (no need for < or>) or any other character
Bluetooth terminal (BTT) shows: “Entered set up mode”
Send <c,2,255,0,255>
BTT shows: “<c,2,255,0,255>"
"Color set"
send more commands or
Send "<d>" (don't forget the < & >) to terminate transaction
BTT shows: “Set up DONE”
So, of course, one can change the "Entered set up mode" message to anything that the Pure Data can recognize as a handshake (alert) character.
In 3. the terminology is start marker, message, end marker to match the content in the code.
In 4., the "end char" is the (or message of choice) that the sender (Pure Data) sends to signify that the sender will send no more commands. When the Arduino receives the it sets the mode to 0 (and can send a signal to Pure Data if you want) and resumes the show().