Возможно ли расширение стековой области в памяти?

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Здравствуйте, знатоки!

Столкнулся со следующей проблемой (Arduino Leonardo). После компиляции скетча получил сообщение:

Global variables use 1 551 bytes (60%) of dynamic memory, leaving 1 009 bytes for local variables. Maximum is 2 560 bytes.

 Но в работе прога выдаёт нечто, наводящее на мысль, что стек проваливается в область переменных, разрушая программу (имеется много динамических массивов). Есть ли выход? Предложения типа "об колено" или "в утиль" уже приняты, прошу не повторяться.

Спасибо.

Mr.Privet
Mr.Privet аватар
Offline
Зарегистрирован: 17.11.2015

попробуйте в строке 20 заменить тип переменный с int на byte.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

У меня что-то не так с чувством юмора, или что Вы имели в виду под "строкой 20"?

К сожалению, где можно, везде поменял типы на минимально возможные, все сообщения и константы перенёс во флеш-память (PROGMEM).

Может, можно как-то задействовать кусок ROM для RAM?

Спасибо.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

Peotr пишет:

У меня что-то не так с чувством юмора, или что Вы имели в виду под "строкой 20"?

строка кода, который ты опубликовал.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

А я что-то публиковал?

Что-то Вы меня совсем запутали...

У меня до строки 167 идёт поясняющий текст со ссылками /*...*/.

Mr.Privet
Mr.Privet аватар
Offline
Зарегистрирован: 17.11.2015

У меня хрустальный шар плохо работает, строку 200 с 20 перепутал, мой косяк....

В общем скеч то выложите... А то эти шары очень не точные... или он опять "секретный"?

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

Mr.Privet пишет:

В общем скеч то выложите... А то эти шары очень не точные... или он опять "секретный"?

я запрещаю публиковать код - зрите в хрустальные шары лентяи

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Опять не понял, что значит "опять секретный", ну, да ладно.

Поскольку скетч занял почти 3000 строк, дабы не разбежались читающие, привожу фрагменты программы, и скриншоты вывода на монитор.

Подскажите, как выложить два рисунка, пжлст.

P.S. Перепутавших форум с утренником в детсаде попрошу более не напрягаться. Спасибо.

    case 221: //Ввод с клавиатуры ПК
       
       H[0][0] = 0.9818507194;
/*Вообще-то это переменные с датчика, константы приведены для анализа работы программы */
        H[0][1] = -0.0112825632;
        H[0][2] = -0.2065759181;

   Cm = 0;      
   Cm++;
   K = 0;
   break;
// *****************

   case 222:

        H[1][0] = 0.980092144;
        H[1][1] = -0.2207651615;
        H[1][2] = 0.0205222368;

   Cm++;
   K = 0;
   break;
// *****************

   case 223: 

        H[2][0] = 0.9811792373;
        H[2][1] = 0.0028126239;
        H[2][2] = 0.22704855162;

   Cm++;
   K = 0;
   break;
// *****************

   case 224:

        H[3][0] = 0.9827007293;
        H[3][1] = 0.2120652198;
        H[3][2] = 0.0007474422;

   Cm++; 
   K = 0;
   break;
// *****************

   case 225: 

        H[4][0] = 0.2229225635;
        H[4][1] = 0.976034069;
        H[4][2] = -0.039058506;

   Cm++; 
   K = 0;
   break;
// *****************

   case 226: 

        H[5][0] = 0.0055322647;
        H[5][1] = 0.9823781013; 
        H[5][2] = 0.1752511262;

   Cm++; 
   K = 0;
   break;
// *****************

   case 227:

        H[6][0] = -0.2111377716;
        H[6][1] = 0.9767766952; 
        H[6][2] = -0.0447000265; 

   Cm++; 
   K = 0;
   break;
// *****************

   case 228:

        H[7][0] = 0.0067814558; 
        H[7][1] = 0.9703422546;
        H[7][2] = -0.2586374282;

   Cm++; 
   K = 0;
   break;
// *****************

  case 229: 

        H[8][0] = 0.2150341272;
        H[8][1] = 0.0286955823;
        H[8][2] = 0.9807870864;

   Cm++;
   K = 0;
   break;
// *****************

   case 2210: 

        H[9][0] = -0.0033152103;
        H[9][1] = -0.1874585151;
        H[9][2] = 0.9883389472; 

   Cm++; 
   K = 0;
   break;
// *****************

   case 2211:

        H[10][0] = -0.2187566757;
        H[10][1] = 0.0302037; 
        H[10][2] = 0.975433731;

   Cm++;  
   K = 0;
   break;
// *****************

   case 2212:

        H[11][0] = -0.0000437498;
        H[11][1] = 0.2456648349;
        H[11][2] = 0.9677185058;

   Cm++; 
   K = 0;
   break;
// *****************

До этого момента прога работает нормально. Вывод на монитор массива H[12][3] соответствует заданному. НО! Индикация нулевых значений (ещё не сформированных и не рассчитанных) массива HH[12][6]  уже имеет вкрапления сторонних переменных!  (см. Рис.1) Похоже на провал стека. Ладно, сформируем массив HH[12][6]:

    case 200:

    if(Cm < 12)
    {
      Serial.print(F("Prozedura NE OK Cm= "));
      Serial.print(F("Cm= "));
      Serial.println(Cm, BIN);
      break;

      else    
    {
      noInterrupts();
      
      float **HH;
      HH = MakeMatr(12, 6);
/* Добавление столбцов справа  */
      PLUS_3C (HH, H_rows, 12, 3);

    Serial.println(F("HH="));
    //  Цикл по строкам
    for (int i = 0; i < 12; i++) 
    {
        // цикл по столбцам внутри строки
        for (int j = 0; j < 6; j ++) 
        {
         Serial.print(HH [i][j],DEC);
        }
        Serial.println();
    }

      DelMatr(HH);
      interrupts();     
       break;   

Вот здесь «портится» уже и массив H[12][3]! (См. Рис.2)

Подпрограммы:

/* Добавление трёх столбцов справа */
void PLUS_3C (float *MatrC[], float *Matr[], const int n, const int m)
{
  int i;
  int j;
      for(i = 0; i < n; i++)
      {
         for(j = 0; j < m; j++)
            MatrC[i][j] = Matr [i][j];
            
            MatrC[i][j] = -sq(Matr [i][j-2]);
            MatrC[i][j+1] = -sq(Matr [i][j-1]);
            MatrC[i][j+2] = 1;
      }
}

//*********************************************


   /* Конструктор матрицы */
    // Запрос памяти 
   float **MakeMatr( size_t n, size_t m )
   {
      float **Matr;  size_t i;
      Matr = (float**) Malloc( (n + 1) * sizeof(float *) );
//      Matr = (float**) Malloc( n * sizeof(float *) );
      for(i = 0; i < n; i++)
         Matr[i] = (float *) Malloc( m * sizeof(float) );
      Matr[n] = NULL;
      return Matr;
   }
//*********************************************
   /* Деструктор матрицы */
   void DelMatr( float *Matr[] )
   {
      size_t i;
      for(i = 0; Matr[i]; i++) 
      {
      free(Matr[i]);
      Matr[i] = NULL;
      }
      free(Matr);
      Matr = NULL;
   }

 

Mr.Privet
Mr.Privet аватар
Offline
Зарегистрирован: 17.11.2015

Peotr пишет:

Опять не понял, что значит "опять секретный", ну, да ладно.


"Опять" это значит что некоторые чудаки боятся выкладывать скетч, или как они его называют "код" дабы его никто не спёр и не использовал их "гениальных" ренений во своё обогащение. Ну есть такие, даже среди моих знакомых. Он придумал какую то хрень и даже по телефону обсуждать боится, чтобы не подслушали и не использовали раньше него. И поэтому они начинают задавать абстрактные вопросы, на которые естественно в лучшем случае получат абстрактные ответы. Поэтому дабы помочь пытливым приходится тролить.

Peotr пишет:

Поскольку скетч занял почти 3000 строк, дабы не разбежались читающие, привожу фрагменты программы, и скриншоты вывода на монитор.


Лучше уж сразу всю правду матку в лицо, кто то испугается и убежит, а кто то подскажет где косяк, кто то подскажет более правильное решение.
Peotr пишет:

Подскажите, как выложить два рисунка, пжлст.


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

P.S. Перепутавших форум с утренником в детсаде попрошу более не напрягаться. Спасибо.


Да мы даже не напрягаемся, нам это в радость.))))
По поводу скетча я лично хз, сейчас профи глянут отпишутся, но по ощущениям Вы пытаетесь "впихнуть невпихуемое". Может есть возможность пожертвовать качеством входных данных дабы уменьшить их объем...

Araris
Offline
Зарегистрирован: 09.11.2012
andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Peotr пишет:

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

Выхода два:

1. Пересмотреть структуру программы с целью уменьшить суммарный объем используемой статической и динамической памяти. Например, полностью отказаться от использования динамических массивов, а использовать во всех подпрограммах единственный статический массив.

2. Если 1 не привело к успеху, рассмотреть возможность использованием контроллера с бОльшим ресурсом.

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

 

PS. Лучше свернуть 3000 строк кода, чем помещать 200, не сворачивая.

nik182
Offline
Зарегистрирован: 04.05.2015

Вопрос. Сколько памяти остается если не использовать динамические массивы, а сразу объявить все массивы по максимуму? В приведенных строках нет динамики. Массивы создаются константами. Так в чём будет разница?

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Спасибо! Не догадался, что в опции "Выбор на сервере" возможно закачать с ПК.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

andriano пишет:

Выхода два:

1. Пересмотреть структуру программы с целью уменьшить суммарный объем используемой статической и динамической памяти. Например, полностью отказаться от использования динамических массивов, а использовать во всех подпрограммах единственный статический массив.

2. Если 1 не привело к успеху, рассмотреть возможность использованием контроллера с бОльшим ресурсом.

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

Я пробовал отказываться от динамических массивов. Пришёл к выводу, что при этом уменьшается загрузка стека, но увеличивается загрузка статической памяти, а поскольку они "двигаются" навстречу друг другу, получается "то на то". Увы.

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

"Первоначальный диагноз", похоже, верен, поскольку в массив "залезают" значения введенных ранее переменных, на рис.1 в 5-й строке "0.9265274047", например.

Повторюсь, нельзя ли как-то "сэмулировать" дополнительный кусок оперативки в ROM? Что-то наподобие использования PROGMEM для констант. Спрошу ещё глупее: или присобачить какую-нибудь приблуду на плате для чего-то вроде XMEM (я понимаю, что у меня не ATmega128)?

Спасибо

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

В 3000 строк кода, думаю конечно же героев разбираться вот так вот запросто, Вы врядли найдете. Поэтому только советы "общего плана":

1. Попробуйте перенести ВСЕ ваши массивы и ВСЕ переменные в глобальную область видимости. В память влезают? Под стек остается достаточно места? Оценить не сложно: смотрите самый глубоко вложенный кусок окда и считаете сколько потребуется места на стеке, памятуя что там "в среднем" окажется кроме 2 байт на точку возврата и по 2-4 байта на параметр (в т.ч. и БАЙТОВЫЙ!) плюсом 2-10 байт на сохранение регистров МК в зависимости от размеров функций и количества локалов в них. Если локалы совместно занимают более 6-10 байт, все остальные тоже плюсуйте к объему стека, так - надежней. Влезает?

Если нет, но "почти", то попробуйте уменьшить количество глобальных/локальных/вложенных_функций тем или иным способом. Варианты:

а) перевод локалов в глобалы с целью уменьшения количества таскаемых параметров по стеку "туда-сюда"; б) совмещение глобалов, которые не требуются одновременно, хотя бы через unit {}; в) развертывание inline (места под программу у вас хватает, верно?) небольших функций и уменьшение вложенности вызовов. г) Перевод глобалов в параметры. последнее несколько противоречит "а", но применяется для разных ситуаций: если глобал постоянно "перевычисляется" в некой функции и используется рядом, то может оказаться что его перевод в "параметры" и смена его времени жизни наоборот сократит требования к памяти .. в общем "не всё так однозначно".

Если нет и "много" - ищите иной камень с большей памятью или присмотритесь к варианту "ATmega128A + расширение SRAM". Это может оказаться как раз "ваш случай". Тут есть в разделе "Общий" пример успешного решения для расширения SRAM на +32 килобайт и мой проект расширителя на +512 килобайт. Но такие расширители или платы в с памятью Вам придется изготавливать практически самостоятельно. Сможете? Мне моя первая попытка сделать расширитель +64кб для Мега2560 окончилась полным фиаско, но я был слишком самонадеян .. :)

 

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Arhat109-2 пишет:

***

 

Благодарю за предложенные рекомендации. По большей части это я уже проделал. Похоже, ситуация патовая. Разве что в рамках конкретного образца снизить точность (перейти с float на int).

Отдельная благодарность за подсказку в работе с расширителями, надо поработать в этом направлении.

Спасибо.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Peotr пишет:

Я пробовал отказываться от динамических массивов. Пришёл к выводу, что при этом уменьшается загрузка стека, но увеличивается загрузка статической памяти, а поскольку они "двигаются" навстречу друг другу, получается "то на то". Увы.

Еще раз.

Пересмотрите организацию программы и определите, сколько памяти Вам надо. Зачастую бывает, что один и тот же кусок памяти можно поочередно использовать разными подпрограммамиЮ, а иногда - нет, все данные должны храниться в памяти одновременно.

Вот определитесь, сколько именно памяти Вам нужно одновременно. Если окажется, что больше 2.0-2.2к - Вы неверно выбрали "камень".

Цитата:

Повторюсь, нельзя ли как-то "сэмулировать" дополнительный кусок оперативки в ROM? Что-то наподобие использования PROGMEM для констант. Спрошу ещё глупее: или присобачить какую-нибудь приблуду на плате для чего-то вроде XMEM (я понимаю, что у меня не ATmega128)?

Нет.

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

В ПЗУ создать фрагмент ОЗУ нельзя. Никак.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

andriano пишет:

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

А можно с этого места по-подробнее?

Спасибо.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Ещё можно запустить программу в Proteus и посмотреть в чём там дело в отладчике.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

uni пишет:

Ещё можно запустить программу в Proteus и посмотреть в чём там дело в отладчике.

Имею неудачный опыт работы с серьёзными программами в Proteus. Но Вы подсказали мне, что не лишне повторить эксперимент, возможно, ранее я был менее внимателен, что-то упустил.

Спасибо.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ну .. если у Вас float данные .. то не ждите "чуда". Проще поменять камень, если не умеете обходится int или long вместо плавающей точки ..

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Нужно просто уметь его готовить. Я использую Proteus 7.7 (насколько мне помнится). Да, есть некоторые ограничения, но в вашем случае, думается, программа может быть отлажена.

Такие проблемы как у вас ищутся как раз при помощи отладчика, если он есть.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Arhat109-2 пишет:

Ну .. если у Вас float данные .. то не ждите "чуда". Проще поменять камень, если не умеете обходится int или long вместо плавающей точки ..

long и float занимают одинаковые 4 байта памяти. Чуда здесь нет. Перевод float в int приведёт к ещё бОльшей потери памяти, если не пойти на снижение точности.

Чудеса творить, не умею.

Спасибо.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

uni пишет:

Нужно просто уметь его готовить. Я использую Proteus 7.7 (насколько мне помнится). Да, есть некоторые ограничения, но в вашем случае, думается, программа может быть отлажена.

Такие проблемы как у вас ищутся как раз при помощи отладчика, если он есть.

Спасибо, обязательно попробую.  У меня тоже Proteus 7.7, правда, "взломанный".

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Peotr пишет:
Возможно ли расширение стековой области в памяти?

Да, конечно, но за счёт кучи, разумеется. Надо?

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Вам точно нужен long? Насколько мне известно, все датчики выдают результат в каких-то своих "попугаях", но как правило это 6,8,10,12,16 и т.д. но бит. То бишь числа - целые. Кто вам мешает проводить вычисления в этих же попугаях, а на показ преобразовывать результат к вещественному числу, если оно требуется? Кстати, если датчик выдает до 16 бит, то целочисленные вычисления могут даже повысить вашу точность вычислений, а про их ускорение я уж имолчу вовсе. Я думаю что в природе наверное есть такие датчики, которые выдают вещественное число, но мне с ними сталкиваться как-то не приходилось в рамках Ардуино.

Исключительно как пример, смотрите: узв. датчик расстояний выдает .. не, не расстояние, а время прохождения сигнала "туда и обратно" .. упс. В целочисленных микросекундах. Для его пересчета часто используют традиционный делитель 59, хотя для нормальных условий скорость звука равна 335м/сек и точное значение 59.70... Казалось бы вот оно то место, где требуется вещественная арифметика. А теперь, если мы немножко поварим головой, то обнаружим что искомое преобразование легко выполняется .. умножением времени в микросекундах на 343 и последующим его делением на 2048 (это сдвиг на 11 разрядов, что достаточно быстро) и .. получаем точность преобразования не хуже 0.1мм на всем диапазоне расстояний этого датчика. И это при том, что сам датчик измеряет время сигнала с точностью 1мм согласно даташиту.

Аналогично, положение серводвигателей задается углом поворота, который для человеков удобно представлять градусами и их долями (вещ. число), а серва понимает .. время выданной "1" в периоде ШИМ (скважность сигнала), которое хранится в 8 или 16 битах. И 8 бит часто хватает "с головой" согласно точности фиксации угла серводвигателем под нагрузкой и без таковой.

Сдается мне, что проблема не столько в памяти, а отсутствии опыта вести расчеты в датчиковых попугаях.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Если хотите, я могу создать мелкую виртуальную машину для целей отладки.

На борту можно иметь: Windows XP Mini 32-bit, Proteus 7.7, Arduino SDK (какой-то версии), Arduino-Makefile (для сборки проектов через свой Makefile, т.е. без использования Arduino IDE), PuTTY, VSPD, Far, Total Commander.

Для каждой платы Arduino можно создать свою простую схему отладки. Обычно это сам мк и терминал.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

ЕвгенийП пишет:

Да, конечно, но за счёт кучи, разумеется. Надо?

Динамические массивы в "куче", если я правильно понимаю.

Подскажите, как за счёт "кучи".

Спасибо.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

Arhat109-2 пишет:

Сдается мне, что проблема не столько в памяти, а отсутствии опыта вести расчеты в датчиковых попугаях.

У меня сложные математические расчёты, поэтому, "попугаи" от датчиков в int разрастаются в динозавров в float. А требования к точности в конечной информации весьма велики.

Спасибо.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

uni пишет:

Если хотите, я могу создать мелкую виртуальную машину для целей отладки.

Честно говоря, плохо понимаю, о чём речь. Если можно, по-подробнее.

Спасибо.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Можно создать виртуальный компьютер (VMWare или VirtualBox), на котором установить всё что нужно для отладки кода Arduino в Proteus. Обычным ардуинщикам лень изучать всё это отладочное окружение, а зря. Процентов 90 вопросов местных отпали бы сами собой, если бы человек увидел живьём как работает его программа или её часть.

Сложные проекты с ethernet платой или чем-то подобным отлаживать так нельзя, но очень многие - можно. Также можно виртуальный контроллер подключать через последовательные порты к реальным устройствам.

Я могу создать такую виртуальную машину и наполнить её тем, чем я обычно пользуюсь при отладке.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Peotr пишет:

Подскажите, как за счёт "кучи".

Вам доступны следующие переменные для управления размером кучи (и, соответсвенно стека)

/* May be changed by the user only before the first malloc() call.  */

size_t __malloc_margin = 32;
char *__malloc_heap_start = &__heap_start;
char *__malloc_heap_end = &__heap_end;

Для того, чтобы получить к ним доступ, просто объявите их у себя как extern. Также Вам доступен текущий указатель стека - SP (менять его не стоит, но для чтения он вполне доступен).

При помощи этих переменных Вы можете манипулировать размером кучи, т.е. делить память между кучей и стэком.

Для контроля "что получается", можете воспользоваться простой программкой (библиотекой. если Вам так нравится), которая печатает сведения о текущей загрузке памяти. Она состоит из файлов

MemoryExplorer.h

#ifndef	MEMORY_EXPLRORER_H
#define	MEMORY_EXPLRORER_H

#include <arduino.h>

extern char *__brkval;

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

class Pointer : public Printable {
public:
	Pointer(const void * ptr = NULL) : m_ptr(ptr) {}

	size_t printTo(Print& p) const {
		char szBuffer[13];
		sprintf(szBuffer, "@%04X(%d)", (unsigned)m_ptr, (unsigned)m_ptr);
		return  p.print(szBuffer);
	}
	
private:
	const void * m_ptr;
};

//
//	Возвращает текущее значение указателя стека
// как беззнаковое целое и макрос для печати указателя стека
//
static inline unsigned getSP(void) { return (unsigned)(SP); }
#define SP_Pointer	(Pointer((void*)SP))

//
//	Возвращает текущее значение адрес "конца" кучи
// как беззнаковое целое и как Pointer для печати
//
static inline unsigned heapEnd(void) { return (__malloc_heap_end) ? (unsigned)__malloc_heap_end : SP - __malloc_margin; }
#define	HE_Pointer	(Pointer((__malloc_heap_end) ? __malloc_heap_end : (void *)(SP - __malloc_margin)))
//
//	Возвращает текущее значение адреса "начала" кучи
//
static inline unsigned heapStart(void) { return (unsigned)__malloc_heap_start; }
#define	HS_Pointer	(Pointer(__malloc_heap_start))
//
// Возвращает текущее значение адреса начала ещё нераспределённого куска памяти
//
static inline unsigned newSoil(void) { return (unsigned)(__brkval ? __brkval : __malloc_heap_start); }
#define	NS_Pointer	(Pointer(__brkval ? __brkval : __malloc_heap_start))


extern void memoryReport(const char *title);

#endif	//	MEMORY_EXPLRORER_H

и MemoryExplorer.cpp

#include <arduino.h>
#include "MemoryExplorer.h"


typedef struct __freelist {
	size_t _size;
	struct __freelist *_next;
} FreeList;

extern FreeList *__flp;
extern char *__brkval;

int getNumberOfBlocksInFreeList() {
	FreeList *fp;
	int i;
	for(i=0,fp=__flp;fp;fp=fp->_next,i++);
	return i;
}


void memoryReport(const char * title) {
	Serial << "---- Memory report: " << title << '\n' <<
		"HEAP:" << HS_Pointer << '-' << HE_Pointer << 
		";\nUnallocated from:" << 		NS_Pointer << 
		";\nStack pointer: " << SP_Pointer << "\nFree List:";
	FreeList *ptr = __flp;
	if (!ptr) Serial << " EMPTY";
	while (ptr) {
		Serial << "\n\tBlock at:" << Pointer(ptr) << "; Size:" << ptr->_size;
		ptr = ptr->_next;
	}
	Serial << "\n-----\n";
}

Там есть функция memoryReport, которая распечатает и доступную кучу, и список свободных областей памяти.

Вот пример её использовниая - MemoryExplorer_Test.ino

#include "MemoryExplorer.h"

void setup(void) {
	Serial.begin(115200);
	memoryReport("At Start");
	int *p = new int[10];
	memoryReport("Got p = new int[10];");
	int *p1 = new int[20];
	memoryReport("Got p1 = new int[20];");
	delete [] p;
	memoryReport("Freed p");
	delete [] p1;
	memoryReport("Freed p1");
}

void loop (void) {}

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

Попробуйте поманипулировать переменными.

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

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

А как быть с фрагментацией кучи?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

uni пишет:

А как быть с фрагментацией кучи?

Это Вопрос ко мне? Или к ТС?

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

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

В общем и целом, на мк нежелательно использовать кучу обычным для ПК способом, а если вы её используете, то должны точно понимать к чему это может приводить. 

Вот список правил, которых нужно придерживаться (либо писать дефрагментатор и вызывать его при каждом выделении памяти):

there seems to be a few usage patterns that are safe from the point of view of heap fragmentation

Нужно ещё раз подумать: А действительно ли нужна куча в моей программе?

Ещё одна ссылка: Optimizing SRAM

ОЗУ

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Я то-то предлагаю?

Я вего лишь ответил на вопрос как "переделить" память между кучей и стеком.

Экскурс в проблемы фрагментации - полный офф-топ в данной теме, давайте прекратим его едва начав, ладно?

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Кто это сказал, что оффтоп? Я вижу код:

   /* Конструктор матрицы */
    // Запрос памяти 
   float **MakeMatr( size_t n, size_t m )
   {
      float **Matr;  size_t i;
      Matr = (float**) Malloc( (n + 1) * sizeof(float *) );
//      Matr = (float**) Malloc( n * sizeof(float *) );
      for(i = 0; i < n; i++)
         Matr[i] = (float *) Malloc( m * sizeof(float) );
      Matr[n] = NULL;
      return Matr;
   }
//*********************************************
   /* Деструктор матрицы */
   void DelMatr( float *Matr[] )
   {
      size_t i;
      for(i = 0; Matr[i]; i++) 
      {
      free(Matr[i]);
      Matr[i] = NULL;
      }
      free(Matr);
      Matr = NULL;
   }

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

Что означает этот комментарий?

/* May be changed by the user only before the first malloc() call.  */

Куча может расти? Если прибавить к этому фрагментацию, то получим третью картинку - наложение областей памяти. Имеет это отношение к теме? Имеет и самое прямое, пока явно не доказано обратное.

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

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Peotr, выведите в терминал адреса, получаемые при динамическом выделении памяти (до момента появления ошибок).

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

uni пишет:

Peotr, выведите в терминал адреса, получаемые при динамическом выделении памяти (до момента появления ошибок).

К сожалению, не знаю, как это оганизовать. Или не достаточно правильно понял вопрос. Под "терминалом" понимается обычный программный терминал для com-порта?

Извините за тупость.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

uni пишет:

Я могу создать такую виртуальную машину...

Я с подобным не имел дела, поэтому плохо представляю этот инструмент. Хорошо бы опробовать. А сколько стОит такая машина?

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

ЕвгенийП пишет:

Вам доступны следующие переменные для управления размером кучи (и, соответсвенно стека)

Спасибо, очень интересный материал для глубокого анализа. Обязательно займусь.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Я не смотрел исходники функции malloc(). Вариантов её работы может быть много. Чтобы глубоко не копать можно просто посмотреть адреса, которые возвращает функция с течением времени (до момента появления ошибок). Обычно это делается при помощи printf() так:

printf( "addr = 0x%04X", addr );

Вы должны вывести значение указателя с помощью Serial.print(). Я использую свою функцию для вывода, она сложная и тут будет не уместна. Вы можете сделать что-то вроде: Serial.println( Matr, HEX ) там, где получаете этот указатель.

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

uni пишет:

Кто это сказал, что оффтоп? 

Я.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Peotr пишет:

Я с подобным не имел дела, поэтому плохо представляю этот инструмент. Хорошо бы опробовать. А сколько стОит такая машина?

Трудно ответить на этот вопрос. Она полностью будет набита пиратскими программами. Легальная стоимость не известна, а вообще она конечно бесплатная.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

uni пишет:

Трудно ответить на этот вопрос.

Трудно понять этот трудный ответ. :))))))))))))

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Peotr, Вы, конечно, можете увеличить стек за счёт кучи, только это вряд-ли Вам поможет. Дело в том, что елси у Вас перепоняется стек, значит он налазит на кучу. Сумма памяти под стек и кучу всё равно константа. Ну, увеличите Вы стек (уменьшив кучу) если им двоим не хватаем места, то и не будет хватать. Лучше внимательно помсотреть на программу.

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

Кстати, та библиотека, что я Вам дал печатает список свободных областей, т.е. это тот самый случай, когда Вы можете просто понаблюдать за фрагментацией и заполнением и, возможно, понять, что не так.

Вот, например, не знаю, как Вы, но многие здесь любят использовать класс String по любому поводу (просто, чтобы число в строку преобразовать, например), а между прочим String - это просто убийца памяти. Особенно ужасно, кода люди умудряются передавать пременные типа String в качестве параметров по значению - это попросту приводит к дублированию памяти выделенной - под String - ради передачи пераметра создаётся ещё одна копия уже имеющейся строки - там ужас, что творится.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

> Трудно понять этот трудный ответ. :))))))))))))

Вот это уже оффтоп, если не обсуждается сам процесс отладки вашей программы.

Если есть интерес, я создам отдельную тему на этот счёт. У меня на ПК "развёрнута" такая машина и я пользуюсь её функционалом, в т.ч. для ответов здесь (если полностью приведён код программы).

К примеру, я промоделировал работу функции вывода цвета для RGB светодиода. Выглядит это так:

Мк может быть любой, на картинке это ATMega2560.

Peotr
Peotr аватар
Offline
Зарегистрирован: 17.02.2014

ЕвгенийП пишет:

Peotr, Вы, конечно, можете увеличить стек за счёт кучи, только это вряд-ли Вам поможет.

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

Стринги, println и прочие излишества в этой проге давно ликвидировал, как класс. :)

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Peotr пишет:

andriano пишет:

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

А можно с этого места по-подробнее?

Спасибо.

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

В этом случае часть данных из тех, что не используется в данный момент (но обязательно понадибится позже - иначе из можно было бы просто стереть), сбрасывавется в файл на SD, а на освободтвшемся месте размещается требуемый массив. Когда надобность в новом массиве отпадет, данные читаются с SD на прежнее место.