Программируем Arduino. Основы работы со скетчами - Монк Саймон Страница 21
Программируем Arduino. Основы работы со скетчами - Монк Саймон читать онлайн бесплатно
Когда происходит вызов функции, в области памяти, называемой стеком, выделяется фрагмент. Представьте подпружиненный дозатор для леденцов, например Pez™, но позволяющий вталкивать леденцы и выталкивать их сверху (рис. 6.3). Под термином «вталкивать» понимается добавление чего-то на стек, а под термином «выталкивать» — извлечение со стека.
Каждый раз, когда вызывается функция, создается кадр стека. Кадр стека — это небольшой объем памяти, где сохраняются параметры и локальные переменные функции, а также адрес возврата, указывающий точку в программе, откуда должно быть продолжено выполнение после завершения функции.
Первоначально стек пуст, но, когда скетч вызовет функцию (пусть это будет функция А), на стеке выделяется пространство под кадр. Если функция А вызовет другую функцию (функцию Б), на вершину стека будет добавлен еще один кадр и теперь в стеке будет храниться две записи. Когда функция Б завершится, ее кадр будет вытолкнут со стека. Затем, когда завершится функция А, ее кадр также будет вытолкнут со стека. Поскольку локальные переменные функции находятся в кадре стека, они не сохраняются между вызовами функции.
Рис. 6.3. Стек
Под стек используется некоторый объем ценной памяти, и большую часть времени на стеке находятся не более трех-четырех кадров. Исключение составляют ситуации, когда функции вызывают сами себя или в цикле вызывают друг друга. В таких случаях есть опасность, что программа исчерпает память для стека.
Например, математическая функция вычисления факториала находит произведение всех целых чисел, предшествующих указанному числу, включая его. Факториал числа 6 равен 6 х 5 х 4 х 3 х 2 х 1 = 720.
Рекурсивный алгоритм вычисления факториала определяется так.
• Если n = 0, факториал числа n равен 1.
• Иначе факториал числа n равен произведению n на факториал (n – 1).
Далее показана реализация этого алгоритма на языке Arduino C:
long factorial(long n)
{
if (n == 0)
{
return 1;
}
else
{
return n* factorial(n — 1);
}
}
Полную версию кода, который вычисляет факториалы чисел и выводит результаты, вы найдете в скетче sketch_06_02_factorial. Люди с математическим складом ума находят такую реализацию весьма искусной. Но обратите внимание на то, что глубина стека в вызове такой функции равна числу, факториал которого требуется найти. Совсем нетрудно догадаться, как реализовать нерекурсивную версию функции factorial:
long factorial(long n)
{
long result = 1;
while (n > 0)
{
result = result * n;
n--;
}
return result;
}
С точки зрения удобочитаемости этот код, возможно, выглядит понятнее, а кроме того, он расходует меньше памяти и работает быстрее. Вообще старайтесь избегать рекурсии или хотя бы ограничивайтесь высокоэффективными рекурсивными алгоритмами, такими как Quicksort (http://ru.wikipedia.org/wiki/Быстрая_сортировка), который очень эффективно упорядочивает массив чисел.
Сохраняйте строковые константы во флеш-памяти
По умолчанию строковые константы, как в следующем примере, сохраняются в ОЗУ и во флеш-памяти — один экземпляр хранится в коде программы, а второй экземпляр создается в ОЗУ во время выполнения скетча:
Serial.println("Program Started");
Но если использовать код, как показано далее, строковая константа будет храниться только во флеш-памяти:
Serial.println(F("Program Started"));
В разделе «Использование флеш-памяти» далее в этой главе вы познакомитесь с другими способами использования флеш-памяти.
Типичные заблуждения
Многие заблуждаются, полагая, что использование более коротких имен переменных позволяет экономить память. В действительности это не так. Компилятор сам заботится об этом и не включает имена переменных в скомпилированный скетч. Другое распространенное заблуждение: комментарии увеличивают размер программы или объем потребляемой ею оперативной памяти. Это не так.
Некоторые также считают, что организация программного кода в виде множества маленьких функций увеличивает размер скомпилированного кода. Обычно этого не происходит, потому что компилятор достаточно сообразителен для того, чтобы в ходе оптимизации кода заменить вызовы функций их фактическими реализациями. Это обстоятельство помогает писать более удобочитаемый код.
Измерение объема свободной памяти
Узнать, какой объем ОЗУ занимает скетч во время выполнения, можно с помощью библиотеки MemoryFree, доступной по адресу http://playground.arduino.cc/Code/AvailableMemory.
Пользоваться этой библиотекой совсем не сложно: в ней имеется функция freeMemory, возвращающая число доступных байтов. Следующий скетч иллюстрирует ее использование:
#include <MemoryFree.h>
void setup()
{
Serial.begin(115200);
}
void loop()
{
Serial.print("freeMemory()=");
Serial.println(freeMemory());
delay(1000);
}
Эта библиотека может пригодиться при диагностике неожиданных проблем, которые, по вашему мнению, могут быть вызваны нехваткой памяти. Конечно же, использование библиотеки ведет к небольшому увеличению потребления памяти.
Уменьшение используемого объема флеш-памяти
По окончании процедуры компиляции скетча в нижней части окна Arduino IDE появится примерно такое сообщение:
Скетч использует 1344 байт (4%) памяти устройства. Всего доступно 32 256 байт.
Эта строка сообщает точный объем флеш-памяти в Arduino, который будет занят скетчем, благодаря чему вы всегда будете знать, насколько близко подошли к пределу в 32 Кбайт. Оказавшись близко к предельному значению, нужно позаботиться об оптимизации использования флеш-памяти. В этом вам помогут рассматриваемые далее рекомендации.
Используйте константы
Многие, стараясь дать имена контактам, определяют для этого переменные, как показано ниже:
int ledPin = 13;
Если вы не собираетесь изменять номер контакта с именем ledPin в процессе выполнения скетча, то вместо переменной можно использовать константу. Просто добавьте слово const в начало объявления:
const int ledPin = 13;
Это поможет сэкономить 2 байта ОЗУ плюс 2 байта флеш-памяти при каждом использовании константы. Для часто используемых переменных экономия может достигать нескольких десятков байтов.
Удалите ненужные трассировочные вызовы
В процессе отладки скетчей для Arduino принято вставлять в код команды Serial.println, помогающие увидеть значения переменных в разных точках программы и определить источники ошибок. Эти команды потребляют значительный объем флеш-памяти. Любое использование Serial.println требует включения в скетч примерно 500 байт библиотечного кода. Поэтому, убедившись в безупречной работе скетча, удалите или закомментируйте все такие команды.
Откажитесь от использования загрузчика
В главе 2 рассказывалось, как запрограммировать микроконтроллер непосредственно через контакты ICSP на плате Arduino с применением аппаратных программаторов. Такой подход поможет сэкономить пару килобайт, так как не требует установки загрузчика.
Статическое и динамическое размещение в памяти
Если вы, подобно автору книги, имеете опыт разработки крупномасштабных систем на таких языках, как Java или C#, вам наверняка приходилось создавать объекты во время выполнения и позволять сборщику мусора освобождать занимаемую ими память без вашего участия. Этот подход к программированию в принципе непригоден для программ, выполняющихся на микропроцессорах, которые имеют всего 2 Кбайт памяти. Ведь в Arduino просто нет никакого сборщика мусора, и, что более важно, в программах, которые пишутся для Arduino, выделение и освобождение памяти во время выполнения редко бывают необходимы.
Жалоба
Напишите нам, и мы в срочном порядке примем меры.