Photo-gate timer with an Arduino

From ShawnReevesWiki
Jump to navigationJump to search

Program listing

This program can be compiled for an Atmel 328P, running at 2MHz (!), paired with a Newhaven 3V i2c LCD display. It will watch for low signals on five input pins.

See Photo-gate timer for a full discussion.

//By Shawn Reeves. 2014.

//Compile in Arduino IDE. Requires PinChangeInt library that isn't part of standard install.
/*On timing:
micros() returns the number of microseconds since the Arduino board began running the current program.
micros() returns an unsigned long integer, so it overflows to zero after 4,294,967,295 µs.
millis() is similar except it counts milliseconds.
NOTA BENE:Both millis() and micros() clear interrupts while they read the timer, so use them sparingly!
*/
/*On interrupts:
The Atmega 328 only has two fully independent hardware interrupts, but it has more grouped-pin interrupts.
We use the PinChangeInt library to enable the PCINT on the Atmega328 ports.
We've programmed using switch...case so that the gates can be tripped in any order,
 but their position in the display goes by how they are hardwired.
What if two interrupts happen simultaneously?
 If two interrupts happen withing microseconds of each other, we might be processing and displaying one
 and mixing up the values.
 So, we should have an array of hit_time variables for each gate.
 This way this can be used as a race-track timer without fear of complications.
*/
#include <PinChangeInt.h>
//Assign meaningful names to the pins
/*Since we are going to cause interrupt on a change on every pin of an involved port,
let's pick fewest number of ports. If there aren't five free pins on any port, then one for the first gate
and another for the rest of the gates.
If using i2c, avoid Port C, since it is partly used for i2c with display.
If using SPI, avoid Port B.
If using UART Serial, avoid Port D.
*/
#define gate0 8   //328 pin 14
#define gate1 2   //328 pin 4
#define gate2 3   //328 pin 5
#define gate3 10  //328 pin 16
#define gate4 9   //328 pin 15
/*On LCD display
We are using an i2c LCD display from Newhaven Display.
The Wire library sends i2c commands and data.
*/
#include <Wire.h>
#define LCD_address 0x3E  //0x7C shifted right one bit
#define command_send 0  //prefix commands to the i2c LCD
#define data_send 0x40
#define line2_address 0xC0
//Declare variables to track interrupt pins:
volatile uint8_t latest_interrupted_pin = 255;//whenever it isn't 255 an interrupt has happened.
volatile unsigned long hit_Time;
unsigned long start_Time;
unsigned long this_Time;
byte first_hit;

void setup() {
  delay(100);//Wait for display to power up.
  pinMode(gate0, INPUT_PULLUP);  //Enable internal pullup resistor--Photogate circuit will drive this input low.
  pinMode(gate1, INPUT_PULLUP);  //Enable internal pullup resistor--Photogate circuit will drive this input low.
  pinMode(gate2, INPUT_PULLUP);  //Enable internal pullup resistor--Photogate circuit will drive this input low.
  pinMode(gate3, INPUT_PULLUP);  //Enable internal pullup resistor--Photogate circuit will drive this input low.
  pinMode(gate4, INPUT_PULLUP);  //Enable internal pullup resistor--Photogate circuit will drive this input low.
  PCintPort::attachInterrupt(gate0, &interrupt_function, RISING);  //Watch the first gate.
  Wire.begin(); // join i2c bus (address optional for master)
  i2c_LCD_setup();
  i2c_LCD_display_waiting();
}
void loop() {
  switch (latest_interrupted_pin){
    case 255://no interrupt since last check
      break;
    case gate0:
      start_Time=hit_Time;
      i2c_LCD_display_tripped();
      first_hit=1;latest_interrupted_pin=255;
      PCintPort::attachInterrupt(gate1, &interrupt_function, RISING);
      PCintPort::attachInterrupt(gate2, &interrupt_function, RISING);
      PCintPort::attachInterrupt(gate3, &interrupt_function, RISING);
      PCintPort::attachInterrupt(gate4, &interrupt_function, RISING);
      break;
    case gate1:
      this_Time=hit_Time - start_Time;
      if (1==first_hit){
        clear_LCD(); first_hit=0;//this is the first gate tripped. Clear the waiting msg and reset our flag.
      }
      LCD_address_go(0);
      i2c_LCD_display_time();latest_interrupted_pin=255;
      break;
    case gate2:
      this_Time=hit_Time - start_Time;
      if (1==first_hit){
        clear_LCD(); first_hit=0;//this is the first gate tripped. Clear the waiting msg and reset our flag.
      }
      LCD_address_go(1);
      i2c_LCD_display_time();latest_interrupted_pin=255;
      break;
    case gate3:
      this_Time=hit_Time - start_Time;
      if (1==first_hit){
        clear_LCD(); first_hit=0;//this is the first gate tripped. Clear the waiting msg and reset our flag.
      }
      LCD_address_go(2);
      i2c_LCD_display_time();latest_interrupted_pin=255;
      break;
    case gate4:
      this_Time=hit_Time - start_Time;
      if (1==first_hit){
        clear_LCD(); first_hit=0;//this is the first gate tripped. Clear the waiting msg and reset our flag.
      }
      LCD_address_go(3);
      i2c_LCD_display_time();latest_interrupted_pin=255;
      break;
  }
}
void interrupt_function() {
  latest_interrupted_pin=PCintPort::arduinoPin;
  hit_Time = millis();
}
void i2c_LCD_display_tripped(){
  clear_LCD();
  Wire.beginTransmission(LCD_address);
  Wire.write(data_send);
  Wire.write("1st gate tripped");
  Wire.endTransmission();
}
void i2c_LCD_display_waiting(){
  clear_LCD();
  Wire.beginTransmission(LCD_address);
  Wire.write(data_send);
  Wire.write("Waiting...");
  Wire.endTransmission();
}
void i2c_LCD_display_time(){
  this_Time = this_Time * 8;//shift since we're using 2MHz instead of 16MHz standard arduino crystal.
  //The above shift makes the milliseconds digit insignificant. For millisecond precision, use a 16MHz system
  Wire.beginTransmission(LCD_address);
  Wire.write(data_send);
  this_Time = this_Time % 100000;  //Ensure value is less than 100000
  Wire.write(48 + (this_Time / 10000));  //Print the 10000 digit
  this_Time = this_Time % 10000;  //Get rid of the 10000 digit
  Wire.write(48 + (this_Time / 1000));  //Print the 1000 digit
  Wire.write(".");
  this_Time = this_Time % 1000;  //Get rid of the 1000 digit
  Wire.write(48 + (this_Time / 100));  //Print the 100 digit
  this_Time = this_Time % 100;  //Get rid of the 100 digit
  Wire.write(48 + (this_Time / 10));  //Print the 10 digit
  /* See discussion above about precision. Uncomment if using 16MHz or precise system.
  this_Time = this_Time % 10;  //Get rid of the 10 digit
  Wire.write(48 + this_Time);  //Print the 1 digit
  */
  Wire.write("s");
  Wire.endTransmission();
}
void LCD_address_go(byte display_position){
  Wire.beginTransmission(LCD_address);
  Wire.write(command_send);
  switch (display_position){
    case 0:
      Wire.write(0x80);//data position byte is one bit followed by 7 for address
      break;
    case 1:
      Wire.write(0x88);//eighth data position, first line
      break;
    case 2:
      Wire.write(0xC0);//second line begins at 0x40 (plus 0x80 for data position command)
      break;
    case 3:
      Wire.write(0xC8);
      break;
  }
  Wire.endTransmission();
}
void i2c_LCD_setup(){
  Wire.beginTransmission(LCD_address);
  Wire.write(command_send);
  Wire.write(B00111000);  //FUNCTION SET 8 bit, 2 line, not double-height, 0, instruction set zero
  delay(20);
  Wire.write(B00111001);  //FUNCTION SET 8 bit, 2 line, not double-height, 0, instruction set one
  delay(20);
  Wire.write(B00010100);  //OSC set 1/5 bias, frequency last three bits
  Wire.write(B01111111);  //Contrast set, four least significant contrast bits
  Wire.write(B01011101);  //Power/ICON set, Icon on, booster on, two most significant contrast bits
  Wire.write(B01101101);  //FOLLOWER CONTROL, follower on, last three bits follower amp ratio
  delay(300);  //Allow new voltage to stabilize from previous command.
  Wire.write(B00111000);  //Back to instruction set zero
  Wire.write(B00001100);  //DISPLAY ON, display on, cursor off, cursor position off
  Wire.write(1);  //Clear display
  Wire.write(B00000110);  //ENTRY MODE, increment cursor, don't shift display
  Wire.endTransmission();    // stop transmitting
}
void clear_LCD(){
  Wire.beginTransmission(LCD_address);
  Wire.write(command_send);
  Wire.write(1);  //Clear display
  Wire.endTransmission();    // stop transmitting
}