Search code examples
arraysif-statementimage-processingarduinoprocessing

Can I make an image sequence in processing move/play with an Arduino ultrasonic sensor?


This is my first post so I apologize in advance if anything is unclear and I will try to learn quickly. I am also a newbie when it comes to programming. Using Arduino Uno, Ultrasonic Sensor HC-SR04, Processing3.5.3

In the processing and Arduino sketches I have provided I am able to play an image sequence, and when the ultrasonic sensor picks up the distance of an object a "1" is printed in the processing console.

I am wondering if I can use this "1" to make an if statement. If the console prints a number larger than 0, then have the image sequence play--else, only one image will be drawn (The gif will pause). I have tried a couple versions of this, but I don't want to pretend like I know what I am doing.

Any leads for me to follow would be wonderful! Or tutorials online!

I feel like there is something incredibly simple that I am just missing... I guess nothing is ever that simple. Thank you for your time :))

ARDUINO CODE:

#define trigPin 9
#define echoPin 10

void setup() {
  Serial.begin (9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop() {
  long distance;
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);           //CHANGE THIS??
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  distance = pulseIn(echoPin, HIGH);

  if (distance <= 2000) { //raise this number to raise the distance to wave hand
    Serial.print("1");
  } else {
    Serial.print("0");
  }
  delay(500);  
}

PROCESSING

import processing.serial.*;

int numFrames = 22; //number of frames in animation
int currentFrame = 0;
PImage[] image = new PImage[22];
Serial myPort;
String instring = "";

void setup() {
  myPort = new Serial(this, Serial.list()[1], 9600);
  myPort.bufferUntil('\n');
  size(1600, 900);
  frameRate(30);
  background(0);
  
  //Load images below
  for(int i = 0; i<image.length; i++) {
    image[i] = loadImage("PatMovingFace" + i + ".png");
  }
}

void draw() {
  //ALL BELOW connects to arduino sensor
  while (myPort.available () > 0) {
    instring = myPort.readString();
    println(instring);
    int sensorAct = Integer.parseInt(instring, 10);
  }
  playGif();   
}

// ALL BELOW will make the pic array animate! Image moves! 

void playGif() {
  currentFrame = (currentFrame+1) % numFrames; //use % to cycle through frames!
  int offset = 0;

  for (int i = 0; i < image.length; i++) {
    //image(image[i] % numFrames), 0, 0);
    image(image[(currentFrame + offset) % numFrames], 0, 0);
    offset += 0;
    image(image[(currentFrame + offset) % numFrames], 0, 0);
    offset+= 0;
  }
}

Solution

  • You could simpify / cleanup playGif() first:

    • offset doesn't seem to do anything at the moment (it "increments" from 0 to 0)
    • image() is called twice with the same coordinates overdrawing the same content on top of itself. once image() call should do
    • you probably don't need the for loop since you're drawing one frame at a time
    • currentFrame is increment to the next frame and loops back to the start at the start of playGif(), however, when rendering the image, the array index is incremented by offset (0) again. it's unclear what the intention is. you can do without
    • currentFrame can control play/pause: if it increments it plays, otherwise it's paused. it's good to have a separation between updating data (currentFrame) and rendering with updated data (image(image[currentFrame],0,0))
    • you could also rename the image array to images so it's more representative of what it is and it's harder to get confused between that and the image() function

    For example playGif() could turn to:

    void playGif(){
          currentFrame = (currentFrame+1) % numFrames; //use % to cycle through frames!
          image(image[currentFrame], 0, 0);
    }
    

    Regarding control using Arduino, as mentioned above, simply check if you get a "1" to update currentFrame (otherwise it will stay(paused) at the same value):

    void playGif(){
          if(instring.equals("1")){
             currentFrame = (currentFrame+1) % numFrames; //use % to cycle through frames!
          }
          image(image[currentFrame], 0, 0);
    }
    

    I'm using equals() above, because it's a String, even though you are sending a single character. (alternatively comparing the first character would've worked if(instring.charAt(0) == '1') )

    I have a few notes on that as well:

    • in the Arduino code you use print(), not println(), which means no \n will be sent, hence no need for myPort.bufferUntil('\n'); nor instring = myPort.readString();
    • you can read a single character with myPort.read(); (which you can by the way compare with == (instead of String's equals()) (e.g. if(myPort.read() == '1'){...)
    • while is blocking and I recommend not using it. It won't make a huge difference in your case since you're sending a single byte, but on more complex programs that send more bytes this will block Processing from rendering a single frame until it's received all the data

    Here's an example:

    import processing.serial.*;
    
    int numFrames = 22; //number of frames in animation
    int currentFrame = 0;
    PImage[] images = new PImage[numFrames];
    Serial myPort; 
    
    void setup() {
      myPort = new Serial(this, Serial.list()[1], 9600);  
      
      size(1600, 900);                                    
      frameRate(30);                                      
      background(0);
    
      //Load images below
      for (int i = 0; i < numFrames; i++)
      { 
        images[i] = loadImage("PatMovingFace" + i + ".png");
      }
    }
    
    void draw() {
      // if there's at least one byte to read and it's '1'
      if(myPort.available() > 0 && myPort.read() == '1'){
        // increment frame, looping to the start
        currentFrame = (currentFrame+1) % numFrames; //use % to cycle through frames!
      }
      // render current frame
      image(images[currentFrame], 0, 0);
    }
    

    A more cautious version, checking for what coould go wrong (serial connection, data loading) would look like this:

    import processing.serial.*;
        
    int numFrames = 22; //number of frames in animation
    int currentFrame = 0;
    PImage[] images = new PImage[numFrames];
    Serial myPort; 
    
    void setup() {
      String[] ports = Serial.list();
      int portIndex = 1;
      if(ports.length <= portIndex){
        println("serial ports index " + portIndex + " not found! check cable connection to Arduino");
        println("total ports: " + ports.length);
        printArray(ports);
        exit();
      }
      try{
        myPort = new Serial(this, ports[portIndex], 9600);  
      }catch(Exception e){
        println("error connecting to serial port: double check the cable is connected and no other program (e.g. Serial Monitor) uses this port");
        e.printStackTrace();
      }  
      size(1600, 900);                                    
      frameRate(30);                                      
      background(0);
    
      //Load images below
      try{
        for (int i = 0; i < numFrames; i++)
        { 
          images[i] = loadImage("PatMovingFace" + i + ".png");
        }
      }catch(Exception e){
        println("image loading error");
        e.printStackTrace();
      }
    }
    
    void draw() {
      // if Arduino connection was successfull
      if(myPort != null){
        // if there's at least one byte to read and it's '1'
        if(myPort.available() > 0 && myPort.read() == '1'){
          // increment frame, looping to the start
          currentFrame = (currentFrame+1) % numFrames; //use % to cycle through frames!
        }
      }else{
        text("serial port not initialised", 10, 15);
      }
      
      if(images[currentFrame] != null){
        // render current frame
        image(images[currentFrame], 0, 0);
      }else{
        text("serial port not loaded", 10, 15);
      }
    }
    

    or with the same thing in grouped using functions:

    import processing.serial.*;
    
    int numFrames = 22; //number of frames in animation
    int currentFrame = 0;
    PImage[] images = new PImage[numFrames];
    
    final int SERIAL_PORT_INDEX = 1;
    final int SERIAL_BAUD_RATE  = 9600;
    Serial myPort; 
    
    void setup() {
      size(1600, 900);                                    
      frameRate(30);                                      
      background(0);
      
      setupArduino();
      //Load images below
      loadImages();
    }
    
    void setupArduino(){
      String[] ports = Serial.list();
      int numSerialPorts = ports.length;
      // optional debug prints: useful to double check serial connection 
      println("total ports: " + numSerialPorts);
      printArray(ports);
      // exit if requested port is not found  
      if(numSerialPorts <= SERIAL_PORT_INDEX){
        println("serial ports index " + SERIAL_PORT_INDEX + " not found! check cable connection to Arduino");
        //exit();
      }
      // try to open port, exit otherwise
      try{
        myPort = new Serial(this, ports[SERIAL_PORT_INDEX], SERIAL_BAUD_RATE);  
      }catch(Exception e){
        println("error connecting to serial port: double check the cable is connected and no other program (e.g. Serial Monitor) uses this port");
        e.printStackTrace();
        //exit();
      }
    }
    
    void loadImages(){
      try{
        for (int i = 0; i < numFrames; i++)
        { 
          images[i] = loadImage("PatMovingFace" + i + ".png");
        }
      }catch(Exception e){
        println("image loading error");
        e.printStackTrace();
        //exit();
      }
    }
    
    void serialUpdateImage(){
      // if Arduino connection was successfull
      if(myPort != null){
        // if there's at least one byte to read and it's '1'
        if(myPort.available() > 0 && myPort.read() == '1'){
          // increment frame, looping to the start
          currentFrame = (currentFrame+1) % numFrames; //use % to cycle through frames!
        }
      }else{
        text("serial port not initialised", 10, 15);
      }
    }
    
    void displayCurrentImage(){
      if(images[currentFrame] != null){
        // render current frame
        image(images[currentFrame], 0, 0);
      }else{
        text("image " + currentFrame + " not loaded", 10, 25);
      }
    }
    
    void draw() {
      
      serialUpdateImage();
      
      displayCurrentImage();
    }