Blinking 3 LED's with a Processing interface

Post here about your Arduino projects, get help - for Adafruit customers!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
gianteye
 
Posts: 10
Joined: Sat Jan 02, 2010 1:18 am

Blinking 3 LED's with a Processing interface

Post by gianteye »

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 array. Below is the code I've come up with so far:

Code: Select all

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'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?

PatrikD
 
Posts: 3
Joined: Mon Jan 28, 2013 1:17 pm

Re: Blinking 3 LED's with a Processing interface

Post by PatrikD »

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.

User avatar
gianteye
 
Posts: 10
Joined: Sat Jan 02, 2010 1:18 am

Re: Blinking 3 LED's with a Processing interface

Post by gianteye »

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?

janekm
 
Posts: 5
Joined: Wed Jan 16, 2013 9:19 am

Re: Blinking 3 LED's with a Processing interface

Post by janekm »

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.

thequux
 
Posts: 2
Joined: Wed Feb 06, 2013 1:57 am

Re: Blinking 3 LED's with a Processing interface

Post by thequux »

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

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;
    }
  }
}

User avatar
gianteye
 
Posts: 10
Joined: Sat Jan 02, 2010 1:18 am

Re: Blinking 3 LED's with a Processing interface

Post by gianteye »

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

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?

thequux
 
Posts: 2
Joined: Wed Feb 06, 2013 1:57 am

Re: Blinking 3 LED's with a Processing interface

Post by thequux »

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

String myVar = "p3=0\n";

User avatar
gianteye
 
Posts: 10
Joined: Sat Jan 02, 2010 1:18 am

Re: Blinking 3 LED's with a Processing interface

Post by gianteye »

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

Code: Select all

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);
  
}

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino”