/*
  6   PE4 ( OC3B/INT4 )   Digital pin 2 (PWM)         EncoderX A
  78  PA0 ( AD0 )         Digital pin 22              EncoderX B
  7   PE5 ( OC3C/INT5 )   Digital pin 3 (PWM)         EncoderY A
  76  PA2 ( AD2 )         Digital pin 24              EncoderY B
  45  PD2 ( RXDI/INT2 )   Digital pin 19 (RX1)        EncoderZ A
  74  PA4 ( AD4 )         Digital pin 26              EncoderZ B

  53  PC0 ( A8 )          Digital pin 37
  54  PC1 ( A9 )          Digital pin 36
  55  PC2 ( A10 )         Digital pin 35
  56  PC3 ( A11 )         Digital pin 34
  57  PC4 ( A12 )         Digital pin 33
  58  PC5 ( A13 )         Digital pin 32
  59  PC6 ( A14 )         Digital pin 31
  60  PC7 ( A15 )         Digital pin 30

  77  PA1 ( AD1 )         Digital pin 23
  75  PA3 ( AD3 )         Digital pin 25
  73  PA5 ( AD5 )         Digital pin 27

*/

//*******************************  Библиотеки  *******************************//
#include "Arduino.h"

#include "LedControl.h"

#include <Keypad.h>

#include <util/delay.h>
//*******************************  /Библиотеки  ******************************//

//*********************************  Макросы  ********************************//
#ifndef MC_CRITICAL_SECTION_START
  #define MC_CRITICAL_SECTION_START  unsigned char _sreg = SREG; cli();
#endif

#ifndef MC_CRITICAL_SECTION_END
  #define MC_CRITICAL_SECTION_END    SREG = _sreg;
#endif
//*********************************  /Макросы  *******************************//

//********************** Объявление переменных, констант *********************//
// Скорость Serial.
#define SERIAL_SPEED    38400

// Отправляем данные в Serial.
uint8_t enableSendDataToSerial = 1;

// Кол-во осей.
#define COUNT_AXIS       3

// Энкодер ось X линия A.
#define ENCODER_X_PIN_A  2
// Энкодер ось X линия B.
#define ENCODER_X_PIN_B  22

// Энкодер ось Y линия A.
#define ENCODER_Y_PIN_A  3
// Энкодер ось Y линия B.
#define ENCODER_Y_PIN_B  24

// Энкодер ось Z линия A.
#define ENCODER_Z_PIN_A  19
// Энкодер ось Z линия B.
#define ENCODER_Z_PIN_B  26

// Кол-во импульсов линейки.
volatile int32_t lineEncoder[COUNT_AXIS];

// Кол-во импульсов линеек.
int32_t lineEncoderValue[COUNT_AXIS];

// Предыдущее значение кол-ва импульсов линеек.
int32_t oldLineEncoderValue[COUNT_AXIS];

// Кол-во пройденных милиметров.
double encoderValue[COUNT_AXIS];

// Отображать данные да мониторе.
uint8_t enableShowDataOnLed = 1;

// Текущий экран.
uint8_t curLed = 0;

// Текущий режим работы экрана.
uint8_t curModeLed[COUNT_AXIS];

// Кол-во экранов, для отображения.
#define COUNT_DISPLAY 1

// Иницилизация 7 сегментного экрана.
LedControl ledLine = LedControl(23, 27, 25, COUNT_DISPLAY);

// Предыдущее значение обновления экрана.
uint32_t ledPreviousMillis = 0;

// Какой экран обновляем.
uint8_t showCurrentLed = 0;

// Кол-во строк клавиатуры.
#define KEYPAD_ROWS       4

// Кол-во колонок клавиатуры.
#define KEYPAD_COLS       4

// Объевление символов клавиатуры.
int8_t hexaKeys[KEYPAD_ROWS][KEYPAD_COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
// Пины строк клавиатуры.
uint8_t rowPins[KEYPAD_ROWS] = {30, 32, 34, 36};
// Пины колонок клавиатуры.
uint8_t colPins[KEYPAD_COLS] = {31, 33, 35, 37};

// Иницилизация клавиатуры.
Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, KEYPAD_ROWS, KEYPAD_COLS);

// Предыдущее значение проверки клавиатуры.
uint32_t keypadPreviousMillis = 0;
//********************* /Объявление переменных, констант *********************//

//****************************** Основные функции ****************************//
void setup()
{
  // Настройка Serial.
  Serial.begin(SERIAL_SPEED);

  // Настраиваем подключение энкодера X.
  pinMode(ENCODER_X_PIN_A, INPUT);
  pinMode(ENCODER_X_PIN_B, INPUT);
  // Включить "подтяжку".
  digitalWrite(ENCODER_X_PIN_A, HIGH);
  digitalWrite(ENCODER_X_PIN_B, HIGH);

  // Настраиваем подключение энкодера Y.
  pinMode(ENCODER_Y_PIN_A, INPUT);
  pinMode(ENCODER_Y_PIN_B, INPUT);
  // Включить "подтяжку".
  digitalWrite(ENCODER_Y_PIN_A, HIGH);
  digitalWrite(ENCODER_Y_PIN_B, HIGH);

  // Настраиваем подключение энкодера Z.
  pinMode(ENCODER_Z_PIN_A, INPUT);
  pinMode(ENCODER_Z_PIN_B, INPUT);
  // Включить "подтяжку".
//  digitalWrite(ENCODER_Z_PIN_A, HIGH);
//  digitalWrite(ENCODER_Z_PIN_B, HIGH);

  // Настраиваем прерывания для линии A энкодера Х.
  attachInterrupt(0, EncoderXPinChangeA, FALLING);
  // Настраиваем прерывания для линии A энкодера Y.
  attachInterrupt(1, EncoderYPinChangeA, FALLING);
  // Настраиваем прерывания для линии A энкодера Z.
  attachInterrupt(4, EncoderZPinChangeA, FALLING);

  // Обнуляем значения энкодеров.
  for (int k = 0; k < COUNT_AXIS; k++)
  {
    lineEncoder[k] = 0;
  }

  // Настройка 7 сегментного экрана.
  for (int k = 0; k < COUNT_DISPLAY; k++)
  {
    // Включение дисплея.
    ledLine.shutdown(k, false);
    // Регулировка яркости.
    ledLine.setIntensity(k, 5);
    // Очищаем экран.
    ledLine.clearDisplay(k);
    ledLine.setDigit(k,0,0,false);
  }

  // Создаем заголовок в Excel.
  // Очистка листа excel.
  Serial.println("CLEARDATA");
  // Заголовки столбцов.
  Serial.println("LABEL,Time,Pulse. X,Pulse. Y,Pulse. Z,X,Y,Z");

  // Немного подождем, вдруг, что-то не успело инициализировать.
  _delay_ms(1000);
}

void loop()
{
  uint8_t encoderIsChange = 0;

  for (int k = 0; k < COUNT_AXIS; k++)
  {
    MC_CRITICAL_SECTION_START
    lineEncoderValue[k]  =  lineEncoder[k];
    MC_CRITICAL_SECTION_END
  
    // Изменились ли показания энкодера.
    if (lineEncoderValue[k] != oldLineEncoderValue[k])
    {
      encoderIsChange |= (1<<(k));
      oldLineEncoderValue[k] = lineEncoderValue[k];
    }
  }

  // Значение энкодера/ов ихменился/лись.
  if (encoderIsChange > 0)
  {
    encoderValue[0] = (double)lineEncoderValue[0] * (double)0.020;
    encoderValue[1] = (double)lineEncoderValue[1] * (double)0.020;
    encoderValue[2] = (double)lineEncoderValue[2] * (double)0.020;

    if (enableSendDataToSerial)
    {
      Serial.print("DATA,TIME,");               // Запись в excel текущей даты и времени.
      Serial.print(lineEncoderValue[0]);        // Запись количество импульсов энкодера Х.
      Serial.print(",");
      Serial.print(lineEncoderValue[1]);        // Запись количество импульсов энкодера Y.
      Serial.print(",");
      Serial.print(lineEncoderValue[2]);        // Запись количество импульсов энкодера Z.
      Serial.print(",");
      Serial.print(encoderValue[0], 3);         // Запись количество мм энкодера Х.
      Serial.print(",");
      Serial.print(encoderValue[1], 3);         // Запись количество мм энкодера Y.
      Serial.print(",");
      Serial.println(encoderValue[2], 3);       // Запись количество мм энкодера Z. 
    }
  }

  if (enableShowDataOnLed)
  {
    // Отображение на мониторе.
    uint32_t ledCurrentMillis = millis();
    if (ledCurrentMillis - ledPreviousMillis >= 40 || showCurrentLed)
    {
      ledPreviousMillis = ledCurrentMillis;
    
      //showDataOnLed(showCurrentLed, lineEncoderValue[showCurrentLed], encoderValue[showCurrentLed]);
      showDataOnLed(showCurrentLed, lineEncoderValue[2], encoderValue[2]);
      
      showCurrentLed++;
      if (showCurrentLed >= COUNT_DISPLAY)
      {
        showCurrentLed = 0;
      }
    }
  }

  // Проверка нажатий кнопок на клавиатуре.
  uint32_t keypadCurrentMillis = millis();
  if (keypadCurrentMillis - keypadPreviousMillis >= 50)
  {
    keypadPreviousMillis = keypadCurrentMillis;

    int8_t customKey = customKeypad.getKey();

    // Если кнопка нажата.
    if (customKey)
    {
      switch( customKey ) 
      {
        case '1':
          if (enableSendDataToSerial)
          {
            enableSendDataToSerial = 0;  
          }
          else 
          {
            enableSendDataToSerial = 1;
          }
          break;
        case '0':
          if (enableShowDataOnLed)
          {
            enableShowDataOnLed = 0;
            ledLine.clearDisplay(0);  
          }
          else 
          {
            enableShowDataOnLed = 1;
          }
          break;
        case 'A':
          curLed = 0;
          break;
        case 'B':
          curLed = 1;
          break;
        case 'C':
          curLed = 2;
          break;
        case '*':
          curModeLed[curLed] = 0;
          //ledPreviousMillis = 0;
          break;
        case '#':
          curModeLed[curLed] = 1;
          //ledPreviousMillis = 0;
          break;
        case 'D':
          MC_CRITICAL_SECTION_START
          lineEncoder[curLed] = 0;
          MC_CRITICAL_SECTION_END

          // Очищаем экран.
          //ledLine.clearDisplay(curLed);
          ledLine.clearDisplay(0);
          break;
      } 
    }
  }
}

//****************************** /Основные функции ***************************//

//***************************** Функции прерываний ***************************//
void EncoderXPinChangeA()
{
  // Состояние вывода изменилось с высокого уровня на низкий.
  if (PINA & (1 << PA0))
  {
    // Состояние вывода изменилось с низкого уровня на высокий.
    lineEncoder[0]++;
  }
  else
  {
    // Состояние вывода изменилось с высокого уровня на низкий.
    lineEncoder[0]--;
  }
}

void EncoderYPinChangeA()
{
  // Состояние вывода изменилось с высокого уровня на низкий.
  if (PINA & (1 << PA2))
  {
    // Состояние вывода изменилось с низкого уровня на высокий.
    lineEncoder[1]++;
  }
  else
  {
    // Состояние вывода изменилось с высокого уровня на низкий.
    lineEncoder[1]--;
  }
}

void EncoderZPinChangeA()
{
  // Состояние вывода изменилось с высокого уровня на низкий.
  if (PINA & (1 << PA4))
  {
    // Состояние вывода изменилось с низкого уровня на высокий.
    lineEncoder[2]++;
  }
  else
  {
    // Состояние вывода изменилось с высокого уровня на низкий.
    lineEncoder[2]--;
  }
}
//***************************** /Функции прерываний **************************//
void showDataOnLed(uint8_t currentLed, int32_t value, double dataValue)
{
  ledLine.clearDisplay(showCurrentLed);
  
  //if (!curModeLed[currentLed])
  if (!curModeLed[2])
  {
    uint32_t lineValueTemp = abs(value);
    showLongOnLed(currentLed, lineValueTemp, 0, 255);
  }
  else 
  {
    uint16_t dataValueInteger = (uint16_t)abs(dataValue);
    double dataValueRemainder = abs(dataValue) - (double)dataValueInteger;
    dataValueRemainder += 0.0005;

    uint8_t digits = 3;
    while (digits-- > 0)
    {
      dataValueRemainder *= 10.0;
      uint8_t singleDigital = (uint8_t)dataValueRemainder;
      ledLine.setDigit(currentLed,digits,singleDigital,false);
      dataValueRemainder -= singleDigital; 
    }

    showLongOnLed(currentLed, dataValueInteger, 3, 3);
  }
  
  if(value < 0) 
  {
     ledLine.setChar(currentLed,7,'-',false);
  }
  else 
  {
     ledLine.setChar(currentLed,7,' ',false);
  }  
}

void showLongOnLed(uint8_t currentLed, uint32_t lineValueTemp, uint8_t numberDigital, uint8_t point)
{
  do 
  {
    uint32_t valueTemp = lineValueTemp;
    lineValueTemp /= 10;
    uint8_t singleDigit = valueTemp - (10 * lineValueTemp);
    ledLine.setDigit(currentLed,numberDigital,singleDigit,numberDigital==point);
    numberDigital++;
  }
  while(lineValueTemp);
}
