Reputation: 361
I have 2 different LED Rings. (one has 16 LEDs and the other 24) I want to make an interface from processing IDE where I can select a color and send this color to the selected ring. Here is the arduino code:
#include <FastLED.h>
#define LED_PIN1 3
#define LED_PIN2 12
#define NUM_LEDS1 16
#define NUM_LEDS2 24
CRGB leds1[NUM_LEDS1];
CRGB leds2[NUM_LEDS2];
int r,g,b;
boolean state = false;
void setup() {
FastLED.addLeds<WS2812, LED_PIN1, GRB>(leds1, NUM_LEDS1);
FastLED.addLeds<WS2812, LED_PIN2, GRB>(leds2, NUM_LEDS2);
Serial.begin(9600);
}
void loop() {
String returnedInput = rgbInput();
String red = returnedInput.substring(0,3); //get 3 values like 255
String green = returnedInput.substring(4,7);
String blue = returnedInput.substring(8,11);
Serial.println(red);
Serial.println(green);
Serial.println(blue);
int r = red.toInt();
int g = green.toInt();
int b = blue.toInt();
if (Serial.available()){
char val = Serial.read();
if(val == '2') { //selects the second LED ring
if(state == false) {
state = true;
for(int i = 0 ; i < 24 ; i++ ){
leds2[i] = CRGB(r, g, b); //turn on all the LEDs on the ring to the selected color
FastLED.show();
FastLED.clear();
FastLED.show();
}}}
}}
String rgbInput() {
//prompt for input
Serial.println("ready");
while(!Serial.available()) {
//if 0, it keeps waiting for the user to enter sth.
}
String userInput = Serial.readStringUntil("\n");
return userInput;
}
I wrote this for the second LED, if I can manage this one, I will do the same for the other one but it's not working.
And here is the Processing codes:
import controlP5.*; //import ControlP5 library
import processing.serial.*;
Serial port;
ControlP5 cp5; //create ControlP5 object
PFont font;
PFont font2;
color col;
Serial serialMonitor;
String prompt;
ColorPicker cp;
void setup(){ //Same as setup in arduino
size(900, 900); //Window size, (width, height)
port = new Serial(this, "COM4", 9600); //Change this to your port
cp5 = new ControlP5(this);
font = createFont ("Georgia Bold", 20);
font2 = createFont ("Georgia Bold",15);
cp = cp5.addColorPicker("PICKER")
.setPosition(500,100)
.setColorValue(color(255,128,0,128))
;
Group configGroup = cp5.addGroup("CONFIGURATION")
.setPosition(90,100)
.setWidth(150)
.setHeight(30)
.setFont(font2)
.setBackgroundColor(color(0,0))
;
cp5.addButton("PICK_ALL") // The button
.setPosition(10, 10) // x and y relative to the group
.setSize(160, 150) // (width, height)
.setFont(font)
.setGroup(configGroup) // add it to the group
;
cp5.addButton("PICK_ONE") // The button
.setPosition(10, 200) // x and y relative to the group
.setSize(160, 150) // (width, height)
.setFont(font)
.setGroup(configGroup) // add it to the group
;
}
void draw(){ //Same as loop in arduino
background(150, 0 , 150); //Background colour of window (r, g, b) or (0 to 255)
}
public void controlEvent(ControlEvent c){
if(c.isFrom(cp)){
int r = int(c.getArrayValue(0));
int g = int(c.getArrayValue(1));
int b = int(c.getArrayValue(2));
int a = int(c.getArrayValue(3));
col = color(r,g,b,a);
}
}
void keyPressed(){
while(serialMonitor.available() > 0)
{
prompt = serialMonitor.readStringUntil (10);
}
println(keyCode);
String sendColor = nf(int(red(col)),3) + "," + nf(int(green(col)),3) + "," + nf(int(blue(col)),3);
println(sendColor);
serialMonitor.write(sendColor);
}
void PICKER(){
port.write('2');
}
void PICK_ALL(){
port.write('t');
}
void PICK_ONE(){
port.write('l');
}
I don't exactly know how to get the RGB values and use them in CRGB function. It is much easier when using a single RGB LED that uses 3 pins. But I couldn't implement it to an LED Ring that uses only 1 pin.
Here is the processing interface for color picking. I can select the colors but nothing is changing in the LED Ring on Arduino.
Upvotes: 1
Views: 344
Reputation: 51847
Reliable serial communication is not trivial. Ideally you would make you own binary communication protocol setting up a packet of bytes with a header that describes how many following bytes actually have data and perhaps even a checksum.
Strings will do to get started and it's great you're using nf()
to make data easier to parse.
One potential gotcha could be the switch between putting a string together (rgbInput()
) and reading one char at a time (char val = Serial.read();
).
I would recommend breaking the problem down into smaller simpler parts, testing/debugging each part, then putting the parts back together one at a time to avoid integration bugs.
For example, the main challenge seems to be serial communication so I'd write a Processing and Arduino sketch to ensure that works reliably before adding in LED control.
Let's go for the option of \n terminated strings, even though it's sending a redundant extra character(e.g. port.write("2\n");
) it would make buffering simpler:
trim()
the string for newlineHere's a basic Arduino sketch that uses the ideas above (and a bit of your code ;) ):
void setup() {
Serial.begin(9600);
}
void loop() {
// check if there are at least two characters to receive
if(Serial.available() > 1){
// buffer the full string until a new line character
String returnedInput = Serial.readStringUntil('\n');
// remove white space (new line char)
returnedInput.trim();
// if it's a single command
if(returnedInput.length() == 1){
char state = returnedInput.charAt(0);
switch(state){
case '2':
Serial.println("parsed 2 command");
break;
case 't':
Serial.println("parsed t command");
break;
case 'l':
Serial.println("parsed l command");
break;
default:
Serial.print("unknown state:");
Serial.println(state);
break;
}
}
// if it's a RGB triplet
else if(returnedInput.length() == 11){
String redString = returnedInput.substring(0, 3); //get 3 values like 255
String greenString = returnedInput.substring(4, 7);
String blueString = returnedInput.substring(8, 11);
int r = redString.toInt();
int g = greenString.toInt();
int b = blueString.toInt();
// constrain values to bytes
r = constrain(r, 0, 255);
g = constrain(g, 0, 255);
b = constrain(b, 0, 255);
// do something with the values (e.g. store globally, etc.)
Serial.print("parsed RGB: #");
Serial.print(r, HEX);
Serial.print(g, HEX);
Serial.print(b, HEX);
Serial.println();
}
// otherwise error message ?
else{
Serial.print("Uknown command: ");
Serial.println(returnedInput);
}
}
}
This should handle string messages with a new line terminator and based on the trimmed length parse either a single char command a 11 char RRR,GGG,BBB string.
You can test directly with Arduino's Serial Monitor.
In your Processing sketch it's unclear why there are two Serial ports (port
and serialMonitor
).
Here's a slightly modified version of your Processing sketch that sends either a couple of single char commands or the colour string:
import controlP5.*; //import ControlP5 library
import processing.serial.*;
PFont font;
PFont font2;
// Arduino serial port
Serial port;
// colour picker values to send to Arduino
int r;
int g;
int b;
// GUI variables
ControlP5 cp5; //create ControlP5 object
ColorPicker cp;
void setup() { //Same as setup in arduino
size(900, 900); //Window size, (width, height)
try {
port = new Serial(this, "/dev/tty.usbserial-A104WS3R", 9600); //Change this to your port
// buffer until new line: this plugs in nicely with serialEvent()
port.bufferUntil('\n');
}catch(Exception e) {
println("error opening serial");
e.printStackTrace();
}
cp5 = new ControlP5(this);
font = createFont ("Georgia Bold", 20);
font2 = createFont ("Georgia Bold", 15);
cp = cp5.addColorPicker("PICKER")
.setPosition(500, 100)
.setColorValue(color(255, 128, 0, 128))
;
Group configGroup = cp5.addGroup("CONFIGURATION")
.setPosition(90, 100)
.setWidth(150)
.setHeight(30)
.setFont(font2)
.setBackgroundColor(color(0, 0))
;
cp5.addButton("PICK_ALL") // The button
.setPosition(10, 10) // x and y relative to the group
.setSize(160, 150) // (width, height)
.setFont(font)
.setGroup(configGroup) // add it to the group
;
cp5.addButton("PICK_ONE") // The button
.setPosition(10, 200) // x and y relative to the group
.setSize(160, 150) // (width, height)
.setFont(font)
.setGroup(configGroup) // add it to the group
;
}
void draw() { //Same as loop in arduino
background(150, 0, 150); //Background colour of window (r, g, b) or (0 to 255)
}
public void controlEvent(ControlEvent c) {
if (c.isFrom(cp)) {
r = int(c.getArrayValue(0));
g = int(c.getArrayValue(1));
b = int(c.getArrayValue(2));
}
}
void keyPressed() {
if(port == null){
println("no serial, ignoring");
return;
}
String sendColor = nf(r, 3) + "," + nf(g, 3) + "," + nf(b, 3) + '\n';
println("sending to Arduino:", sendColor);
port.write(sendColor);
}
void PICKER() {
println("PICKER");
if (port != null) port.write("2\n");
}
void PICK_ALL() {
println("PICK_ALL");
if (port != null) port.write("t\n");
}
void PICK_ONE() {
println("PICK_ONE");
if (port != null) port.write("l\n");
}
void serialEvent(Serial s){
println("from Arduino:", s.readString());
}
Overall notice bits of error checking: always a good idea when working with Serial :)
Once this works as expected you can combine the FastLED
control.
Here's a suggestion:
#include <FastLED.h>
#define LED_PIN1 3
#define LED_PIN2 12
#define NUM_LEDS1 16
#define NUM_LEDS2 24
CRGB leds1[NUM_LEDS1];
CRGB leds2[NUM_LEDS2];
// ring 1 color
int r1,g1,b1;
// ring 2 color
int r2,g2,b2;
// toggle wether to update r1,g1,b1 or r2,g2,b2 when a new colour arrives
boolean updateRing1 = true;
void setup() {
// setup serial
Serial.begin(9600);
// setup LEDs
FastLED.addLeds<WS2812, LED_PIN1, GRB>(leds1, NUM_LEDS1);
FastLED.addLeds<WS2812, LED_PIN2, GRB>(leds2, NUM_LEDS2);
}
void loop() {
handleSerial();
driveLEDRings();
}
void handleSerial(){
// check if there are at least two characters to receive
if(Serial.available() > 1){
// buffer the full string until a new line character
String returnedInput = Serial.readStringUntil('\n');
// remove white space (new line char)
returnedInput.trim();
// if it's a single command
if(returnedInput.length() == 1){
char state = returnedInput.charAt(0);
switch(state){
case '2':
Serial.println(F("parsed 2 command"));
break;
case 't':
Serial.println(F("parsed t command: switching to ring #1"));
updateRing1 = true;
break;
case 'l':
Serial.println(F("parsed l command: switching to ring #2"));
updateRing1 = false;
break;
default:
Serial.print(F("unknown state:"));
Serial.println(state);
break;
}
}
// if it's a RGB triplet
else if(returnedInput.length() == 11){
String redString = returnedInput.substring(0, 3); //get 3 values like 255
String greenString = returnedInput.substring(4, 7);
String blueString = returnedInput.substring(8, 11);
int r = redString.toInt();
int g = greenString.toInt();
int b = blueString.toInt();
// constrain values to bytes
r = constrain(r, 0, 255);
g = constrain(g, 0, 255);
b = constrain(b, 0, 255);
// do something with the values (e.g. store globally, etc.)
Serial.print(F("parsed RGB: #"));
Serial.print(r, HEX);
Serial.print(g, HEX);
Serial.print(b, HEX);
Serial.println();
// handle ring colour update
if(updateRing1){
r1 = r;
g1 = g;
b1 = b;
}else{
r2 = r;
g2 = g;
b2 = b;
}
}
// otherwise error message ?
else{
Serial.print("Uknown command: ");
Serial.println(returnedInput);
}
}
}
void driveLEDRings(){
//update ring 1
for(int i = 0 ; i < NUM_LEDS1; i++){
leds1[i] = CRGB(r1, g1, b1);
}
//update ring 2
for(int i = 0 ; i < NUM_LEDS2; i++){
leds2[i] = CRGB(r2, g2, b2);
}
// display
FastLED.show();
}
Note: the above code isn't tested with wired RGB LEDs so it might not work but hopefully it illustrates the idea. (You should double check the baud rate, pins, RGB colour channels, etc.)
If the above works as expected using Serial Monitor you can get back to testing the Processing interface sketch.
Additionally bare in mind 9600 is quite a low baud rate. You should test with higher baud rates (e.g. 115200, 57600) and if it's stable use those to avoid delays driving LEDs while buffering serial data. In general avoid / minimise blocking (while) loops where you can.
Overall, the idea is to delete/remove anything you don't need in your code to drill down to the problem: work it out in isolation. Once that's debugged and working reliably add new code one bit a time, testing after each addition (otherwise you risk introducing more bugs instead of one which is harder to spot / fix).
Upvotes: 2