//----------------------------------------------------------------------------

//Класс поддержки внешней EEPROM

//----------------------- Используемые ресурсы: ------------------------------

//Используется внешняя микросхема EEPROM типа 24С16, которая подключена
//к пинам SCL (PB8), SDA (PB9). Интерфейс I2C эмулируется программно.
//К сожалению, аппаратный I2C1 (remap) использовать нельзя,
//так как он конфликтует с SPI1 (remap): на выводе PB5
//всегда единица (см. errata).
//Для формирования таймингов шины используется таймер TIM16.
//При записи адреса производится проверка ответа ASK.
//Если ответа нет, производятся повторные попытки записи адреса.
//Если ответ не получен в течение времени таймаута EEPROM_WRTM
//(это максимальное время записи в EEPROM), то возвращается признак ошибки.

//----------------------------------------------------------------------------

#include "main.hpp"
#include "i2csw.hpp"
#include "eeprom.hpp"
#include "eemap.hpp"

//----------------------------------------------------------------------------
//----------------------------- Класс TEEPROM: -------------------------------
//----------------------------------------------------------------------------

//------------------------------ Конструктор: --------------------------------

TEeprom::TEeprom(void)
{
  TI2Csw::Init();
  Error = 0;
  SelectPreset(0);
}

//------------- Запись адреса с ожиданием готовности EEPROM: -----------------

//addr - адрес слова.
//Возвращает true если обнаружен ответ EEPROM

bool TEeprom::SetAddress(uint8_t pre, uint16_t addr)
{
  bool ask;
  uint16_t a = (uint16_t)pre * EE_PRE_SIZE + addr;
  ByteAddress = (a << 1) & 0xFE;
  PageAddress = (a >> 6) & 0x0E;
  TSysTimer::TimeoutStart_ms(EEPROM_WRTM);
  do
  {
    TI2Csw::Start();
    ask = TI2Csw::Write(I2C_ADDR | PageAddress);
  }
  while(!ask && !TSysTimer::TimeoutOver_ms());
  if(ask)
  {
    TI2Csw::Write(ByteAddress);
  }
  else
  {
    TI2Csw::Stop();
  }
  return(ask);
}

//----------------------------------------------------------------------------
//------------------------- Интерфейсные методы: -----------------------------
//----------------------------------------------------------------------------

//------------------------- Выбор номера пресета: ----------------------------

//Все операции записи-чтения будут выполняться только внутри секции EEPROM,
//принадлежащей данному пресету. Валидность устанавливается или сбрасывается
//индивидуально для каждой секции.

void TEeprom::SelectPreset(uint8_t p)
{
  if(p < EE_PREN)
  {
    Preset = p;
    fValid = (Rd16(EE_SIGN) == EE_SIGNATURE);
  }
}

//---------------------- Чтение данных из EEPROM: ----------------------------

//addr - адрес слова в секции EEPROM текущего пресета.
//Возвращает слово данных из EEPROM для текущего пресета.

uint16_t TEeprom::Rd16(uint16_t addr)
{
  if(SetAddress(Preset, addr))
  {
    TI2Csw::Stop();
    TI2Csw::Start();
    TI2Csw::Write(I2C_ADDR | PageAddress | I2C_RD);
    uint8_t data_l = TI2Csw::Read(I2C_ACK);
    uint8_t data_h = TI2Csw::Read(I2C_NACK);
    return(WORD(data_h, data_l));
  }
  return(0);
}

//---------------------- Запись данных в EEPROM: -----------------------------

//addr - адрес слова в секции EEPROM текущего пресета.
//data - слово данных для записи в секцию EEPROM.

void TEeprom::Wr16(uint16_t addr, uint16_t data)
{
  if(SetAddress(Preset, addr))
  {
    TI2Csw::Write(LO(data));
    TI2Csw::Write(HI(data));
    TI2Csw::Stop();
  }
}

//----------------- Чтение данных с проверкой валидности: --------------------

//addr - адрес слова в секции EEPROM текущего пресета.
//def - значение по умолчанию.
//Возвращает слово данных из EEPROM для текущего пресета.
//Если секция EEPROM не валидна, или при чтении произошла ошибка,
//возвращает значение def.

uint16_t TEeprom::Read(uint16_t addr, uint16_t def)
{
  if(fValid) return(Rd16(addr)); return(def);
}

//----------------- Запись данных с проверкой обновления: --------------------

//addr - адрес слова в секции EEPROM текущего пресета.
//data - слово данных для записи в секцию EEPROM.
//Запись производится только в том случае, если новые данные отличаются.

void TEeprom::Write(uint16_t addr, uint16_t data)
{
  if(Rd16(addr) != data) Wr16(addr, data);
  if(Rd16(addr) != data) Error = 1;
}

//-------------------------- Запись сигнатуры: -------------------------------

//Запись сигнатуры для секции EEPROM текущего пресета.

void TEeprom::Validate(void)
{
  Write(EE_SIGN, EE_SIGNATURE);
  fValid = (Rd16(EE_SIGN) == EE_SIGNATURE);
}

//----------------------- Сброс флага валидности: ----------------------------

//Сброс флага валидности для текущего пресета.

void TEeprom::Invalidate(void)
{
  fValid = 0;
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
