more CIID work: teaching binary counting

In this post I’m going to focus on the code part of our project because some other people working on pictures and videos up and I’ll link them in when they’re ready [UPDATE vimeo].

How Computers Count

Our task for our workshop with Massimo Banzi, David Mellis, and Daivd Gauthier, was to create an interactive application that people could use to learn how the binary counting system using the Arduino. After a lot of back and forth we came up with a system that involved using either a rotary encoder to set a byte or a series of button to set bits in the byte. The values would be displayed on a monitor using a Processing sketch, eight servos that rotated to show the 1 or 0 of the bit, and a series of eight lit-up decimal numbers and the user could see how the bits of a byte create the number. I was handed the task of making everything work on the electronics and code side while my team-mates built the box out of wood, cardboard, and acrylic. We used a rotary encoder for a dial, buttons with LEDs inside of them, a variety of 5V hobby servos, 8 white LEDs, and a laptop to display the Processing sketch.

Technical details: I needed the Arduino Mega, because driving 8 Servos requires 8 PWM pins, which only the Mega provides. To power the 8 servos I used an external power supply that could provide really precise voltage: 5.5V at about 1 Amp seemed did the trick. Reading the buttons and lighting up the LEDs for the decimal numbers wasn’t tough, and the for the rotary encoder I used interrupts. A few tips: for more than 3 servos, on any Arduino board, you should use an external power supply. Don’t do Serial.write() in an interrupt method. Keep your debounce code for multiple buttons as simple as possible. Mark all variables that are going to be modified in an interrupt method as volatile. Don’t use 0 and 1 so that you don’t have to disconnect the pins before uploading code to the Arduino because when the TX/RX pins are being used, the program running on the Arduino can’t be updated. When separating +5V power and PWM signals, always connect the ground of the PWM signal and the ground of the +5V, otherwise the PWM signal isn’t going to be read correctly.

Now onto the Arduino code:

// define all the servo pins,
#define SERVO_1 8
#define SERVO_2 9
#define SERVO_3 2 
#define SERVO_4 3
#define SERVO_5 4
#define SERVO_6 5
#define SERVO_7 6
#define SERVO_8 7
// define all the button pins
#define BUTTON__8 53
#define BUTTON__7 51
#define BUTTON__6 49
#define BUTTON__5 47
#define BUTTON__4 45
#define BUTTON__3 43
#define BUTTON__2 41
#define BUTTON__1 39
// define all the leds w/in the buttons
// and the LEDs, they're powered off the same pin
// to simplify things
#define BUTTON_LED_1 38
#define BUTTON_LED_2 40
#define BUTTON_LED_3 42
#define BUTTON_LED_4 44
#define BUTTON_LED_5 46
#define BUTTON_LED_6 48
#define BUTTON_LED_7 50
#define BUTTON_LED_8 52

// the two encoder pins
#define ENCODER_B 19
#define ENCODER_A 20

#include "Servo.h"

int state;
// states for the app
#define ENCODER_HAS_BEEN_UPDATED 0
#define WAITING_FOR_ENCODER_TIMEOUT 1
#define UPDATE_VAL_FROM_ENCODER 2
#define UPDATE_VAL_FROM_BTNS 3

int buttonIn[8] = {BUTTON__1, BUTTON__2, BUTTON__3, BUTTON__4, BUTTON__5, BUTTON__6, BUTTON__7, BUTTON__8}; 
int servoPin[8] = {SERVO_1, SERVO_2, SERVO_3, SERVO_4, SERVO_5, SERVO_6, SERVO_7, SERVO_8};
int buttonLEDS[8] = {BUTTON_LED_1, BUTTON_LED_2, BUTTON_LED_3, BUTTON_LED_4, BUTTON_LED_5, BUTTON_LED_6, BUTTON_LED_7, BUTTON_LED_8};

// array of servos
Servo servos[8];
// this is the final value we'll use for all the LEDs and Servo
boolean bits[8];

// to debounce all buttons
int buttonStates[8];

// encoder values, since they're going to 
volatile int timeSinceEncoderChange;
volatile int encoderValue;
volatile boolean newBits[8];

void setup()
{
  // getting started
  Serial.begin(38400);
  // set all the button pins to be inputs
  int i;
  for(i = 0; i<8; i++) {
    pinMode(buttonIn[i], INPUT);
  }
  
 for(i = 0; i<8; i++) {
    pinMode(buttonLEDS[i], OUTPUT);
    buttonStates[i] = LOW;
    buttonDebounce[i] = 0;
  }
  
  for(i = 0; i<8; i++) {
   pinMode(servoPin[i], OUTPUT);
  }
  
  for(i = 0; i<8; i++) {
    servos[i].attach(servoPin[i]);
  }
  // attach the interrupt value using CHANGE rather than HIGH/LOW
  attachInterrupt(3, doEncoder, CHANGE);  // encoder pin on interrupt 3 - pin 19
  encoderValue = 0;
  // set the inital state
  state = UPDATE_VAL_FROM_BTNS;
  
  pinMode(ENCODER_B, INPUT);
  pinMode(ENCODER_A, INPUT);
  
}

void loop()
{  
  int i, val;
  boolean hasChanged = false;
 
  if(state == UPDATE_VAL_FROM_BTNS)
  {
    val = 0;
    for(i = 0; i<8; i++) {
      if(checkButton(i))
      {
        hasChanged = true;
      }
      
    }
   // make a decimal number out of a binary number
    if(hasChanged) {
      for(i = 7; i > -1; i--) {
        val = val < < 1;
        if(bits[i]) {
          val += 1;
        }
      }
      encoderValue = val;
      // update all the servos with the bits
      for(i = 0; i<8; i++) {
      if(bits[i]) {
        servos[i].write(179);
      } else {
        servos[i].write(0);
      }
      
      Serial.print(encoderValue);
      Serial.flush();
    }
      
    }
    // update all LEDs with the bits
    for(i = 0; i<8; i++) {
      if(bits[i]) {
        digitalWrite(buttonLEDS[i], HIGH);
      } else {
        digitalWrite(buttonLEDS[i], LOW);
      }
    }
  }
  // when the encoder has been changed and it's 1 second
  // since that change
  else if(state == UPDATE_VAL_FROM_ENCODER)
  {
    
    for(i = 0; i<8; i++) {
      bits[i] = newBits[i];
      if(bits[i]) {
        digitalWrite(buttonLEDS[i], HIGH);
        servos[i].write(179);
      } else {
        digitalWrite(buttonLEDS[i], LOW);
        servos[i].write(0);
      }
    }
    
    Serial.print(encoderValue);
    Serial.flush();
    state = UPDATE_VAL_FROM_BTNS;
  }
  // encoder has just been updated, wait 500ms
  else if(state == ENCODER_HAS_BEEN_UPDATED) 
  {
    timeSinceEncoderChange = millis();
    state = WAITING_FOR_ENCODER_TIMEOUT;
  }
 // check whether we can update the encoder value yet
  else if(state == WAITING_FOR_ENCODER_TIMEOUT)
  { 
    countdownFromEncoderChange();
  }
  
  
}

void countdownFromEncoderChange()
{
  if(millis() - timeSinceEncoderChange > 1000)
  {
    state = UPDATE_VAL_FROM_ENCODER;
  }
}

void doEncoder()
{
  // encoders are: equal values = going right
  if(digitalRead(ENCODER_A) == digitalRead(ENCODER_B))
  {
    encoderValue++;
    if(encoderValue > 255) encoderValue = 0;
  }
  // unequal values == going left
  else
  {
    encoderValue--;
    if(encoderValue < 0) encoderValue = 255;
  }
  // make a binary number out of a decimal number
  int tmp;
  tmp = encoderValue & 128;
  newBits[7] = (tmp == 128);
  tmp = encoderValue & 64;
  newBits[6] = (tmp == 64);
  tmp = encoderValue & 32;
  newBits[5] = (tmp == 32);
  tmp = encoderValue & 16;
  newBits[4] = (tmp == 16);
  tmp = encoderValue & 8;
  newBits[3] = (tmp == 8);
  tmp = encoderValue & 4;
  newBits[2] = (tmp == 4);
  tmp = encoderValue & 2;
  newBits[1] = (tmp == 2);
  tmp = encoderValue & 1;
  newBits[0] = (tmp == 1);

  state = ENCODER_HAS_BEEN_UPDATED;
}

// assumes that there's no noise in the circuit
boolean checkButton(int pin)
{   
  int reading = digitalRead(buttonIn[pin]);

  // check to see if you just pressed the button 
  // (i.e. the input went from LOW to HIGH),  and you've waited 
  // long enough since the last press to ignore any noise:  

  // If the switch changed, due to noise or pressing:
  if (reading != buttonStates[pin] && reading == HIGH)
  {
    buttonStates[pin] = reading;
    bits[pin] = !bits[pin];
    return true;
  }
  
  buttonStates[pin] = reading;
  return false;

}

So, it's kind of long, but not that long and not particularly complex. The Processing sketch, on the other hand, is brutally simple:

import processing.serial.*;

Serial serialInstance;
PFont fzDig;

String received;
	
public void setup(){ 
  size(1400, 800);
  serialInstance = new Serial(this, Serial.list()[0], 38400);
  received = "";

  fzDig = loadFont("fzd255.vlw");
		
} 
	 
public void draw(){ 
  background(255);
		
  if(serialInstance.available() > 0) received = "";
   while ( serialInstance.available() > 0 ) {
    // so we'll store the value that we get from the Serial port
    received += serialInstance.readString();
    //println(received); // just for debugging
   }
  fill(0, 0, 0);
  textFont(fzDig, 500);
  text(received, 500, 450);
} 

The files are available here ciid_physcomp. Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>