Проблема с чтением/записью строк в EEPROM

Тема в разделе "Arduino & Shields", создана пользователем YeS, 4 дек 2017.

  1. YeS

    YeS Гик

    Помогите пожалуйста найти проблему в скетче. Задача - записать во внешнюю EEPROM по I2C несколько строк. Записываю побайтово, но когда начинаю читать вылезает неадекватный результат. Разделители строк не там где нужно. Вот код:
    Код (C++):
    #include <Wire.h>
    #define DEVICE_1 0x50 //Адрес устройства
    String testString[] = {"NTP Server Name: ntp21.vniiftri.ru\n", "NTP Server IP:\n", "Time Zone: +3\n", "Syncronize Every (sec): 3600\n"};
    //String testString[] = {"11111\n", "222\n", "3333333\n", "44444\n"};
    uint16_t nextAddress, previousAddress;

    void setup() {
      Wire.begin();
      Serial.begin(9600);
      Serial.println("Поехали!");
    }

    void loop() {
      nextAddress = 50;
      previousAddress = 50;
      for (int i = 0; i < 1000; i++) EEPROM_WriteByte(DEVICE_1, i, 0xFF);
      for (int i = 0; i < 4; i++){
        nextAddress = writingToEEPROM(previousAddress, testString[i]) + 1;
        previousAddress = nextAddress;
      }
      nextAddress = 50;
      previousAddress = 50;
      for (int i = 0; i < 4; i++){
        nextAddress = readingFromEEPROM(previousAddress) + 1;
        previousAddress = nextAddress;
      }
      Serial.println();
      Serial.println("На сим конец");
      while(1){;} // Останавливаем цикл
    }

    // Запись байта в EEPROM
    void EEPROM_WriteByte(int dev, uint16_t Address, byte data)
    {
      Wire.beginTransmission(dev);
      Wire.write(Address >> 8);
      Wire.write(Address & 0xFF);
      Wire.write(data); // посылаем байт
      delay(6); //в Datasheet описана задержка записи в 5мс
      Wire.endTransmission();
    }

    //Чтение байта из EEPROM
    byte EEPROM_ReadByte(int dev, uint16_t Address) {
      byte rdata = 0xFF;
      Wire.beginTransmission(dev);
      Wire.write(Address >> 8);
      Wire.write(Address & 0xFF);
      Wire.endTransmission();
      Wire.requestFrom(dev,1); // считываем один байт данных
      if (Wire.available()) rdata = Wire.read();
      return rdata;
    }

    // Запись строки в EEPROM
    uint16_t writingToEEPROM(uint16_t firstAddress, String stringToWrite){
      uint16_t lastAddress = firstAddress;
      // Преобразуем тестовую строку в байты
      byte buf[201];
      stringToWrite.getBytes(buf, 200);
     
      Serial.print("Начинаем запись c адреса = "); Serial.print(lastAddress);
      for (uint16_t i = 0; i < 200; i++){
        EEPROM_WriteByte(DEVICE_1, i + lastAddress, buf[i]);
        if (buf[i] == '\n'){ // Каждая строка в массиве строк заканчивается символом \n
          buf[i] = '\0';
          String outputString = ((char*)buf);    
          Serial.print(" Запись завершена. "); Serial.print("Строка '"); Serial.print(outputString); Serial.print("'. lastAddress = "); Serial.println(lastAddress);
          return lastAddress;
        }
        lastAddress++;
      }
    }

    // Чтение строки из EEPROM
    uint16_t readingFromEEPROM(uint16_t firstAddress){
      uint16_t lastAddress = firstAddress;
      Serial.print("Начинаем чтение строки с адреса: "); Serial.print(lastAddress);
      byte buf[200];
      for (uint16_t i = 0; i < 200; i++){
        buf[i] = EEPROM_ReadByte(DEVICE_1, i + lastAddress);
        if (buf[i] == '\n'){
          buf[i]='\0';
          Serial.print(" по "); Serial.println(lastAddress);
          String outputString = ((char*)buf);
          Serial.print("Прочитана строка '"); Serial.print(outputString); Serial.println("'");
          return lastAddress;
        }
        lastAddress++;
      }
    }
     
    А вот вывод:
    Код (C++):
    Поехали!
    Начинаем запись c адреса = 50 Запись завершена. Строка 'NTP Server Name: ntp21.vniiftri.ru'. lastAddress = 84
    Начинаем запись c адреса = 85 Запись завершена. Строка 'NTP Server IP:'. lastAddress = 99
    Начинаем запись c адреса = 100 Запись завершена. Строка 'Time Zone: +3'. lastAddress = 113
    Начинаем запись c адреса = 114 Запись завершена. Строка 'Syncronize Every (sec): 3600'. lastAddress = 142
    Начинаем чтение строки с адреса: 50 по 110
    Прочитана строка 'NTP Server Name: ntp21.vnTime ZoSyncronize Every (sec): 3600'
    Начинаем чтение строки с адреса: 111 по 112
    Прочитана строка ':'
    Начинаем чтение строки с адреса: 113 по 113
    Прочитана строка ''
    Начинаем чтение строки с адреса: 114 по 142
    Прочитана строка 'Syncronize Every (sec): 3600'

    На сим конец
     
    Думаю где то с преобразованиями строк в байтовые массивы косяк, но не понимаю где. Может и ошибаюсь. Помогите люди добрые!
     
  2. brokly

    brokly Гик

    Поэтому считается моветоном писать на суржике :) Есть такая штука у стринга c_str . Погуглите, перед преобразованием стринга в массив байт вызовите этот самый c_str.
     
    YeS нравится это.
  3. DIYMan

    DIYMan Guest

    1. Про с_str() уже сказали, getBytes просто не нужно.
    2. Передача параметра в функцию по значению - суксь. Это я про "int16_t writingToEEPROM(uint16_t firstAddress, String stringToWrite)". Надо как минимум :
    Код (C++):
    int16_t writingToEEPROM(uint16_t firstAddress, String& stringToWrite)
    И оперативку не будете так бездумно насиловать.

    3. По остальному - лучше всего выкинуть весь код и написать заново. Некрасивый он. А некрасивый код, по определению, взлетать не должен :)

    З.Ы. Про "выкинуть" - это, конечно, шутка. Но... Переформатируйте код понаглядней, что ли - у вас там всё слиплось, глаза в кучу собираются :)
     
    YeS нравится это.
  4. b707

    b707 Гуру

    Выкинуть, выкинуть целиком!!!!
    И забыть как страшный сон!

    АФТАР!!! ЕПРОМ вам не файл. Вы его тестами убьете раньше, чем код отладите.

    1. Зачем вы забиваете весь ЕПРОМ байтами 0xFF - что за ересь?
    2. Зачем вы пишете в Епром названия текстовых полей? Вот этот весь бред: "NTP Server Name" "NTP Server IP" и тд? - так никто не делает!

    Запомните: ресурс ЕПРОМ надо экономить. Выделите постоянное место для каждой сохраненной строки и сохраняйте в ЕЕПРОМ только меняющиеся значения, без названий. А названия, если нужны - сохраняйте в тексте скетча в строковых константах:
    const char field1_name[] = "NTP Server Name";
     
  5. YeS

    YeS Гик

    Вроде ж там миллион перезаписей заявлен.
    Забиваю, потому что в выводе были как то замечены строки оставшиеся с предыдущих тестов.
    Это тестовый массив. Что было под рукой то и скопипастил.
     
  6. YeS

    YeS Гик

    Спасибо. Попробую.
    Благодарю.
    Согласен. Плод длительного мудрения и мытарств ))
     
  7. DIYMan

    DIYMan Guest

    На первый раз потянет. Но лучше - чуть проще и понятней написать, типа такого (заодно учтём, что могут использоваться внешние EEPROM, поэтому функции работы с памятью вынесем отдельно, чтобы поменять потом всего в одном месте:

    Код (C++):
    //--------------------------------------------------------------------
    void MemWrite(uint16_t address, uint8_t val)
    {
      EEPROM.write(address, val);
    }
    //--------------------------------------------------------------------
    uint8_t MemRead(uint16_t address)
    {
      return EEPROM.read(address);
    }
    //--------------------------------------------------------------------
    uint16_t WriteString(uint16_t address, const String& val)
    {
      uint16_t len = val.length();
      uint8_t* ptr = (uint8_t*) &len;
     
      for(uint8_t i=0;i<sizeof(uint16_t);i++)
        MemWrite(address++, *b++);
       
      for(uint16_t i=0;i<len;i++)
        MemWrite(address++, val[i]);
       
        return len + sizeof(uint16_t);
    }
    //--------------------------------------------------------------------
    String ReadString(uint16_t& address)
    {
      String result;
     
      uint16_t len;
      uint8_t* ptr = (uint8_t*) &len;
     
      *ptr++ = MemRead(address++);
      *ptr = MemRead(address++);
     
      for(uint16_t i=0;i<len;i++)
        result += (char) MemRead(address++);
     
      return result;
    }
    //--------------------------------------------------------------------
    // теперь тестируем
    //--------------------------------------------------------------------
    void setup()
    {
      Serial.begin(9600);
     
      uint16_t addr = 10;
     
      addr += WriteString(addr, "test string 1");
      addr += WriteString(addr, "test string 2");
     
      addr = 10;
     
      Serial.println(ReadString(addr));
      Serial.println(ReadString(addr));
     
    }
    //--------------------------------------------------------------------
    void loop()
    {
    }
    //--------------------------------------------------------------------
     
    Писал навскидку, но принцип, надеюсь, понятен - пишем длину строки, потом её данные.
     
  8. YeS

    YeS Гик

    Спасибо. Принцип понятен.
    но это не прокатит. В строках будет присутствовать кириллица.

    Пока сделал так:
    Код (C++):
    #include <Wire.h>
    #define DEVICE_1 0x50 //Адрес устройства
    #define START_ADDRESS 100 //Начальный адрес записи строк
    String testString[] = {"NTP Server Name: ntp21.vniiftri.ru\n", "А тут ваще такая строчка.\n", "NTP Server IP:\n", "Time Zone: +3\n", "Syncronize Every (sec): 3600\n"};
    uint16_t nextAddress;

    void setup() {
      Wire.begin();
      Serial.begin(9600);
    }

    void loop() {
      nextAddress = START_ADDRESS;
      for (int i = 0; i < 5; i++) nextAddress = writingToEEPROM(nextAddress, testString[i]) + 1;

      nextAddress = START_ADDRESS;
      for (int i = 0; i < 5; i++) nextAddress = readingFromEEPROM(nextAddress) + 1;

      while(1){;} // Останавливаем цикл
    }

    // Функция записи байта в EEPROM
    void EEPROM_WriteByte(int dev, uint16_t Address, byte data)
    {
      Wire.beginTransmission(dev);
      Wire.write(Address >> 8);
      Wire.write(Address & 0xFF);
      Wire.write(data); // посылаем байт
      delay(6); //в Datasheet описана задержка записи в 5мс
      Wire.endTransmission();
    }

    //Функция чтения байта из EEPROM
    byte EEPROM_ReadByte(int dev, uint16_t Address) {
      byte rdata = 0xFF;
      Wire.beginTransmission(dev);
      Wire.write(Address >> 8);
      Wire.write(Address & 0xFF);
      Wire.endTransmission();
      Wire.requestFrom(dev,1); // считываем один байт данных
      if (Wire.available()) rdata = Wire.read();
      return rdata;
    }

    uint16_t writingToEEPROM(uint16_t firstAddress, String& stringToWrite){
      uint16_t lastAddress = firstAddress;
      byte buf[200];
      stringToWrite.getBytes(buf, 200);

      for (int i = 0; i < 200; i++){
        EEPROM_WriteByte(DEVICE_1, i + firstAddress, buf[i]);
        if (buf[i] == '\n') return lastAddress;
        lastAddress++;
      }
    }

    uint16_t readingFromEEPROM(uint16_t firstAddress){
      uint16_t lastAddress = firstAddress;
      byte buf[200];
      for (uint16_t i = 0; i < 200; i++){
        buf[i] = EEPROM_ReadByte(DEVICE_1, i + firstAddress);
        if (buf[i] == '\n'){
          buf[i] = '\0';
          String outputString = ((char*)buf);
          Serial.print("Прочитана строка '"); Serial.print(outputString); Serial.println("'");
          return lastAddress;
        }
        lastAddress++;
      }
    }
     
    Последнее редактирование: 5 дек 2017
  9. DIYMan

    DIYMan Guest

    Если бы вы посмотрели на исходники String, то поняли бы, что прокатит даже с китайским, не то, что с кириллицей. String хранит длину буфера, а не юзает strlen для подсчёта.
     
  10. b707

    b707 Гуру

    У меня все-таки стойкое ощущение, что вы неправильно подощли к планированию программы. Не могу себе представить переменные, в которых может присутсвовать кириллица. Строки, пункты меню, сообщения диагностики - могу. Но это все константы и их в ЕПРОМ не хранят. А вот переменные ???
     
  11. DIYMan

    DIYMan Guest

    Ну почему нет: например, подпись к датчику на экране, если юзеру позволяется это редактировать. Имя юзера на каком-то ресурсе, куда коннектиться - тоже вполне себе может быть кириллическим - тоже хранить надо. По личному опыту - такие вещи имеют место быть.

    Но это, конечно, не отменяет сомнения во вменяемости кода ТС, ибо я тоже пока склонен видеть сплошные непонятки, типа "NTP Server Name: ntp21.vniiftri.ru" (зачем префикс, если можно просто хранить адрес сервера???).
     
    b707 и arkadyf нравится это.
  12. b707

    b707 Гуру

    Учитывая предыдущий вопрос ТС, "Как записать файл в ЕПРОМ" - мне кажется, что YeS собирается хранить в ЕПРОМ типичный текстовой конфиг, который обычно содержит пары <field>:<value>
     
    arkadyf нравится это.
  13. DIYMan

    DIYMan Guest

    Даже при таком коленкоре налицо кривизна, ибо конфиг - это, например:
    Код (C++):
    ntp=ntp21.vniiftri.ru
    message=Hello, world!
    а никак не
    Код (C++):
    NTP Server Name: ntp21.vniiftri.ru
    Ибо, во-первых, избыточность имени параметра, и, во-вторых - лишние символы, тот же пробел - накохер он нужен? Впрочем, мы гадаем, ибо неведомо нам, чего хочет ТС.
     
    b707 нравится это.
  14. b707

    b707 Гуру

    нет, необязательно... давно существуют генераторы конфигов, которые поддерживают длинные имена с пробелами.
    То, что пытается сохранить ТС - выглядит как типичный конфиг от сетевого или десктопного проекта. Если поиграть в угадайку :) - Вероятно, YeS решил, что взять и использовать в ардуине готовый конфиг с десктопа будет проще, чем конвертировать его в переменные ардуины. Однако мне кажется. что временное "упрощение" на начальном этапе обернется кучей проблем в будущем - в частности, извлекать данные в программе на ардуино из такого конфига будет существенно сложнее, чем просто из переменных.
    Я уж не говорю о скорости работы и расходе памяти для такого решения.
     
  15. YeS

    YeS Гик

    Названия помещений.
     
  16. YeS

    YeS Гик

    Я выложил тестовую программку. Что вы привязались все к длинным значениям полей? Я же писал что взял эти строковые переменные для примера. Разумеется в ЕЕПРОМ не будет записано полностью "NTP Server Name: bla-bla-b.la". В худшем случае в ЕЕРРОМ пойдёт "ntpsnbla-bla-b.la". Просто отрабатываю только запись данных пока. А "NTP Server Name: bla-bla-b.la" это для удобства пользователя, который будет заполнять конфиг своими данными.
     
  17. b707

    b707 Гуру

    мы привязались не длинным значениям, а к самому принципу. И по вашему ответу я вижу. что угадал. Вытаки собираетесь засунуть в ЕПРОМ конфиг целиком.
    Дело ваше, но не советую. Иначе мы тут еще увидим целую череду вопросов, как потом с этим конфигом работать в ардуине.
    Хотя... наверное... и так увидим. Судя по тому, что через 2 года регистрации на форуме вы все еще задаете вопросы начального уровня
     
  18. YeS

    YeS Гик

    Про этот конфиг не увидите, потому что данное устройство прекрасно распознаёт конфиг с флешки и вообще уже год бесперебойно работает. Переписать работу под конфиг полученный из ЕЕПРОМ врят ли будет проблемой. Просто появилась идея избавиться от постоянно воткнутой флешки, коль уж имеется простаивающая ЕЕПРОМ.
    За предыдущие ответы хочу выразить вам благодарность. Если же вы устали за 2 года от моих вопросов начального уровня, то прошу вас не принимать их близко к сердцу. Именно вам я их задавать не собирался. Увы, не получается непрерывно сидеть 2 года за изучением программирования. Семья, дети, парочка работ... Так что, если в течение ближайших пары лет вы увидите здесь от меня очередной вопрос начального уровня, прошу вас, не стесняйтесь его игнорировать. Поверьте я совершенно не желаю вас затруднять.