The new Raspberry Pi B Model B+ is here - Pick one up in the store and check our detailed guide!

Blinking 3 LED's with a Processing interface

by GiantEye on Mon Jan 28, 2013 11:03 am

Eventually this sketch will be for turning solenoid valves on and off, but blinking LED's is a a good stepping stone. It's for this project, turning air on and off to some pneumatic robots.

So, I'd like to use a visual interface in Processing to blink three LED's on and off without using "delay();". The point is to use a slider interface to change the period of time each LED spends on with a constant off time (~100ms). It's essentially a super slow PWM.

I'm mashing up this with the first code example here. The processing sketch I'm using for the interface is here. The only think I really need the interface for is handing a value between 0 and 255 to the incomingByte[i] array. Below is the code I've come up with so far:

Code: Select all | TOGGLE FULL SIZE
const int ledPin[] = {5,6,3};
int ledState = LOW;
long previousMillis = 0;

long interval = 100;

int incomingByte[3];

void setup() {
  Serial.begin(9600);
  for (int i=0; i<3; i++) {
    pinMode(ledPin[i], OUTPUT);
  }
}

void loop()
{
  unsigned long currentMillis = millis();

 if (Serial.available() >= 3) {
    for (int i=0; i<3; i++) {
      incomingByte[i] = Serial.read();
 
      if(currentMillis - previousMillis > incomingByte[i]) {
        previousMillis = currentMillis;   
   
        if (ledState == LOW)
          ledState = HIGH;
        else
          ledState = LOW;
   
        digitalWrite(ledPin[i], ledState);
      }
    }
 }
}


The results are pretty erratic, with only one LED at a time doing the blink routine. When you drag the dots around the interface one LED will start blinking, turning the other one that was blinking off. Right now the on time and off time are both == incomingByte[i]. I'd like to get the "off" time set by a variable so I can play with it and adjust the on time with the interface.

Any advice?
User avatar
GiantEye
 
Posts: 10
Joined: Sat Jan 02, 2010 12:18 am
Location: NJUSA

Re: Blinking 3 LED's with a Processing interface

by PatrikD on Mon Jan 28, 2013 12:33 pm

Not sure why you insist this should be done without delay(). One approach would be to keep track howhow long it will take until the next LED transition., and just delay() until that time. Keep track of 3 separate TimeToNextBlink counters, delay equal to the minimum of the three, and update all three accordingly when the delay() returns.

A more elegant option is to figure out how to set up three separate timers, and run all the blinking off interrupts.
PatrikD
 
Posts: 1
Joined: Mon Jan 28, 2013 12:17 pm

Re: Blinking 3 LED's with a Processing interface

by GiantEye on Mon Jan 28, 2013 12:50 pm

I'm not entirely certain, but doesn't delay(1000) cause everything on the chip to keep doing what it's doing until 1000ms have elapsed? Since I'd like to vary the 3 LED's blinking independently of one another, wouldn't the delay() function act like a master clock, causing all 3 LED's to stay in their current state until the time has elapsed?
User avatar
GiantEye
 
Posts: 10
Joined: Sat Jan 02, 2010 12:18 am
Location: NJUSA

Re: Blinking 3 LED's with a Processing interface

by janekm on Tue Jan 29, 2013 7:31 am

It looks like you are only changing the state of the LEDs if you have received 3 bytes of data on the serial port. If I understand you correctly, you want the LEDs to keep blinking independently of whether serial data is currently being transmitted, so you may wish to try moving that part of the code ouf of the "if (Serial.available() >= 3)" block.
janekm
 
Posts: 5
Joined: Wed Jan 16, 2013 8:19 am

Re: Blinking 3 LED's with a Processing interface

by thequux on Wed Feb 06, 2013 1:05 am

GiantEye and I discussed this off-forum, and we decided that it would be far easier to have each pin blink with a constant period, but variable duty cycle. Here is some code to do that:

Code: Select all | TOGGLE FULL SIZE
const int pin[] = {6,5,3};
const int pincount = sizeof(pin) / sizeof(*pin); // number of items in pinset

unsigned long pin_time[pincount] = {0}; // 0-255
unsigned long last_cycle_start = 0;
unsigned long cycle_length = 100; // in milliseconds

void check_serial();
void setup() {
  for (int i = 0; i < pincount; i++) {
    pinMode(pin[i], OUTPUT);
    digitalWrite(pin[i], HIGH);
  }
  Serial.begin(9600); // this gives us about 96 updates/sec, assuming 10 bytes/update, which should be more than enough
  last_cycle_start = millis();
 
}

void loop() {
  check_serial();
  // there is a latent bug here; every 50 days, there will be a short cycle. This probably doesn't matter, but here's where you'd fix it if it did.
  unsigned long cur_time = millis();
  unsigned long cycle_pos = cur_time - last_cycle_start;
  if (cycle_pos > cycle_length) { // this is where the bug is.
    last_cycle_start = cur_time;
    for (int i = 0; i < pincount; i++)
      if (pin_time[i] != 0)
        digitalWrite(pin[i], HIGH);
  } else {
    // cycle_length can't be that long; in fact, it can't be longer than an int.
    int cv = cycle_pos * 256 / cycle_length;
   
    for (int i = 0; i < pincount; i++)
      digitalWrite(pin[i], (cv >= pin_time[i]) ? LOW : HIGH);
  }
  delay(10);
  //Serial.print('.');
}

void handle_command(char cmd, int reg, int value) {
  switch(cmd) {
    case 'c':
      // set cycle time.
      if (value <= 0) {
        Serial.println("!Cycle length negative");
        return;
      }
      cycle_length = value;
      break;
    case 'p':
      if (reg > pincount || reg < 1) {
        Serial.println("!Invalid pin no");
        return;
      } else if (value < 0 || value > 256) {
        Serial.println("!Pin duty cycle out of range");
        return;
      }
      pin_time[reg-1] = value;
      break;
    default:
      Serial.println("!Invalid command");
      return;
  }
  Serial.println("+OK");
}
     
     

void check_serial() {
  // protocol: lines containing command byte, ascii register number, '=', new value, then newline.
  // commands are:
  //  - c: cycle time, in ms. No register number applies.
  //  - p: set pin duty cycle. Register number is pin number (1-n). Value is from 0-256, where 256 is fully on.
  // response is either '!' followed by an error message, or '+OK', optionally followed by a response. No commands currently have a response.
  // Examples:
  //   c=1000  // set cycle time to 1s
  //   p1=0    // turn off pin 1
  //   p2=256  // turn on pin 2
  //   p3=128  // set pin 3 to half on
 
  const int
    ST_CMD = 0,
    ST_REGNO = 1,
    ST_VALUE = 2,
    ST_ERR = 3;
  static const char* errMsg = NULL;
  static int regno = 0;
  static int value = 0;
 
  static int state = 0;
  static char cmd = 0;
 
  // check if there's a serial byte waiting, and handle it.
  while (Serial.available() > 0) {
    int inByte = Serial.read();
    switch(state) {
      case ST_CMD:
        if (inByte == '\n' || inByte == '\0')  {
          // invalid command
          Serial.println("!No command found");
          break;
        }
        cmd = inByte;
        state = ST_REGNO;
        regno = 0;
        break;
      case ST_REGNO:
        if (inByte == '=') {
          state = ST_VALUE;
          value = 0;
          break;
        } else if (inByte >= '0' && inByte <= '9') {
          // no bounds checking. Regno can overflow
          regno = regno * 10 + inByte - '0';
        } else {
          state = ST_ERR;
          errMsg = "Expected digit or '=' in register no";
        }
        break;
      case ST_VALUE:
        if (inByte == '\r') {
          // windows box.
          break;
        } if (inByte == '\n') {
          state = ST_CMD;
          handle_command(cmd, regno, value);
          regno = 0;
          break;
        } else if (inByte >= '0' && inByte <= '9') {
          // no bounds checking. Regno can overflow
          value = value * 10 + inByte - '0';
        } else {
          state = ST_ERR;
          errMsg = "Expected digit or newline in value";
        }
        break;
      case ST_ERR:
        if (inByte == '\n') {
          Serial.print("!");
          Serial.println(errMsg);
          state = ST_CMD;
        }
        break;
    }
  }
}
thequux
 
Posts: 2
Joined: Wed Feb 06, 2013 12:57 am

Re: Blinking 3 LED's with a Processing interface

by GiantEye on Fri Feb 08, 2013 2:22 am

TQ - I can't thank you enough for hacking on this. After hooking up some test rigs with the bot, it looks like it's exactly what I need. I'm trying to come up with the smallest Processing program possible to pass commands to the Arduino board. Here's what I've been assuming the sequence of things needed to send commands looks like, but I'm missing something crucial along the way.

Code: Select all | TOGGLE FULL SIZE
import processing.serial.*;
Serial myPort;
String myVar = "p3=0";

void setup () {
    myPort = new Serial(this, Serial.list()[0], 9600);

}
 
void draw () {
    myPort.write(myVar);
}

Any suggestions?
User avatar
GiantEye
 
Posts: 10
Joined: Sat Jan 02, 2010 12:18 am
Location: NJUSA

Re: Blinking 3 LED's with a Processing interface

by thequux on Fri Feb 08, 2013 2:40 am

It's probably missing the newline at the end; you can add a newline to a string in Processing with
"\n". In other words, the declaration of "myVar" would be

Code: Select all | TOGGLE FULL SIZE
String myVar = "p3=0\n";
thequux
 
Posts: 2
Joined: Wed Feb 06, 2013 12:57 am

Re: Blinking 3 LED's with a Processing interface

by GiantEye on Sat Feb 09, 2013 12:45 am

TQ - That works perfectly. Thank you. Here's the interface I wrote. I'm getting great results.

Code: Select all | TOGGLE FULL SIZE
import controlP5.*;
import processing.serial.*;

Serial myPort;
String p1 = "p1=0\n";
String p2 = "p2=0\n";
String p3 = "p3=0\n";

ControlP5 cp5;
int myColor = color(0,0,0);

int p1slider = 0;
int p2slider = 0;
int p3slider = 0;
int Frequency = 0;
Slider abc;

void setup() {
  size(450,400);
  noStroke();
  cp5 = new ControlP5(this);

myPort = new Serial(this, Serial.list()[0], 9600);
     
  // add a vertical slider
  cp5.addSlider("p1slider")
     .setPosition(100,50)
     .setSize(200,20)
     .setRange(0,256)
     .setValue(0)
     .setDecimalPrecision(0)
     ;
     
  cp5.addSlider("p2slider")
     .setPosition(100,100)
     .setSize(200,20)
     .setRange(0,256)
     .setValue(0)
     .setDecimalPrecision(0)
     ;

  cp5.addSlider("p3slider")
     .setPosition(100,150)
     .setSize(200,20)
     .setRange(0,256)
     .setValue(0)
     .setDecimalPrecision(0)
     ;
     
  cp5.addSlider("Frequency")
     .setPosition(100,200)
     .setSize(200,20)
     .setRange(10,250)
     .setValue(50)
     .setDecimalPrecision(0)
     ;
     
  cp5.addButton("off1")
     .setValue(0)
     .setPosition(315,50)
     .setSize(30,20)
     ;
 
   cp5.addButton("off2")
     .setValue(0)
     .setPosition(315,100)
     .setSize(30,20)
     ;

   cp5.addButton("off3")
     .setValue(0)
     .setPosition(315,150)
     .setSize(30,20)
     ;

   cp5.addButton("AllOff")
     .setValue(0)
     .setPosition(315,200)
     .setSize(30,20)
     ;

   cp5.addButton("Dia1")
     .setValue(0)
     .setPosition(100,250)
     .setSize(30,20)
     ;

   cp5.addButton("Dia2")
     .setValue(0)
     .setPosition(150,250)
     .setSize(30,20)
     ;

   cp5.addButton("Dia3")
     .setValue(0)
     .setPosition(200,250)
     .setSize(30,20)
     ;

  // reposition the Label for controller 'slider'
  cp5.getController("p1slider").getValueLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("p1slider").getCaptionLabel().align(ControlP5.RIGHT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("p2slider").getValueLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("p2slider").getCaptionLabel().align(ControlP5.RIGHT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("p3slider").getValueLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("p3slider").getCaptionLabel().align(ControlP5.RIGHT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("Frequency").getValueLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
  cp5.getController("Frequency").getCaptionLabel().align(ControlP5.RIGHT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
}

void draw() {
 
  fill(150);
  rect(0,0,width,height);
 
  //cp5.getController("p1slider").setValue(0);
 
  //add diagonal buttons, off buttons, all off button
 
  String p1 = "p1=" + p1slider + "\n";
  String p2 = "p2=" + p2slider + "\n";
  String p3 = "p3=" + p3slider + "\n";
  String freq = "c=" + Frequency + "\n";
   
  if(cp5.getController("off1").isMousePressed() == true ){
  p1slider = 0;
  cp5.getController("p1slider").setValue(0);
  }
 
  if(cp5.getController("off2").isMousePressed() == true ){
  p2slider = 0;
  cp5.getController("p2slider").setValue(0);
  }
 
  if(cp5.getController("off3").isMousePressed() == true ){
  p3slider = 0;
  cp5.getController("p3slider").setValue(0);
  }
 
  if(cp5.getController("AllOff").isMousePressed() == true ){
  p1slider = 0;
  p2slider = 0;
  p3slider = 0;
  cp5.getController("p1slider").setValue(0);
  cp5.getController("p2slider").setValue(0);
  cp5.getController("p3slider").setValue(0);
  }
 
  if(cp5.getController("Dia1").isMousePressed() == true ){
  p1slider = 256;
  p2slider = 256;
  p3slider = 0;
  cp5.getController("p1slider").setValue(256);
  cp5.getController("p2slider").setValue(256);
  cp5.getController("p3slider").setValue(0);
  }
 
  if(cp5.getController("Dia2").isMousePressed() == true ){
  p1slider = 256;
  p2slider = 0;
  p3slider =256;
  cp5.getController("p1slider").setValue(256);
  cp5.getController("p2slider").setValue(0);
  cp5.getController("p3slider").setValue(256);
  }
 
  if(cp5.getController("Dia3").isMousePressed() == true ){
  p1slider = 0;
  p2slider = 256;
  p3slider = 256;
  cp5.getController("p1slider").setValue(0);
  cp5.getController("p2slider").setValue(256);
  cp5.getController("p3slider").setValue(256);
  }
   
  myPort.write(p1);
  myPort.write(p2);
  myPort.write(p3);
  myPort.write(freq);
 
}
User avatar
GiantEye
 
Posts: 10
Joined: Sat Jan 02, 2010 12:18 am
Location: NJUSA