Руководство по памяти Arduino
Подробный разбор типов памяти в Arduino: Flash, SRAM и EEPROM — как устроены, как измерить использование и как оптимизировать код под ограниченные ресурсы.
Микроконтроллер (MCU) — это интегральная схема, созданная для выполнения конкретных задач. Он собирает данные из окружающей среды, обрабатывает их и формирует нужный результат. Сегодня микроконтроллеры встречаются буквально везде: в умных часах, электромобилях и даже на поверхности Марса.
Одна из ключевых частей любого микроконтроллера — это память. Память хранит информацию временно или постоянно и используется для самых разных целей. В этом руководстве мы разберём организацию памяти в микроконтроллерах Arduino, научимся измерять её использование и применять практические приёмы оптимизации.
Что такое память?
Блоки памяти — неотъемлемая часть современных встраиваемых систем. Блоки памяти — это полупроводниковые устройства, которые хранят и выдают информацию; центральный процессор (CPU) использует данные из памяти для выполнения конкретных задач.
Как показано на схемах ниже, память в микроконтроллерах обычно представляют в виде массивов. Массив памяти разбит на ячейки, каждая из которых имеет уникальный адрес. Информация хранится в виде двоичных разрядов (бит), как правило организованных в байты (8 бит).
Память в вычислительных системах бывает энергозависимой и энергонезависимой. Энергозависимая память хранит данные только пока система работает — при отключении питания всё стирается. Энергонезависимая память сохраняет данные даже после отключения питания.
Архитектуры памяти: краткий обзор
Архитектура компьютера — обширная тема. Здесь мы рассмотрим только то, что нужно для понимания организации памяти в микроконтроллерах Arduino.
В начале эпохи вычислительных машин сложились две основные архитектуры: фон Неймана и Гарвардская.
Архитектура фон Неймана
Архитектура фон Неймана, названная в честь математика и физика Джона фон Неймана, появилась в середине 1940-х годов. Её также называют Принстонской архитектурой. Главная особенность: данные и программные инструкции хранятся в одном общем блоке памяти.

Архитектура фон Неймана: CPU обращается к единой памяти через одну шину данных.
Оба типа данных (программный код и переменные) доступны CPU через одну и ту же шину. Почти все цифровые компьютеры исторически строились именно по этому принципу.
Гарвардская архитектура
Гарвардская архитектура, названная в честь компьютера Harvard Mark I, также появилась в середине 1940-х. Её ключевая особенность — два отдельных блока памяти: один для хранения программных инструкций, другой для данных. CPU обращается к ним по разным шинам.

Гарвардская архитектура: память команд и память данных разделены и доступны через независимые шины.
Современные архитектуры: гибридный подход
Современные вычислительные системы используют гибридные архитектуры, сочетающие достоинства обоих подходов.
Микроконтроллеры предназначены для встраиваемых приложений: они должны надёжно и эффективно решать конкретные задачи при ограниченных ресурсах. Именно поэтому Гарвардская архитектура наиболее распространена в микроконтроллерах — небольшая память программ и данных должна быть доступна одновременно. Тем не менее некоторые семейства микроконтроллеров используют гибридные или фон-неймановские модели.
Архитектуры плат Arduino
Платы Arduino строятся преимущественно на двух семействах микроконтроллеров: AVR® и ARM®. Микроконтроллеры AVR® основаны на Гарвардской архитектуре, тогда как ARM® может использовать как фон-неймановскую, так и Гарвардскую модели. В таблице ниже приведены архитектуры микроконтроллеров различных плат Arduino:
| Board | Microcontroller | Family | Architecture |
|---|---|---|---|
| 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 организованы в три основные категории:
FlashRAMEEPROM
Flash
Flash-память — часть ROM микроконтроллера. Именно здесь хранится прошивка устройства. Возьмём для примера знаменитый скетч Blink.ino: при компиляции создаётся бинарный файл, который записывается во Flash-память Arduino. При включении платы скетч выполняется из Flash.
RAM
RAM в микроконтроллере — место хранения временных данных времени выполнения: переменных функций, буферов и т.д. Чаще всего в микроконтроллерах используется SRAM — статическая RAM на основе триггеров, хранящих по одному биту. Реже встречается DRAM.
EEPROM
EEPROM (Erasable Programmable Read-Only Memory) — также часть ROM. Технически Flash — это разновидность EEPROM. Главное отличие: EEPROM управляется на уровне байта (запись и стирание побайтово), тогда как Flash — на уровне блока.
Организация памяти в платах Arduino
Поскольку Arduino использует и AVR®, и ARM®, организация памяти в этих архитектурах различается. В AVR-архитектуре на базе Гарвардской модели память организована так, как показано на схеме ниже:

Карта памяти AVR: Flash, EEPROM и SRAM с разбивкой на секции.
SRAM в AVR-платах Arduino разделена на несколько секций:
TextDataBSSStackHeap
Секция text содержит инструкции, загруженные во Flash; секция data — инициализированные переменные скетча; секция BSS — неинициализированные данные; секция stack — данные функций и прерываний; секция heap — переменные, создаваемые во время выполнения программы.
В гибридных ARM-архитектурах реализуется карта памяти с конфигурацией адресного пространства 32-, 36- или 40-бит в зависимости от требований SoC (SoC) с дополнительной DRAM. Карта памяти обеспечивает интерфейс с дизайном SoC, сохраняя основное управление системой на уровне высокоуровневого кода. Инструкции доступа к памяти позволяют управлять модулями прерываний и встроенной периферией из высокоуровневого кода. Всем этим управляет блок управления памятью (MMU).
MMU отвечает за работу с памятью. Его главная задача — дать процессору возможность выполнять несколько задач независимо, каждую в своём виртуальном адресном пространстве. Для этого MMU использует таблицы трансляции, которые связывают виртуальные адреса с физическими. Виртуальный адрес управляется программно, физический адрес определяется таблицей трансляции.
Пример организации памяти в ARM-микроконтроллере (виртуальная и физическая) показан ниже:

Организация памяти в ARM-микроконтроллерах: виртуальное и физическое адресные пространства.
Память ARM-микроконтроллера разделена на следующие секции:
- Виртуальный адрес:
Kernel code and dataApplication code and data
- Физический адрес:
ROMRAMFlashPeripherals
В таблице ниже приведена организация памяти для конкретных плат Arduino:
| Board | Microcontroller | Family | Architecture | 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 (basic configuration) | 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-платах можно гибко настраивать. Подробности — в Pro site.
Измерение использования памяти в Arduino
Статистика использования памяти помогает понять, насколько эффективно организован код. Загрузка памяти — важный показатель при разработке, потому что ресурсы микроконтроллера конечны: программа должна работать без выхода на предельную загрузку памяти, иначе возникнут непредсказуемые сбои. Отслеживать можно как доступную RAM, так и оставшееся место во Flash.
Чтобы избежать проблем во время выполнения, система на микроконтроллере никогда не должна достигать максимального заполнения памяти.
Измерение Flash-памяти
Flash-память Arduino можно измерить прямо в Arduino IDE. Поскольку Flash хранит код приложения, IDE сообщает об использовании Flash через консоль компилятора — это позволяет разработчику видеть, сколько Flash уже занято.
Ниже показан вывод консоли компилятора для AVR-платы Arduino Nano:

Измерение Flash-памяти на плате Arduino Nano (AVR): IDE показывает занятый и доступный объём.
Вывод для ARM-платы Arduino MKR WAN 1310:

Измерение Flash-памяти на плате Arduino MKR WAN 1310 (ARM).
Вывод для ARM-платы Arduino Portenta H7:

Измерение Flash-памяти на плате Arduino Portenta H7 (ARM).
Обратите внимание: формат вывода компилятора отличается для AVR- и ARM-плат.
Измерение SRAM
Иногда код успешно компилируется и загружается, но плата зависает в произвольный момент. Чаще всего причина — нехватка SRAM или её чрезмерное потребление. Чтобы найти проблему, нужно понять, в каком месте кода память расходуется сверх нормы. Следующий пример позволяет измерить использование SRAM на AVR-платах Arduino:
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.
Для ARM-плат Arduino используется другой код:
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 Майкла П. Флага.
Измерение EEPROM
Для работы с EEPROM в Arduino IDE уже встроена стандартная библиотека. Библиотека EEPROM позволяет читать, записывать и стирать данные EEPROM. Пример записи и чтения одного байта с помощью функций write и read:
#include <EEPROM.h>
void setup() {
}
void loop {
// Запись данных по указанному адресу памяти EEPROM
EEPROM.write(address, value);
// Чтение данных по указанному адресу памяти EEPROM
EEPROM.read(address);
}
Чтобы полностью очистить EEPROM (записать нули во все ячейки):
#include <EEPROM.h>
void setup() {
}
void loop {
for (int i = 0 ; i < EEPROM.length() ; i++) {
// Очистка памяти EEPROM
EEPROM.write(i, 0);
}
Подробнее об управлении EEPROM — в this guide.
Оптимизация использования памяти в Arduino
Знать, как код расходует память — это первый шаг. Следующий — оптимизация использования памяти. Требования к проекту меняются: например, может потребоваться перенести код на плату с меньшим объёмом памяти. Оптимизация также снижает вычислительную сложность — задачи выполняются быстрее при меньших затратах ресурсов.
Рассмотрим конкретные приёмы оптимизации.
Оптимизация Flash-памяти
Flash-память — самый очевидный кандидат на оптимизацию. Объём скомпилированного кода можно существенно сократить, обратив внимание на несколько деталей.
Удалите неиспользуемые ресурсы
Удалите неиспользуемые библиотеки и остатки кода: функции, которые больше не нужны, и «висящие» переменные. Это заметно уменьшит размер скомпилированного кода и упростит сборку.
Модульные задачи
Модульный подход означает оборачивание повторяющегося кода в функции с параметрами. Это сохраняет чистоту структуры кода и снижает объём памяти, необходимый для дополнительных задач. Модульный код легче отлаживать и проще масштабировать.
Оптимизация SRAM
SRAM — вероятно, самый критичный ресурс в микроконтроллере. Нехватка SRAM — самая распространённая проблема с памятью в Arduino-проектах.
Обёртка String с помощью F()
Функции Serial.print() и Serial.println() по умолчанию используют пространство SRAM для хранения строковых литералов. Чтобы этого избежать, оборачивайте строки в обёртку F(). Например:
Serial.println(F("Something"));
Оборачивание строки Something в F() перемещает её во Flash-память, освобождая SRAM. Flash значительно объёмнее SRAM, поэтому хранить строки там выгоднее. Однако не злоупотребляйте Serial.print() и Serial.println() — Flash тоже не безгранична. Используйте обёртку там, где это действительно важно.
PROGMEM
SRAM занимают не только строки, но и глобальные переменные. Глобальные и статические переменные загружаются в SRAM и «выдавливают» секцию heap в сторону секции stack. Чем больше таких переменных — тем меньше свободной SRAM.
PROGMEM (Program Memory) позволяет хранить данные переменных во Flash-памяти — аналогично обёртке F(), но с одним недостатком: скорость чтения данных из Flash ниже, чем из RAM. Поэтому важно заранее решить, какие переменные критичны по скорости, а какие можно вынести во Flash.
Пример использования PROGMEM на AVR-плате Arduino:
#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 ...};
// Хранение char в PROGMEM
const char greetMessage[] PROGMEM = {"Something"};
Подробнее о PROGMEM — в Arduino Language Reference.
Для ARM-плат Arduino аналогичное решение реализуется с помощью static const над переменными:
static const int Variable = Data;
Особенности использования static на разных уровнях:
- Уровень пространства имён (Namespace Level)
— Переменная явно или неявно объявляется статической. Если static указан явно — это явная статическая декларация; без него — неявная.
- Уровень функции (Function Level)
— Если объявлено внутри static, данные любого применимого типа сохраняются между вызовами функции.
- Уровень класса (Class Level)
— Объявление static означает, что данные разделяются между всеми экземплярами класса.
Отказ от динамического выделения памяти
Динамическое выделение памяти удобно, когда RAM достаточно. Но для микроконтроллеров, где каждый байт на счету, это опасная практика.
Динамическое выделение памяти вызывает фрагментацию heap. Фрагментированные области RAM не могут быть повторно использованы — это «мёртвые байты». Кроме того, освобождение памяти через free() не всегда уменьшает размер heap. Чтобы минимизировать фрагментацию, придерживайтесь следующих правил:
- Отдавайте предпочтение stack, а не heap:
- Память stack не фрагментируется и полностью освобождается при выходе из функции. Heap может не освободить память даже после явного указания. Используйте локальные переменные и избегайте вызовов
malloc, calloc, realloc.
- Сокращайте глобальные и статические данные (по возможности):
- Пока программа работает, память под эти данные не освобождается. Константные данные занимают место постоянно.
- Используйте короткие строки и литералы:
- Один символ
charзанимает один байт SRAM. Чем короче строка — тем лучше. Используйте строки только там, где они действительно нужны.
- Массивы тоже должны быть минимального размера. Если нужно изменить размер массива — переопределите его в коде. Это трудоёмко, но оправдано при малом числе небольших массивов. Для сложных случаев рассмотрите массив с ограниченным максимальным размером.
Функция reserve()
Если код работает со строками, размер которых меняется в процессе работы, используйте reserve(). Эта функция резервирует буфер и заранее выделяет память под строковую переменную, предотвращая фрагментацию памяти при изменении размера строки.
Пример использования reserve():
// String_Variable — переменная типа String
// Alloc_Size — объём предварительно выделяемой памяти в байтах, тип unsigned int
String_Variable.reserve(Alloc_Size);
Подробнее о функции reserve() — в Arduino Language Reference.
Управление размером буфера
Фоновые процессы тоже требуют памяти для работы. Размер буфера можно задавать вручную — это позволяет уменьшить потребление памяти. Представьте массив, под который выделено в три раза больше места, чем реально используется — это расточительство.
Рассмотрим пример: последовательная связь в Arduino. Serial — один из самых часто используемых сервисов в Arduino-проектах; работает через встроенную библиотеку Serial (внешние библиотеки могут эмулировать последовательную связь программно). Для работы последовательная связь выделяет буфер фиксированного размера. Если высокая скорость передачи данных не требуется, размер буфера можно уменьшить — это сэкономит SRAM. Для этого измените следующую строку в файле HardwareSerial.h, который находится в папке установки Arduino IDE:
#define SERIAL_TX_BUFFER_SIZE 64
#define SERIAL_RX_BUFFER_SIZE 64
Внешние библиотеки, как правило, тоже позволяют настраивать размеры буферов для оптимизации памяти.
Правильный выбор типов данных
Грамотное использование типов данных — основа хорошей архитектуры кода. Соблазнительно выбирать самый удобный тип, но важно учитывать, сколько памяти он занимает.
Типы данных существуют для корректного представления и обработки данных. Не злоупотребляйте «широкими» типами там, где достаточно «узких». Проектируйте и выделяйте память под реальные требования — это оставит запас для будущих задач.
Основные типы данных в Arduino:
| Type | Byte Length | Range of Values |
|---|---|---|
boolean | 1 | Limited to logic true and false |
char | 1 | -128 to 127 |
unsigned char | 1 | 0 to 255 |
byte | 1 | 0 to 255 |
int | 2 | -32,768 to 32,767 |
unsigned int | 2 | 0 to 65,535 |
word | 2 | 0 to 65,535 |
long | 4 | -2,147,483,648 to 2,147,483,647 |
unsigned long | 4 | 0 to 4,294,967,295 |
float | 4 | -3.4028235E+38 to 3.4028235E+38 |
double | 4 | -3.4028235E+38 to 3.4028235E+38 |
Оптимизация EEPROM
Оптимизация EEPROM обычно не требуется: данные, хранящиеся в EEPROM, не нуждаются во Flash как источнике. Важно помнить: не стоит разгружать SRAM за счёт EEPROM. Данные SRAM по природе своей энергозависимы, а перенос их в EEPROM (энергонезависимую память) означает, что они там «останутся навсегда».
Главное ограничение EEPROM — конечное число циклов записи. Операция read (чтение) не ограничена, но операция write (запись) обычно ограничена ~100 000 циклами. Поэтому:
- Записывайте в EEPROM только действительно важные параметры, которые редко меняются.
- Никогда не вызывайте
writeвнутри циклаloop()— это быстро исчерпает ресурс записи.
Эмуляция EEPROM с помощью Flash
Flash-память тоже имеет ограниченный ресурс записи, как и EEPROM. Оба типа со временем теряют способность удерживать данные. EEPROM основана на NOR-памяти, Flash — на NAND, поэтому EEPROM дороже. EEPROM работает побайтово, Flash — блоками.
Иногда разработчику нужно использовать EEPROM как альтернативное хранилище, но из-за её ограничений это неудобно. Решение — эмулировать EEPROM с помощью Flash. Библиотека FlashStorage от Christian Maglie позволяет это сделать.
Библиотека FlashStorage позволяет использовать Flash как замену EEPROM. Но помните об ограничениях: Flash тоже имеет конечный ресурс write циклов. Функцию EEPROM.commit() нельзя вызывать внутри цикла — это быстро исчерпает ресурс Flash и приведёт к потере данных.
Типичные ошибки и практические замечания
- Зависание без видимых причин — почти всегда признак нехватки SRAM. Измерьте свободную память с помощью приведённых выше примеров.
- Строки в
Serial.print()безF()— один из самых частых источников утечки SRAM. Всегда оборачивайте строковые литералы вF(). - Избыток глобальных переменных — они занимают SRAM постоянно, даже когда не нужны. Переносите данные в локальные переменные или во Flash с помощью
PROGMEM. - Запись в EEPROM в
loop()— быстро убивает ресурс EEPROM. Записывайте только при реальном изменении данных. - Динамическое выделение памяти (
malloc,new) — на микроконтроллерах ведёт к фрагментации heap. По возможности используйте статическое выделение.
Дополнительные материалы
Организация памяти в микроконтроллерных системах — обширная тема. Для углублённого изучения рекомендуем:
- Документация по ядру 8-bit AVR® в Microchip® Developer help site — подробная информация о CPU AVR.
- Документация по архитектуре ARM в documentation site — технические руководства по Cortex-M0+, Cortex-M4 и другим процессорам.
Список литературы
[1] S. F. Barrett and D. J. Pack, Microchip AVR® Microcontroller Primer: Programming and Interfacing, Third Edition, 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.