Основы отладки
Узнайте основы отладки систем на базе микроконтроллеров.
Авторы: José Bagur, Taddy Chung
Последняя ревизия: 10.04.2026
Встраиваемые системы — это системы на основе микропроцессоров или микроконтроллеров с выделенной операционной ролью. В отличие от настольных компьютеров, ноутбуков или игровых консолей, состоящих из отдельных компонентов, встраиваемые системы интегрируют всё необходимое аппаратное и программное обеспечение для конкретной задачи в одном устройстве. Сегодня встраиваемые системы повсюду: автомобили, камеры, бытовая техника, мобильные устройства — лишь некоторые примеры.
Проектирование встраиваемых систем сложно, поскольку оно сочетает разработку аппаратуры, прошивки и программного обеспечения в одном устройстве или продукте. Чтобы создавать качественную прошивку и ПО для встраиваемых систем, отладка — необходимый этап в процессе разработки. Отладка — это процесс подтверждения того, что многие вещи, которые мы считаем истинными и работающими в нашем коде, действительно таковы. Мы находим «баг» в коде, когда одно или несколько наших предположений оказываются неверными.
Примечание
Люди по всему миру говорят о «багах» давно; даже Томас Алва Эдисон использовал это слово в своё время. «Баг» использовался как старое слово для «монстра»; как гремлины в механике, баги вредны.
Эта статья описывает различные инструменты и техники отладки, применяемые для поиска багов в системах на микроконтроллерах, особенно в системах на основе аппаратуры Arduino®.
Инструменты и техники отладки
Существуют базовые инструменты и техники отладки, которые мы можем применять для проверки кода:
Компилятор и синтаксические ошибки.
Традиционные техники: trace code и GPIO.
Удалённые отладчики.
Симуляторы.
In-circuit эмуляторы и in-circuit отладчики.
Аппаратные инструменты: мультиметры, логические анализаторы, осциллографы и программно-определяемые радио (SDR).
Рассмотрим каждый из инструментов и техник.
Компилятор и синтаксические ошибки
Компиляция — это преобразование высокоуровневого кода в машинный язык, понимаемый процессором, например, микроконтроллером. В этом процессе компилятор также помогает выявить синтаксические ошибки. Они указывают на проблему с синтаксисом программы; например, когда в конце инструкции пропущена точка с запятой, компилятор выдаёт синтаксическую ошибку.
Компилятор Arduino IDE 2 показывает синтаксическую ошибку в скетче.
Использование компилятора для отладки синтаксических ошибок может быть непростым; рассмотрим две распространённые ситуации:
Компилятор показывает 100 ошибок: это обычно не означает, что ошибок 100; компилятор часто сбивается на некоторое время, найдя ошибку. Он пытается восстановиться и продолжить после первой ошибки, но иногда сообщает ложные. Только первое сообщение об ошибке действительно надёжно; пробуйте исправлять по одной и пересобирать программу.
Странные сообщения компилятора: ошибки компилятора показываются на сжатом жаргоне, иногда трудном для чтения, но содержащем ценную информацию. Внимательно читайте сообщение об ошибке; оно всегда укажет, где именно в коде произошла ошибка.
Традиционные техники: trace code и GPIO
Добавление trace code — пожалуй, простейшая и наиболее базовая техника отладки во встраиваемых системах. Метод состоит в добавлении в программу trace-кода для печати сообщений или значений переменных (используя функцию
Serial.print()
например) по мере выполнения программы. Например, определение того, зависает ли определённая функция, можно сделать с помощью trace-кода, как показано ниже:
// Print a message if the execution gets here
Serial.println("Code got here");
// Try to execute myFunction1()
myFunction1();
// Print a message if the execution gets here
Serial.println("Code got here, myFunction1 executed");
// Try to execute myFunction2()
myFunction2();
// Print a message if the execution gets here
Serial.println("Code got here, myFunction2 executed");
Использование trace-кода для отладки обычно применимо только на ранних этапах разработки кода встраиваемой системы. Добавление trace-кода требует значительного процессорного времени и ресурсов. Поэтому это может легко нарушать критические по времени задачи. Кроме того, если UART используется для других задач, отображение trace-кода может быть проблематичным.
Совет
Можно передавать строки из flash-памяти в инструкцию Serial.print(), обернув их макросом F(); например, Serial.println(F("Code got here")) печатает строку из flash.
Другая техника trace-кода — сброс стратегической информации в массив во время выполнения, чтобы потом, например по завершении программы, изучить содержимое массива; этот метод также называют dump into array. Допустим,
good
и
bad
— две стратегические переменные, которые мы хотим зафиксировать. Первый шаг — определить буфер отладки в RAM:
#define DUMP_BUFFER_SIZE 32
unsigned char goodBuffer[DUMP_BUFFER_SIZE];
unsigned char badBuffer[DUMP_BUFFER_SIZE];
unsigned long count = 0;
Переменная
count
используется как индекс в буфер; она должна быть проинициализирована нулём перед началом отладки. Код ниже сбрасывает стратегическую информацию из
good
и
bad
в буфер:
void Save_Debug_Buffer(void) {
if (count < DUMP_BUFFER_SIZE) {
goodBuffer[count] = good;
badBuffer[count] = bad;
count++;
}
}
Пины ввода/вывода общего назначения (GPIO) могут помочь в отладке, когда UART занят или trace-код не особенно полезен. Например, мы можем включать или выключать встроенный светодиод платы Arduino®, вставляя инструкцию
digitalWrite(LED_BUILTIN, HIGH)
до или после сомнительных областей программы, как показано ниже. Если светодиод загорается, мы знаем, что определённая строка кода выполнилась:
// Print a message if the execution gets here
Serial.println("Code got here");
// Try to execute myFunction1()
myFunction1();
// Turn on the built-in LED for one second to indicate that myFunction1 was executed
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
// Try to execute myFunction2()
myFunction2();
// Turn on the built-in LED for one second to indicate that myFunction2 was executed
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
Удалённые отладчики
Удалённая отладка — ещё один распространённый подход к отладке встраиваемых систем. Удалённые отладчики работают, подключая встраиваемую систему к хост-компьютеру и используя ПО на хосте для взаимодействия с аппаратурой встраиваемой системы. Удалённые отладчики полезны, когда среда разработки находится на другой архитектуре, отличной от целевой. Например, разработка кода на Windows для системы на базе ARM-микроконтроллера.
Удалённые отладчики обычно состоят из двух основных частей: front-end debugger и back-end debugger.
Front-end отладчик содержит пользовательский интерфейс (графический или командной строки) и предоставляет программисту выбор по выполнению кода на аппаратуре встраиваемой системы.
Back-end отладчик, известный также как «debug monitor», специфичен для конкретной архитектуры процессора или семейства и обычно работает с внешним аппаратным средством, например ICE или ICD. Он стартует при сбросе процессора и обрабатывает выполнение инструкций между front-end отладчиком и аппаратурой встраиваемой системы.
Совет
Инструмент отладчика — недавно появившаяся, но менее известная функция Arduino IDE 2. См. этот туториал, показывающий использование отладчика Arduino® IDE 2 на поддерживаемых платах.
Симуляторы
Симуляторы — это инструменты для симуляции функциональности и набора инструкций целевого процессора. Они обычно симулируют только функционал процессора, но не его окружение и внешние компоненты. Симуляторы полезны на ранних стадиях разработки, когда есть только ПО, но аппаратура ещё не реализована.
Совет
Tinkercad Circuits — отличный симулятор для начинающих в экосистеме Arduino®. Он может симулировать набор инструкций Arduino® UNO и работу нескольких электронных компонентов: резисторов, светодиодов, моторов, ЖК-экранов и некоторых датчиков.
Симуляция схемы в Tinkercad Circuits.
In-Circuit эмуляторы и In-Circuit отладчики
In-circuit emulator (или ICE) — специализированный инструмент, позволяющий разработчикам исследовать состояние процессора, пока выполняется конкретная программа. ICE сами по себе считаются встраиваемыми системами; они являются копией целевого процессора и его памяти (RAM и ROM); поэтому они дают неинвазивный способ отладки кода на целевом процессоре. Исторически ICE были инструментом выбора разработчиков встраиваемых систем, но по мере роста сложности процессоров и тактовой частоты ICE становились дороже, и их доступность значительно снизилась.
ICE Mikrotek начала 1990-х для микропроцессоров 8086.
Binarysequence, CC BY-SA 3.0, через Wikimedia Commons.
In-circuit debugger (или ICD) — также специализированный инструмент, подключаемый между хост-компьютером и процессором для более быстрой и удобной отладки приложений в реальном времени; этот инструмент использует часть памяти и GPIO целевого микроконтроллера во время отладки. С ICD разработчики получают доступ к встроенному в CPU модулю отладки через интерфейс (например, JTAG). Этот модуль позволяет загружать, запускать, останавливать и шагать по процессору.
SEGGER J-Link EDU JTAG/SWD debug-зонд.
SEGGER Microcontroller GmbH & Co, CC BY-SA 3.0, через Wikimedia Commons.
Примечание
Принципиальное отличие между ICE и ICD — в ресурсах, используемых для управления отладочной целью. У ICE ресурсы предоставляются эмуляционным оборудованием; у ICD — целевым процессором.
Поддержка ICD на Arduino®
Платы Arduino® с микроконтроллером SAMD поддерживают ICD-отладку:
Zero.
Плата Arduino® Zero имеет встроенный отладчик Atmel® Embedded Debugger (EDGB). Помимо поддержки программирования и отладки, EDGB также позволяет потоковую передачу данных между хостом и целевым процессором. См. этот туториал, чтобы научиться использовать возможности отладки Arduino® Zero с Arduino IDE 2.
Arduino® Zero EDGB.
Платы Arduino® с микроконтроллером SAMD имеют встроенные возможности on-chip debug; их можно использовать с внешним ICD-инструментом через интерфейсы JTAG или SWD. CMSIS-DAP-совместимые debug-зонды можно использовать с Arduino IDE 2 «из коробки» без файла конфигурации; нестандартные зонды требуют специальной конфигурации. См. туториалы:
Платы Arduino® Portenta H7, H7 Lite и H7 Lite Connected из семейства Pro также поддерживают ICD-отладку; они используют отладчик TRACE32 от Lauterbach. TRACE32 позволяет тестировать встраиваемое железо и ПО, используя интерфейс in-circuit debug процессоров. См. туториал:
Arduino® Portenta H7.
Аппаратные инструменты
Разработчики встраиваемых систем и обычные разработчики ПО различаются в одном ключевом аспекте: «близости» к аппаратуре; разработчики встраиваемых систем обычно ближе к железу. Есть несколько инструментов, которые они используют, чтобы понять, что происходит с аппаратурой — это очень помогает при низкоуровневой отладке ПО. Эти инструменты — мультиметры, логические анализаторы, осциллографы и программно-определяемые радио (SDR).
Рассмотрим каждый из аппаратных инструментов отладки. Базовое понимание этих инструментов значительно улучшает навыки отладки.
Примечание
Мультиметры, логические анализаторы, осциллографы и SDR помогают отладить взаимодействие процессора и других электронных частей встраиваемой системы. Эти инструменты не контролируют поток выполнения кода.
Мультиметры
Цифровой мультиметр (DMM) — аппаратный инструмент, измеряющий два или более электрических значения, обычно напряжение (вольты), ток (амперы) и сопротивление (омы). DMM — отличный инструмент и одно из самых фундаментальных средств тестирования для отладки электрических проблем во встраиваемой системе.
Цифровой мультиметр.
oomlout, CC BY-SA 2.0, через Wikimedia Commons.
Логические анализаторы
Логический анализатор — аппаратный инструмент, специально разработанный для захвата, отображения и измерения электрических сигналов в цифровой схеме. Состоит из нескольких цифровых входных пинов, способных определять, находится ли электрический сигнал на конкретном логическом уровне (1 или 0). Логические анализаторы также могут показывать соотношение и тайминг разных сигналов в цифровой схеме и часто способны анализировать протоколы цифровой связи (например, SPI).
24 МГц 8-канальный логический анализатор.
SparkFun Electronics, CC BY-SA 2.0, через Wikimedia Commons.
Осциллографы
Осциллограф — аппаратный инструмент, графически отображающий электрические сигналы и показывающий, как они меняются во времени. Сигналы измеряются с помощью датчика.
50 МГц 4-канальный осциллограф.
Dave Jones from Australia, CC BY-SA 2.0, через Wikimedia Commons.
Программно-определяемые радио
Программно-определяемое радио (SDR) — система радиосвязи, использующая ПО для модуляции и демодуляции радиосигналов. Традиционная обработка опирается на аппаратные компоненты, что ограничивает их перепрограммирование. SDR гораздо гибче, поскольку их можно перенастраивать программно.
SDR HackRF.
Alexander Neumann, Public domain, через Wikimedia Commons.
Отладка с аппаратными инструментами
Существует несколько техник отладки, и использование светодиода как метки прохождения процесса — простейший и быстрейший метод. Индикатор устанавливается в разных точках интереса для визуальной проверки корректного выполнения задач. Например, можно поставить несколько точек, включая или выключая светодиод в одной точке за раз для пошаговой проверки. Это даст достаточно информации, чтобы строить дополнительный слой кода или переходить к следующему сектору структуры. Метод не даст детальной информации о регистрах или обмене данными, поэтому он применим к простым линейным структурам кода. Удобен, когда отладчик недоступен и нужно быстро понять поведение кода.
Иногда светодиоды отсутствуют или недоступны; в системе нет способа сделать визуальный осмотр. Однако в этом случае можно использовать осциллограф для мониторинга состояния GPIO напрямую. Осциллограф также можно использовать, чтобы видеть, выдаёт ли код определённую обратную связь, переключая GPIO в нужное логическое состояние. DMM также может пригодиться для той же задачи.
Чтобы получить максимум от осциллографа и GPIO — измеряйте производительность, то есть электрические и временные характеристики сигнала. Например, ненужная задержка в коде:
void myFunction() {
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("Code got here");
count++;
digitalWrite(LED_BUILTIN, LOW);
}
Длительность выполнения
myFunction()
можно измерить, переводя GPIO в высокое состояние при начале выполнения; по завершении — в низкое. Осциллограф затем покажет, заняло ли выполнение функции точно заданное время, дольше или короче, или есть ли неучтённое электрическое поведение.
Теперь поговорим о беспроводной связи. Беспроводная связь — ключевая возможность при разработке новых IoT-устройств с разными требованиями. Аппаратура Arduino® не исключение. Вопрос: как отлаживать беспроводную связь между устройствами?
Простой и распространённый метод — использование acknowledge-флагов. Их применение помогает понять поведение устройства при установленной связи, предоставляя их текущий статус. Этот процесс встречается также в физических протоколах связи (I2C, SPI). Из-за разных типов протоколов в беспроводной связи использование acknowledge-флагов отличается своими правилами. Простейший способ подтвердить успешный обмен — проверить лог данных на каждом конце. Аппаратные инструменты (DMM, осциллографы, логические анализаторы) могут добавить деталей.
Однако не всё подключено на физическом слое — есть абстрактный слой. В беспроводной связи это электромагнитные волны, распространяющиеся в пространстве. Зачем нам отлаживать электромагнитные волны? Иногда нужно проверить, что конфигурация беспроводного приёмопередатчика конкретной встраиваемой системы корректна, например, его мощность передачи. SDR могут пригодиться: их можно использовать как дешёвые анализаторы спектра. Анализатор спектра — аппаратный инструмент, измеряющий магнитуду сигнала относительно частоты; в основном используется для измерения спектра мощности сигнала. Использование SDR как анализатора спектра — обычно опциональный метод отладки в процессе разработки, обеспечивающий более устойчивый дизайн.
Совет
Несколько программ работают с SDR; GQRX — одно из популярных, open-source, кросс-платформенное (Linux, macOS). AirSpy и CubicSDR — другие популярные программы (Windows, Linux, macOS).
Показанное визуальное представление сигнала через ПО SDR можно использовать для проверки выходной мощности передачи и количества переданных данных. Это поможет визуализировать конфигурацию беспроводной связи устройства. Можно проверить мощность приёма и передачи, число переданных байт, и частоту, на которой идёт передача. Эти свойства можно отлаживать через спектр частот для лучшей производительности беспроводной связи.
Сигналы LoRa, показанные на спектрограмме. Источник: The Things Network.
Пример техник отладки
Простой пример продемонстрирует реализацию некоторых техник отладки в экосистеме Arduino®. Используем плату Arduino® Nano 33 BLE Sense с её встроенным IMU. Пример использует данные акселерометра, гироскопа и магнитометра одновременно:
/*
Program:
- Debugging_techniques_example.ino
Description:
- This example combines data from the LSM9DS1 accelerometer, gyroscope, and magnetometer into a single code. On top of it,
it helps to understand different methods and techniques of debugging when the code structure combines multiple tasks.
The circuit:
- Arduino Nano 33 BLE Sense.
Code based on examples created by Riccardo Rizzo, Jose García, and Benjamin Dannegård.
Modified by Taddy Ho Chung and José Bagur (16/02/22).
*/
#include <Arduino_LSM9DS1.h>
#define DUMP_BUFFER_SIZE 32
unsigned char GoodBuffer[DUMP_BUFFER_SIZE];
unsigned char BadBuffer[DUMP_BUFFER_SIZE];
unsigned long count = 0;
uint8_t good, bad = 0;
float x, y, z, ledvalue;
int degreesX = 0, degreesY = 0;
int plusThreshold = 30, minusThreshold = -30;
void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println("- Started");
if (!IMU.begin()) {
Serial.println("- Failed to initialize IMU!");
bad++;
save_debug_buffer();
disp_debug_buffer();
debug_stop();
}
accelermeter_setup();
gyroscope_setup();
}
void loop() {
for (int i = 0; i < 5; i++) {
accelerometer_task();
gyroscope_task();
magnetometer_task();
}
save_debug_buffer();
debug_stop();
}
// Accelerometer setup
void accelermeter_setup() {
Serial.print(F("- Accelerometer sample rate: "));
Serial.print(IMU.accelerationSampleRate());
Serial.println(F(" Hz"));
}
// Read accelerometer data in all three directions task
void accelerometer_task() {
if (IMU.accelerationAvailable()) {
Serial.println(F("- Accelerometer data ready"));
IMU.readAcceleration(x, y, z);
good++;
} else {
Serial.println(F("- Accelerometer data not ready"));
bad++;
}
if (x > 0.1) {
x = 100 * x;
degreesX = map(x, 0, 97, 0, 90);
Serial.print(F("- Tilting up "));
Serial.print(degreesX);
Serial.println(F(" degrees"));
}
if (x < -0.1) {
x = 100 * x;
degreesX = map(x, 0, -100, 0, 90);
Serial.print(F("- Tilting down "));
Serial.print(degreesX);
Serial.println(F(" degrees"));
}
if (y > 0.1) {
y = 100 * y;
degreesY = map(y, 0, 97, 0, 90);
Serial.print(F("- Tilting left "));
Serial.print(degreesY);
Serial.println(F(" degrees"));
}
if (y < -0.1) {
y = 100 * y;
degreesY = map(y, 0, -100, 0, 90);
Serial.print(F("- Tilting right "));
Serial.print(degreesY);
Serial.println(F(" degrees"));
}
delay(1000);
}
// Gyroscope setup
void gyroscope_setup() {
Serial.print(F("- Gyroscope sample rate = "));
Serial.print(IMU.gyroscopeSampleRate());
Serial.println(F(" Hz"));
Serial.println();
Serial.println(F("- Gyroscope in degrees/second"));
}
// Read gyroscope data in all three directions task
void gyroscope_task( ) {
if (IMU.gyroscopeAvailable()) {
IMU.readGyroscope(x, y, z);
Serial.println(F("- Gyroscope data ready"));
good++;
} else {
Serial.println(F("- Gyroscope data not ready"));
bad++;
}
if(y > plusThreshold) {
Serial.println(F("- Collision front"));
delay(500);
}
if(y < minusThreshold) {
Serial.println(F("- Collision back"));
delay(500);
}
if(x < minusThreshold) {
Serial.println(F("- Collision right"));
delay(500);
}
if(x > plusThreshold) {
Serial.println(F("- Collision left"));
delay(500);
}
}
// Read magnetometer data in all three directions task
void magnetometer_task(){
IMU.readMagneticField(x, y, z);
if(x < 0) {
ledvalue = -(x);
}
else {
ledvalue = x;
}
analogWrite(LED_BUILTIN, ledvalue);
delay(500);
}
// For debugging purposes
void save_debug_buffer(void) {
if (count < DUMP_BUFFER_SIZE) {
GoodBuffer[count] = good;
BadBuffer[count] = bad;
disp_debug_buffer();
count++;
}
}
// Debugging array buffer
void disp_debug_buffer() {
Serial.println(F("\n Debugging array buffer result >>"));
Serial.print(F("- Good marks: "));
Serial.println(GoodBuffer[count]);
Serial.print(F("- Bad marks: "));
Serial.println(BadBuffer[count]);
}
void debug_stop() {
Serial.flush();
exit(1);
}
Полный код объединяет акселерометр, гироскоп и магнитометр в единую структуру. Поскольку задействованы задачи из разных модулей, они разделены на функции. В код включена техника trace code (dump в массив) для понимания, как именно работает код.
good
и
bad
метки расположены в точках интереса и сбрасываются в назначенные массивы для отображения в конце.
Важно знать, когда останавливать код во время отладки. Например, остановка после первого прогона даёт следующий результат:
Один runtime-инстанс отладочного массива.
В Arduino Serial Monitor мы видим, что у нас 1 good-метка и 1 bad-метка. Good пришёл от гироскопа (данные были готовы), bad — от акселерометра (данные не готовы). То есть акселерометр не успевает подготовить данные до измерительной задачи. Можно прогнать несколько итераций до сектора сброса:
5 runtime-инстансов отладочного массива.
Акселерометр выполнил задачу без проблем, кроме первого прогона, что дало 9 good и 1 bad. Инструкции
Serial.println(F())
в setup модулей и в задачах также показывают, прошёл ли код операции без проблем.
Дополнительно можно изменить loop, добавив
digitalWrite(12, HIGH)
перед задачами и
digitalWrite(12, LOW)
после, чтобы измерить время их выполнения с помощью GPIO 12 и осциллографа. Это поможет понять и потребление энергии:
void loop() {
for (int i = 0; i < 5; i++) {
digitalWrite(12, LOW);
accelerometer_task();
gyroscope_task();
magnetometer_task();
digitalWrite(12, HIGH);
}
save_debug_buffer();
debug_stop();
}
Использование осциллографа и GPIO для измерения времени выполнения задач.
Заключительные мысли об отладке
Отладка — необходимый этап в разработке надёжного ПО для встраиваемых систем. Закончим статью упоминанием четырёх наиболее важных фаз отладки, описанных Robin Knoke в статье об отладке embedded C, опубликованной в журнале Embedded Systems Programming:
Тестирование: фаза проверяет возможности embedded-ПО, стимулируя его широким диапазоном входных значений в разных средах.
Стабилизация: фаза попыток контролировать условия, генерирующие конкретный баг.
Локализация: фаза сужения диапазона возможностей до изоляции бага в конкретном сегменте кода.
Коррекция: фаза устранения бага из ПО.
Знание потенциальных причин багов позволяет принять стратегии, минимизирующие их возникновение. Существует много техник отладки и внешних устройств, помогающих в этом процессе. Возможно, некоторым проектам не нужны внешние отладчики. Однако когда ПО предъявляет разные требования, особенно к масштабируемости, всё резко меняется. Техники и внешние отладчики поддержат процесс разработки, обеспечивая сложное ПО.
Отладка может быть недооценённым аспектом разработки встраиваемых систем, но это её самый серьёзный и важный инструмент. Если мы хотим разрабатывать надёжные встраиваемые системы, процесс отладки должен последовательно применяться для достижения этих целей.
Дополнительные материалы
Если хотите узнать больше об инструментах и техниках отладки:
Хотите улучшить навыки отладки и инженерии? Очень рекомендуется книга Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems Дэвида Дж. Аганса.
Хотите узнать больше о цифровых мультиметрах? См. статью от Fluke®.
Об осциллографах? См. статью от Tektronix®.
О логических анализаторах? См. статью от Saleae®.
Об анализаторах спектра? См. статью от Tektronix®.
О SDR? См. видео-серию от Great Scott Gadgets — полный курс по SDR.
Литература
[1] P. Koopman, Better Embedded System Software. S.L.: Drumnadrochit Press, 2010.
[2] D. J. Agans, Debugging: The Nine Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems. New York: Amacom, 2002.
[3] M. Barr and A. Massa, Programming Embedded Systems: with C and GNU Development Tools. Johanneshov: MTM, 2013.
[4] J. W. Valvano, Embedded Systems: Introduction to ARM® Cortex™-M Microcontrollers. United States: Self-published, 2015.