Помогите пожалуйста найти проблему в скетче. Задача - записать во внешнюю 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' На сим конец Думаю где то с преобразованиями строк в байтовые массивы косяк, но не понимаю где. Может и ошибаюсь. Помогите люди добрые!
Поэтому считается моветоном писать на суржике Есть такая штука у стринга c_str . Погуглите, перед преобразованием стринга в массив байт вызовите этот самый c_str.
1. Про с_str() уже сказали, getBytes просто не нужно. 2. Передача параметра в функцию по значению - суксь. Это я про "int16_t writingToEEPROM(uint16_t firstAddress, String stringToWrite)". Надо как минимум : Код (C++): int16_t writingToEEPROM(uint16_t firstAddress, String& stringToWrite) И оперативку не будете так бездумно насиловать. 3. По остальному - лучше всего выкинуть весь код и написать заново. Некрасивый он. А некрасивый код, по определению, взлетать не должен З.Ы. Про "выкинуть" - это, конечно, шутка. Но... Переформатируйте код понаглядней, что ли - у вас там всё слиплось, глаза в кучу собираются
Выкинуть, выкинуть целиком!!!! И забыть как страшный сон! АФТАР!!! ЕПРОМ вам не файл. Вы его тестами убьете раньше, чем код отладите. 1. Зачем вы забиваете весь ЕПРОМ байтами 0xFF - что за ересь? 2. Зачем вы пишете в Епром названия текстовых полей? Вот этот весь бред: "NTP Server Name" "NTP Server IP" и тд? - так никто не делает! Запомните: ресурс ЕПРОМ надо экономить. Выделите постоянное место для каждой сохраненной строки и сохраняйте в ЕЕПРОМ только меняющиеся значения, без названий. А названия, если нужны - сохраняйте в тексте скетча в строковых константах: const char field1_name[] = "NTP Server Name";
Вроде ж там миллион перезаписей заявлен. Забиваю, потому что в выводе были как то замечены строки оставшиеся с предыдущих тестов. Это тестовый массив. Что было под рукой то и скопипастил.
На первый раз потянет. Но лучше - чуть проще и понятней написать, типа такого (заодно учтём, что могут использоваться внешние 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() { } //-------------------------------------------------------------------- Писал навскидку, но принцип, надеюсь, понятен - пишем длину строки, потом её данные.
Спасибо. Принцип понятен. но это не прокатит. В строках будет присутствовать кириллица. Пока сделал так: Код (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++; } }
Если бы вы посмотрели на исходники String, то поняли бы, что прокатит даже с китайским, не то, что с кириллицей. String хранит длину буфера, а не юзает strlen для подсчёта.
У меня все-таки стойкое ощущение, что вы неправильно подощли к планированию программы. Не могу себе представить переменные, в которых может присутсвовать кириллица. Строки, пункты меню, сообщения диагностики - могу. Но это все константы и их в ЕПРОМ не хранят. А вот переменные ???
Ну почему нет: например, подпись к датчику на экране, если юзеру позволяется это редактировать. Имя юзера на каком-то ресурсе, куда коннектиться - тоже вполне себе может быть кириллическим - тоже хранить надо. По личному опыту - такие вещи имеют место быть. Но это, конечно, не отменяет сомнения во вменяемости кода ТС, ибо я тоже пока склонен видеть сплошные непонятки, типа "NTP Server Name: ntp21.vniiftri.ru" (зачем префикс, если можно просто хранить адрес сервера???).
Учитывая предыдущий вопрос ТС, "Как записать файл в ЕПРОМ" - мне кажется, что YeS собирается хранить в ЕПРОМ типичный текстовой конфиг, который обычно содержит пары <field>:<value>
Даже при таком коленкоре налицо кривизна, ибо конфиг - это, например: Код (C++): ntp=ntp21.vniiftri.ru message=Hello, world! а никак не Код (C++): NTP Server Name: ntp21.vniiftri.ru Ибо, во-первых, избыточность имени параметра, и, во-вторых - лишние символы, тот же пробел - накохер он нужен? Впрочем, мы гадаем, ибо неведомо нам, чего хочет ТС.
нет, необязательно... давно существуют генераторы конфигов, которые поддерживают длинные имена с пробелами. То, что пытается сохранить ТС - выглядит как типичный конфиг от сетевого или десктопного проекта. Если поиграть в угадайку - Вероятно, YeS решил, что взять и использовать в ардуине готовый конфиг с десктопа будет проще, чем конвертировать его в переменные ардуины. Однако мне кажется. что временное "упрощение" на начальном этапе обернется кучей проблем в будущем - в частности, извлекать данные в программе на ардуино из такого конфига будет существенно сложнее, чем просто из переменных. Я уж не говорю о скорости работы и расходе памяти для такого решения.
Я выложил тестовую программку. Что вы привязались все к длинным значениям полей? Я же писал что взял эти строковые переменные для примера. Разумеется в ЕЕПРОМ не будет записано полностью "NTP Server Name: bla-bla-b.la". В худшем случае в ЕЕРРОМ пойдёт "ntpsnbla-bla-b.la". Просто отрабатываю только запись данных пока. А "NTP Server Name: bla-bla-b.la" это для удобства пользователя, который будет заполнять конфиг своими данными.
мы привязались не длинным значениям, а к самому принципу. И по вашему ответу я вижу. что угадал. Вытаки собираетесь засунуть в ЕПРОМ конфиг целиком. Дело ваше, но не советую. Иначе мы тут еще увидим целую череду вопросов, как потом с этим конфигом работать в ардуине. Хотя... наверное... и так увидим. Судя по тому, что через 2 года регистрации на форуме вы все еще задаете вопросы начального уровня
Про этот конфиг не увидите, потому что данное устройство прекрасно распознаёт конфиг с флешки и вообще уже год бесперебойно работает. Переписать работу под конфиг полученный из ЕЕПРОМ врят ли будет проблемой. Просто появилась идея избавиться от постоянно воткнутой флешки, коль уж имеется простаивающая ЕЕПРОМ. За предыдущие ответы хочу выразить вам благодарность. Если же вы устали за 2 года от моих вопросов начального уровня, то прошу вас не принимать их близко к сердцу. Именно вам я их задавать не собирался. Увы, не получается непрерывно сидеть 2 года за изучением программирования. Семья, дети, парочка работ... Так что, если в течение ближайших пары лет вы увидите здесь от меня очередной вопрос начального уровня, прошу вас, не стесняйтесь его игнорировать. Поверьте я совершенно не желаю вас затруднять.