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

Содержание

Arduino.ru

Расширение оперативной памяти Arduino

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

Подскажите пожалуйста, как можно расширить оперативную память Arduinовского микроконтроллера

В конечном итоге должно получится что-то типо навигатора без GPS приемника, который будет обрабатывать движимую картинку. На сколько я понимаю ОЗУ стандартного микроконтроллера недостаточно для обработки движимой карты. Какую плату лучше взять и что с ней нужно делать?

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

Можно использовать карту sd для расширения памяти.

Но «обрабатывать движимую картинку» не вижу как это реализовать на Arduino в чистом виде.

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

>Можно использовать карту sd для расширения памяти.

Я думаю тут имелось ввиду расширение именно оперативной памяти. Что-бы можно было объявить буффер размером в 20kb,уйти очень глубоко в рекурсию и т.п.. С sd данные нужно будет подгружить и обрабатывать «по кусочкам». Это как в компе — есть «оперативка», а есть «винчестер». SD-карта это «винчестер», а хочется побольше «оперативки».

Поддержка «внешней памяти» похоже есть в камнях «ATmega640/1280/2560». Там, вроде, можно подключить до 64-рех килобайт «как родные». Копать нужно откуда-то из docs.google.com/viewer , страница 27, раздел «Using the external memory interface».

Если сможете разобратся и подключить — отпишитесь тут. Думаю многим будет «интерестно/полезно».

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

А вот тут hackaday.com/2011/09/05/upgrading-ram-in-an-arduino-mega/ похоже вообще до 512kb внешней подключили. Правда как я понял «одновременно» можно только к 64k достучатся, нужно «переключить банки». И похоже что все это именно для работы с видео. Как и у вас.

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

безумная затея но если вы хотите изобретать велосипед возмите старую память от компа организуйти виртуальную 32 битную щину использую 32 ножки мк и флаг вам в руки ну или уйье совсем в нереал взяв флеш микросхему памяти с SPI интерфейсом от оперативки написать код инторпретатор адреса

А МОЖНО СДЕЛАТЬ ПО УМУ либо дождаться выхода новой платы ардуино http://arduino.cc/forum/index.php?topic=72664.0 если питаете слабость к этому названию или взять более подходящий контроллер благо их миллионы и цены на порядок дешевли ардуино

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

ну или уйье совсем в нереал взяв флеш микросхему памяти с SPI интерфейсом от оперативки написать код инторпретатор адреса

Не получится — оперативная память подразумевает частые операции записи. Даже при ресурсе 10^6 циклов записи флеш-память достаточно быстро протрется до дыр. Для оперативки подходят только чистые RAM-чипы.

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

тут и нет частых рециклов записал и читай себе или вы карту раждый день будите перепровлять ?

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

Карта — да. Карта будет записываться однократно и читаться много-много раз. Но читать ее придется в оперативную память, в этой же оперативной памяти производить все необходимые преобразования, подготовку графических данных, скорее всего — формирование в RAM-буфере изображения, потом — пересылку этого изображения на дисплей. Исходя из минимального разрешения 320х240 и 24-бит цветности плюс запас на всяко-разные необходимые при вычислениях переменные, получим минимальную потребность в RAM где то в районе 300К.

Именно в RAM — то есть в памяти, некоторые участки которой будут перезаписываться с частотой 1-100 раз в секунду (а то и чаще). Что на всяких Flash, eeprom, NAND приведет к «протиранию» этих некоторых участков через 10-100 тыс секунд работы программы. То есть то ли через сутки, то ли через год.

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

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

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

[. ] кадровый буфер рормирунтся в мк и все

Где конкретно в мк формируется кадровый буфер?

Каков его размер кадрового буфера?

С помощью каких операций формируется кадровый буфер?

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

конкретно в памяти мк вы резервируете под переменную

считываете данные из флешки пишите их в переменную

выталкиваете их в жки

стираете переменную и повторяете все заново

сколько надо на вывод одной строки ?

сколько строк в секуду вы можите с меги выдовать ?

мне кажется люди уже мыслят гегогерцами и гегобайтами

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

сколько надо на вывод одной строки ?

Организация флэш-памяти такова, что быстрый доступ возможен только при постраничном чтении-записи. Минимальный размер страницы — 512 байт (может оказаться и больше — нужно смотреть спецификацию на конкретную микросхему памяти). Поэтому «переменная» будет у вас длиной как минимум 512 байт (буфер называется). В ATMega у вас на все про все — 2048 байт (это на ATMega328, 168 и 8 вообще рассматривать для этой задачи нельзя). В которых надо разместить все переменные (не дай бог строковые), часть которой отъедает — и нехило отъедает — стек, которая, кроме того, используется под буфер UART (128 байт). В общем, просто перекачать часть флеш-памяти в память дисплея, может быть, и хватит. А вот для элементарного зума или наложения на карту каких-нибудь спрайтов — сомневаюсь.

сколько строк в секуду вы можите с меги выдовать ?

Чисто теоретически — на полноцветный дисплей 320х240 — где-то 2800 строк по 320 пикселов при кристалле 16 МГц.

— если не потребуется дополнительных вычислений для определения места очередной строки в файле в котором хранится карта и будут отсутствовать накладные расходы на перемещение указателей в точку, где начинается очередная считываемая строка

— если принимаемые из флеш-памяти не потребуют никакой дополнительной обработки

— если удастся каждый пиксел графический информации пропихивать за три такта (поскольку R, G и B)

— если очередная передаваемая строка не окажется расположенной на двух соседних страницах флеш-памяти. В этом случае придется «сделать остановку» на чтение очередной страницы флеш-памяти.

В общем, если все срастется максимально удачно и ваш алгоритм выжмет из конкретного железа максимум возможного (то есть будет неспособен показать те же результаты на несколько измененной конфигурации), то этот показатель, может быть, уменьшится еще в пару раз. А если не повезет — то и в 5-6-10 раз. То есть получим 1-2-3 кадра в секунду. Или другими словами — заметное невооруженным глазом перестроение изображения. Несмертельно.

Как вы правильно заметили, есть люди, которые мыслят ГЕГОбайтами и ГЕГОгерцами. Но делают они это не на железе, в котором жалкие КИЛОбайты и МЕГАгерцы.

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

Источник

Подключение внешней SRAM 512 Кбайт к Arduino Mega. Часть 1 — Теория

Arduino Arduino Mega

Очень часто при разработке приложений и систем на базе Arduino встает проблема нехватки памяти, присущая среде программирования микроконтроллеров. С помощью серия Arduino Mega возможно решение данной проблемы, т.к.имеются варианты исполнения данной платформы с Flash-памятью программ 128 КБайт или 256 КБайт. А как насчет встроенной памяти SRAM? Максимальный объем в нашем случае (Arduino Mega) не превышает 8 КБайт.

К счастью, серия Mega позволяет работать с внешней SRAM, и что самое главное, программа может получать беспрепятственно доступ к ней, как если бы это была внутренняя SRAM микроконтроллера. В статье мы рассмотрим аппаратное и программное решение задачи расширения памяти на базе микроконтроллера Atmel AVR ATmega1280.

XMEM

В техническом описании на микроконтроллер ATmega1280 содержится вся информация для инженеров, необходимая для расширения памяти. В документе это 9 глава «External Memory Interface» (XMEM). Блок схема ниже поясняет, как микроконтроллер организует связь с внешней памятью.

Рис. 1. Блок-схема организации работы с внешней памятью с помощью XMEM. Блок в центре — высокоскоростной 8-битный регистр-защелка.

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

Мультиплексирование и защелка

Адресное пространство Arduino Mega позволяет подключать внешнюю память объемом до 64 КБайт при 8-битной организации. Обычно для этого требуется немалое количество линий ввода/вывода: 16 для адресной шины, 8 для шины данных и как минимум еще 2 для управления.

Чтобы сократить количество используемых линий ввода/вывода, для подключения внешней памяти, микроконтроллер мультиплексирует младшие 8 адресных линий с 8 линиями данных, экономя при этом 8 линий. При такой реализации используется 8-битная защелка (8-разрядный регистр), при этом временные диаграммы работы с внешней памятью следующие:

Рис. 2. Временные диаграммы работы интерфейса внешней памяти.

Период времени, обозначенный красной линией, показывает промежуток между установкой действительного адреса (достоверные данные на линиях A0 – A15) и началом передачи данных (действительные данные на линиях D0 – D7). Логика микроконтроллера начинает операции с внешней памятью, подтверждая действительный адрес на линиях A0 – A15. Мультиплексированные линии проходят через 8-разрядный регистр-защелку, который работает в «прозрачном» режиме (transparent mode). Затем регистр переключается в режим хранения (Hold mode), при котором игнорируются изменения на входе и продолжается хранение последних принятых данных на выходе. Микроконтроллер затем устанавливает действительные данные на мультиплексированных линиях (это уже будут данные D0 – D7) и, в результате, мы имеем успешно установленные все линии для работы с внешней памятью и сохранили 8 линий ввода/вывода, которые могут понадобиться при работе основного приложения.

Стоит заметить, что регистр-защелка должен успевать работать на тактовой частоте 16 МГц, поэтому серия 74HC не подойдет для нашей цели. В техническом описании на микроконтроллер рекомендуется серия 74AHC.

Адресация при объеме SRAM более 64 КБайт

Теперь, основная задача – это работа с внешней памятью объемом 512 КБайт с адресным пространством всего на 64 КБайт. Решение – разделить 512 КБайт на 8 банков памяти по 64 КБайт и сделать управление «видимостью» одного банка в каждый момент времени с использованием 3 выводов микроконтроллера.

Память объемом 512 КБайт при обращении к ней требует 19-битного адреса (адресные линии A0 – A18). Линии A0 – A15 мы подключим по интерфейсу xmem (интерфейс внешней памяти микроконтроллера) и управление 3 оставшимися (A16 – A18) осуществим с помощью цифровых линий ввода/вывода микроконтроллера.

Рис. 3. Разделение внешней SRAM на 8 банков памяти объемом 64 КБайт.

Следующее ограничение, которое накладывает карта памяти микроконтроллеров ATmega – нижние 8 КБайт SRAM (адресное пространство для такого объема) всегда занимаются внутренней памятью микроконтроллера. Это означает, что адресация внешней памяти осуществляется в диапазоне 0х2200 – 0хFFFF, т.е. мы теряем 69632 байта из общего объема 524288 Байт внешней SRAM. В спецификации на микроконтроллер объясняется метод адресации этих потерянных 8 КБайт, однако автор посчитал, что в данной задаче это не актуально.

Во второй части статьи представлены принципиальная схема подключения внешней ОЗУ к микроконтроллеру, список компонентов и рисунки печатной платы.

Перевод: Vadim по заказу РадиоЛоцман

Источник

Оптимизация кода

С ростом навыков и созданием всё более глобальных проектов вы столкнётесь с тем, что “Ардуина” перестанет справляться с тем объёмом работы, который вы хотите от неё получить. Может банально не хватать быстродействия в расчётах, обновлении информации на дисплеях, отправки данных и прочих ресурсозатратных действий, а ещё может просто закончиться память! Самое страшное, когда заканчивается оперативная память: она может это сделать абсолютно незаметно и устройство начнёт вести себя неадекватно, перезагрузится или попросту зависнет. Как этого избежать? Нужно оптимизировать свой код! Информации по этому поводу в Интернете очень мало, поэтому я опишу всё, с чем сталкивался лично.

С чем компилятор справится сам

Модификатор volatile

Компилятор оптимизирует действия с переменными, которые не помечены как volatile , так как это прямая команда “не оптимизируй меня”. Это важный момент, потому что действия с такими переменными (если они нужны) надо оптимизировать вручную. Компилятор не будет оптимизировать вычисления, вырезать неиспользуемые переменные и конструкции с их применением!

Вырезание неиспользуемых переменных и функций

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

Оптимизация вычислений

Компилятор оптимизирует некоторые вычисления:

  • Заменяет типы данных на более оптимальные там, где это возможно и не повлияет на результат. Например val /= 2.8345 выполняется в 4 раза дольше, чем val /= 2.0 , потому что 2.0 была заменена на 2 .
  • Заменяет операции целочисленного умножения на степени двойки (2^n) битовым сдвигом. Например, val * 16 выполняется в два раза быстрее, чем val * 12 , потому что будет заменена на val .
    • Примечание: для операций целочисленного деления такая оптимизация не проводится и её можно сделать вручную: val >> 4 выполняется в 15 раз быстрее, чем val / 16 .
  • Заменяет операции взятия остатка от деления % на степени двойки битовой маской (остаток от деления на 2^n можно вычислить через битовую маску: val & n ). Таким образом например 100 % 10 выполняется в 17 раз дольше, чем 100 % 8 , учитывайте это.
  • Предварительно вычисляет всё, что можно вычислить (константы). Например val /= 7.8125 выполняется столько же, сколько val /= (2.5*10.0/3.2+12.28*3.2) , потому что компилятор заранее посчитал и подставил результат всех действий с константами.
  • Использует для умножения и деления целых чисел ячейку в два байта (знаковую). Это очень опасно, потому что результат может оказаться больше. Для выражений, результат которых превосходит 32’768, нужно принудительно приказать компилятору выделить больше памяти при помощи (long) или другими способами, мы разбирали это в уроке про математические операции.

Вырезание условий и свитчей

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

Если объявить num как обычную переменную – в скомпилированный код попадёт вся конструкция целиком, три условия или весь свитч. Если num сделать константой const или дефайном #define – компилятор вырежет весь блок условий или свитч и оставит только содержимое, которое получается при заданном num . В этом очень легко убедиться, скомпилировав код и посмотрев на объём занимаемой памяти в логе компилятора. При помощи данного трюка можно ускорить выполнение некоторых функций и уменьшить занимаемое ими место в памяти, например для создания универсальной библиотеки.

Рассмотрим весьма полезный пример: функция быстрого чтения состояния цифрового пина для ATmega328 (остальные быстрые аналоги ищи тут):

Вызов fastRead(переменная) занимает 6 тактов процессора (0.37 мкс), вызов fastRead(константа) – 1 такт (0.0625 мкс)! Для сравнения, вызов стандартной digitalRead(переменная) занимает 58 тактов, а digitalRead(константа) – 52 такта. То есть при помощи оптимального кода и понимания логики работы компилятора можно сделать “digitalRead()” в 58 раз быстрее, чем это предлагает библиотека Arduino.h, при том ничуть не теряя в удобстве использования!

Если вы пишете свою библиотеку или класс, то всё будет чуть труднее: константы внутри класса не являются для компилятора весомым поводом для вырезания условий и свитчей, даже если это const и он объявлен в списке инициализации класса. Для того, чтобы компилятор вырезал условие или свитч внутри реализации методов класса, ему нужна внешняя константа/дефайн или шаблон template . Напомню, что шаблон позволяет также создавать внутри класса массив заданного размера, об этом рассказывал в уроке про библиотеки.

Тестовый класс с дигиталРидами (для AVR) разных вариантов:

Результаты бенчмарка в тактах процессора (для AVR):

volatile variable constant define external const template const
digitalRead 58 58 58 52 52 52
pinRead 6 6 6 1 1 1
bitRead(PIND, pin); 3 1 1 1 1 1

Оптимизация скорости

Начнём с вопроса как измерить время выполнения кода. Для большинства случаев достаточно стандартной конструкции с millis()/micros() – запомнить текущее время, выполнить действие, вычесть запомненное время из текущего. Точность измерения тем выше, чем меньше в “измеряемом” коде используется запрет прерываний. Минимальная единица измерения (для AVR Arduino) – 4 микросекунды, это разрешение функции micros().

Для “взрослой” оптимизации кода (вычисления, IO) этой точности будет мало. Вот вам инструмент для замера времени выполнения кода с точностью до одного такта процессора (0.0625 микросекунды для 16 МГц тактирования), выводит время выполнения в тактах процессора и микросекундах. Код работает на таймере 1, максимальная длительность измерения – 4 миллисекунды! Код для ATmega328 (Arduino NANO).

Использовать переменные соответствующих типов

Тип переменной/константы не только влияет на занимаемый ей объём памяти, но и на скорость вычислений! Привожу таблицу для простейших не оптимизированных компилятором вычислений. В реальном коде время может быть меньше. Примечание: время приведено для AVR и кварца 16 МГц.

Тип данных Время выполнения, мкс
Сложение и вычитание Умножение Деление, остаток
int8_t 0.44 0.625 14.25
uint8_t 0.44 0.625 5.38
int16_t 0.89 1.375 14.25
uint16_t 0.89 1.375 13.12
int32_t 1.75 6.06 38.3
uint32_t 1.75 6.06 37.5
float 8.125 10 31.5

Как вы можете заметить, время вычислений отличается в разы даже для целочисленных типов данных, так что всегда нужно прикидывать, какая максимальная величина будет храниться в переменной и выбирать соответствующий тип данных. Стараться не использовать 32-битные числа там, где они не нужны, а также по возможности не использовать float . В то же время, умножить long на float будет выгоднее, чем делить long на целое число. Такие моменты можно считать заранее как 1/число и умножать вместо деления в критических ко времени выполнения моментах кода. Также читай об этом чуть ниже.

Отказаться от float

Из таблицы выше можно увидеть, что на действия с числами с плавающей точкой микроконтроллер тратит в несколько раз больше времени по сравнению с целочисленными типами. Дело в том, что у большинства микроконтроллеров AVR (что стоят на Ардуинах) нет аппаратной поддержки вычислений float чисел и эти вычисления производятся программными средствами. На взрослых микроконтроллерах ARM такая поддержка, к слову, имеется. Что же делать? Просто избегайте использования float там, где задачу можно решить целочисленными типами. Если нужно перемножить-переделить кучу float ‘ов, то можно перевести их в целочисленный тип, умножив на 10-100-1000, смотря какая нужна точность, вычислить, а затем результат снова перевести в float . В большинстве случаев это получается быстрее, чем вычислять float напрямую:

Существует также такая штука как fixed point – числа с фиксированной точкой. С точки зрения пользователя они являются обычными десятичными дробями, но по факту являются целочисленными типами и вычисляются соответственно быстрее. Нативной поддержки fixed point в Arduino нет, но можно работать с ними при помощи самописных функций, макросов или библиотек, под спойлером найдёте пример, который можно использовать на практике:

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

Выбирать множители степенями двойки

Как рассказано в первой главе, компилятор заменяет целочисленные операции умножения на (2^n) битовыми сдвигами, которые выполняются гораздо быстрее. Как это использовать: по возможности писать свои алгоритмы так, чтобы в математических операциях получались степени двойки (2 4 8 16 32 64 128…). Например, умножение числа на 16 выполняется в два раза быстрее, чем на 15. Речь идёт о нескольких микросекундах, но иногда и это бывает важно. Примечание: слово целочисленный здесь не просто так, для float трюк не работает!

Заменить деление битовым сдвигом

Что касается целочисленного деления на степени двойки, то компилятор не заменяет его сдвигом и это можно и нужно сделать вручную. Например, деление long числа на 16 ( val / 16 ) выполняется в 15 раз дольше, чем операция сдвига с таким же результатом: val >> 4 (сдвинуть на 4 бита, 16 == 2 в степени 4). Для лонгов получаем 40 мкс на деление, и 2.5 мкс на сдвиг. Экономия! Примечание: слово целочисленный здесь не просто так, для float трюк не работает!

Заменить деление умножением на float

Опять же по таблице выше можно увидеть, что деление для всех типов данных выполняется гораздо дольше умножения, поэтому иногда бывает выгоднее заменить деление на целое число умножением на float . И да, пытаться усидеть на двух стульях, стараясь не использовать float и использовать его вместо деления:

Заменить возведение в степень умножением

Для возведения в степень у нас есть удобная функция pow(a, b) , но в целочисленных расчётах лучше ей не пользоваться: она выполняется гораздо дольше ручного перемножения, потому работает с float , даже если скормить ей целое:

Оптимизировать остаток от деления

Операция остаток от деления % выполняется сравнительно долго, как и само деление. Нужно помнить, что компилятор оптимизирует остаток от деления на 2^n , заменяя его битовой маской, взятие которой выполняется за пару тактов процессора, что в несколько десятков раз быстрее! Например val % 8 будет автоматически оптимизировано в val & 0b111 . Нужно по возможности писать свой алгоритм так, чтобы остаток от деления искался именно от 2^n . Например, при работе с кольцевым буфером можно сделать его размер равным 16, 32, 64, 128… и ускорить тем самым операцию перехода в начало буфера, как это обычно делается buffer_pos % buffer_size .

Предварительно вычислять то, что можно вычислить

Некоторые сложные вычисления требуют выполнения одних и тех же действий несколько раз. Гораздо быстрее будет создать локальную переменную, в неё “посчитать” и использовать в дальнейших расчётах. Примечание: большинство расчётов компилятор оптимизирует сам, например действия с константами и конкретными цифрами.

Ещё хороший пример: расчёт величин, которые ведут себя предсказуемо, например гармонические функции sin() и cos() . На их вычисление уходит довольно-таки много времени – 119.46 мкс! На практике синусы/косинусы практически никогда не вычисляют средствами микроконтроллера, их вычисляют заранее и сохраняют в виде массива. Да, опять два стула: тратить время на вычисление или занимать память уже посчитанными данными. Также не забываем, что компилятор сам оптимизирует вычисления и делает это весьма неплохо.

Не использовать delay() и подобные задержки

Вполне очевидный совет: не используйте delay() там, где можно обойтись без него. А это 99.99% случаев. Используйте таймер на millis() , как мы изучали в уроке про многозадачность.

Заменить Ардуино-функции их быстрыми аналогами

Если в проекте очень часто используется периферия микроконтроллера (АЦП, цифровые входы/выходы, генерация ШИМ…), то нужно знать одну вещь: Ардуино (на самом деле Wiring) функции написаны так, чтобы защитить пользователя от возможных ошибок. Внутри этих функций находится куча различных проверок и защит “от дурака”, поэтому они выполняются гораздо дольше, чем могли бы. Также некоторая периферия микроконтроллера настроена так, что работает очень медленно. Пример: digitalWrite() и digitalRead() выполняются около 3.5 мкс, когда прямая работа с портом микроконтроллера занимает 0.5 мкс, что почти на порядок быстрее. analogRead() выполняется 112 мкс, хотя если его настроить чуть по-другому, он будет выполняться почти в 10 раз быстрее, не особо потеряв в точности. О таком “разгоне” Ардуино мы поговорим в отдельном уроке. В статье полезные алгоритмы для Arduino я выложил несколько “быстрых и лёгких” аналогов Ардуино-функциям.

Использовать switch вместо else if

В ветвящихся конструкциях со множественным выбором по значению целочисленной переменной стоит отдавать предпочтение конструкции switch-case , она работает быстрее else if (изучали в уроках про условия и выбор). Но помните, что:

  • switch работает только с целочисленными данными
  • “Кейсы” должны быть константами

Помнить про порядок условий

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

Если flag имеет значение false , функция getSensorState() даже не будет вызвана! if будет сразу пропущен (или выполнен else , если он есть). Этим нужно пользоваться, расставляя условия в порядке возрастания процессорного времени, которое требуется для их вызова/выполнения, если это функции. Например, если наша getSensorState() тратит какое-то время для выполнения, то мы ставим её после флага, который является просто переменной. Это позволит сэкономить процессорное время в те моменты, когда флаг имеет значение false .

Использовать битовые операции

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

Использовать указатели и ссылки

Вместо передачи “объекта” в качестве аргумента функции, передавать его по ссылке или по указателю: процессор не будет выделять память под копию аргумента и создавать эту копию в качестве формальной переменной – это сэкономит время! Подробнее про указатели и ссылки читайте в отдельном уроке.

Использовать макро и встроенные функции

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

Использовать константы

Константы ( const или #define ) “работают” гораздо быстрее переменных при передаче их в качестве аргументов в функции. Делайте константами всё, что не будет меняться в процессе работы программы! Пример:

Почему это происходит? Компилятор оптимизирует код, и с константными аргументами он может выбросить из функции почти весь лишний код (если там есть, например, блоки if-else ) и она будет работать быстрее.

Миновать loop

Функция loop() является вложенной во внешний цикл с некоторыми дополнительными проверками, поэтому если вам очень важно минимальное время между итерациями loop() – просто работайте в своём цикле for(;;) , например вот так:

Кодить на ассемблере (шутка)

Arduino IDE поддерживает ассемблерные вставки, в которых на одноимённом языке можно давать прямые команды процессору, что обеспечивает максимально быстрый и чёткий код. Но у нас в семье о таком не шутят =)

Оптимизация памяти

Чаще всего мы сталкиваемся с нехваткой памяти: постоянной Flash или оперативной SRAM. После компиляции кода мы получаем сообщение о занимаемом объёме Flash/SRAM, это ценная информация. Flash память можно забивать на 99%, её объём не изменяется в процессе работы устройства, чего не скажешь о SRAM. Допустим, на момент запуска программы у нас занято 80% оперативной памяти, но в процессе работы могут появляться и исчезать локальные переменные, которые добьют занимаемый объём до 100% и устройство скорее всего перезагрузится или зависнет. Опасность ещё в том, что “раздел” оперативной памяти начинает фрагментироваться, т.е. появляются маленькие пустые места, которые микроконтроллер не может занять новыми появляющимися данными. Да, всё как на компьютере, только кнопки “дефрагментировать” у нас нет. Поэтому нужно или учиться вручную заниматься менеджментом памяти, или стараться оставлять побольше свободной SRAM.

Прилагаю скетч-пример с функцией, которая выводит объём свободной SRAM. Скачать с FTP сайта (нажать правой кнопкой – сохранить файл).

Использовать переменные соответствующих типов

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

Название Вес Диапазон
boolean 1 байт 0 или 1, true или false
char (int8_t) 1 байт -128… 127
byte (uint8_t) 1 байт 0… 255
int (int16_t) 2 байта -32 768… 32 767
unsigned int (uint16_t) 2 байта 0… 65 535
long (int32_t) 4 байта -2 147 483 648… 2 147 483 647
unsigned long (uint32_t) 4 байта 0… 4 294 967 295
float (double) 4 байта -3.4028235E+38… 3.4028235E+38

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

Использовать define

Для хранения констант в стиле номеров пинов, каких-то настроек и постоянных значений используйте не глобальные переменные, а #define . Таким образом константа будет храниться в коде программы, во Flash памяти, которой много.

Использовать директивы препроцессора

Если у вас какой-то комплексный проект, где перед прошивкой включаются/выключаются некоторые куски кода или библиотеки – используйте условную компиляцию при помощи директив #if , #elif , #ifdef и прочие, о которых мы говорили в уроке про условия

Использовать progmem

Для хранения больших объемов постоянных данных (массив битмапов для вывода на дисплеи, строки с текстом, “таблицы” синуса или других корректирующих значений) используйте PROGMEM – возможность хранить и читать данные во Flash памяти микроконтроллера, которой гораздо больше, чем оперативной. Особенность состоит в том, что данные во Flash пишутся во время прошивки, и изменить их потом будет нельзя, можно только прочитать и использовать.

Читайте подробный урок по PROGMEM.

Использовать F() макро

Если в проекте используется вывод в COM порт фиксированных текстовых данных, то каждый символ будет занимать один байт оперативной памяти, также это относится к строковым данным и выводам на дисплей. У нас есть на вооружении встроенный инструмент, который позволяет хранить строки во Flash памяти, использовать его удобнее того же PROGMEM . Работает очень просто и эффективно, позволяя делать девайс с расширенным общением/отладкой через Serial порт и не думать о забитой оперативке:

Не использовать float

Как мы обсуждали в уроке про типы данных, поддержка вычислений с float является программной (для AVR), то есть грубо говоря для вычислений “подключается библиотека”. Однократно использовав в коде все арифметические действия с float , вы подключите около 1000 байт кода во Flash память для поддержки этих вычислений. Также продублирую пример из предыдущей главы: если нужно хранить много float значений в оперативной или EEPROM памяти, то есть смысл заменить их целочисленными. Как это сделать без потери точности:

Не использовать объекты классов Serial и String

Пожалуй самые “жирные” по занимаемой памяти библиотеки – это стандартные объекты Serial и String. Если в коде появляется Serial, он сразу же забирает себе минимум 998 байт Flash (3% для ATmega328) и 175 байт SRAM (8% для ATmega328). Как только начинаем использовать строки String – прощаемся с 1178 байтами Flash (4% для ATmega328). Если Serial всё таки нужен – попробуйте использовать сильно облегчённый аналог стандартной библиотеки – microUART.

Использовать однобитные флаги

Вы должны быть в курсе, что логический тип данных boolean занимает в памяти Arduino не 1 бит, как должен занимать, а целых 8, т.е. 1 байт. Это вселенская несправедливость, ведь по сути мы можем сохранить в одном байте 8 флагов true / false , а на деле храним только один. Но выход есть: паковать биты вручную в байт, для чего нужно добавить несколько макросов. Пользоваться этим не очень удобно, но в критической ситуации, когда важен каждый байт, можно и заморочиться. Смотрите примеры:

Источник

Adblock
detector