сохранение переменных в EEPROM

Тема в разделе "Arduino & Shields", создана пользователем Толик Иванов, 6 дек 2015.

  1. Толик Иванов

    Толик Иванов Administrator Администратор

    есть программа в которой есть изначально по умолчанию установленные переменные.
    Код (C++):
      hLedOn=10;
      mLedOn=10;
      hLedOff=10;
      mLedOff=11;
    далее в программе значения этих переменных изменяются и пересохраняются
    Код (C++):
      hLedOn=hLedOn+1;
      mLedOn=mLedOn+1;
      hLedOff=hLedOff+1;
      mLedOff=mLedOff+1;
    тем самым значения равны уже
    Код (C++):
      int hLedOn=11;
      mLedOn=11;
      hLedOff=11;
      mLedOff=12;
    но после отключения питания, при новом запуске Ардуино, данные переменных остаются равны
    Код (C++):
      hLedOn=10;
      mLedOn=10;
      hLedOff=10;
      mLedOff=11;
    Вопрос в том, как все же после отключения питания, при новом запуске Ардуино, получать ранее измененные значения переменных?
     
  2. DrProg

    DrProg Вечный нерд

    Сначала для удобство записи/чтения сделать стуктуру:
    Код (C++):
    struct strLed {   // структура данных
      int hLedOn;
      int mLedOn;
      int hLedOff;
      int mLedOff;
    }
    strLed Led;
    Процедуры записи/чтения. Первая при запуске МК считывает данные, вторая записывает их после изменения в программе (или перед завершением работы):
    Код (C++):
    void getEEPROM () {                  // чтение данных
      if (EEPROM.read(0) != 110) {  // данных нет, записываем по умолчанию
        Led.hLedOn = 10;
        Led.mLedOn = 10;
        Led.hLedOff = 10;
        Led.mLedOff = 11;
        EEPROM.update(1, Led);
        EEPROM.update(0, 110); // отметили наличие данных
      }
      EEPROM.get(1, Led);
    }

    void saveEEPROM () { // запись данных
      EEPROM.update(1, Led);
    }
    Обращаться к данным соответственно как к членам структуры: Led.hLedOn и так далее.
     
    Tomasina, RozalievAndrey и Толик Иванов нравится это.
  3. Толик Иванов

    Толик Иванов Administrator Администратор

    а что значит (EEPROM.read(0) != 110)
    почему 110? что это за число?
     
  4. DrProg

    DrProg Вечный нерд

    Любое число которое даст программе понять были ли записаны в EEPROM данные раньше или там пусто. Если пусто пишет по умолчанию, в следующий раз не пишет а сразу считывает.
     
  5. Толик Иванов

    Толик Иванов Administrator Администратор

    ааа, тоесть я могу написать что угодно от 0 до 255?
     
  6. DrProg

    DrProg Вечный нерд

    0-254
     
    ИгорьК нравится это.
  7. Толик Иванов

    Толик Иванов Administrator Администратор

    в своем примере я использую тройка модуль RTC, в библиотеке которого написано что они тоже используют память EEPROM. значит ли это, что если тройка модуль записывает значения в EEPROM по адресу "0" или "1", а я перезапишу на этот адрес значение своих переменных, то есть вероятность ошибки считывания данных модулем RTC? и как в таком случае определить какие адреса не заняты?
     
  8. Толик Иванов

    Толик Иванов Administrator Администратор

    файл .cpp библиотеки RTC
    Код (C++):
    /****************************************************************************/
    //  Function: Cpp file for troyka-RTC
    //  Hardware: DS1307
    //  Arduino IDE: Arduino-1.6.5
    //  Author:  Igor Dementiev
    //  Date:    Sep 10,2015
    //  Version: v1.0
    //  by www.amperka.ru
    /****************************************************************************/

    #include <Wire.h>
    #include "TroykaRTC.h"
    #include <EEPROM.h>

    uint8_t RTC::decToBcd(uint8_t val) {
        return ( (val/10*16) + (val%10) );
    }

    // Convert binary coded decimal to normal decimal numbers
    uint8_t RTC::bcdToDec(uint8_t val) {
        return ( (val/16*10) + (val%16) );
    }

    void RTC::begin() {
        Wire.begin();
    }

    /* Function: The clock timing will start */

    void RTC::start() {     // set the ClockHalt bit low to start the rtc
        Wire.beginTransmission(DS1307_I2C_ADDRESS);
    // Register 0x00 holds the oscillator start/stop bit
        Wire.write((uint8_t)0x00);
        Wire.endTransmission();
        Wire.requestFrom(DS1307_I2C_ADDRESS, 1);
    // save actual seconds and AND sec with bit 7 (sart/stop bit) = clock started
        _second = Wire.read() & 0x7f;
        Wire.beginTransmission(DS1307_I2C_ADDRESS);
        Wire.write((uint8_t)0x00);
    // write seconds back and start the clock
        Wire.write((uint8_t)_second);
        Wire.endTransmission();
    }
    /*Function: The clock timing will stop */
    void RTC::stop() {  // set the ClockHalt bit high to stop the rtc
        Wire.beginTransmission(DS1307_I2C_ADDRESS);
        // Register 0x00 holds the oscillator start/stop bit
        Wire.write((uint8_t)0x00);
        Wire.endTransmission();
        Wire.requestFrom(DS1307_I2C_ADDRESS, 1);
        // save actual seconds and OR sec with bit 7 (sart/stop bit) = clock stopped
        _second = Wire.read() | 0x80;
        Wire.beginTransmission(DS1307_I2C_ADDRESS);
        Wire.write((uint8_t)0x00);
        // write seconds back and stop the clock
        Wire.write((uint8_t)_second);
        Wire.endTransmission();
    }
    /****************************************************************/
    /*Function: Read time and date from RTC */
    void RTC::read() {
    // Reset the register pointer
        Wire.beginTransmission(DS1307_I2C_ADDRESS);
        Wire.write((uint8_t)0x00);
        Wire.endTransmission();
        Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
    // A few of these need masks because certain bits are control bits
        _second    = bcdToDec(Wire.read() & 0x7f);
        _minute    = bcdToDec(Wire.read());
        // Need to change this if 12 hour am/pm
        _hour      = bcdToDec(Wire.read() & 0x3f);
        _dayOfWeek  = bcdToDec(Wire.read());
        _day = bcdToDec(Wire.read());
        _month      = bcdToDec(Wire.read());
        _year      = bcdToDec(Wire.read()) + 2000;
    }
    /*******************************************************************/
    /*Frunction: Write the time that includes the date to the RTC chip */
    void RTC::set(uint8_t hour, uint8_t minute, uint8_t second, uint16_t day,
                  uint8_t month, uint8_t year, uint8_t dow) {
        _hour = hour;
        _minute = minute;
        _second = second;
        _day = day;
        _month = month;
        _year = year-2000;
        _dayOfWeek = dow;

        Wire.beginTransmission(DS1307_I2C_ADDRESS);
        Wire.write((uint8_t)0x00);
        // 0 to bit 7 starts the clock
        Wire.write(decToBcd(_second));
        Wire.write(decToBcd(_minute));
        // If you want 12 hour am/pm you need to set bit 6
        Wire.write(decToBcd(_hour));
        Wire.write(decToBcd(_dayOfWeek));
        Wire.write(decToBcd(_day));
        Wire.write(decToBcd(_month));
        Wire.write(decToBcd(_year));
        Wire.endTransmission();
    }

    void RTC::set(const char* compileTimeStamp) {
        int i = 0;
    //  Serial.println(compileTimeStamp);

    /*--------------------DayOfWeek----------------------------*/
        char dow[4];
        for (i = 0; i < 3; i++)
            dow[i] =  compileTimeStamp[i];
        dow[i] = '\0';

        if (strcmp(dow, "Mon") == 0)
            _dayOfWeek = 1;
        else if (strcmp(dow, "Tue") == 0)
            _dayOfWeek = 2;
        else if (strcmp(dow, "Wed") == 0)
            _dayOfWeek = 3;
        else if (strcmp(dow, "Thu") == 0)
            _dayOfWeek = 4;
        else if (strcmp(dow, "Fri") == 0)
            _dayOfWeek = 5;
        else if (strcmp(dow, "Sat") == 0)
            _dayOfWeek = 6;
        else if (strcmp(dow, "Sun") == 0)
            _dayOfWeek = 7;

    /*--------------------------Day----------------------------*/
        _day = ((compileTimeStamp[8] - '0') * 10 + (compileTimeStamp[9] - '0'));

    /*--------------------------Month---------------------------*/
        char month[4];
        for (i = 0; i < 3; i++)
            month[i] =  compileTimeStamp[i+4];
        month[i] = '\0';

        if (strcmp(month, "Jan") == 0)
            _month = 1;
        else if (strcmp(month, "Feb") == 0)
            _month = 2;
        else if (strcmp(month, "Mar") == 0)
            _month = 3;
        else if (strcmp(month, "Apr") == 0)
            _month = 4;
        else if (strcmp(month, "May") == 0)
            _month = 5;
        else if (strcmp(month, "Jun") == 0)
            _month = 6;
        else if (strcmp(month, "Jul") == 0)
            _month = 7;
        else if (strcmp(month, "Aug") == 0)
            _month = 8;
        else if (strcmp(month, "Sep") == 0)
            _month = 9;
        else if (strcmp(month, "Oct") == 0)
            _month = 10;
        else if (strcmp(month, "Nov") == 0)
            _month = 11;
        else if (strcmp(month, "Dec") == 0)
            _month = 12;

    /*--------------------------Year----------------------------*/
        _year = ((compileTimeStamp[20] - '0') * 1000
            + (compileTimeStamp[21] - '0') * 100
            + (compileTimeStamp[22] - '0') * 10
            + (compileTimeStamp[23] - '0')) - 2000;
    /*--------------------------Time----------------------------*/
        _hour = ((compileTimeStamp[11] - '0') * 10
            + (compileTimeStamp[12] - '0'));

        _minute = ((compileTimeStamp[14] - '0') * 10
            + (compileTimeStamp[15] - '0'));

        _second = ((compileTimeStamp[17] - '0') * 10
            + (compileTimeStamp[18] - '0'));

        unsigned int hash =  _hour * 60 * 60 + _minute  * 60 + _second;

        if (EEPROMReadInt(0) != hash) {
            EEPROMWriteInt(0, hash);
            Wire.beginTransmission(DS1307_I2C_ADDRESS);
            Wire.write((uint8_t)0x00);
        // 0 to bit 7 starts the clock
        Wire.write(decToBcd(_second));
        Wire.write(decToBcd(_minute));
        // If you want 12 hour am/pm you need to set bit 6
        Wire.write(decToBcd(_hour));
        Wire.write(decToBcd(_dayOfWeek));
        Wire.write(decToBcd(_day));
        Wire.write(decToBcd(_month));
        Wire.write(decToBcd(_year));
        Wire.endTransmission();
    }
    }

    void RTC::getTimeStr(char* output, uint8_t len) const {
        char buff[8];
        if (_hour < 10)
            buff[0] = '0';
        else
            buff[0] = char((_hour / 10) + 48);
        buff[1] = char((_hour % 10) + 48);
        buff[2] = ':';

        if (_minute < 10)
            buff[3] = '0';
        else
            buff[3] = char((_minute / 10) + 48);
        buff[4] = char((_minute % 10) + 48);
        buff[5] = ':';

        if (_second < 10)
            buff[6] = '0';
        else
            buff[6] = char((_second / 10) + 48);
        buff[7] = char((_second % 10) + 48);
        buff[8] = '\0';

        len--;
        int i = 0;
        while (i < 8 && len > 0) {
            output[i] = buff[i];
            i++;
            len--;
        }
        output[i] = '\0';
    }

    void RTC::getDateStr(char* output, uint8_t len) const {
        char buff[10];
        if (_day < 10)
            buff[0] = '0';
        else
            buff[0] = char((_day / 10)+ 48);
        buff[1] = char((_day % 10) + 48);
        buff[2] = '.';

        if (_month < 10)
            buff[3] = '0';
        else
            buff[3] = char((_month / 10) + 48);
        buff[4] = char((_month % 10) + 48);
        buff[5] = '.';
        buff[6]=char((_year / 1000)+48);
        buff[7]=char(((_year % 1000) / 100)+48);
        buff[8]=char(((_year % 100) / 10)+48);
        buff[9]=char((_year % 10)+48);
        buff[10]='\0';

        len--;
        int i = 0;
        while (i < 10 && len > 0) {
            output[i] = buff[i];
            i++;
            len--;
        }
        output[i] = '\0';
    }

    void RTC::getDOWStr(char* output, uint8_t len) const {
        char buff[10];
        switch (_dayOfWeek) {
            case 1: {
                strcpy(buff, "Monday");
                break;
            }
            case 2: {
                strcpy(buff, "Tuesday");
                break;
            }
            case 3: {
                strcpy(buff, "Wednesday");
                break;
            }
            case 4: {
                strcpy(buff, "Thursday");
                break;
            }
            case 5: {
                strcpy(buff, "Friday");
                break;
            }
            case 6: {
                strcpy(buff, "Saturday");
                break;
            }
            case 7: {
                strcpy(buff, "Sunday");
                break;
            }
        }

        int i = 0;
        len--;
        int s = strlen(buff);
        while (i < s && len > 0) {
            output[i] = buff[i];
            i++;
            len--;
        }
        output[i] = '\0';
    }

    //Запись двухбайтового числа в память
    void EEPROMWriteInt(int address, int value)
    {
        EEPROM.write(address, lowByte(value));
        EEPROM.write(address + 1, highByte(value));
    }

    //Чтение числа из памяти
    unsigned int EEPROMReadInt(int address)
    {
        byte lowByte = EEPROM.read(address);
        byte highByte = EEPROM.read(address + 1);

        return (highByte << 8) | lowByte;
    }
     
     
  9. Sindbad

    Sindbad Гик

    Вот, кстати, хороший вопрос к Амперке. Такие вещи как использование общего ресурса, в данном случае EEPROM, в библиотеке надо документировать.

    А по существу, вы же видите, что EEPROM используется только в функции
    Код (C++):
    void set(const char* compileTimeStamp)
    Если её не использовать, то обращений к EEPROM из библиотеки не будет.
    Время можно устанавливать функцией
    Код (C++):
    void set(uint8_t hour, uint8_t minute, uint8_t second, uint16_t day, uint8_t month, uint8_t year, uint8_t dow);
    Или можно убрать обращение к EEPROM из первой функции

    И ошибки в RTC все равно не будет. В худшем случае установится давно устаревшее время. А вот в вашей программе могут возникнуть ошибки, т.к. в 0 и 1 байтах EEPROM окажутся данные, которые вы туда не записывали.
     
  10. Толик Иванов

    Толик Иванов Administrator Администратор

    я вывел в сериал значения всех ячеек. Заняты только нулевая и первая. Остальные выдает 255, что я так понимаю означает что они еще не использовались. В моей программе около 15 разных переменных значение которых меньше 100. Значит при записи каждой указать адрес начиная со второго и значение переменной? И еще, что если записывать значения сразу через функцию update?
     
  11. Sindbad

    Sindbad Гик

    Тоже будет работать.

    https://www.arduino.cc/en/Reference/EEPROMUpdate

    Обратите внимание вот на эту фразу:

    The EEPROM memory has a specified life of 100,000 write/erase cycles, so you may need to be careful about how often you write to it

    Если писать новое значение в ячейку раз в минуту, то ячейка проживет пару месяцев. Если делать это при каждом вызове loop(), то ячейка проживет менее 10 минут. Т.е. обращаться с ячейками как с обычными переменными не стоит. И сохранять нужно только то, что действительно должно сохраниться при выключении питания.
     
  12. Толик Иванов

    Толик Иванов Administrator Администратор

    да, я именно поэтому и хочу использовать функцию update.
    update(address, value)
    - заменяет значение value по адресу address, если её старое содержимое отличается от нового;
    если я правильно понял то даже в случае если данная функция стоит в loop то перезаписывать она будет значения по адресу только в случае что прежнее значение отличается. а значит и память eeprom прослужит дольше. Или все же update тоже будет убивать память?
     
  13. Толик Иванов

    Толик Иванов Administrator Администратор

    короче реализовал следующим образом
    захожу в меню настройка параметров
    настраиваю час включения света и минуты
    нажимаю кнопку подтвердить и переменные обновляются и записываются в EEPROM через update
    Код (C++):
    EEPROM.update(100, hLedOn );
    EEPROM.update(101, mLedOn );
     
    а в setup прописываю
    Код (C++):
    hLedOn = EEPROM.read(100);
    mLedOn = EEPROM.read(101);
    получается что при отключении питания все уже сохранено, а при включении питания переменным присваиваются значения из соответствующих ячеек.

    надеюсь что таким образом я не убью EEPROM.
     
  14. DrProg

    DrProg Вечный нерд

    Вы знакомы с тем как работает функция EEPROM.update?
     
    ИгорьК нравится это.
  15. Толик Иванов

    Толик Иванов Administrator Администратор

    ну я как я понял что она сравнивает новое значение с предыдущим и в случае если отличается, она его перезаписывает.
     
  16. DrProg

    DrProg Вечный нерд

    Да. То есть если значения не менялись то они и записываться не будут, что здорово экономит ресурс EEPROM и позволяет не париться о сохранении лишний раз.
     
    ИгорьК нравится это.
  17. Толик Иванов

    Толик Иванов Administrator Администратор

    ну значит я правильно сделал? Тем более я обновляю данные не в цикле бесконечно, а только когда вхожу в меню настройки параметров и выходя из него обновляю измененные значения.
     
  18. DrProg

    DrProg Вечный нерд

    Так и надо, записывать только тогда, когда они меняются.
     
  19. Толик Иванов

    Толик Иванов Administrator Администратор

    я уж было подумал, что вопрос о том знаю ли я как работает функция update, значит что я чтото жестко накосячил.
     
  20. DrProg

    DrProg Вечный нерд

    Ну спрашиваете же "запорю EEPROM или нет". Если им регулярно пользоваться, то рано или поздно запорите (примерно через 100.000 циклов записи). Задача в том, чтобы насиловать его как можно меньше, чтобы не запороть как можно дольше.

    Еще вот решение на случай если устройство будет работать очень долго и автономно (например при полете на Марс в автоматическом режиме). Создаете еще две переменные - счетчик записей например unsigned int (хотя он считает всего до 65 535) и счетчик счетчиков, когда первый переполняется, второй увеличивается на единицу. Адрес данных вычисляется как длина хранящихся данных умноженных на счетчик счетчика. То есть избегая износа памяти данные будут автоматически менять ячейки на свежие и так пока не EEPROM не кончится весь. Но к тому времени вы уже отправите следующий марсоход.
     
    ИгорьК и Толик Иванов нравится это.