Запись и чтение из eeprom массивов символов

maks.dav
Offline
Зарегистрирован: 13.12.2015

Имеется следующая задача:

Есть шилд

Необходимо записать в память EEPROM несколько динамических массивов символов, затем их считать из EEPROM в массивы.

Длина массивов первоначально неизвестна, поэтому они динамические с максимальным количеством элементов (в моём случае 20).

Делается это для экономии оперативной памяти, т.к. работа идёт с большим количеством массивов.

Запись и чтение происходит следующим образом:

#include <EEPROM.h>

char* str = "text";

char* str1;
char* str2;

...

// Выделяем память для str1
str1 = new char[20];

// Вставляем какие-то данные
str1[0] = 'a';
str1[1] = 'b';
str1[2] = 'c';

// Запись в EEPROM str1
EEPROM.put(address1, str1);

// Освобождаем память от str1
delete [] str1;


// Запись в EEPROM str = "text" - статический массив символов
EEPROM.put(address, str);

*****************************************
Перезагружаем контроллер
*****************************************

// Читаем из EEPROM в str1
EEPROM.get(address1, str1);   // Читает ерунду

// Читаем из EEPROM в str2
EEPROM.get(address2, str2);   // Читает нормально - str = "text"!..

Что я делаю не так??

maks.dav
Offline
Зарегистрирован: 13.12.2015

Поправки:

Шилд - MEGA2560

// Читаем из EEPROM в str2
36 EEPROM.get(address, str2);   // Читает нормально - str = "text"!..

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Какую ерунду читает - "abc" + всякую херь?

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

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav - вряд ли вы так хоть что-то сэкономите, так как для чтения из ЕЕПРОМ память все равно придется выделять, причем ровно столько же. сколько знаимает массив.

Если строчки не менются. куда полезнее было бы сохранять их PROGMEM

maks.dav
Offline
Зарегистрирован: 13.12.2015

sadman41, нет, только всякую херь

maks.dav
Offline
Зарегистрирован: 13.12.2015

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

Тогда каким образом строка str = "text" читается в неразмеченную область памяти?

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

maks.dav - вряд ли вы так хоть что-то сэкономите, так как для чтения из ЕЕПРОМ память все равно придется выделять, причем ровно столько же. сколько знаимает массив.

b707, подскажите, тогда каким образом из eeprom получается читать в такой же неразмеченный массив (указатель) массив символов str = "text"?

b707 пишет:

Если строчки не менются. куда полезнее было бы сохранять их PROGMEM

строчки меняются

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav пишет:

Тогда каким образом строка str = "text" читается в неразмеченную область памяти?

Вы язык Си, видно, второй день как начали изучать?

В строке str = "text" вы инициализируете строку константой в момент создания, поэтому под нее выделяется память. А str1 и str2 у вас просто пустые указатели, память под них не выделена и прочитать в них из ЕЕПРОМ ничего нельзя, сначала нужно выделить память оператором new. поэтому я и пишу, что этим методом никакой экономии памяти не будет

sadman41
Offline
Зарегистрирован: 19.10.2016

maks.dav пишет:

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

Тогда каким образом строка str = "text" читается в неразмеченную область памяти?

Совпадение множества факторов.
Если есть интерес разобраться в деталях, берите объектный код, дизассемблируйте.

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

В строке str = "text" вы инициализируете строку константой в момент создания, поэтому под нее выделяется память. А str1 и str2 у вас просто пустые указатели, память под них не выделена и прочитать в них из ЕЕПРОМ ничего нельзя, сначала нужно выделить память оператором new

Читаю я не в строку str, которая была до этого инициализирована массивом "text" (естественно размер массива будет равен 4 в этом случае), а в указатель str2 - и читается прекрасно, без всякой ереси в начале или конце. Вот этот момент для меня пока не понятен вообще никак, почему так происходит?

maks.dav
Offline
Зарегистрирован: 13.12.2015

sadman41 пишет:
Совпадение множества факторов.

удачное совпадение

maks.dav
Offline
Зарегистрирован: 13.12.2015

Хорошо, есть ли какой-то механизм, или ещё что, чтобы контролировать момент, когда ОЗУ близка к заполнению?

Просто в конкретном проекте, если на каждый указатель цеплять размеченную память, боюсь, можно не подрасчитать и зависнуть..

P.S. Абсолютно все массивы с данными используются в работе, никакой массив освобождать нельзя

sadman41
Offline
Зарегистрирован: 19.10.2016

Вот интересно: почему ответ автомеханика "дороги херовые, ёпть" на вопрос "почему лопнула правая стойка?" сразу понятен клиенту и он не стремиться узнать отчего левая-то целая.

Но ответ "произведена запись в незарезервированную область памяти" отчего-то, де-факто, является неудовлетворительным и неясным.

sadman41
Offline
Зарегистрирован: 19.10.2016

Выделяйте память только в необходимые моменты (выделил-считал-вывел-освободил) или используйте один и тот же массив-буфер и для ввода и для вывода.

А средства контроля есть, конечно. Ищите "AVR Freememory".

maks.dav
Offline
Зарегистрирован: 13.12.2015

sadman41 пишет:
используйте один и тот же массив-буфер и для ввода и для вывода.

хорошо, если использую буфер, считываю одну строку, я могу её прицепить к одному указателю, затем считать в буфер новую строку и прицепить к другому, не получится, что мои 2 указателя будут привязаны к одной и той же области памяти?

maks.dav
Offline
Зарегистрирован: 13.12.2015

или для буфера всегда применять оператор new? чтобы он всегда размечал память по новым адресам

sadman41
Offline
Зарегистрирован: 19.10.2016

Селяви. Одновременно вы не сможете уместить в RAM больше данных, чем её объем. По-очерёдно - пожалуйста. А когда работаешь с МК, то приходится раскорячиваться как та пресловутая корова из фильма "ДМБ".

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav пишет:

хорошо, если использую буфер, считываю одну строку, я могу её прицепить к одному указателю, затем считать в буфер новую строку и прицепить к другому, не получится, что мои 2 указателя будут привязаны к одной и той же области памяти?

Блин, вот же непонятливый :) "прицпив строку к указателю", вы потратите ровно столько памяти, сколько она занимала до записи в ЕЕПРОМ :)

Иначе никак, строки должны где-то лежать. Если у вас все строки "нужны  и используются" - они будут занимать ровно столько памяти. сколько их размер в сумме. И не байтом меньше.

повторяю, чудес не бывает. вашим способом расход памяти не уменьшить ни на байт, поймите

maks.dav
Offline
Зарегистрирован: 13.12.2015

sadman41 пишет:
Селяви. Одновременно вы не сможете уместить в RAM больше данных, чем её объем. По-очерёдно - пожалуйста.

Если я правильно понимаю, динамические типы данных хранятся в ОЗУ, а статические и объявленные в глобальных переменных во флэш?

sadman41
Offline
Зарегистрирован: 19.10.2016

Нет, они будут перегружены в RAM, так как подразумевается, что значение переменной может быть изменено.

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

"прицпив строку к указателю", вы потратите ровно столько памяти, сколько она занимала до записи в ЕЕПРОМ :)

т.е. вы хотите сказать, что str = {'t', 'e', 'x', 't'} и str = "text" - в памяти располагаются как-то по-разному?

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav пишет:

Если я правильно понимаю, динамические типы данных хранятся в ОЗУ, а статические и объявленные в глобальных переменных во флэш?

Нет в МК все данные хранятся в ОЗУ, в том числе и статические и глобальные. Во флеш хранятся только константы - и то только в том случае, если программист явно обьявил. что эти данные должны помещаться там, например ключевым словом PROGMEM.

И надо понимать, что данные во флеш не могут быть перезаписаны из программы), так что если ваши данные в ходе программы должны менятся - их можно поместить только в ОЗУ

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav пишет:

т.е. вы хотите сказать, что str = {'t', 'e', 'x', 't'} и str = "text" - в памяти располагаются как-то по-разному?

это почему?

sadman41
Offline
Зарегистрирован: 19.10.2016

Уточню: PROGMEM препятствует перегрузке значения в RAM. Изначально-то все задекларированное находится в Programm space, т.е. флеше.

А оптимизатор тривиальные типы констант вообще может в RAM не переносить, а заюзать как дефайн. Во всяком случае - замена дефайна на const переменную оверхеда мне не даёт, а вот типы путать не позволяет.

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

Нет в МК все данные хранятся в ОЗУ, в том числе и статические и глобальные. Во флеш хранятся только константы - и то только в том случае, если программист явно обьявил. что эти данные должны помещаться там, например ключевым словом PROGMEM.

И надо понимать, что данные во флеш не могут быть перезаписаны из программы), так что если ваши данные в ходе программы должны менятся - их можно поместить только в ОЗУ

Я прошу прощения, за свою дотошность, пишу в основном прикладные программы на C#, там никогда не приходилось применять указатели и тому подобные вещи, поэтому некоторые моменты мне не очень ясны. Да и с контроллерами и их архитектурами, периферией я, таки всё же, на вы.

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

maks.dav пишет:

т.е. вы хотите сказать, что str = {'t', 'e', 'x', 't'} и str = "text" - в памяти располагаются как-то по-разному?

это почему?

Ну так для динамического массива str1 = new char[4] выделяется память 4 байта, так же как и для str = "text" тоже 4 байта, только в первом случае мы каждому элементу присваиваем значение, а во втором они присвоены при объявлении - но по факту это внешне одинаковые массивы. По логике и записываются в eeprom одинаково по 4 байта на каждый массив, но почему-то в первом случае при считывании прога уже не знает, сколько байт записали, а во втором знает, т.к. считывает конкретно эти 4 байта слова "text" без проблем. Или я опять не догоняю?:)

sadman41
Offline
Зарегистрирован: 19.10.2016

По логике в первом случае вы передаёте указатель на область памяти, а во втором - массив известной длины (хотя я уж засомневался).

С телефона неудобно смотреть что там генерирует темплейт eeprom.put(), но у меня есть подозрения, что при передаче указателя очень сложно определить сколько именно байт необходимо запулить в eeprom. ASCIIZ строка не хранит свою длину атрибутом.

Этот простой вопрос требует лазанья по кишкам Wiring, а нам лень ))

Но вот, тем не менее. Если передать туда указатель (т.е. переменную, хранящую адрес), то запись начнется с адреса указателя. Т.е. с "адреса адреса". Думаю, что там, на длину 2 байта, хранятся нечеловекочитаемые данные.

template< typename T > const T &put( int idx, const T &t ){
        EEPtr e = idx;
        const uint8_t *ptr = (const uint8_t*) &t;
        for( int count = sizeof(T) ; count ; --count, ++e )  (*e).update( *ptr++ );
        return t;
    }

 

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav пишет:

Ну так для динамического массива str1 = new char[4] выделяется память 4 байта, так же как и для str = "text" тоже 4 байта

сразу ошибка. Для строчки str = "text" выделяется 5 байт. потому что такой способ инициализации записывает в память строку с нуль-терминатором,  в то время как при создании массива str1 = new char[4] о терминаторе вы должны позаботится сами.

Если вы этого не делаете - не записываете в конце строки нулевой байт - это может обьяснять. почему у вас программа работает с этими строками по разному.

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

сразу ошибка. Для строчки str = "text" выделяется 5 байт. потому что такой способ инициализации записывает в память строку с нуль-терминатором,  в то время как при создании массива str1 = new char[4] о терминаторе вы должны позаботится сами.

Если вы этого не делаете - не записываете в конце строки нулевой байт - это может обьяснять. почему у вас программа работает с этими строками по разному.

А вот это как раз-таки, видимо, решение моей загадки!! Я об этом даже не слышал...

Спасибо, что быстро не сдались и не ушли с обсуждения темы!:)

sadman41
Offline
Зарегистрирован: 19.10.2016

Это не является непосредственным решением данной проблемы, это ремарка. 

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

maks.dav Вам же sadman41 написал, что

EEPROM.put(address1, str1);

Запишет в EEPROM не строку, а ее адрес !!!

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707, если я правильно понял, если у меня информативность строки 4 байта, я должен объявить массив на 5 байт и в 5-й записать тупо \0?

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

Видимо надо на индусском ???

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir пишет:

maks.dav Вам же sadman41 написал, что

EEPROM.put(address1, str1);

Запишет в EEPROM не строку, а ее адрес !!!

а чтобы записать строку, что нужно сделать??

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

На МК поднимать "кучу" с целью экономить память - КАЛАМБУР !

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir пишет:

Видимо надо на индусском ???

??? не понял, к чему это?

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir пишет:

На МК поднимать "кучу" с целью экономить память - КАЛАМБУР !

да, я как бы понял этот момент ещё в начале темы, поэтому больше его не поднимал

меня заинтересовали другие нюансы: с особенностью записи массивов строк в eeprom

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

Это к тоvу что "0" в конце выдал бы a b c и всякую хрень, если бы это писалось в EEPROM.

Что бы записать значение надо разименовать указатель на это значение. В C# разве не так же ???

b707
Offline
Зарегистрирован: 26.05.2017

maks.dav пишет:

b707, если я правильно понял, если у меня информативность строки 4 байта, я должен объявить массив на 5 байт и в 5-й записать тупо \0?

это зависит от того, как вы с этой строкой собираетесь работать. Но если строчки у вас переменной длины - то варианта только два, либо хранить длину строки в отдельной переменной, либо ставить терминатор в конце.

Нужно отметить два момента:

1. Садман прав, EEPROM.put() в данном случае. похоже, не подходит. Лучше напишите процедуру сохранения строки сами, там и кода-то будет строчки три.

2. И еще раз повторю - даже если вы научитесь правильно записывать строки в ЕЕПРОМ и потом читать оттуда - вашу основную задачу это не решит, память в так не съэкономите.

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir пишет:

Это к тоvу что "0" в конце выдал бы a b c и всякую хрень, если бы это писалось в EEPROM.

Ну так этого момента я не знал. Поэтому сижу тут и уточняю эти все нюансы..

Что вы предлагаете? конкретной длины массив писать и читать? ну это вроде и так понятно, что 

sandman41, предположил, что непонятный момент связан с отсуствием "\0" в качестве последнего байта строки. и то, что в готовой строке "text" он присутствует - я склонен ему верить, - это всё к вопросу: почему в первом случае мы отлично можем читать массив таким способом, а в другом у нас возникает проблема. И уже завтра проверю, сегодня уже поздновато, да мозгу нужно отдохнуть и переварить информацию..

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Нюанс тут в том, что put() - темплейт, формирующий функцию записи. И без мозголомства он может записать тип данных известного на этапе компиляции размера. 

Например, put() для long сработает, потому что размер long известен и sizeof() вернёт размерность 4.

put() для char name[]="john" тоже сработает, так как размер известен и sizeof() вернёт 5.

С указателями же put() будет работать предсказуемо херово, если только не вмешается оптимизатор и не сделает предрасчет длины строки (или ещё не отмочит чтонить).

Кстати, get() с параметром типа "указатель" тоже будет работать как бог на душу положит.

maks.dav
Offline
Зарегистрирован: 13.12.2015

b707 пишет:

maks.dav пишет:

b707, если я правильно понял, если у меня информативность строки 4 байта, я должен объявить массив на 5 байт и в 5-й записать тупо \0?

это зависит от того, как вы с этой строкой собираетесь работать. Но если строчки у вас переменной длины - то варианта только два, либо хранить длину строки в отдельной переменной, либо ставить терминатор в конце.

Нужно отметить два момента:

1. Садман прав, EEPROM.put() в данном случае. похоже, не подходит. Лучше напишите процедуру сохранения строки сами, там и кода-то будет строчки три.

2. И еще раз повторю - даже если вы научитесь правильно записывать строки в ЕЕПРОМ и потом читать оттуда - вашу основную задачу это не решит, память в так не съэкономите.

Спасибо за помощь, по поводу 2го варианта я ещё выше понял, что нужно выбрать другой подход к этой проблеме

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

В #26 даже исходник функции Put вам нашли !!!

maks.dav
Offline
Зарегистрирован: 13.12.2015

sadman41 пишет:

Нюанс тут в том, что put() - темплейт, формирующий функцию записи. И без мозголомства он может записать тип данных известного на этапе компиляции размера. 

Например, put() для long сработает, потому что размер long известен и sizeof() вернёт размерность 4.

put() для char name[]="john" тоже сработает, так как размер известен и sizeof() вернёт 5.

С указателями же put() будет работать непредсказуемо, если только не вмешается оптимизатор и не сделает предрасчет длины строки (или ещё не отмочит чтонить).

Кстати, get() с параметром типа "указатель" тоже будет работать как бог на душу положит.

Большое спасибо за пояснение, по крайней мере нарисовалась уже общая картина, с чем мне нужно работать!:)

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

Надо глянуть в листинг - что там компилятор накропал ...

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir пишет:

В #26 даже исходник функции Put вам нашли !!!

за что ему отдельная благодарность:))

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

Чутка поправил функцию put:

    template< typename T > const T &put( int idx, const T &t ){
        EEPtr e = idx;
        const uint8_t *ptr = (const uint8_t*) &t;
        //for( int count = sizeof(T) ; count ; --count, ++e )  (*e).update( *ptr++ );
		for( int count = sizeof(T) ; count ; --count, ++e )  Serial.println(*ptr++ );
        return t;
    }

И запустил код ТС ...

В обоих случаях (str и str1) в EEPROM записался бы адрес переменной, а не ее значение (что в принципе и не удивительно, раз передаём указатель - указатель и запишется ... если разименовать, то записывается первый символ)!!!

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

А ты сомневалса?

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

Меня смутило что str якобы читался верно...

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir пишет:

Меня смутило что str якобы читался верно...

Komandir, он не якобы читался, он нормально читался, но спасибо за проделанный эксперимент и результат, видимо действительно это какие-то неясные совпадения..

maks.dav
Offline
Зарегистрирован: 13.12.2015

Komandir, у меня возник вопрос: если str инициализировать как константу (const), она будет храниться в флэше? или тоже в озу?

И ещё: при использовании оператора delete [] str1 (например), после этого момента, когда выводишь переменную - затирается только первая половина символов, вторая половина остаётся - он так и должен работать?