Как увеличить память ардуино уно
Перейти к содержимому

Как увеличить память ардуино уно

  • автор:

Arduino.ru

В микроконтроллере ATmega168 , используемом на платформах Arduino, существует три вида памяти:

  • Флеш-память: используется для хранения скетчей.
  • ОЗУ (Статическая оперативная память с произвольным доступом): используется для хранения и работы переменных.
  • EEPROM (энергонезависимая память): используется для хранения постоянной информации.

Флеш-память и EEPROM являются энергонезависимыми видами памяти (данные сохраняются при отключении питания). ОЗУ является энергозависимой памятью.

Микроконтроллер ATmega168 имеет:

  • 16 Кб флеш-памяти (2 Кб используется для хранения загрузчика)
  • 1024 байта ОЗУ
  • 512 байт EEPROM

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

char message[] = «I support the Cape Wind project.»;

занимает 32 байта из общего объема ОЗУ (каждый знак занимает один байт). При наличии большого объема текста или таблиц для вывода на дисплей возможно полностью использовать допустимые 1024 байта ОЗУ.

При отсутствии свободного места в ОЗУ могут произойти сбои программы, например, она может записаться, но не работать. Для определения данного состояния требуется превратить в комментарии или укоротить строки скетча (без изменения кода). Если после этого программа работает корректно, то на ее выполнение был затрачен весь объем ОЗУ. Существует несколько путей решения данной проблемы:

  • При работе скетча с программой на компьютере можно перебросить часть данных или расчетов на компьютер для снижения нагрузки на Arduino.
  • При наличии таблиц поиска или других больших массивов можно использовать минимальный тип данных для хранения значений. Например, тип данных int занимает два байта, а byte — только один (но может хранить небольшой диапазон значений).
  • Неизменяемые строки и данные во время работы скетча можно хранить во флеш-памяти. Для этого необходимо использовать ключ PROGMEM.

Для использования EEPROM обратитесь к библиотеке EEPROM.

Оптимизация кода Ардуино и ускорение работы.

В этом примере, я покажу как можно сократить использование памяти и ускорить работу программы в 5 раз. Думаете это невозможно или трудно? Как оказалось совсем не трудно. Надо всего лишь придерживаться нескольких правил и ваш код будет работать в 5 раз быстрее, а памяти Ардуино вам хватит на любую проект.

Приветствую всех моих подписчиков и гостей канала.
Сегодня поговорим про оптимизацию кода в скетче. Что нужно сделать чтобы не тратить драгоценную память и как сделать так, чтобы ваш проект летал.
Начнём с самого начала. Сколько занимает пустой скетч. Для этого откроем Arduino IDE и загрузим в плату пустой скетч. Вообще то он не пустой, так как в нём уже есть пара функций, это setup и loop. Как от них избавиться я покажу чуть позже. Видим, что сейчас мы уже заняли некоторый объём памяти.
444 байта или 1% FLASH памяти.

Эта строка показывает объем флеш-памяти в Arduino, занятым скетчем, и процент от предела в 30 Кбайт, так как 2 Кбайт уже занято загрузчиком. Внимательно смотрите за этими данными, при превышении 70 процентов возможны сбои в работе программы.
и 9 байт динамической памяти ОЗУ или RAM.

ОЗУ в Arduino используется для хранения переменных и других данных, имеющих отношение к выполняющейся в данный момент программе. ОЗУ — энергозависимая память и после отключения питания память очищается.

Теперь добавим Serial.begin что бы иметь возможность выводить информацию в монитор порта. Значения сразу изменились. Программа пока ничего не делает, но уже потребляет
1438 байта или 4% памяти FLASH и 184 байта или 8% динамической памяти. Получается, что эта функция занимает 1000 байт в памяти Ардуино и 175 байт динамической памяти. Пустячок, но не приятно.

Добавим простую строчку вывода на экран. Значения подскочили. И теперь памяти стало
1496 байта или 4% памяти FLASH и 210 байта или 10% динамической памяти. Небольшое изменение, но это всего 1 ничего не значащая строчка.
Этим мы подготовили программу для того чтобы измерить скорость скетча. Измерять будем в микросекундах, так как это самое минимальное что может измерить процессор.

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

Узнаем и сохраним в переменную TIME1 текущее время начала выполнения кода.
Создадим цикл на 1000 итераций. Если проще, то светодиод мигнёт 1000 раз, но вы этого не заметите, так как это произойдёт очень быстро. Затем мы сохраним время после выполнения кода и подсчитаем разницу времени.
То же самое сделаем с другим примером, но там мы не будем обращаться к digitalwrite а будем работать напрямую с регистрами контроллера. Как я сказал дальше я всё объясню. Так же сохраним время до и после, и подсчитаем разницу.

Ну и в конце узнаем во сколько раз второй код выполняется быстрее первого.
Откроем монитор порта и посмотрим. Так как весь код я разместил в setup, то он сработает всего 1 раз.
Видите, на выполнение 1 примера контроллеру понадобилось 7544 микросекунды, а второй пример занял всего 1324. Итого получилось что обращаясь непосредственно к портам контроллера мы получаем ускорение работы в 5 раз. Правда не плохо?
Это значит, что 1 выполнение команды digitalwrite занимает примерно 7,5 микросекунды, а обращение к порту и запись непосредственно в регистр 1,3 микросекунды.

Памяти стало еще немного меньше.
1716 байта или 5% памяти FLASH и 194 байта или 9% динамической памяти. Мы истратили ещё 220 байт памяти и 20 байт динамической памяти.
Теперь выведем на экран значения. Так как цикл loop крутится бесконечно, то и время работы будет постоянно увеличиваться. Вычислив среднее значение мы получим 200 – 208 микросекунд. Так что среднее работы почти пустого скетча 200 микросекунд. Много это или мало решайте сами. Кстати так вы узнаете оптимист вы или пессимист. Кто не понял, это отсылка на стакан, какой он полупустой или наполовину полный.

Подведём итог.
Пустой скетч занимает 444 байта FLASH памяти и 9 байт динамической памяти ОЗУ. Слегка заполненный занимает уже 1716 байта или 5% памяти FLASH и 194 байта или 9% динамической памяти.
Мы истратили 4% FLASH памяти, и 9% памяти ОЗУ.

Это было про ускорение работы, а теперь снова вернёмся к оптимизации кода.

Теперь загрузим обычный пример блинк и посмотрим сколько он занимает. Мигать будем светодиодом который находится на плате Ардуино и который подключен к 13 выводу Ардуино УНО и Ардуино НАНО.
Вы сами видите сколько памяти задействовано при работе этого скетча. А можно ли как-то уменьшить это значение. Оказывается да, и несколькими способами. Самым агрессивным, но немного трудный способ я сейчас покажу.
А потом перейдём к более простому, и если останется время, то я более подробно остановлюсь на сложном способе.
Во первых полностью уйдём от setup и loop. Заменим их одной main.
Сначала посмотрим на распиновку платы Ардуино НАНО, у УНО будет аналогично.
Возьмём порт B. Именно там и находится наш светодиод.

  • Нулевой бит этого порта соответствует выходу d8
  • Первый порту d9
  • Второй порту d10
  • Третий порту d11
  • Четвёртый порту d12
  • Пятый порту d13

А так как светодиод находится на тринадцатом выводе получается нам нужен пятый бит, так как отсчёт ведётся от нуля.
Отсчитываем пятый бит справа на лево и ставим единичку для включения светодиода и нолик для выключения. Delay здесь немного отличается от привычного нам, но 1000 миллисекунд, по прежнему равно 1 секунде. Загружаем скетч и смотрим результат. Светодиоды мигают так же как и в прежнем скетче, раз в секунду, но посмотрите на то сколько занято памяти.
Мы получили экономию памяти больше чем в 5 раз. Думаю, что это не плохой результат. Но сомневаюсь, что многие будут использовать этот метод, поэтому будем использовать более простой и привычный нам Ардуинщикам способ.
Для этого примера я собрал небольшую схему состоящую из 8 светодиодов и LED индикатора. Конечно можно было бы работать и с 1 светодиодом, но тогда будет очень маленький разрыв в значениях и это будет не так заметно.
Скетч я специально не оптимизировал, что бы показать как не надо делать. И наверняка многие из так делают, и им будет интересно посмотреть почему так делать не правильно.
В начале идёт блок комментариев и я советую вам всегда вначале писать что это за программа, когда вы её сделали и что она делает. Потому что через некоторое время вы полностью забудете что это и это будет вам подсказкой.

Дальше идёт подключение LCD индикатора. Затем я создал именованные переменные соответствующие цветам светодиодов. Всегда старайтесь называть переменные так что бы было понятно для чего он были созданы, и номера выходов Ардуино к которым они были подключены.
Затем я сделал несколько пауз для мигания светодиодами. Все они имеют разный интервал. Сначала они мигают часто, но с каждым новым светодиодом мигание становится реже.
Подключил Serial.begin и сделал несколько настроек индикатора. Так как для работы светодиодов выводы Ардуино должны быть установлены как выходы, то я так и сделал. Выходы надо указывать обязательно в отличие от входов. Так что если вы подключаете какой-нибудь датчик и он передаёт данные на вход Ардуино, то указывать INPUT не обязательно, хотя и не запрещено.
Ну и в цикле loop мы по очереди включаем светодиоды делаем паузу, выводим сообщение на индикатор и пишем в монитор порта, что включили светодиод. Затем делаем паузу чтобы видеть что светодиод зажёгся, и гасим его, опять же пишем в монитор что светодиод погас. Снова делаем паузу и переходим к следующему светодиоду. В конце включаем все светодиоды и выключаем их. И вот такой простенький пример занимает аж 27% памяти Ардуино и целых 67% памяти ОЗУ.
Теперь будем оптимизировать код.

Начнём с комментариев. Многие не пишут комментарии, потому что боятся что это занимает память контроллера. Проверим так ли это. Для теста помимо обычного комментария я добавил ещё стихотворение, что бы было побольше текста.
Вы видите сколько памяти сейчас занимает наш код. Я выведу этот результат сверху экрана, что бы постоянно видеть первоначальный результат. А теперь удалим комментарий и посмотрим сколько у нас теперь памяти. Как видите, памяти осталось столько же. Так что не экономьте на комментариях, они вам ещё не раз пригодятся.

Поехали дальше.
Разберёмся с переменными которые мы создаём в которых мы сохраняем номера выводов микроконтроллера. Например, я использовал 8 светодиодов и подключил их к выходам с 5 по 12 микроконтроллера и использовал для этого переменные с типом int. Это можно делать, но это не правильно, так как каждая такая переменная отбирает по 2 байта памяти.
И вообще переменные надо использовать только для тех значений которые изменяются в процессе работы, поэтому они и называются переменными. Нам же здесь надо использовать константы или директивs препроцессора, типа define. Давайте проверим тот и другой способ.
Сначала изменим значение на константы и загрузим скетч. Как видите значения не изменились. Теперь попробуем поменять на define. Объём памяти остался неизменным. Как же так. А всё дело в том, что умный компилятор может распознать какие переменные в процессе работы кода не изменяются и оптимизировать их. Но лучше на это не надеяться, а сразу указать один из этих двух способов. По желанию. Я в примере оставлю define. Так как он был последним.

Теперь подробнее разберёмся с паузами.
Так как мы заранее знаем какие они должны быть, то мы можем подобрать для них тип переменной. Для экономии памяти старайтесь выбирать наименьший из возможных типов. Так как это напрямую влияет на размер памяти. Вот самые распространённые типы. Так как у меня максимальное значение 1500, то я выбираю тип int. Так как он поддерживает значение от -32768 до 32767. Если у вас значение до 65535, то вы можете указать тип переменной unsigned int то есть беззнаковое и у вас всё равно будет занято 2 байта в памяти, а вот если у вас 65536, то тогда придётся указать тип переменной long А ЭТО УЖЕ 4 БАЙТА ПЯМЯТИ. Ну надеюсь понятно рассказал.
Теперь давайте попробуем эти переменные заменить на константы и посмотрим сможем ли мы сэкономить немного памяти. У нас получилось сэкономить 2 байта. Если честно, то странно, я думал, что получится сэкономить побольше. Проверим, может мы что делаем не так?

И если компилятор действительно такой умный и не используемые в коде переменные делает константами, то попробуем изменить их значение в коде и посмотреть что будет. Будем изменять паузу на единичку. Мы получили ошибку, так как все паузы сейчас объявлены как константы и в коде их нельзя изменить. Сделаем их снова переменными типа int.
Прошиваем код и видим, что у нас уменьшился объём флэш памяти и увеличился объём динамической памяти на 2 байта. Как раз столько занимает переменная типа инт. Значит всё работает и компилятор действительно умный, и не используемые в коде переменные преобразует в константы. Теперь изменим все паузы и посмотрим на сколько вырастет объём памяти.
Объём действительно подрос. И динамическая память теперь весит на 20 байт больше – это как раз 10 переменных пауза по 2 байта. Но наша задача уменьшить код а не увеличить. Поэтому возвращаем все на место и паузы, уже больше по привычке чем по необходимости ставим в define.

Пойдём дальше. Основная наша задача освободить как можно больше динамической памяти. Флэш можно забивать почти под завязку. А вот динамическая память нам очень нужна, а её всего 2 килобайта. Поэтому все строки что мы выводим в монитор порта мы перебросим во флэш, тем самым освободим от них ОЗУ.
Для этого перед каждой строчкой ставим макрос F() он размещает строку во флеш память, тем самым освобождая динамическую память. Флэш 32 килобайта а динамическая всего 2 кило, есть разница?

Прошиваем ардуино и смотрим результат. И вот эта не сложная операция освободила нам больше 30% динамической памяти, уменьшив размер в байтах в 2 раза. Задействовав всего 6% флэш памяти.

Посмотрим не пропало ли чего. Нет, строки как выводились, так и продолжают выводиться. Так что всё работает.

Теперь как и обещал рассмотрим сравнение двух примеров обычный и с доступом к регистрам портов. Это конечно тема для отдельного видео, поэтому здесь я расскажу очень коротко.
Из основного примера урока выкинем всё лишнее. Лишним оказались все переменные – это паузы и номера выводов микроконтроллера. Все эти значения будем сразу писать в код. Это конечно не правильно, позволит нам существенно сократить объём памяти. У нас получился вот такой короткий код, на 64 строчки.
А здесь этот же код, но с обращением к регистрам. Светодиоды мы подключили к выводам с 5 по 12 Ардуино. Смотрите насколько короче запись обращения к регистрам чем обычная запись с pinmode().
А так мы мигаем светодиодами. Эти строчки замена digitalWrite. Код сократился уже до 47 строчек.

Здесь, на примере установки цифрового пина Ардуино d7 я показал два способа установки регистров в HIGH и LOW, какой использовать выбирайте сами.
D7 находится на порту D, в седьмом регистре если считать с 0. Чтобы включить светодиод, надо установить его в единицу, а выключить в ноль.
Но как и у любого способа здесь тоже есть положительные и отрицательные сторона, свои за и против.
Это результат двух скомпилированных примеров. Какой из них с доступом к регистром думаю говорить не надо.

Со своей задачей мы справились. Мне хотелось рассказать побольше, но и так видео получилось большим, целых 17 минут, при среднем просмотре моих уроков в 3 минуты. Редко кто доходит и до половины. Если будут желающие то я сниму продолжение, есть ещё очень много способов уменьшить размер кода и увеличить скорость. Это только вершина айсберга. Спасибо тем кто досмотрел до конца.

Как увеличить память ардуино уно

Память EEPROM

Каждый микроконтроллер имеет в своем составе несколько типов памяти, которые имеют свои особенности и свое предназначение. Обычно мы имеем дело только с двумя типами: во Flash хранится программа и загрузчик, в SRAM программа загружается при выполнении. Но есть и ещё один очень полезный вид памяти — это EEPROM, энергонезависимая память, предназначенная для хранения разного рода настроек.

Далеко не во все микроконтроллеры производители встраивают EEPROM. Так, например, в Atmega328 (Arduino Uno) есть 1 кб такой памяти, в Atmega2560 (Arduino Mega) — целых 4кб. А в STM32 её нет вовсе.

Для справки. Современные микросхемы EEPROM могут выдерживать до миллиона циклов перезаписи, а данные в них могут храниться до 200 лет!

На этом уроке мы научимся записывать и читать данные из EEPROM, которая находится на борту Arduino Uno, а также поработаем с внешней EEPROM памятью с I2C интерфейсом.

Список необходимых компонентов

Для выполнения всех экспериментов в данном уроке, кроме модуля памяти, потребуются: Ардуино-совместимый контроллер, макетная плата, кнопка, резистор, потенциометр и немного проводов вилка-вилка и вилка-розетка. Необходимые компоненты можно добавить в корзину прямо здесь, и затем оформить заказ в нашем интернет-магазине.

Модули памяти в Arduino

Все микроконтроллеры AVR, на основе которых создана Arduino, имеют три вида памяти (как, впрочем, и большинство «бытовых» микроконтроллеров):

  • Flash-память (ROM) – в ней хранится программа, выполняемая процессором
  • ОЗУ (RAM) – статическая оперативная память, используется для размещения переменных, массивов и т.п.
  • EEPROM (ЭСППЗУ) – используется для хранения пользовательских данных

Flash и EEPROM являются так называемыми энергонезависимыми памятями (volatile memory). Это означает, что данные в них сохраняются и не изменяются даже при пропадании питания.

Пример на основе микроконтроллера ATMega 168

Давайте рассмотрим конкретный микроконтроллер ATMega 168, который применялся в старых версиях Arduino (NG, Duemilanove, ProMini). Он имеет на борту:

  • 16Кб памяти программ, из которых 2Кб используется для загрузчика кода.
  • 1Кб памяти ОЗУ
  • 0.5Кб (512Б) ЭСППЗУ (Электрически Стираемое Перепрограммируемое Постоянное Запоминающее Устройство)

Если провести аналогию между вашим десктопом и Arduino, можно заметить, что у ардуино до неприличия мало оперативной памяти. Необходимо обращать на это внимание и не допускать переполненности ОЗУ!

К примеру, строка:

char message[] = “I learn how to use Arduino memories.”;

Занимает в ОЗУ аж 36 байт, по байту на символ! Если полностью израсходовать оперативную память (к примеру, создать очень много длинных строк) могут произойти различные баги, например, программа будет зависать или не будет выполняться вообще.

Чтобы выявить этот баг, можно попробовать закомментировать в коде строки и попробовать запустить код без них. Если скетч выполнится корректно, то проблема именно в переполнении ОЗУ.

Для борьбы с этой ошибкой можно использовать следующие методы:

  • Переложить часть расчётов с Arduino, либо на другой контроллер, либо на ПК, если проект с ним связан
  • Правильно использовать типы данных (если достоверно известно, что значение переменной не будет больше 255, то можно использовать byte вместо int – это сэкономит байт памяти)
  • Перенести часть кода из ОЗУ в память программ с помощью спецификатора PROGMEM или макроса F()

Мы не рассмотрели работу с EEPROM; для этого существует отдельная библиотека EEPROM.

Если памяти для хранения данных вам катастрофически не хватает, рекомендуем обратить внимание на эти модули памяти для Arduino:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *