/*
 * Library Inclusions
 */
#include "LCD.h"
#include "LiquidCrystal_I2C.h"  // use for the LCD display
#include <Adafruit_INA219.h>    // used for current sensing on scl and sda pins
  
/*
 * Definitions
 */
#define SET_INT               0             // default integer value of 0
#define RESET_INT             0             // reset the integer value to 0

#define SR_BAUD_RATE          9600          // default UART baud rate of 9600
#define SERIAL_PRINT_RATE     10            // number of milliseconds for each print to serial monitor

#define HEX_ADDRESS_PAD       (0x27)        //
#define MAX_COL_DISPLAY       20            // the number of columns of the display
#define MAX_ROW_DISPLAY       4             // the number of rows of the display

#define BEGIN_OF_BYTE         0             // beginning of the byte for reading
#define END_OF_BYTE           8             // ending of the byte of reading
#define MAX_NES_OUT           (1023.00)     // maximum output for the NES controller to move the DC motor
#define MIN_NES_OUT           (0.00)        // minimum output for the NES controller to move the DC motor
#define NES_MOVE_RATE         (10.23)       // movement rate of the NES output
#define BUTTON_UP             (0b11101111)  // the byte value for a button up press on the NES
#define BUTTON_DOWN           (0b11011111)  // the byte value for a button down press on the NES
#define BUTTON_START          (0b11110111)  // the byte value for a start button press on the NES
#define BUTTON_A              (0b11111110)  // the byte value for a button down press on the NES
#define BUTTON_B              (0b11111101)  // the byte value for a start button press on the NES

#define BUTTON_PRESS_RATE     20            // rate at which the user can press the button for input
#define BUTTON_DISALLOW       1             // stop user from sending NES inputs
#define BUTTON_ALLOW          0             // allow the user to send an NES input
#define MOTOR_RATE            10            // movement rate of the motor
#define MUL_THRESH            (5)           // multiplicative threshold to determine if the drop is within the NES range
#define OUTPUT_SCALE          (0.263)       // scale to print the degrees at which the DC motor should be at.
#define THREE_DIGITS          100           // integer value for when a number will have three digits
#define TWO_DIGITS            10            // integer value for when a number will have two digits

Adafruit_INA219 ina219;                     // define the ina219 object
 
enum arduino_pin_num { PIN_0 = 0, PIN_1, PIN_2, PIN_3, PIN_4, PIN_5, PIN_6, PIN_7 }; // enumeration for pin definitions

/*
 * Initialization
 */
LiquidCrystal_I2C lcd(HEX_ADDRESS_PAD, PIN_2, PIN_1, PIN_0, PIN_4, PIN_5, PIN_6, PIN_7); // display pin setup

float nesOutput = ( float )SET_INT;     // intitialize the output of the NES to 0
float motorPos = ( float )SET_INT;      // initialize the printing motor degree to 0
int dropPotent = SET_INT;               // initialize the drop across the potentiometer to 0
int dropPotentPin = A0;                 // this is the pin that will measure the voltage drop across the potentiometer
int targetPosition = ( float )SET_INT;  // this is the target for the motor position

float current_mA = 0;                 // current from the ina current sensor

int millisSinceSer = SET_INT;         // milliseconds since the last time we printed serial
int millisPreviousSer = SET_INT;      // previous milliseconds check

int millisSinceNES = SET_INT;         // milliseconds since we last reset the button allow
int millisPreviousNES = SET_INT;      // previous milliseconds check
boolean buttonPressed = BUTTON_ALLOW; // a latch to disallow or allow the user an input from hte NES      

byte outPWMFwd = 3;                   // this is the pin that will output a PWM to move the DC motor forward
byte outPWMRev = 5;                   // this is the pin that will output a PWM to move the DC motor backwards
byte NESData = 9;                     // this is the pin that the Data connection is connected to
byte NESLatch = 11;                   // this is the pin that the Latch (otherwise known as strobe) connection is connected to
byte NESClock = 12;                   // this is the pin that the Clock connection is connected to
byte NESButtonData;                   // This is where we will store the received data that comes from the NES Control Pad

uint32_t currentFrequency;            // define the current frequency of the ina current sencor
/*
 * Program
 */

/**************************************************
 * Name: setup()
 * Purpose: Initialize different library functions
 * and pre-set macros to allow for use in the loop
 * function
 * In Program Calls: None
 **************************************************/
void setup() {
  Serial.begin(SR_BAUD_RATE); // serial data will be sent at 9600bps
  pinMode(NESLatch, OUTPUT);  // Latch connection is an output
  pinMode(NESClock, OUTPUT);  // Clock connection is an output
  pinMode(NESData, INPUT);    // Data connection is an Input (because we need to receive the data from the control pad)

  pinMode(outPWMFwd, OUTPUT); // pin mmode for the forward motor pin
  pinMode(outPWMRev, OUTPUT); // pin mmode for the backward motor pin

  digitalWrite(outPWMFwd, LOW);                 // Initialize motor state to off
  digitalWrite(outPWMRev, LOW);                 // Initialize motor state to off
  dropPotent = analogRead( dropPotentPin );     // read the drop potential across the potentiometer
  nesOutput = ( float )dropPotent;              // set the NES output to the current drop to stop rapid movement when initializing
  targetPosition = nesOutput;                   // just change of variable name 
  motorPos = targetPosition;                    // just change of variable name
  lcd.begin(MAX_COL_DISPLAY, MAX_ROW_DISPLAY);  // setup the array of the display row and column
  lcd.setBacklightPin(3,POSITIVE);              // setup the backlight pin
  lcd.setBacklight(HIGH);                       // set the backlight on
  lcd.setCursor(3,0);                           // move the cursor to the 3rd column 0th row
  lcd.clear();                                  // clear the LCD

  ina219.begin();                     // Initialize the INA219.
  ina219.setCalibration_16V_400mA();  // To use a slightly lower 32V, 1A range (higher precision on amps):
}

/**************************************************
 * Name: loop()
 * Purpose: Loops infinitely to allow for continous
 * use of the arduino board untill power is lost
 * In Program Calls: 
 * GetNESControllerData();
 * convertNESControllerData();
 * handleComparison();
 * printPosition();
 **************************************************/
void loop(){                     
  GetNESControllerData();     // this calls the function to grab the NES control pad data and it will store it in 'NESButtonData'
  convertNESControllerData(); // this function converts the NES input to a position location
  drive();                    // drives the motors toward the targetPosition
  printPosition();            // this function prints the position info to the LCD display
}

/**************************************************
 * Name: getMotorAmps()
 * Purpose: reads in the data from the INA129 ic 
 * relating to current draw of the motor 
 * In Program Calls: printPosition
 **************************************************/
void getMotorAmps(){
  //shuntvoltage = ina219.getShuntVoltage_mV();
  //busvoltage = ina219.getBusVoltage_V();
  current_mA = ina219.getCurrent_mA(); // get the current milliAmps from the ina219
  //power_mW = ina219.getPower_mW();
  //loadvoltage = busvoltage + (shuntvoltage / 1000);
 }

/**************************************************
 * Name: GetNESControllerData()
 * Purpose: Reads the byte input from the NES controller 
 * In Program Calls: None
 **************************************************/
void GetNESControllerData(){                            
  digitalWrite(NESLatch, HIGH);                       // we need to send a clock pulse to the latch (strobe connection)
  digitalWrite(NESLatch, LOW);                        // 
  for(int x=BEGIN_OF_BYTE; x<=END_OF_BYTE; x++){      // serially transmit NES_Data to the Arduino
      bitWrite(NESButtonData,x,digitalRead(NESData)); // write one bit at a time to NESButtonData var
      digitalWrite(NESClock, HIGH);                   // Pulse the clock to feed the next bit onto the 4021 shift register
      digitalWrite(NESClock, LOW);                    // 
  }
}

/**************************************************
 * Name: convertNESControllerData()
 * Purpose: Converts the NES data to a position 
 * solution which it wants the DC motor to be at
 * In Program Calls: None
 **************************************************/
void convertNESControllerData(){
  millisSinceNES = (millis() - millisPreviousNES) + millisSinceNES; 
                                                  // add number of m-s since last millisecond check to a counter
  millisPreviousNES = millis();                   // save previous number of milliseconds
  if (millisSinceNES > BUTTON_PRESS_RATE){        // if the current number of miliseconds counter is greater than 20
    millisSinceNES = RESET_INT;                   // reset the counter
    buttonPressed = BUTTON_ALLOW;                 // allow the user to press the button again
  }
  if((NESButtonData == BUTTON_UP) 
   && ( float )nesOutput < ( MAX_NES_OUT - NES_MOVE_RATE )
   && buttonPressed == BUTTON_ALLOW){             // if the up button was pressed and we have not hit the max position yet
          buttonPressed = BUTTON_DISALLOW;        // register that the button has been pressed
          nesOutput = nesOutput + NES_MOVE_RATE;  // add 2.7 degrees to the position
        }
  if((NESButtonData == BUTTON_DOWN)
   && ( float )nesOutput > ( MIN_NES_OUT + NES_MOVE_RATE )
   && buttonPressed == BUTTON_ALLOW){             // if the down button was pressed and we have not hit the min position yet
        buttonPressed = BUTTON_DISALLOW;          // register that the button has been pressed
        nesOutput = nesOutput - NES_MOVE_RATE;    // remove 2.7 degrees to the position
  }
  if((NESButtonData == BUTTON_START)
   && ( float )nesOutput > ( MIN_NES_OUT + NES_MOVE_RATE )
   && buttonPressed == BUTTON_ALLOW){
    targetPosition = nesOutput;                   // latch the value from the nes controller to the target position  
  }
  if((NESButtonData == BUTTON_A)
   && buttonPressed == BUTTON_ALLOW){
    nesOutput = 0;                                // set the NES output to 0
    targetPosition = 0;                           // set the target position to 0
  }
  if((NESButtonData == BUTTON_B)
   && buttonPressed == BUTTON_ALLOW){
    nesOutput = MAX_NES_OUT;                      // set the NES output to 1023
    targetPosition = MAX_NES_OUT;                 // set the target position to 1023
  }
}

/**************************************************
 * Name: drive()
 * Purpose: drives the motors toward the target  
 * In Program Calls: None
 **************************************************/
void drive(){

  digitalWrite(outPWMFwd, LOW);                   // write low out to the motor forward pin
  digitalWrite(outPWMRev, LOW);                   // write low to the motor reverse pin
  printSerial();                                  // print data to serial
  
  while(dropPotent > (targetPosition + ( NES_MOVE_RATE * MUL_THRESH ))){  // while the drop across the potentiometer is 
                                                                          // greater than the target position
    digitalWrite(outPWMFwd, HIGH);                                        // write high to the motor forward pin
    printSerial();                                                        // print data to the serial
    dropPotent = analogRead(dropPotentPin);                               // get the drop across the potentiometer
    printPosition();                                                      // print data to the LCD
  }
  while(dropPotent < (targetPosition - ( NES_MOVE_RATE * MUL_THRESH ))){  // while the drop across the potentiometer is
                                                                          // less than the target position
    digitalWrite(outPWMRev, HIGH);                                        // write high to the motor reverse pin
    printSerial();                                                        // print data to the serial
    dropPotent = analogRead(dropPotentPin);                               // get the drop across the potentiometer
    printPosition();                                                      // print data to the LCD
  }
  
  digitalWrite(outPWMFwd, LOW);                           // write low to the motor forward pin
  digitalWrite(outPWMRev, LOW);                           // write low to the motor reverse pin
}

/**************************************************
 * Name: printSerial();
 * Purpose: Handles the PWM output for moving the
 * servo for when the NES controller sends an input
 * In Program Calls: None
 **************************************************/
void printSerial()
{
  getMotorAmps();                                           // get the current across the motor
  millisSinceSer = (millis() - millisPreviousSer) + millisSinceSer;  
                                                            // add number of m-s since last millisecond check to a counter
  millisPreviousSer = millis();                             // save previous number of milliseconds
  if (millisSinceSer > SERIAL_PRINT_RATE){                  // if the current number of m-s is more than out alloted time
    millisSinceSer = RESET_INT;                             // reset the milliseconds since our last check
    Serial.print(dropPotent);                               // print the drop across the potentiometer w/ newline
    Serial.print(' ');                                      // print a blank space for tokenizing
    Serial.print(nesOutput);                                // print the NES output
    Serial.print(' ');                                      // print a blank space for tokenizing
    Serial.print(current_mA);                               // print the current across the motor
    Serial.println();                                       // print a newline to allow for easy differentiation
  }
}

/**************************************************
 * Name: printPosition()
 * Purpose: Prints information to LCD display
 * In Program Calls: None
 **************************************************/
void printPosition(){

    dropPotent = analogRead(dropPotentPin); // read the drop across the potentiometer
    motorPos = (dropPotent*OUTPUT_SCALE);   // scale the counter value
    getMotorAmps();                         // get the amps from the motor

    lcd.setCursor(1,0);                     // move the cursor to the 5th column 3rd row
    lcd.print("Target ");                   // print the message "Target " to the LCD
    lcd.print(targetPosition*OUTPUT_SCALE); // print the target position multiplied by the scale to the LCD
    
    lcd.setCursor(5,1);                     // move the cursor to the 5th column 3rd row
    lcd.print(motorPos);                    // print the motor position
    lcd.print(" degrees");                  // print the message " degrees" to the LCD
  
  if(motorPos < ( float )THREE_DIGITS){     // if the motor position value is less than three digits
    lcd.setCursor(18,1);                    // move the cursor to the 18th column 3rd row
    lcd.print(" ");                         // replace the character there with a space
  }
  if(motorPos < ( float )TWO_DIGITS){       // if the motor position value is less than two digits
    lcd.setCursor(17,1);                    // move the cursor to the 17th column 3rd row
    lcd.print(" ");                         // replace the character there with a space
  }

    lcd.setCursor(1,2);                     // move the cursor to the 1st column 2nd row
    lcd.print("miliamps: ");                // print the message "miliamps: " to the LCD
    lcd.print(current_mA);                  // print the miliamps to the LCD

    lcd.setCursor(1,3);                     // move the cursor to the 1st column 3rd row
    lcd.print("NES Output: ");              // print the message "miliamps: " to the LCD
    lcd.print(nesOutput);                   // print the nes output to the LCD
}
