/*******************************************
Program                 : DC-Cal Nano
Compiler                : WinAVR
System Clock            : 1 MHz
*******************************************/
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
//******************************************
// Служебные переменные и константы
#define M_RAW         0
#define M_100mV       1
#define M_1V          2
#define M_10V_10mA    3
#define M_100V        4
#define M_1A          5
#define MODES         6
const long int DELTA[7] = {1000000,100000,10000,1000,100,10,1};
const long int MAX[MODES] = {0xFFFFF,1000000,1000000,1000000,1000000,1000000};
const long int MIN[MODES] = {0,0,0,0,0,0};
const int DOT_POS[MODES] = {7,3,1,2,3,1};
int MODE, CUR_POS;
long int BUFFER;
//******************************************
// Конфигурация калибратора, сохраняемая в EEPROM
EEMEM int64_t CFG_CAL_ZERO = 12;     
EEMEM int64_t CFG_CAL_FS[MODES] = {0xFFFFF,1014324,1014324,1014324,1014324,1014324};
//******************************************
// Текущие калибровочные константы
int64_t CAL_ZERO;     
int64_t CAL_FS[MODES];
//******************************************
// Алиасы кнопок на клавиатуре
#define k_Right     0
#define k_Up        3
#define k_Left      2
#define k_Down      1
#define k_Center    4
//******************************************
#define _KeyBufferMax 10                // Длина FIFO буфера клавиатуры
#define _MaxKeys 5                      // Общее число клавиш в устройстве
#define _MaxCycles 4                    // Число циклов опроса, после которого клавиша считается нажатой
volatile char _KeyBuffer[_KeyBufferMax];// Клавиатурный буфер нажатых клавиш
volatile int  _KeyBufferPos;            // Текущая позиция в клавиатурном буфере
volatile int  _KeyCycles[_MaxKeys];     // Массив счётчиков циклов нажатия клавиш
const unsigned char _KeyLoADC[5] = {29,76,111,146,0}; 
const unsigned char _KeyHiADC[5] = {76,111,146,184,29};
//******************************************
#define Beep_On()  TCCR1A|=(1<<COM1A0); // Разрешить изменение вывода OC1A.
#define Beep_Off() TCCR1A =TCCR1A&~(1<<COM1A0);
//******************************************
// Микрокоманды работы с сигналами DAC
#define DAC_SCLK_SET() PORTC |= 0b00000010
#define DAC_SCLK_CLR() PORTC &= 0b11111101
#define DAC_SYNC_SET() PORTC &= 0b11111110
#define DAC_SYNC_CLR() PORTC |= 0b00000001
#define DAC_SDIN_CLR() PORTC &= 0b11111011
#define DAC_SDIN(x)    PORTC = (PORTC & 0b11111011)|((x & 0b00000001)<<2)
// Адреса регистров управления DAC AD5791
#define CLRC_REG    0x300000
#define CTRL_REG    0x200000
#define DATA_REG    0x100000
#define SCTRL_REG   0x400000
// Биты регистров управления DAC AD5791
#define LDAC        1<<0
#define CLR         1<<1
#define RESET       1<<2
#define RBUF        1<<1
#define OPGND       1<<2
#define DACTRI      1<<3
#define BIN2sC      1<<4
#define SDODIS      1<<5
// Биты управления линейностью DAC AD5791
#define LINCOMP10   0b0000<<6
#define LINCOMP12   0b1001<<6
#define LINCOMP16   0b1010<<6
#define LINCOMP19   0b1011<<6
#define LINCOMP20   0b1100<<6
//******************************************
// Микрокоманды работы с LCD 0802A
#define LCD_RS_SET()  PORTD |= 0b00000100
#define LCD_RS_CLR()  PORTD &= 0b11111011
#define LCD_RW_SET()  PORTD |= 0b00001000
#define LCD_RW_CLR()  PORTD &= 0b11110111
#define LCD_E_SET()   PORTD |= 0b00000010
#define LCD_E_CLR()   PORTD &= 0b11111101
#define LCD_DB_CLR(x) PORTD &= 0b00001111
#define LCD_DB_HI(x)  PORTD = (PORTD & 0b00001111)|( x & 0b11110000)
#define LCD_DB_LO(x)  PORTD = (PORTD & 0b00001111)|((x & 0b00001111)<<4)
#define LCD_BF()      (PIND & 0b10000000)>>7
// Текстовые строки для вывода процедурой LCD_Write8
char Msg_DCCal[8]   = {' ','D','C','-','C','a','l',' '};
char Msg_Nano[8]    = {'N','a','n','o',' ','V','.','2'};
char Msg_Correct[8] = {'C','o','r','r','e','c','t',':'};
char Msg_Cal[8]     = {' ','C','a','l','i','b','r','?'};
char Msg_0V[8]      = {' ',' ',' ','0','V',' ',' ',' '};
char Msg_100mV[8]   = {' ',' ','1','0','0','m','V',' '};
char Msg_1V[8]      = {' ',' ',' ','1','V',' ',' ',' '};
char Msg_10V_10mA[8]= {'1','0','V','/','1','0','m','A'};
char Msg_100V[8]    = {' ',' ','1','0','0','V',' ',' '};
char Msg_1A[8]      = {' ',' ',' ','1','A',' ',' ',' '};
char Msg_Exit[8]    = {' ',' ','E','x','i','t',' ',' '};
char Msg_CLR[8]     = {' ',' ',' ',' ',' ',' ',' ',' '};
char Msg_Ok[8]      = {' ',' ',' ','O','k','!',' ',' '};
char Msg_Starting[8]= {'S','t','a','r','t','i','n','g'};
char Msg_Select[8]  = {'<','S','e','l','e','c','t','>'};
char* NAME[MODES]   = {Msg_Cal,Msg_100mV,Msg_1V,Msg_10V_10mA,Msg_100V,Msg_1A};
//******************************************
// Процедура ожидания готовности индикатора
//******************************************
void LCD_Wait (void)
{
    char old_PORTD = PORTD;
    char old_DDRD = DDRD;
    DDRD   = 0b00001111;
    PORTD |= 0b11110000;
    LCD_RS_CLR();
    LCD_RW_SET();
    while (1) {
      LCD_E_SET();
      _delay_us(5);
      char is_busy = LCD_BF();
      LCD_E_CLR();    
      _delay_us(1);
      LCD_E_SET();
      _delay_us(1);
      LCD_E_CLR();
      _delay_us(1);          
      if (!is_busy) break;     
    };
    DDRD  = old_DDRD;
    PORTD = old_PORTD;
}
//******************************************
// Процедура установки адреса для операций записи
//******************************************
void LCD_Addr (unsigned char a)
{
    LCD_Wait();
    LCD_RS_CLR();
    LCD_RW_CLR();
    LCD_E_SET();
    LCD_DB_HI((a|0x80));       // Set DDRAM address
    _delay_us(1);
    LCD_E_CLR();
    _delay_us(1);
    LCD_E_SET();
    LCD_DB_LO(a);
    _delay_us(1);
    LCD_E_CLR();
}
//******************************************
// Процедура выдачи команды на индикатор
//******************************************
void LCD_Cmd (unsigned char c)
{
    LCD_Wait();
    LCD_RS_CLR();
    LCD_RW_CLR();
    LCD_E_SET();
    LCD_DB_HI(c);      
    _delay_us(1);
    LCD_E_CLR();
    _delay_us(5);
    LCD_E_SET();
    LCD_DB_LO(c);
    _delay_us(1);
    LCD_E_CLR();
}
//******************************************
// Процедура выдачи символа на индикатор
//******************************************
void LCD_Write (char b)
{
    LCD_Wait();
    LCD_RS_SET();
    LCD_RW_CLR();
    LCD_E_SET();
    LCD_DB_HI(b);      
    _delay_us(1);
    LCD_E_CLR();
    _delay_us(1);
    LCD_E_SET();
    LCD_DB_LO(b);
    _delay_us(1);
    LCD_E_CLR();
}
//******************************************
// Процедура выдачи на индикатор 8 символов
//******************************************
void LCD_Write8 (char* arr)
{
    for (int i=0; i<8; i++) LCD_Write(arr[i]);
}
//******************************************
// Очистка дисплея
//******************************************
void LCD_Clear (void)
{
    LCD_Addr(00);      
    for(int j=0; j<8; j++) LCD_Write(0x20);
    LCD_Addr(0x40); 
    for(int j=0; j<8; j++) LCD_Write(0x20);
}
//****************************************** 
// Переслать 24 битное слово в AD5791
//****************************************** 
void DAC_Write (long int data)
{
    DAC_SYNC_SET();
    long int tmp = data;
    for (char i = 1; i<=24; i++) {
        DAC_SDIN(tmp>>23);
        tmp = tmp << 1;
        _delay_us(1);
        DAC_SCLK_SET();
        _delay_us(1);
        DAC_SCLK_CLR();
    };    
    DAC_SYNC_CLR();
    DAC_SCLK_CLR();
    DAC_SDIN_CLR();
}
//******************************************
// Очистить буфер нажатых клавиш
//****************************************** 
void KeyClear (void)
{
    for (int i=0; i<_KeyBufferMax; i++) _KeyCycles[i] = 0; // Очищаем клавиатурный буфер 
    for (int i=0; i<_MaxKeys; i++) _KeyBuffer[i] = 0;        // Очищаем массив флагов
    _KeyBufferPos = 0;
}
//******************************************
// Есть ли в буфере код нажатой клавиши?
//****************************************** 
unsigned char KeyPressed (void)
{
    return(_KeyBufferPos!=0);
}
//******************************************
// Получить код нажатой клавиши
//****************************************** 
unsigned char GetKey (void)
{
    if (_KeyBufferPos==0) return(0);    // Если буфер пуст, возвратить код k_Center
    else {
      char _key = _KeyBuffer[0];        // Иначе возвращаем код из буфера и смещаем его на 1 позицию
      for (int i=0; i<_KeyBufferMax-1; i++) _KeyBuffer[i] = _KeyBuffer[i+1];
      _KeyBufferPos--;
      return(_key);
    }
}
//******************************************
// Ждать нажатия и получить код нажатой клавиши
//****************************************** 
unsigned char ReadKey (void)
{
    while (!KeyPressed());    
    return(GetKey());
}
//******************************************
// Обработчик прерываний таймера для опроса клавиатуры
//****************************************** 
ISR (TIMER2_COMP_vect)
{
    ADCSR |= (1<<ADSC);                             // запускаем преобразование
    while (ADCSR & (1<<ADSC)) {};                   // не закончилось ли преобразование
    unsigned char key_raw = ADCH;     
    for (int idx=0; idx<5; idx++) {                 // перебираем массив интервалов кодов АЦП
      char key_state = (key_raw>=_KeyLoADC[idx]) && (key_raw<_KeyHiADC[idx]);// анализируем попадение в интервал
      if ((_KeyCycles[idx]==_MaxCycles) && (key_state)) {
        if (_KeyBufferPos < _KeyBufferMax) {        // если позволяет длина буфера,
          _KeyBuffer[_KeyBufferPos] = idx;          // добавляем в него код нажатой клавиши
          _KeyBufferPos++;
        };
      };
      if (!key_state) _KeyCycles[idx]=0;            // обновляем состояние клавиши
      else {
        _KeyCycles[idx]++;
        if (_KeyCycles[idx] > _MaxCycles) _KeyCycles[idx] = _MaxCycles+1;
      }
    };   
}
//******************************************
// Инициализация калибратора
//****************************************** 
void Init (void)
{
// Инициализация интерфейсов, таймера, АЦП
    DDRB  = 0b00000010;
    PORTB = 0b11111101;
    DDRD  = 0b11111111;
    PORTD = 0b00000000;
    DDRC  = 0b11011111;
    PORTC = 0b00000001;
    OCR1AH=0; OCR1AL=0x56;                  // 2000 Гц    
    TCCR1A =TCCR1A&~(1<<COM1A0);            // Запретить изменение вывода OC1A
    TCCR1B|=(1<<WGM12)|(0<<CS11)|(1<<CS10); // Режим CTC. Тактировать с делителем на 64
    TCCR1A|=(1<<FOC1A);
    ADMUX = (0<<REFS1)|(1<<REFS0)|(1<<ADLAR)|(0<<MUX3)|(1<<MUX2)|(0<<MUX1)|(1<<MUX0);
    ADCSR = (1<<ADEN)|(0<<ADSC)|(0<<ADFR)|(0<<ADIE)|(0<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
    SREG |= _BV(SREG_I);                    // Глобальное разрешение прерываний
// Инициализация LCD
    _delay_ms(1000);
    LCD_RS_CLR();
    LCD_RW_CLR();
    LCD_E_SET();
    LCD_DB_HI(0b00110000);
    _delay_us(5);
    LCD_E_CLR();
    _delay_ms(5);
    LCD_E_SET();
    LCD_DB_HI(0b00110000);
    _delay_us(5);
    LCD_E_CLR();
    _delay_ms(5);
    LCD_E_SET();
    LCD_DB_HI(0b00100000);  // 4-bit mode
    _delay_us(5);
    LCD_E_CLR();
    _delay_ms(5);
    LCD_Cmd(0b00101000);    // 2 lines, 5x8 dot
    LCD_Cmd(0b00001111);    // Display, cursor, blinking
    LCD_Cmd(0b00000110);    // Cursor/display shift
    LCD_Cmd(0b00000001);    // Display clear
// Инициализация клавиатуры
    ASSR = 0x00;                            // Инициализируем таймер 2: асинхронный режим отключен
    TCCR2 |= (0<<CS20)|(0<<CS21)|(1<<CS22); // Коэффициент предделения 64
    TCNT2 = 0x00;                           // Начальное значение 0
    OCR2 = 0xFE;                            // Регистр сравнения на максимум
    TIMSK = (1<<OCIE2);                     // Прерывание разрешено
    KeyClear();
// Инициализация DAC
    LCD_Clear();                    // Выводим заставку
    LCD_Addr(0); LCD_Write8(Msg_DCCal);
    LCD_Addr(0x40); LCD_Write8(Msg_Nano);
    _delay_ms(5000);                // Задержка на время разогрева LM399H
    Beep_On(); _delay_ms(10); Beep_Off();
    LCD_Clear();
    LCD_Addr(0); LCD_Write8(Msg_Starting);
    LCD_Addr(0x40); 
    LCD_Cmd(0b00001100);            // Временно отключаем курсор и мигание
    for (int i=0; i<8; i++) {LCD_Write(255); _delay_ms(500);}
    LCD_Cmd(0b00001111);            // Включаем курсор и мигание
    // Отключаем SDO и clamp, режим 1х с компенсацией вх. тока ОУ, линейность для ИОН<=10 В
    DAC_Write(CTRL_REG | RBUF | SDODIS | LINCOMP10 | BIN2sC);
    DAC_Write(DATA_REG | 0x0);      // Устанавливаем начальный нулевой код ЦАП
    DAC_Write(SCTRL_REG | LDAC);    // Загружаем в DAC
    DAC_Write(SCTRL_REG);
}
//******************************************
// Загрузить конфигурацию из EEPROM
//******************************************
void Config_Load (void)
{
    eeprom_read_block(&CAL_ZERO,&CFG_CAL_ZERO,8);
    eeprom_read_block(&CAL_FS,&CFG_CAL_FS,MODES*8);
}
//******************************************
// Обновить конфигурацию в EEPROM
//******************************************
void Config_Update (void)
{
    eeprom_update_block(&CAL_ZERO,&CFG_CAL_ZERO,8);
    eeprom_update_block(&CAL_FS,&CFG_CAL_FS,MODES*8);
}
//******************************************
// Обработка данных по калибровочному уравнению
//******************************************
void SetOutput (void)
{
    if (MODE==M_RAW) 
      DAC_Write(DATA_REG | BUFFER);
    else {
      int64_t tmp = (CAL_FS[MODE]-CAL_ZERO)*(int64_t)BUFFER*(int64_t)10/(int64_t)10000000+(int64_t)CAL_ZERO;
      DAC_Write(DATA_REG | tmp);
    }
    DAC_Write(SCTRL_REG | LDAC);    
    DAC_Write(SCTRL_REG);
}
//******************************************
// Вывести на LCD дисплей результат 
//******************************************
void Display (void)
{
    char BufferLCD[8];
    for (unsigned char j=0; j<=7; j++) BufferLCD[j] = 0x20;
    long int tmp = BUFFER;
    for(unsigned char j=0; j<7; j++) {
      BufferLCD[6-j] = 0x30+(tmp % 10);
      tmp /= 10;
    }
    LCD_Addr(DOT_POS[MODE]);
    LCD_Write('.');
    LCD_Addr(0);
    for(unsigned char j=0; j<7; j++) {
      if (j<DOT_POS[MODE]) 
        LCD_Addr(j);
      else 
        LCD_Addr(j+1);
      LCD_Write(BufferLCD[j]);
    }
    if (CUR_POS<DOT_POS[MODE]) LCD_Addr(CUR_POS);
    else LCD_Addr(CUR_POS+1);
}
//******************************************
// Меню для набора значения напряжения/силы тока/кода ЦАП
//******************************************
void Selector (void)
{
//    CUR_POS = 0;
    Display();
    SetOutput();
    char SelIsDone = 0;
    while (!SelIsDone) {
      char k = ReadKey();    
      switch (k) {
        case k_Center:
          SelIsDone = 1;  
        break;
        case k_Left:
          if (CUR_POS>0) CUR_POS--;
        break;
        case k_Right:
          if (CUR_POS<6) CUR_POS++;
        break;
        case k_Up:
          if (BUFFER+DELTA[CUR_POS]<=MAX[MODE]) BUFFER+=DELTA[CUR_POS];
        break;
        case k_Down:
          if (BUFFER-DELTA[CUR_POS]>=MIN[MODE]) BUFFER-=DELTA[CUR_POS];
        break;
      }
      Beep_On(); _delay_ms(2); Beep_Off();  
      Display();
      SetOutput();
    }
}
//******************************************
// Меню калибровки
//******************************************
void Calibration (void)
{
    LCD_Clear();
    LCD_Addr(0x00); LCD_Write8(Msg_Correct);
    _delay_ms(500);
    int Old_MODE = MODE;
    MODE = M_RAW;
    int pos = 0; char CalIsDone = 0;
    char k;
    while(!CalIsDone) {                      
      LCD_Addr(0x40); 
      switch (pos) {
        case 0:
          LCD_Write8(Msg_Exit);
          k = ReadKey();
          CalIsDone = (k==k_Center);
          LCD_Clear();
        break;
        case 1:
          LCD_Write8(Msg_0V);
          k = ReadKey();
          if (k==k_Center) {
            BUFFER = CAL_ZERO;
            Selector();
            CAL_ZERO = BUFFER;
            Config_Update();
            LCD_Addr(0x00); LCD_Write8(Msg_Ok); _delay_ms(1000);
          };
        break;
        default:
          LCD_Write8(NAME[pos-1]);
          k = ReadKey();
          if (k==k_Center) {
            BUFFER = CAL_FS[pos-1];
            Selector();
            CAL_FS[pos-1] = BUFFER;
            Config_Update();
            LCD_Addr(0x00); LCD_Write8(Msg_Ok); _delay_ms(1000);
          };
      };
      if (k==k_Left) pos--;
      if (k==k_Right) pos++;
      if (k==k_Up) pos--;
      if (k==k_Down) pos++;
      if (pos<0) pos = 6;
      if (pos>6) pos = 0;
      LCD_Addr(0x00); LCD_Write8(Msg_Correct);
      Beep_On(); _delay_ms(2); Beep_Off();  
    }
    Beep_On(); _delay_ms(20); Beep_Off();  
    LCD_Clear();
    MODE = Old_MODE;
    BUFFER = 0;
}
//******************************************
int main (void)
{
    Config_Load();                  // Загружаем конфигурацию из EEPROM
    cli();                          
    Init();                         // Инициализируем всё
    sei();                          // Разрешаем прерывания
    
    Beep_On(); _delay_ms(50); Beep_Off();
    MODE = M_10V_10mA;              // Начальный режим 10V_10mA
    BUFFER = MIN[MODE];
    SetOutput();
    LCD_Addr(0x40); LCD_Write8(Msg_10V_10mA);
    while(1) {                      // Основной цикл
      LCD_Addr(0x0);
      LCD_Write8(Msg_Select);
      char k = ReadKey();
      Beep_On(); _delay_ms(2); Beep_Off();
      if ((k==k_Left) && (MODE>0)) {
        MODE--; 
//        BUFFER = MIN[MODE];
      };
      if ((k==k_Right) && (MODE<MODES-1)) {
        MODE++; 
//        BUFFER = MIN[MODE];
      };
      LCD_Addr(0x40);
      if (MODE==M_RAW) {
        LCD_Write8(Msg_Cal);
        LCD_Addr(0x0);
        LCD_Write8(Msg_CLR);
      }
      else  
        LCD_Write8(NAME[MODE]);
      if ((k==k_Center)&&(MODE==M_RAW)) {
        Calibration();
//        MODE = M_10V_10mA;
//        LCD_Addr(0x40); LCD_Write8(Msg_10V_10mA);
      }
      if (MODE!=M_RAW)
        switch (k) {
          case k_Center:
            Selector();
          break;
          case k_Up:
            BUFFER = MAX[MODE];
            Selector();
          break;
          case k_Down:
            BUFFER = MIN[MODE]; 
            Selector();
          break;
        };
    }
    return 0;
}
