// set this to true if your board uses an MCP23008 port ectender to allow sharing
// of the processor's pin that receives the IR receiver's signals. this is true for the 
// physics 398DLP group 1 circuit.
#define MCP23008 true

/*************************************************************************************
This program is IR_receiver.ino. 

George Gollin
University of Illinois at Urbana-Champaign

March 3, 2021

Software is for a 38 kHz TSOP38238 IR sensor and Adafruit mini remote control
(product ID 389). Pablo, Yoshi, and SuperYoshi's M0 have these.

Here is the NEC (Nippon Electric Company) IR transmission format use by various 
remote control hand sets. See
https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol

Logical '0' – a 562.5µs pulse burst followed by a 562.5µs space, 
  with a total transmit time of 1.125ms
Logical '1' – a 562.5µs pulse burst followed by a 1.6875ms space, 
  with a total transmit time of 2.25ms

When a key is pressed on the remote controller, the message transmitted consists of the following, 
in order:
  * a 9ms leading pulse burst (16 times the pulse burst length used for a logical data bit)
  * a 4.5ms space
  * the 8-bit address for the receiving device
  * the 8-bit logical inverse of the address
  * the 8-bit command
  * the 8-bit logical inverse of the command
  * a final 562.5µs pulse burst to signify the end of message transmission.

The full message takes about 68 milliseconds to transmit. The data bits are sent LSB first.

There are also "repeat codes": if the key on the remote controller is kept depressed, a 
repeat code will be issued, typically around 40ms after the pulse burst that signified 
the end of the message. A repeat code will continue to be sent out at 108ms intervals, 
until the key is finally released. 

The repeat code consists of the following, in order:

  * a 9ms leading pulse burst
  * a 2.25ms space
  * a 562.5µs pulse burst to mark the end of the space (and hence end of the transmitted repeat code).

I will use the micros() function to estimate the time of various pulse train features.

A "normal" button push generates a total of 68 edges, either 0 -> 1 or 1 -> 0: a start 
pulse (2 edges), 8 address data pulses (2 edges each), 8 inverted address data pulses 
(2 edges each), 8 command data pulses (2 edges each), 8 inverted command data pulses 
(2 edges each), and a terminating pulse (2 edges).

A repeat code will generate 4 edges.

Recognizing the pulse type:
1. both types begin with a 9 ms positive pulse.
2. A normal button push pulse train hask a low level that lasts 4.5 ms after the 9 ms pulse. 
   A repeat code pulse train has a low level that only lasts 2.25 ms after the 9 ms pulse.
3. A data 0 pulse comprises a 0.56 ms positive pulse followed by a 0.56 ms low level.
4. A data 1 pulse comprises a 0.56 ms positive pulse followed by a 1.69 ms low level.

Note that the final pulse that terminates the pulse train is needed to define the low level
duration of the final data pulse.

Let's handle the pulse trains this way. 
  First pulse: 
  Assume that the pulse train is properly formatted. For the first pulse: 
  a. Identify a candidate 9 ms start pulse by seeing that its leading edge 
  (0 -> 1 transition) comes at least 20 ms after the last 1 -> 0 edge that had 
  been detected. If the pulse isn't about 9ms long, throw an error. Then determine
  the duration of the low period after the positive pulse. If it's 4.5ms than we have
  a normal button push pulse train coming our way. If it's 2.25ms then we have a repeat code.
  If neither is the case, throw an error.

  Subsequent pulses:
  If we have a repeat code, detect the falling edge of the second pulse, return to
  the user the key code of the previous key, and quit.
  If we have a normal button push pulse train, read the next 32 pulses, as well as the 
  train-terminating final pulse. Check that the address and inverted address agree, and
  also that the command and inverted command bits agree. Return the key code to the user.

  Include a time-out check in the reading of pulses. 

*********************************************************************/

// some libraries:
#include <Wire.h>
#include <string.h>
#include <LiquidCrystal.h>
#if MCP23008
// only include this library if we have an MCP23008 on our PCB.
#include "Adafruit_MCP23008.h"
#endif

//////////////////////////// IR receiver stuff //////////////////////

// width of the high portion of the start-of-message pulse, in microseconds
const uint32_t message_start_high_width = 9000;
// width of the low portion of the start-of-message pulse, in microseconds
const uint32_t message_start_low_width = 4500;
// allowed jitter on these values
const uint32_t message_start_jitter = 100;

// width of the high portion of the true data pulse, in microseconds
const uint32_t true_data_high_width = 563;
// width of the low portion of the true data pulse, in microseconds
const uint32_t true_data_low_width = 563;
// allowed jitter on these values
const uint32_t true_data_jitter = 150;

// width of the high portion of the false data pulse, in microseconds
const uint32_t false_data_high_width = 563;
// width of the low portion of the false data pulse, in microseconds
const uint32_t false_data_low_width = 1688;
// allowed jitter on these values
const uint32_t false_data_jitter = 150;

// width of the high portion of the end-of-message pulse, in microseconds
const uint32_t end_of_message_high_width = 563;
// width of the low portion of the end-of-message pulse, in microseconds
const uint32_t end_of_message_low_width = 0;
// allowed jitter on these values
const uint32_t end_of_message_jitter = 150;

// width of the high portion of the repeat code pulse, in microseconds
const uint32_t repeat_code_high_width = 9000;
// width of the low portion of the repeat code pulse, in microseconds
const uint32_t repeat_code_low_width = 2250;
// allowed jitter on these values
const uint32_t repeat_code_jitter = 300;

// normal button-push pulse train length and number of edges
#define NORMAL_PULSE_TRAIN_LENGTH 34
#define NORMAL_PULSE_TRAIN_EDGES  68

// address bits and command bits carried by the IR transmission, LSB first
uint8_t address_bits[8];
uint8_t address_bar_bits[8];
uint8_t command_bits[8];
uint8_t command_bar_bits[8];

// store times (from micros()) and values in arrays...
uint32_t IRtime_array[NORMAL_PULSE_TRAIN_EDGES];
int IRval_array[NORMAL_PULSE_TRAIN_EDGES];

// a pointer into the array
int edge_count;

// we will use a non-interrupt timeout.
uint32_t start_time_ms;

// maximum time (milliseconds) to stay in the loop before throwing an error:
// const uint32_t time_out_ms = 75;
// const uint32_t time_out_ms = 150;
const uint32_t time_out_ms = 300;
// const uint32_t time_out_ms = 10000;

uint32_t key_code_detected;

// the remote control has buttons laid out in 7 rows and 3 columns.
#define NUMBER_OF_BUTTONS 21

// these are the codes transmitted for each button. Note that the
// format from the Adafruit device isn't quite standard: one of
// the address-bar bits is different from what it should be.
const uint8_t key_code[NUMBER_OF_BUTTONS] = 
{
  0xFF, 0xFE,  0xFD,
  0xFB, 0xFA,  0xF9,
  0xF7, 0xF6,  0xF5,
  0xF3, 0xF2,  0xF1,
  0xEF, 0xEE,  0xED,
  0xEB, 0xEA,  0xE9,
  0xE7, 0xE6,  0xE5
};

// Note that this is an array of pointers to strings, not an array of strings!
const char *key_cap[NUMBER_OF_BUTTONS] = 
{
  "vol-", "playpause","vol+",
  "setup", "up", "stop/mode", 
  "left", "enter/save", "right",
  "0/10+", "down", "back",
  "1", "2", "3",
  "4", "5", "6",
  "7", "8", "9"
};

// I had tried using an external pin-triggered interrupt to identify when 
// a button has been pushed: we are using A2 (pin 16) on an Adafruit 
// Radio Feather M0 to get the IR receiver's signals. 
// But this causes SERIOUS PROBLEMS with all sorts of other things, so let's 
// not use it ANYMORE.
#define IR_pin 13

// these may prove useful later on.
#define IR_BUTTON_ENTER 7
#define IR_BUTTON_BACKSPACE 11
#define IR_BUTTON_0 9
#define IR_BUTTON_1 12

int oldIRval, newIRval;

// flag to indicate we just saw (but haven't yet done anything with) an 
// IR pulse
bool just_saw_IR_pulse;
// hexadecimal button code, for example 0xFF 
uint8_t IR_button_code;
// key code, which runs from 0 to 20.
uint8_t IR_button_key_code;
// button code: 0 - 20 and -1 if no button.
int just_pushed_IR_button;

// one of the ways to read IR pulses is to poll the IR-receiving pin, loading
// detected button pushes into an array. Do it this way: allow the user a few 
// seconds to notice the initial request for a remote control input, then keep 
// putting successive button-presses into the array as long as another (shorter)
// time-out interval has not elapsed.

// we'll load the button IDs into an array as we get them.
#define BUTTON_ID_ARRAY_LENGTH 100

// stuff to keep track of button pushes. this is a circular array: when we hit 
// the end, we'll start loading it from the beginning again.
int IR_buttons_pushed[BUTTON_ID_ARRAY_LENGTH];
int IR_index_oldest_button;
int IR_index_most_recent_button;

// time to poll the IR receiver?
bool is_it_time_to_poll_IR;

//////////////////////////// LCD stuff //////////////////////

// initialize the LCD library by associating any needed LCD interface pins
// with the M4 pin numbers to which they are connected
const int rs = 12, en = 11, d4 = 5, d5 = 6, d6 = 9, d7 = 10;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//////////////////////////// MCP23008 I2C port expander stuff //////////////////////

// Connect pins #3, 4 and 5 of the expander to ground (address selection)
// Connect pin #6 to 5V reset disable)

Adafruit_MCP23008 mcp;

// here's which bit goes to M4_PIN13: the net (see the circuit diagram) is GPS_1_IR_0_BIT.
// Note that when 
#define GPS_1_IR_0_WHICH_BIT 7
uint8_t GPS_1_IR_0_BIT;
#define WE_ARE_WATCHING_GPS 1
#define WE_ARE_WATCHING_IR 0

// we also control a red LED from GP0.
#define RED_LED_WHICH_BIT 0
uint8_t RED_LED_BIT = 0;
#define TURN_LED_OFF 0
#define TURN_LED_ON 1

// declare which M4 pin sees the IR receiver signal. Note that there is an MCP23008
// port selector that needs to be set to feed the IR signal to the M4 pin 13, 
// instead of the GPS PPS data line. 
int GPS_PPS_pin = 13;
  
/////////////////////////////////////////////////////////////////////////



//fn/////////////////////////////////////////////////////////////////////////////////
// setup
/////////////////////////////////////////////////////////////////////////////////////

void setup() 
{
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial) {}

  Serial.println("\n************* test the IR receiver *************\n");

  // declare the GPS PPS and IR receiver pin to be an M4 input 
  pinMode(GPS_PPS_pin, INPUT);
  // pinMode(GPS_PPS_pin, INPUT_PULLUP);
  
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  delay(100);

  #if MCP23008
  // only include the following if we have an MCP23008 on our PCB.

  // set up the port extender so we can control whether the M4 will
  // see the PPS line from the GPS or from the IR device..
  setup_MCP23008();

  // blink the red led a few times.
  Serial.println("Let.s blink the red LED a few times.");
  
  // Print a message to the LCD.
  lcd.setCursor(0, 0);
  lcd.print("Blink th red LED");
  lcd.setCursor(0, 1);
  lcd.print("for two seconds ");

  blink_LEDs(2);

  // now set the GPS/IR selection line to 0 to feed the IR signal 
  // to the M4's pin 13.

  // GPS_1_IR_0_BIT = WE_ARE_WATCHING_GPS;
  GPS_1_IR_0_BIT = WE_ARE_WATCHING_IR;

  mcp.digitalWrite(GPS_1_IR_0_WHICH_BIT, GPS_1_IR_0_BIT);
#endif

  // initialize the IR remote control. we don't actually poll (or test) it here.
  setup_IR();

  // Let's give the user a chance to test the IR remote control. If we don't 
  // have a serial monitor window open, the polling function will time out. (That's OK.)
  Serial.println("Test the IR remote control now. Polling... ENTER/SAVE button to quit.");

  poll_IR_remote_control();

  Serial.println(F("End of IR remote control test."));

  lcd.setCursor(0, 0);
  //         0123456789012345
  lcd.print(F("IR remote test  "));
  lcd.setCursor(0, 1);
  lcd.print(F("all done.       "));

  // clear the time-to-poll-the-IR remote control flag.
  is_it_time_to_poll_IR = false;

}

//fn/////////////////////////////////////////////////////////////////////////////////
// loop
/////////////////////////////////////////////////////////////////////////////////////

void loop() {
  // nothing happens after setup
}

//fn////////////////////////////////////////////////////////////////////////////////////
// check_for_IR_button()
////////////////////////////////////////////////////////////////////////////////////////

int check_for_IR_button()
{
  // see if a button was pushed. return which one as an integer 0 - 20.
  // if no button was pushed, return -1.

  int return_code = -1;

  // rather than doing a pin-chamnge interrupt, we will look to see if 
  // the pin voltage has dropped upon entering here. For this to work
  // properly, we'll have to look at the pin within about 9 ms of the start 
  // of the pulse train.

  // in my circuit, IR pin is normally at +3 V when nothing is happening.
  // Let's invert this so the pulse train consists of positive pulses.
  newIRval = 1 - digitalRead(IR_pin);
  
  if(newIRval) {just_saw_IR_pulse = true;}

  // Have we detected the start of a pulse train from the IR remote control?

  if(just_saw_IR_pulse)
  {
    just_saw_IR_pulse = false;
    
    // Serial.println(F("got_IR_pulse_ISR called just now"));
  
    // Since an NEC standard IR signal comprises 34 pulses, we don't want to
    // retrigger as the next 33 pulses arrive. We'll do this by putting a delay
    // at the end of this if block.

    // call the function that reads the rest of the IR code. Since the
    // first pulse in an NEC transmission is about 9 milliseconds long,
    // the processor is fast enough to process the interrupt before the
    // pulse ends. Codes are 8-bit hex, like 0xF1. 
    IR_button_code = read_IR();

    // echo what the IR remote control sent us.
    Serial.print(F("IR transmitter button code (hex): ")); 
    Serial.print(IR_button_code, HEX);

    // figure out which button this actually was. Since I am working with a 21-button
    // controller from Adafruit, I might not understand the pulse trains from a 
    // different device.
    
    bool unknown_button = true;
  
    for(int i = 0; i < NUMBER_OF_BUTTONS; i++)
    {
      // see if the hex button code matches the code assigned to the ith button.
      if(IR_button_code == key_code[i])
      {
        // here when we have a match. echo to the serial monitor if it's still up.
        Serial.print(F(". button i = "));
        Serial.print(i);
        Serial.print(F("  (\""));
        Serial.print(key_cap[i]);
        Serial.print(F("\")"));

        return_code = i;
        unknown_button = false;

        // now put what we got into the buttons-pushed array. things are 
        // generally initialized to -1. This is a circular buffer, so it
        // will wrap around. So we will figure out where to put the button
        // number for the just-pushed button.

        // is the very first button push we are seeing?
        if(IR_index_most_recent_button < 0) 
        {
          // yes, so set the indices to point to the start of the array.
          IR_index_most_recent_button = 0;
          IR_index_oldest_button = 0;

        // have we already wrapped around once (or more)? If so, 
        // IR_index_most_recent_button will be LESS than IR_index_oldest_button 
        // by one unit.
        } else if(IR_index_most_recent_button < IR_index_oldest_button) {
          // we've wrapped around already.
          if(IR_index_oldest_button < BUTTON_ID_ARRAY_LENGTH - 1)
          {
            // As long as we aren't about to crash past the end of the array, do this:
            IR_index_most_recent_button++;
            IR_index_oldest_button++;
          } else {
            // We need to wrap around again since we're about to load the last element
            // in the array
            IR_index_most_recent_button = BUTTON_ID_ARRAY_LENGTH - 1;
            IR_index_oldest_button = 0;
          } 

        // have we, last time, filled the last element in the array? if so, wrap around.
        } else if(IR_index_most_recent_button ==  BUTTON_ID_ARRAY_LENGTH - 1) {
          IR_index_most_recent_button = 0;
          IR_index_oldest_button = 1; 
        } else {
          // this is the usual case until we wraparpiuond the first time. we don't
          // need to change the index of the oldest button push, which is (still) zero.
          IR_index_most_recent_button++;
        }

        // enter the button number (0 - 20) into the array.
        IR_buttons_pushed[IR_index_most_recent_button] = return_code;                     
        break;
      // here's the end of the block in which we have identified which button was pushed.
      }

    // here's the end of the loop over all possible buttons.
    }

    // print a message if we didn't recognize the button.
    if(unknown_button) {Serial.print(F(". button not recognized."));}
    Serial.print(F("\n"));

    // push some info to the LCD, then split.
    
    if(return_code < 0 || return_code > 9)
    {
      lcd.setCursor(12, 0);
    } else {
      lcd.setCursor(13, 0);
    }
    lcd.print(F("IR"));
    lcd.print(return_code);    

    // short delay so we don't have to muck around with repeat codes from the
    // remote control.
    delay(250);

  // end of the just_saw_IR_pulse true loop.
  }
  // all done, so split.
  return return_code;
}

//fn////////////////////////////////////////////////////////////////////////////////////
// poll_IR_remote_control()
////////////////////////////////////////////////////////////////////////////////////////

void poll_IR_remote_control()
{
  // Let's poll the IR detector to see if the user is giving us button pushes.
  // light up the red LED to indicate that it's time to do the IR thing.

  blink_LEDs(2);

  //           0123456789012345 
  lcd.setCursor(0, 0);
  lcd.print(F("polling IR rcvr:"));
  lcd.setCursor(0, 1);
  lcd.print(F("ENTER/SAVE= stop"));

  // give the user a few seconds to notice. keep accepting button pushes until 
  // an ENTER/SAVE is detected, or there's an inactivity time out.
  // millis() value when we enter, or else get a button push. 

  uint32_t entry_time_value = millis();
  uint32_t t0_value = entry_time_value;

  // how long (milliseconds) until we declare that time's up:
  uint32_t time_is_up = 1000000;

  // a counter, to keep track of the number of button pushes we've received
  int how_many_pushes = 0;

  while(millis() - t0_value < time_is_up) 
  {
    int the_button_ID = check_for_IR_button();

    // if a button push was detected, restart the clock. Also write to LCD.
    if(the_button_ID >= 0) 
    {
      t0_value = millis();
      how_many_pushes++;

      //           0123456789012345 
      lcd.setCursor(0, 0);
      lcd.print(F("detected button "));
      
      lcd.setCursor(0, 1);
      lcd.print(F("                "));
      lcd.setCursor(0, 1);
      lcd.print(key_cap[the_button_ID]);      
    }

    // if we detected an ENTER/SAVE, break out of here.
    if(the_button_ID == IR_BUTTON_ENTER) {break;}
    
  }

  Serial.print(F("In poll_IR_remote_control: buttons detected = "));
  Serial.println(how_many_pushes);
  Serial.print(F("Elapsed time (ms) in polling routine: "));
  Serial.println(millis() - entry_time_value);

}

//fn//////////////////////////////////////////////////////////////////////////////
// poll_IR()
//////////////////////////////////////////////////////////////////////////////////

int poll_IR()
{
  // poll the IR receiver to see if user has new instructions for Pablo.
  // the polling routine will time out after a few secons if the user does
  // not respond.

  lcd.setCursor(0, 0);
  lcd.print(F("just entered    "));
  lcd.setCursor(0, 1);
  lcd.print(F("poll_IR   "));

  poll_IR_remote_control();
  int return_code = 0;

  lcd.setCursor(0, 0);
  lcd.print(F("poll_IR..."));
  lcd.setCursor(0, 1);
  lcd.print(F("return =        "));
  lcd.setCursor(9, 1);
  lcd.print(return_code);  

  return return_code;
}

//fn//////////////////////////////////////////////////////////////////////////////
// read_IR()
//////////////////////////////////////////////////////////////////////////////////

uint8_t read_IR()
{
  // this is a loop (with a time-out) to read a pulse train from an IR receiver.
  // see comments above for format.

  bool debug = false;

  // we will use a non-interrupt-based timeout so record entry time now.
  start_time_ms = 0;
  oldIRval = 0;
  edge_count = 0;
  uint8_t return_code;

  // initialize the address and command arrays.
  for(int i = 0; i < 8; i++)
  {
    address_bits[i] = 0;
    address_bar_bits[i] = 0;
    command_bits[i] = 0;
    command_bar_bits[i] = 0;
  }
  // once we detect our first edge, watch for possible time out. We need to 
  // avoid excess latency in, for example, print statements.

  // read all the edges. The condition to keep on is 
  //      (1) no edges seen yet OR 
  //      (2) we haven't hit the time-put limit yet AND we haven't seen too many pulse edges. 

  while(!edge_count ||
        (millis() - start_time_ms < time_out_ms && edge_count < NORMAL_PULSE_TRAIN_EDGES))
  { 
    // Start the clock ticking when we get the first edge. the entire pulse train 
    // should arrive in about 68 ms. Here's the time, approximately, of the first edge:
    if(!edge_count) {start_time_ms = millis();}
     
    // in my circuit, IR pin is normally at +3 V when nothing is happening.
    // Let's invert this so the pulse train consists of positive pulses.
    newIRval = 1 - digitalRead(IR_pin);

    // if(debug) {Serial.print(" newIRval = "); Serial.println(newIRval);}
  
    // did the value change? store what we have.
    if(oldIRval != newIRval)
    { 
      IRtime_array[edge_count] = micros();
      IRval_array[edge_count] = newIRval;
      if(debug) 
      {
        Serial.print(" newIRval = "); Serial.println(newIRval);
        Serial.print(" oldIRval was "); Serial.println(oldIRval);
      }
      oldIRval = newIRval;      
      edge_count++;
      if(debug) {Serial.print(" edge_count = "); Serial.println(edge_count);}
    }
  }
      
  // end up here after we've read a pulse train or else timed out.  

  // with the third edge we have enough to determine if it is a normal
  // train or a repeat code. If it's a repeat code, just return the previous
  // key code. check this now.
  uint32_t dt10 = IRtime_array[1] - IRtime_array[0];
  uint32_t dt21 = IRtime_array[2] - IRtime_array[1];

  int dt10_check = abs(int(dt10 - repeat_code_high_width));
  int dt21_check = abs(int(dt21 - repeat_code_low_width));

  // is the first positive pulse a repeat code?
  if( dt10_check < repeat_code_jitter &&
      dt21_check < repeat_code_jitter )
  {
    // Serial.print(F("detected a repeat code. "));
    return_code = key_code_detected;
    return return_code;
  }

  // if we timed out, inform the user. keep in mind that we've incremented edge_count at the
  // end of the above loop, thus the < in the next line. Note that a repeat code will have
  //edge_count of 4, so don't worry about this.
  if(edge_count < NORMAL_PULSE_TRAIN_EDGES && edge_count != 4)
  {
    Serial.print(F(">>>>>>> timed out while reading IR pulse edges. edge_count = "));
    Serial.println(edge_count);
    return_code = 0x00;
    return return_code;
  }
            
  // now let's look at the individual bits we were sent. For ABSOLUTELY MYSTERIOUS 
  // REASONS I needed to insert a little bit of delay here, otherwise the program freezes
  // and the SAMD21 bootloader gets weird on me..
  delay(10);

  for(int i = 2; i < edge_count; i = i + 2) 
  {
    // note that pulse numbers go from 1 to 32 for IR address and command data bits.
    int pulse_number = i / 2;
    
    dt10 = IRtime_array[i + 1] - IRtime_array[i];
    dt21 = IRtime_array[i + 2] - IRtime_array[i + 1];

    dt10_check = abs(int(dt10 - true_data_high_width));
    dt21_check = abs(int(dt21 - true_data_low_width));
    
    if(dt21_check < int(true_data_jitter)) 
    {
      int the_bit = 1;
      if(pulse_number <= 8)
      {
        address_bits[pulse_number - 1] = the_bit;

      } else if(pulse_number <= 16)
      {
        address_bar_bits[pulse_number - 9] = the_bit;

      } else if(pulse_number <= 24)
      {
        command_bits[pulse_number - 17] = the_bit;
      
      } else if(pulse_number <= 32)
      {      
        command_bar_bits[pulse_number - 25] = the_bit;
      }
    }
    
    dt10_check = abs(int(dt10 - false_data_high_width));
    dt21_check = abs(int(dt21 - false_data_low_width));
    
    if(dt21_check < int(false_data_jitter)) 
    {
      int the_bit = 0;
      if(pulse_number <= 8)
      {
        address_bits[pulse_number - 1] = the_bit;

      } else if(pulse_number <= 16)
      {
        address_bar_bits[pulse_number - 9] = the_bit;

      } else if(pulse_number <= 24)
      {
        command_bits[pulse_number - 17] = the_bit;
      
      } else if(pulse_number <= 32)
      {      
        command_bar_bits[pulse_number - 25] = the_bit;
      }
    }   
  }  

  // calculate the key/command code here.
  key_code_detected = 0;
  int two_power = 1;
  
  for(int i = 0; i <= 7; i++)
  {
    key_code_detected = key_code_detected + command_bits[i] * two_power;
    two_power = 2 * two_power;  
  }

/*
  Serial.print(F("(hexadecimal) key code detected: ")); Serial.print(key_code_detected, HEX);

  for(int i = 0; i < NUMBER_OF_BUTTONS; i++)
  {
    if(key_code_detected == key_code[i])
    {
      Serial.print(F("  ("));
      Serial.print(key_cap[i]);
      Serial.println(F(")"));
      break;
    }
  }
*/

  return_code = key_code_detected;
  return return_code;
  
}

//fn/////////////////////////////////////////////////////////////////////////////////
// setup_IR()
/////////////////////////////////////////////////////////////////////////////////////

int setup_IR() 
{

  // Serial.println(F("inside setup_IR"));

  // set up the IR remote control.
  int return_code = 0;
  key_code_detected = 0;
  just_saw_IR_pulse = false;

  // intialize IR transmitter pulse train arrays
  
  for (int i = 0; i < NORMAL_PULSE_TRAIN_EDGES; i++)
  {
    IRtime_array[i] = 0;
    IRval_array[i] = 0;
  }

  // initialize the button array indices. no buttons yet, so -1.
  IR_index_oldest_button = -1;
  IR_index_most_recent_button = -1;

  // intialize list of pushed IR buttons.
  for (int i = 0; i < BUTTON_ID_ARRAY_LENGTH; i++)
  {
    IR_buttons_pushed[i] = -1;
  }

  // set the IR pin to be an input.
  pinMode(IR_pin, INPUT);

  // all done.
  return return_code;
}

//fn////////////////////////////////////////////////////////////////////////////////
// setup_MCP23008()
////////////////////////////////////////////////////////////////////////////////////

int setup_MCP23008() 
{

  // I use an MCP23008 port extender to decide whether the GPS PPS line
  // talks to the M4 pin 13 or the IR decoder does.

  // use default I2C address 0
  mcp.begin();


  mcp.pinMode(RED_LED_WHICH_BIT, OUTPUT);
  mcp.digitalWrite(RED_LED_WHICH_BIT, TURN_LED_OFF);

  mcp.pinMode(GPS_1_IR_0_WHICH_BIT, OUTPUT);
  mcp.digitalWrite(GPS_1_IR_0_WHICH_BIT, WE_ARE_WATCHING_IR);

  int return_code = 0;

  return return_code;
}

//fn////////////////////////////////////////////////////////////////////////////////////
// blink_LEDs()
////////////////////////////////////////////////////////////////////////////////////////

void blink_LEDs(int how_many_secs) 
{

  #if MCP23008
  // only include the following if we have an MCP23008 on our PCB.

  int delay_debug = 100;
  int delay_number =  5 * how_many_secs;

  for(int ijk = 1; ijk <= delay_number; ijk++)
  {
    RED_LED_BIT = 1;
    mcp.digitalWrite(RED_LED_WHICH_BIT, TURN_LED_ON);
    delay(delay_debug);  

    RED_LED_BIT = 0;
    mcp.digitalWrite(RED_LED_WHICH_BIT, TURN_LED_OFF);
    delay(delay_debug);  
  }
  #endif
}
