Sunday, 25 March 2012

My DIY Arduino MIDI controller - 4. Finding more pins, part II + LDR Calibration

What I want to do at this point is to develop an Arduino sketch that allows me to:

  • Read from sensors and display the result on a serial monitor
  • Use a tact switch for each sensor to toggle between 3 different modes: continuous reading, freeze (so that I can take my hand off the LDR and its value remains the same), and LDR calibration, because I want to be able to use the controller under various ambient light conditions. To prevent accidental calibration, the switch will be have to be hold down for 1/2 second. Shorter presses will toggle between continuous reading and freeze.

I started by writing a short sketch that allows the user to toggle between 3 modes: reading, calibrating and serial out, for just one LDR, no matter how long the switch was pressed, it was just cycling through the different modes.

During the calibration mode, the LDR was read and its minimum and maximum values were stored until the user changed the mode. This is done by simply comparing the current reading to the minimum value, and each time the reading is lower than the minimum, the minimum is set to the current reading. Same logic for max.

The reading and serial out modes worked the same but used different displays: the value of the LDR was read, then mapped to a 0-255 range according to the calibration values. In reading mode, the reading was sent to an LED through a PWM pin. In serial out mode, it was sent to the serial monitor.

Things started to get a lot more complicated when I decided to add in the same time the freeze function (measuring as well the time each switch was pressed for entering calibration mode instead of just cycling through the 3 modes), and several sensors through a 4051 multiplexer. The code was well thought (for a beginner like me) but I stupidily forgot to set all the pin modes in the setup which caused some weird errors. Had to check the circuit with a voltmetre and spent numerous hours (I swear) trying to debug the code... Note to self: always check the basic stuff before checking the complex parts of a code!

This is the circuit:

The blue wires go to the select pins of the 4051. The orange wires go to the switches (for which I'm using a pull-down resistor). The yellow wires go to the LEDs through 220Ω resistors. The green wires connect the sensors (a tact switch, a LDR and a pot) to the 4051 y0, y1 and y2 pins. There is another green wire that connects the 4051 output to the Arduino analog pin 0.


And the final code, which I tried to comment as much as possible. This is not supposed to be a tutorial but if my efforts can help somebody else, I'll be happy:

/*
    This script calibrates and reads from sensors using a 4051
*/

// switch & sensor management
const int sensors_number = 3;   // number of sensors to use
boolean last_switch_state[] = {0, 0, 0};
boolean current_switch_state[sensors_number];
int sensor_min[] = {0, 0, 0};      // default min value
int sensor_max[] = {1023, 1023, 1023};// default max value
int sensor_value[sensors_number];            // needs to be global to work with switch{} below
int output_sensor_value[sensors_number];     // needs to be global if we want to freeze it
int last_sensor_value[sensors_number];       // because we will only send data if there is a change in the reading

// time management
long debounce_millis[sensors_number];
int debounce_threshold = 50;    // above 20ms the reading will be considered valid
long high_press_timer[sensors_number];
int calibration_trigger = 500;  // a 500ms press will activate calibration mode
int startup_delay = 1500;       // ms to wait at startup

//  LED management
long led_timer[sensors_number];
int led_blinking_time = 300;    // blinking time set to 300ms
boolean led_state[] = {0, 0, 0};  // default LED state

// modes
/* 0 = reading
   1 = freeze
   2 = calibration */
int sensor_mode[] = {0, 0, 0};

// Arduino pins
int switchPin[] = {2, 3, 4};
int ledPin[] = {8, 12, 13};
int commonPin = 0;       // the pin we will read from, connected to the 4051
int s0 = 5;              // these following 3 are connected to the 4051 select pins
int s1 = 6;
int s2 = 7;

// the bits we will write to select the 4051 to read from
boolean r0;
boolean r1;
boolean r2;

void setup() {
  // pins setting
  for (int i = 0; i < sensors_number; i++) {
    pinMode(ledPin[i], OUTPUT);
    pinMode(switchPin[i], INPUT);
  }
  pinMode(s0, OUTPUT);
  pinMode(s1, OUTPUT);
  pinMode(s2, OUTPUT);

  // initialize serial communication
  Serial.begin(9600);
  Serial.println(sensor_mode[0]);
  
  // LED check and delay before reading switches
  for (int i = 0; i < sensors_number; i++) {
    digitalWrite(ledPin[i], HIGH);
  }
  delay(startup_delay);
  for (int i = 0; i < sensors_number; i++) {
    digitalWrite(ledPin[i], led_state[i]);
  }
}


void loop() {
  
  // we will repeat the whole thing once per sensor
  for (int i = 0; i < sensors_number; i++) {
  
    // these bits are sent to the 4051 to tell it which of its pins we want to read
    // according to the value of 'i'
    r0 = bitRead(i,0); // we convert the sensor number into a 3 bit number
    r1 = bitRead(i,1); // if 0: r0 = 0, r1 = 0, r2 = 0
    r2 = bitRead(i,2); // if 1: r0 = 1, r1 = 0, r2 = 0
    digitalWrite(s0, r0);
    digitalWrite(s1, r1);
    digitalWrite(s2, r2);
  
    /*****************************************
        READING SWITCH AND SELECTING MODES
    *****************************************/
  
    // read switch
    current_switch_state[i] = digitalRead(switchPin[i]);
  
    // if state changed
    if (current_switch_state[i] != last_switch_state[i])
      debounce_millis[i] = millis();

    // if the counter is above the debouncing threshold
    if ((millis() - debounce_millis[i]) > debounce_threshold) {
       
      // if switch is HIGH (pressed)
      if (current_switch_state[i] == HIGH) {
        // if high press timer not set, set it
        if (high_press_timer[i] == 0) high_press_timer[i] = millis();
        //  if it is set, compare to calibration activation threshold. If over (and mode not already 2):
        else if ((millis() - high_press_timer[i] >= calibration_trigger) && (sensor_mode[i] != 2)) {
          // reset previous end values
          sensor_min[i] = analogRead(commonPin);
          sensor_max[i] = analogRead(commonPin);
          // activate calibration mode
          sensor_mode[i] = 2;
          // start led blink timer
          led_timer[i] = millis();
          led_state[i] = -led_state[i] + 1;  // toggle led_state to show we're entering calibration mode
        }
      }

      // if switch is LOW (released)
      else {
        // if we had a short press
        if (  millis()-high_press_timer[i] < calibration_trigger
              && millis()-high_press_timer[i] > 0) {
          // go from calibration to freeze mode if applicable
          if (sensor_mode[i] == 2) sensor_mode[i] = 1;  // will be toggled to 0 in the next line
          // and toggle anyway (so eventually we get from calibration to continuous reading mode)
          sensor_mode[i] = -sensor_mode[i] + 1;
          led_state[i] = sensor_mode[i];  // so if mode = 0, led is off, and if mode = 1 led is on
        }
        // we then reset the high press timer to enable calibration mode again
        high_press_timer[i] = 0;
      }
    }
  
    // save switch state
    last_switch_state[i] = current_switch_state[i];
  
    /***********
        MODES
    ***********/

    switch (sensor_mode[i]) {
  
      // continuous reading mode
      case 0:
        // read sensor
        sensor_value[i] = analogRead(commonPin);
        // in case the value is beyond the min or max
        sensor_value[i] = constrain(sensor_value[i], sensor_min[i], sensor_max[i]);
        // then map to calibration values
        output_sensor_value[i] = map(sensor_value[i], sensor_min[i], sensor_max[i], 0, 255);
    
      // freeze and continuous reading modes
      case 1:
        //Serial.println(String(i) + ": " + String(output_sensor_value[i]));
        Serial.print(String(i) + ": " + output_sensor_value[i]);
        if (i == sensors_number - 1) Serial.println();
        else Serial.print(" --- ");
        break;

      // calibration mode    
      case 2:
        // read sensor
        sensor_value[i] = analogRead(commonPin);
        // store new values if applicable
        if (sensor_value[i] < sensor_min[i]) sensor_min[i] = sensor_value[i];
        if (sensor_value[i] > sensor_max[i]) sensor_max[i] = sensor_value[i];
        // print current ends
        int p0 = r0;
        int p1 = r1;
        int p2 = r2;
        Serial.println(String(i)+" ("+String(p0)+String(p1)+String(p2)+") "+"> Current: "+String(sensor_value[i])+" - Min: " + String(sensor_min[i]) + " - Max: " + String(sensor_max[i]));
        // toggle the led if the time has come
        if (millis() - led_timer[i] > led_blinking_time) {
          led_state[i] = -led_state[i] + 1;
          // and reset the timer for the next blink
          led_timer[i] = millis();
        }
      
    }  // endswitch
  
    // print mode
    digitalWrite(ledPin[i], led_state[i]);
    
  }  // endfor
}

Oh, and I made small wire jumpers! That looks so much sexier!



<< Previous: 3. Finding more pins, part I: the 4051

2 comments: