Руководство по памяти Arduino
Узнайте о встроенных блоках памяти плат Arduino® в этой статье.
Авторы: Arduino, José Bagur, Taddy Chung. Последнее обновление: 29.12.2023
Микроконтроллер (также известный как MCU) — это интегральная схема (ИС), как правило используемая для выполнения конкретных приложений или задач. Обычно такая ИС собирает информацию или данные из окружающей среды, обрабатывает их и генерирует конкретные выходные сигналы в соответствии с полученными данными. Сегодня микроконтроллеры повсюду: они являются неотъемлемой частью современных встроенных систем, которые можно найти практически везде в нашем мире — от умных часов до электромобилей; они даже присутствуют на поверхности Марса прямо сейчас.
Одной из важнейших частей микроконтроллера является его память; память хранит информацию временно или постоянно в микроконтроллерах и может использоваться в различных целях. В этой статье мы исследуем организацию памяти в микроконтроллерах с акцентом на те, что используются в платах Arduino®. Мы также рассмотрим несколько способов управления, измерения и оптимизации использования памяти в системах на базе Arduino.
Что такое память?
Блоки памяти являются важнейшими компонентами современных встроенных систем, особенно основанных на микроконтроллерах. Блоки памяти — это полупроводниковые устройства, которые хранят и извлекают информацию или данные; центральный процессор (CPU) микроконтроллера использует и обрабатывает данные, хранящиеся в блоках памяти, для выполнения конкретных задач.
Как показано на изображении ниже, блоки памяти в микроконтроллерах обычно описываются как массивы. Массивы памяти разделены на ячейки, которые могут хранить данные и доступны через уникальный идентификатор, представляющий их адрес или позицию относительно массива памяти. Информация в ячейках памяти хранится в виде двоичных цифр (битов), обычно организованных в байты (8 бит); она также может быть впоследствии извлечена MCU или другими компонентами микроконтроллерной системы.
Память в вычислительных системах может быть энергозависимой или энергонезависимой. Энергозависимая память — это временная память; это означает, что данные хранятся, пока система работает, но навсегда теряются при отключении питания. Энергонезависимая память — это постоянная память; данные не теряются даже при отключении питания системы.
Архитектуры памяти: введение
Компьютерная архитектура — обширная тема; мы сосредоточимся на общей картине, которая позволит нам понять, как организована память в микроконтроллерах, используемых в платах Arduino®.
На заре вычислительной техники возникли две компьютерные архитектуры, то есть организации компонентов внутри вычислительной системы: фон Неймана и Гарвардская.
Архитектура фон Неймана
Архитектура фон Неймана, названная в честь математика, физика и учёного в области вычислительной техники Джона фон Неймана, была впервые представлена в середине 40-х годов. Она также известна как Принстонская архитектура. Эта архитектура хранит программные данные и инструкции в одном блоке памяти.
Архитектура фон Неймана.
Оба компонента доступны для CPU через одну коммуникационную шину, как показано ниже. Архитектура фон Неймана является фундаментальной, поскольку практически все проекты цифровых компьютеров основаны на этой архитектуре.
Гарвардская архитектура
Гарвардская архитектура, названная в честь релейного компьютера Harvard Mark I, была впервые представлена в середине 40-х годов. Главная особенность этой архитектуры заключается в том, что она использует два отдельных блока памяти: один для хранения программных инструкций и один для хранения программных данных. Оба блока памяти в Гарвардской архитектуре доступны для CPU через разные коммуникационные шины.
Гарвардская архитектура.
Современные архитектуры: гибридные решения
Современные вычислительные системы используют гибридные архитектурные модели, которые максимизируют производительность, используя лучшее из обоих миров — моделей фон Неймана и Гарварда.
Микроконтроллеры обычно используются во встроенных приложениях. Они должны надёжно и эффективно выполнять определённые задачи при ограниченных ресурсах; именно поэтому Гарвардская архитектурная модель в основном используется в микроконтроллерах: микроконтроллеры имеют небольшую память программ и данных, к которой необходим одновременный доступ. Однако Гарвардская архитектура используется не всегда; некоторые семейства микроконтроллеров используют гибридные архитектурные модели или архитектуру фон Неймана.
Архитектуры плат Arduino®
Платы Arduino® в основном основаны на двух семействах микроконтроллеров: AVR® и ARM®. Хотя микроконтроллеры семейства AVR® основаны на Гарвардской архитектуре, микроконтроллеры семейства ARM® могут основываться как на архитектуре фон Неймана, так и на Гарвардской. Следующая таблица обобщает архитектуры микроконтроллеров плат Arduino:
Плата |
Микроконтроллер |
Семейство |
Архитектура |
|---|---|---|---|
UNO Mini |
ATmega328P |
AVR |
Harvard |
UNO Rev3 |
ATmega328P |
AVR |
Harvard |
UNO WiFi Rev2 |
ATmega4809 |
AVR |
Harvard |
UNO Rev3 SMD |
ATmega328P |
AVR |
Harvard |
Leonardo |
ATmega32u4 |
AVR |
Harvard |
Mega 2560 Rev3 |
ATmega2560 |
AVR |
Harvard |
Micro |
ATmega32u4 |
AVR |
Harvard |
Zero |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
Portenta H7 |
STM32H747 |
Arm® Cortex®-M4/M7 |
Harvard |
Nicla Sense ME |
nRF52832 |
Arm® Cortex®-M4 |
Harvard |
Nano RP2040 Connect |
RP2040 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR FOX 1200 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR NB 1500 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR Vidor 4000 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR WiFi 1010 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR Zero |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR1000 WIFI |
ATSAMW25H18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR WAN 1300 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
MKR WAN 1310 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
Nano |
ATmega328P |
AVR |
Harvard |
Nano Every |
ATmega4809 |
AVR |
Harvard |
Nano 33 IoT |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
Nano 33 BLE |
nRF52840 |
Arm® Cortex®-M4 |
Harvard |
Nano 33 BLE Sense |
nRF52840 |
Arm® Cortex®-M4 |
Harvard |
Типы памяти
Все различные блоки памяти внутри микроконтроллера можно разделить на два основных типа: RAM и ROM. RAM (от Random-Access Memory, память с произвольным доступом) в микроконтроллерных системах является энергозависимой памятью, используемой для хранения временных данных, например переменных встроенного программного обеспечения системы. ROM (от Read-Only Memory, постоянное запоминающее устройство) в микроконтроллерных системах является энергонезависимой памятью, используемой для хранения постоянных данных, например встроенного программного обеспечения системы.
RAM и ROM в микроконтроллерных системах организованы в три основные категории:
Flash
RAM
EEPROM
Flash-память
Flash-память в микроконтроллерных системах является частью ROM. В Flash-памяти хранится встроенное программное обеспечение системы для последующего выполнения. Например, вспомним знаменитый скетч
Blink.ino
: когда мы компилируем этот скетч, мы создаём бинарный файл, который затем сохраняется во Flash-памяти платы Arduino. Скетч выполняется при включении платы.
RAM
RAM в микроконтроллерных системах — это место, где хранятся временные данные или данные времени выполнения; например, переменные, созданные функциями программы. RAM в микроконтроллерах обычно представляет собой SRAM — тип RAM, использующий триггер (flip-flop) для хранения одного бита данных. Существует также другой тип RAM, встречающийся в микроконтроллерах: DRAM.
EEPROM
В микроконтроллерных системах EEPROM (Erasable Programmable Read-Only Memory — электрически стираемое программируемое постоянное запоминающее устройство) также является частью ROM; фактически, Flash-память является разновидностью EEPROM. Основное отличие между Flash-памятью и EEPROM заключается в способе управления ими: EEPROM может управляться на уровне байта (запись или стирание), тогда как Flash-памятью управляют на уровне блока.
Распределение памяти в платах Arduino®
Как уже говорилось, платы Arduino® в основном основаны на двух семействах микроконтроллеров — AVR® и ARM®; важно знать, что распределение памяти в обеих архитектурах различается. В архитектуре Гарварда на базе AVR память организована, как показано на изображении ниже:
Карта памяти AVR.
Важно упомянуть об организации SRAM в платах Arduino на базе AVR — она разделена на различные секции:
TextDataBSSStackHeap
Секция text содержит инструкции, загружаемые во Flash-память; секция data содержит переменные, инициализированные в скетче; секция BSS содержит неинициализированные данные; секция stack хранит данные функций и прерываний, а секция heap хранит переменные, создаваемые во время выполнения.
В гибридных архитектурах ARM реализована так называемая карта памяти с различными конфигурациями адресного пространства — 32-битной, 36-битной и 40-битной — в зависимости от требований адресного пространства системы на кристалле (SoC) с дополнительной DRAM. Карта памяти обеспечивает интерфейс с архитектурой SoC, при этом сохраняя большинство элементов управления системой на уровне кода высокого уровня. Инструкции доступа к памяти могут использоваться в коде высокого уровня для управления модулями прерываний и встроенными периферийными устройствами. Всё это контролируется блоком управления памятью (MMU).
Ресурс памяти управляется MMU. Основная роль MMU — позволить процессору выполнять несколько задач независимо в их собственном виртуальном адресном пространстве памяти; MMU затем использует таблицы трансляции для установления связи между виртуальными и физическими адресами памяти. Виртуальный адрес управляется программно с помощью инструкций памяти, а физический адрес — это система памяти, которая управляется в зависимости от входных данных таблицы трансляции, предоставляемых виртуальным адресом.
Пример организации памяти в микроконтроллерах на базе ARM — виртуальной и физической — показан на изображении ниже:
Организация памяти в микроконтроллерах на базе ARM.
Память микроконтроллера на базе ARM организована по следующим секциям в рамках упомянутых ранее типов адресов:
Виртуальный адрес:
Kernel code and data(код и данные ядра)Application code and data(код и данные приложения)
Физический адрес:
ROMRAMFlashPeripherals(периферийные устройства)
Следующая таблица обобщает распределение памяти конкретных плат Arduino®:
Плата |
Микроконтроллер |
Семейство |
Архитектура |
Flash |
SRAM |
EEPROM |
|---|---|---|---|---|---|---|
UNO Mini |
ATmega328P |
AVR |
Harvard |
32kB |
2kB |
1kB |
UNO Rev3 |
ATmega328P |
AVR |
Harvard |
32kB |
2kB |
1kB |
UNO WiFi Rev2 |
ATmega4809 |
AVR |
Harvard |
48kB |
6kB |
256B |
UNO Rev3 SMD |
ATmega328P |
AVR |
Harvard |
32kB |
2kB |
1kB |
Leonardo |
ATmega32u4 |
AVR |
Harvard |
32kB |
2.5kB |
1kB |
Mega 2560 Rev3 |
ATmega2560 |
AVR |
Harvard |
256kB |
8kB |
4kB |
Micro |
ATmega32u4 |
AVR |
Harvard |
32kB |
2.5kB |
1kB |
Zero |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
Portenta H7 (базовая конфигурация) |
STM32H747 |
Arm® Cortex®-M4/M7 |
Harvard |
16MB |
8MB |
— |
Nicla Sense ME |
nRF52832 |
Arm® Cortex®-M4 |
Harvard |
512kB |
64kB |
— |
Nano RP2040 Connect |
RP2040 |
Arm® Cortex®-M0+ |
Von Neumann |
— |
264kB |
— |
MKR FOX 1200 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR NB 1500 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR Vidor 4000 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR WiFi 1010 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR Zero |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR1000 WIFI |
ATSAMW25H18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR WAN 1300 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
MKR WAN 1310 |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
Nano |
ATmega328P |
AVR |
Harvard |
32kB |
2kB |
1kB |
Nano Every |
ATmega4809 |
AVR |
Harvard |
48kB |
6kB |
256B |
Nano 33 IoT |
ATSAMD21G18 |
Arm® Cortex®-M0+ |
Von Neumann |
256kB |
32kB |
— |
Nano 33 BLE |
nRF52840 |
Arm® Cortex®-M4 |
Harvard |
1MB |
256kB |
— |
Nano 33 BLE Sense |
nRF52840 |
Arm® Cortex®-M4 |
Harvard |
1MB |
256kB |
— |
Примечание
Объём SDRAM и Flash-памяти для профессиональных плат (Pro) в значительной степени настраиваем. Для получения дополнительной информации посетите сайт Arduino Pro.
Измерение использования памяти в платах Arduino®
Статистика использования памяти помогает понять особенности управления ресурсами, на которое влияет спроектированная структура кода. Нагрузка на память — это одна из характеристик, которая даст вам представление о том, насколько эффективно спроектирован код. Это критически важный элемент разработки, поскольку ресурсы внутри микроконтроллерной системы ограничены; программное обеспечение всегда должно работать, не достигая максимальной нагрузочной способности, чтобы избежать проблем. Нагрузку на память можно наблюдать либо как доступную RAM для конкретных задач, либо как оставшуюся ёмкость Flash-памяти для необходимого запаса.
Важно
Чтобы избежать проблем во время выполнения, микроконтроллерные системы всегда должны работать, не достигая максимальной ёмкости памяти.
Давайте подробнее поговорим об измерении использования памяти в платах Arduino®.
Измерение Flash-памяти
Flash-память на платах Arduino® можно измерить с помощью Arduino IDE. Как уже говорилось, Flash-память — это место, где хранится код приложения; Arduino IDE сообщает об использовании Flash-памяти через консоль вывода компилятора, чтобы разработчики знали, какой объём Flash-памяти используется.
Например, вывод консоли компилятора IDE для платы Arduino® на базе AVR — Nano — показан на изображении ниже:
Измерение Flash-памяти в плате Arduino® на базе AVR.
Вывод консоли компилятора IDE для платы Arduino® на базе ARM — MKR WAN 1310 — показан на изображении ниже:
Измерение Flash-памяти в плате Arduino® на базе ARM.
Вывод консоли компилятора IDE для другой платы Arduino® на базе ARM — Portenta H7 — показан на изображении ниже:
Измерение Flash-памяти в плате Arduino® на базе ARM (Portenta H7).
Обратите внимание, что вывод компилятора меняется в зависимости от того, является ли плата AVR-based или ARM-based.
Измерение SRAM
Иногда возникают ситуации, когда даже при успешной компиляции и загрузке кода IDE в плату происходят внезапные остановки. Эти проблемы, вероятно, связаны с чрезмерным потреблением памяти или недостаточным объёмом памяти для выделения. Необходимо понять, в каком секторе кода потребление памяти выходит за пределы доступных ресурсов, чтобы решить эту проблему. Следующий пример кода можно использовать для измерения использования SRAM в платах Arduino® на базе AVR:
void display_freeram() {
Serial.print(F("- SRAM left: "));
Serial.println(freeRam());
}
int freeRam() {
extern int __heap_start,*__brkval;
int v;
return (int)&v - (__brkval == 0
? (int)&__heap_start : (int) __brkval);
}
Помните, что секция heap — это место, где хранятся переменные, создаваемые во время выполнения. В коде:
__heap_start— начало секцииheap.__brkval— последний использованный указатель адреса памяти в секцииheap.
Следующий пример кода можно использовать для измерения использования SRAM в платах Arduino® на базе ARM:
extern "C" char* sbrk(int incr);
void display_freeram(){
Serial.print(F("- SRAM left: "));
Serial.println(freeRam());
}
int freeRam() {
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
}
Приведённый выше код взят из библиотеки Arduino-MemoryFree от Michael P. Flaga.
Измерение EEPROM
Управление памятью EEPROM можно легко осуществить с помощью встроенных библиотек, уже установленных в Arduino IDE. Библиотека EEPROM может использоваться для чтения, записи и стирания памяти EEPROM. Следующий код показывает, как байт информации можно сохранить в памяти EEPROM и затем считать с помощью функций write и read:
#include <EEPROM.h>
void setup() {
}
void loop() {
// Запись данных по конкретному адресу памяти EEPROM
EEPROM.write(address, value);
// Чтение данных по конкретному адресу памяти EEPROM
EEPROM.read(address);
}
Также можно очистить всю память EEPROM, установив её в 0, как показано в коде ниже:
#include <EEPROM.h>
void setup() {
}
void loop() {
for (int i = 0 ; i < EEPROM.length() ; i++) {
// Очистка памяти EEPROM
EEPROM.write(i, 0);
}
}
Совет
Для получения дополнительной информации об управлении памятью EEPROM вы можете обратиться к руководству по EEPROM.
Оптимизация использования памяти в системах на базе Arduino
Знание того, как код использует ресурсы памяти системы, — это лишь первая рекомендуемая задача в процессе разработки; совершенно другая задача — оптимизация использования памяти. Как может подразумевать термин «разработка», требования могут изменяться или корректироваться в зависимости от внешних факторов, таких как снижение ёмкости устройства из-за недоступности компонентов. Таким образом, архитектура кода может потребовать оптимизации для работы на сниженных ограниченных ресурсах памяти.
Процесс оптимизации использования памяти также подразумевает снижение вычислительной сложности, сокращение дополнительного времени, необходимого для обработки задач, при использовании меньшего количества ресурсов памяти для выполнения тех же задач. Процесс оптимизации использования памяти может способствовать общему процессу оптимизации кода, так как позволит более подходящим образом управлять памятью, требуя разработки интеллектуальных алгоритмов.
Рассмотрим некоторые методы оптимизации использования памяти.
Оптимизация Flash-памяти
Оптимизация Flash-памяти — это, пожалуй, наиболее очевидный потенциальный источник оптимизации. Flash-память — это место, где ёмкость, используемая скомпилированным кодом, может быть значительно сокращена при учёте некоторых деталей.
Удаление неиспользуемых ресурсов
Удаление новых источников включает неиспользуемые библиотеки и остатки кода. Остатки кода могут состоять из больше не используемых функций и «плавающих» переменных, которые занимают ненужное место в памяти. Это значительно улучшит размер скомпилированного кода и сделает процесс компиляции более понятным.
Модульные задачи
Модульные задачи означают функции, которые оборачивают код, используемый повторно или непрерывно, принимая различные параметры. Это отличный способ поддерживать чистую структуру кода и производительность, при этом уменьшая пространство памяти, необходимое для дополнительных задач, которые могут потребоваться.
Это ведёт к компактной структуре кода, которую гораздо легче понять при необходимости отладки, и требует от разработчика учёта вычислительной сложности при проектировании структуры кода или конкретного алгоритма.
Оптимизация SRAM
SRAM, вероятно, является наиболее важным блоком памяти внутри микроконтроллерной системы; оптимизация использования SRAM важна для проектирования надёжных микроконтроллерных систем. Нехватка SRAM — это обычно наиболее распространённые проблемы с памятью; оптимизация SRAM может помочь в снижении этого типа проблем.
Идеальный способ использования команды вывода данных — использование обёртки строк F() вокруг литералов. Смотрите пример ниже:
String Wrapper (обёртка строк)
Инструкции Serial.print() или Serial.println() используют пространство SRAM, что может быть удобно, но нежелательно. Идеальный способ использования инструкции Serial.print() или Serial.println() — с применением обёртки строк F() вокруг литералов. Например:
Serial.println(F("Something"));
Оборачивание строки Something с помощью обёртки F() перемещает строки только во Flash-память, не используя при этом пространство SRAM. Использование обёртки F() можно рассматривать как выгрузку таких данных во Flash-память вместо SRAM. Flash-память значительно объёмнее SRAM, поэтому лучше использовать пространство Flash-памяти, чем SRAM, которая будет использовать секцию heap. Это не означает, что пространство памяти всегда будет доступно, поскольку Flash-память также имеет ограниченный объём. Не рекомендуется засорять код инструкциями Serial.print() или Serial.println(), используйте их там, где они наиболее важны в коде.
PROGMEM
Не только строки занимают пространство SRAM — глобальные переменные также занимают немало пространства SRAM. Глобальные и статические переменные передаются в пространство SRAM и сдвигают секцию памяти heap в сторону stack. Пространство, занятое этими переменными, переданными в SRAM, сохраняется на своём месте и не изменяется; это означает, что чем больше таких переменных создаётся, тем больше места они занимают, и, следовательно, система испытывает проблемы из-за неудовлетворительного управления памятью.
PROGMEM, что расшифровывается как Program Memory (Программная память), можно использовать для хранения данных переменных в пространстве Flash-памяти, точно так же как описанная ранее обёртка F(), но использование PROGMEM имеет один недостаток: скорость чтения данных. Использование RAM обеспечит значительно более высокую скорость чтения данных, но PROGMEM, поскольку использует Flash-память, будет медленнее RAM при том же объёме данных. Таким образом, важно проектировать код, зная, какие переменные являются критически важными, а какие нет или имеют более низкий приоритет.
Использование PROGMEM в плате Arduino® на базе AVR показано в примере кода ниже:
#include <avr/pgmspace.h>
// Базовая структура PROGMEM
const PROGMEM DataType Variable_Name[] = {var0, var1, var2 ...};
// Хранение беззнакового 16-битного целого числа
const PROGMEM uint16_t NumSet[] = {0, 1, 1, 2, 3, 5, 8 ...};
// Хранение символа в PROGMEM
const char greetMessage[] PROGMEM = {"Something"};
Совет
Подробнее о PROGMEM можно прочитать в справочнике языка Arduino.
Для плат Arduino® на базе ARM для реализации аналогичного решения необходимо использовать static const для переменных.
static const int Variable = Data;
Использование различается на разных уровнях, которые резюмируются следующим образом:
Уровень пространства имён (Namespace Level)
На уровне пространства имён мы указываем на переменные, и различие состоит в том, объявлено ли
staticили нет. Если объявлено, это означает явное статическое объявление переменной; в противном случае — неявное статическое объявление.
Уровень функции (Function Level)
Если объявлено внутри
static, любые применимые данные будут управляться между вызовами функций.
Уровень класса (Class Level)
На уровне класса объявление
staticозначает, что любые применимые данные будут общими между экземплярами.
Нединамическое выделение памяти
Динамическое выделение памяти обычно является подходящим методом, если размер RAM системы достаточно велик; однако для микроконтроллерных систем, таких как встроенные системы, не рекомендуется подсчитывать каждый байт RAM.
Динамическое выделение памяти вызывает фрагментацию heap. При фрагментации heap многие затронутые ею области RAM не могут быть повторно использованы, оставляя мёртвые байты, которые могли бы быть использованы для других задач. Кроме того, когда динамическое выделение памяти освобождает занятое пространство, это не обязательно уменьшает размер heap. Поэтому, чтобы по возможности избежать фрагментации heap или RAM, можно следовать следующим правилам:
Приоритет стека перед heap:
Память стека (stack) не подвержена фрагментации и может быть полностью освобождена при возврате из функции. Heap, напротив, может не освобождать пространство, даже если ему было дано такое указание. Использование локальных переменных поможет в этом; старайтесь не использовать динамическое выделение памяти, которое состоит из различных вызовов:
malloc,calloc,realloc.
Сокращение глобальных и статических данных (по возможности):
Пока код выполняется, область памяти, занятая этими данными, не будет освобождена. Данные не будут изменены, поскольку константные данные занимают ценное пространство.
Используйте короткие строки/литералы:
Рекомендуется сохранять строки/литералы как можно короче. Один символ (char) занимает один байт RAM, поэтому чем короче, тем лучше использование пространства памяти. Это не означает, что можно хранить их короткими и использовать в нескольких разных местах кода. Используйте их там, где это необходимо, и сохраняйте их как можно короче, чтобы освободить RAM для других задач.
Массивы также рекомендуется делать минимального размера. Если потребуется изменить размер массива, вы всегда можете переустановить его размер в коде. Это может быть утомительным, а также неэффективным методом — жёстко задавать размеры массивов. Однако если код использует небольшие размеры массивов и менее трёх массивов, ручного изменения размера может быть достаточно, если требования известны. Интеллектуальный способ — создать массив с изменяемым размером, но с ограничением. Задачи будут использовать массив, не выходя за границу размера. Таким образом, это подходит для обширного кода. При этом предел размера массива должен быть проанализирован и сохранён как можно меньшим.
Функция Reserve
Для задач в коде, работающих со строками, которые меняют размер в зависимости от результата операции, функция reserve() — то, что нужно. Эта функция поможет зарезервировать буферное пространство и предварительно выделить его для строковой переменной, изменяя её размер и избегая фрагментации памяти. Строковая переменная, меняющая свой размер, может быть результатом, например, переменной типа int, обёрнутой для использования в качестве строки.
Следующий код показывает, как использовать инструкцию reserve():
// String_Variable — переменная типа String
// Alloc_Size — объём памяти для предварительного выделения в байтах, тип unsigned int
String_Variable.reserve(Alloc_Size);
Совет
Для получения дополнительной информации о функции reserve() посетите справочник языка Arduino.
Управление размером буфера
Фоновые процессы также требуют пула памяти для своих целей обработки. Система будет работать в соответствии с размером определённого пула памяти. Этот размер буфера может быть задан пользователем, что позволяет уменьшить выделяемый объём памяти. Представьте определение размера массива переменных, где важно не выделять чрезмерный размер, когда используется лишь треть заданного размера.
Рассмотрим пример: последовательная связь в Arduino. Последовательная связь — это регулярно используемый сервис в системах на базе Arduino; последовательная связь в Arduino работает с использованием предустановленной библиотеки Serial (внешние библиотеки также могут эмулировать последовательную связь с помощью программного обеспечения). Среди фоновых сервисов последовательная связь определяет необходимый пул памяти как буфер заданного размера. Если высокоскоростная последовательная связь не входит в требования, размер последовательного буфера можно переопределить для экономии памяти. Это можно легко сделать, изменив следующую строку кода в файле HardwareSerial.h, который можно найти в папке установки Arduino IDE:
#define SERIAL_TX_BUFFER_SIZE 64
#define SERIAL_RX_BUFFER_SIZE 64
Примечание
Внешние библиотеки, как правило, можно модифицировать для оптимизации размеров буферов, используемых при выполнении конкретных задач библиотек.
Корректное использование типов данных
Реализация подходящих типов данных ведёт к хорошей общей архитектуре кода. Разработчикам может быть желательно использовать простейший или наиболее доступный тип данных для работы с данными в коде. Однако важно учитывать объём памяти, занимаемый при использовании определённых типов данных.
Типы данных существуют для облегчения работы с форматом потока данных и их обработки без незаконного доступа. Незаконный доступ в плане типов данных означает обработку данных в коде в несовместимом формате. Поэтому рекомендуется не злоупотреблять типами данных и использовать только удобные типы для каждого бита данных. Лучше проектировать и выделять память в соответствии с требованиями, что поможет зарезервировать некоторое пространство памяти, если в дальнейшем спроектированным задачам потребуется дополнительное пространство.
Следующая таблица показывает базовые типы данных значений в Arduino:
Тип |
Длина (байт) |
Диапазон значений |
|---|---|---|
|
1 |
Ограничен логическими true и false |
|
1 |
-128 до 127 |
|
1 |
0 до 255 |
|
1 |
0 до 255 |
|
2 |
-32 768 до 32 767 |
|
2 |
0 до 65 535 |
|
2 |
0 до 65 535 |
|
4 |
-2 147 483 648 до 2 147 483 647 |
|
4 |
0 до 4 294 967 295 |
|
4 |
-3.4028235E+38 до 3.4028235E+38 |
|
4 |
-3.4028235E+38 до 3.4028235E+38 |
Оптимизация EEPROM
Оптимизация памяти EEPROM, как правило, не требуется; данные, которые должны использоваться пространством EEPROM, не нуждаются во Flash-памяти в качестве источника хранения. Кроме того, не является хорошей практикой выгружать данные SRAM в EEPROM. Данные SRAM размещаются с учётом энергозависимости, поэтому выгрузка в пространство EEPROM, которое является энергонезависимой памятью, означает, что выгруженные данные будут «выгравированы» в пространстве EEPROM.
При работе с EEPROM крайне важно знать, что операция write ограничена. Операция read для EEPROM неограничена; однако операция write конечна и обычно ограничена 100 000 циклами. Таким образом, важно сохранять только необходимые параметры для датчиков или модулей для работы с преимущественно неизменными данными. Кроме того, следует избегать реализации операций write в циклах, чтобы не допустить постоянных операций write; эти операции должны быть минимизированы во время работы системы.
Эмуляция EEPROM с помощью Flash-памяти
Поскольку EEPROM ограничена циклами операции записи, это также относится и к Flash-памяти. Обе они подвержены потере данных по истечении определённого производителем жизненного цикла. EEPROM основана на памяти типа NOR, тогда как Flash-память имеет тип NAND, что делает EEPROM более дорогостоящей, чем Flash-память. EEPROM работает с данными побайтово, тогда как Flash-память обращается к данным блоками.
Иногда разработчику приходится использовать EEPROM в качестве альтернативного хранилища для операций задач, но мы знаем, что это будет нецелесообразным кодированием из-за её размера и свойств. Для решения этой проблемы можно использовать Flash-память для эмуляции EEPROM. Благодаря библиотеке FlashStorage, созданной Кристианом Мальи (Christian Maglie), можно эмулировать EEPROM с помощью Flash-памяти.
Предупреждение
Библиотека FlashStorage поможет вам использовать Flash-память для эмуляции EEPROM, но, конечно, помните о свойствах EEPROM при использовании библиотеки. Как и EEPROM, Flash-память также ограничена в циклах write. С двумя новыми дополнительными функциями, указанными в библиотеке, EEPROM.commit() не следует вызывать внутри функции loop, иначе это исчерпает циклы операции write Flash-памяти, что приведёт к потере способности хранить данные.
Дополнительные материалы и ресурсы
Архитектуры памяти в микроконтроллерных системах — весьма обширная тема; если вы хотите узнать больше по этой теме, ознакомьтесь со следующими ссылками:
Документация по 8-битному ядру AVR® на сайте Microchip® Developer. Здесь вы найдёте подробную информацию о 8-битном центральном процессоре (CPU) AVR®.
Сайт документации по архитектуре ARM. Здесь вы найдёте подробную информацию о различных процессорах ARM. Обратите внимание на технические справочные руководства Cortex-M0+ и Cortex-M4.
Список литературы
[1] S. F. Barrett and D. J. Pack, Microchip AVR® Microcontroller Primer: Programming and Interfacing, Third Edition (Synthesis Lectures on Digital Circuits and Systems), Morgan & Claypool, 2019.
[2] J. Y. Yiu, The Definitive Guide to Arm® Cortex®-M0 and Cortex-M0+ Processors, Second ed., Newnes, 2015.
[3] J. Yiu, The Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors, Third ed., Newnes, 2014.