В этой статье мы рассмотрим настройку отладчика и научимся использовать его возможности для анализа работы микроконтроллера. Мы разберём вкладку Registers, где отображаются регистры общего назначения ядра Cortex-M4, вкладку SFRs с регистрами периферии (например, USART, GPIO, TIM), а также Live Expressions, позволяющую в реальном времени наблюдать за значениями переменных. Такой подход помогает понять, как именно выполняется программа и как изменяются регистры во время её работы.
Первичная настройка отладчика.
На панели инструментов кликаем на стрелочку рядом с жуком.
Выбираем Run Configuration.
Затем кликаем на STM32 C/C Application и строчку под ней.
В закладке main выбираем название проекта. Так же есть возможность редактирования настроек среды и прочего через Configure Workspace Settings, здесь пока ничего не трогаем.
Переходим в закладку Debugger. Тут выбрано Autostart local GDB server.
GDB — это стандартный отладчик GNU (GNU Debugger), который умеет управлять кодом (ставить брейкпоинты, шагать, смотреть память и переменные).
Следующим звеном в цепи отладки является ST-LINK это аппаратный отладчик и программатор от компании STMicroelectronics для микроконтроллеров STM32. На плате STM32F4 Discovery это микроконтроллер STM32F103.На нём есть специальная программа и он нужен для прошивки и отладки нашего основного микроконтроллера. Без него пришлось бы подключать внешний отладчик. (С его помощью можно прошивать другие микроконтроллеры, не только тот что на плате)
Работает он через интерфейсы SWD или JTAG. SWD более новый интерфейс, у него всего два контакта идущих к микроконтроллеру, поэтому используется чаще всего он.
Получаем следующую схему.
CubeIDE (GDB client)
⇓debug commands (break, step, read var)
GDB Server (st-link_gdbserver)
⇓ low-level commands (via USB)
ST-LINK (hardware/firmware)
⇓SWD / JTAG signals
STM32 microcontroller
Далее выбираем интерфейс и ищем отладчик, нажав кнопку Scan
Внизу вкладки Debugger есть возможность выбора Shared ST-LINK.Устанавливаем если используем например Stm32 Cube monitor вместе с нашим отладчиком.
Во вкладке Main нашего отладчика выбираем место нашего проекта.
На этом минимальные настройки окончены. теперь Нажимаем кнопку Debug и входим в наш отладчик.
Отладка переменных.
Когда мы вошли в наш отладчик с правой стороны появляется окно со значениями отладкой которых можно заниматься.Для примера создадим переменную counter.
/* USER CODE BEGIN PV */ uint32_t counter = 0; /* USER CODE END PV */
и будем каждую секунду увеличивать её значение на 1
while (1) { counter++; HAL_Delay(1000); }
Цикл while выполняется после всех инициализаций. Ставим breakpoint на строке с HAL_Delay и видим что наша переменная увеличивается на 1 после каждого круга.В режиме отладки HAL_Delay не будет ждать фактически указанное время.
Если вы указали глобальную переменную, она может не отобразиться в окне справа. Поэтому проще добавить её в выражения и отслеживать текущее состояние там. Для этого нажимаем правой кнопкой мыши на переменную и выбираем Add Watch Expression.
В отличие от обычных точек останова и просмотра переменных, Live Expressions позволяет обновлять данные «на лету», практически без вмешательства в процесс выполнения кода. Здесь можно добавлять не только отдельные переменные, но и целые выражения.
Отладка регистров общего назначения.
Во вкладке Registers находятся регистры общего назначения. General Purpose and FPU Register Group ядра ARM Cortex-M4.Они не связаны с периферией STM32 напрямую.Содержат следующие регистры.
- R0–R12 – обычные регистры общего назначения (в них во время работы кладутся переменные, адреса, временные значения).
- SP (Stack Pointer) – указатель стека (где сейчас лежит верх стека).
- LR (Link Register) – хранит адрес возврата из функции.
- PC (Program Counter) – счетчик команд, указывает, какая инструкция сейчас выполняется.
- xPSR (Program Status Register) – флаги процессора (Zero, Negative, Carry, Overflow, режимы).
- FPU регистры (S0–S31) – для операций с плавающей точкой (float/double), плюс регистр FPSCR (статус FPU).
Отладка регистров специального назначения.
Находятся во вкладке SFRs. Делятся на две группы. Cortex-M4 и STM32F407( для каждого микроконтроллера свой ).
Cortex-M4 это ядро ARM, общее для всех микроконтроллеров STM32F4.
Там лежат системные регистры ядра, которые определены ARM:
- NVIC – Nested Vectored Interrupt Controller (управление прерываниями)
- SysTick – системный таймер (его и HAL_Delay() использует)
- SCB – System Control Block (информация об исключениях, настройка стека и т.п.)
- MPU – Memory Protection Unit (если есть)
- DWT/ITM – отладочные блоки
То есть, это «внутренности процессора». Они одинаковые для всех STM32 на Cortex-M4.
STM32F407 это уже периферия конкретного микроконтроллера:
GPIO (порты A, B, C, D…),RCC (тактирование),USART, SPI, I2C,TIMx (таймеры),ADC, DAC,DMA,и т.д.
При выборе любого устройства нам виден, есть его адрес, название и список регистров.Например USART2.
Пример с UART.
Возьмём для примера передачу данных по UART. Передавать будет из UART2 в UART3. Все настройки можно почитать в этой статье ( https://beartronix.com/ru/uart-usart ). Сначала давайте создадим буфер передачи и приёма и отследим их состояние при пошаговой отладке.
uint8_t txData[] = "Hello from USART"; // что будем отправлять uint8_t rxData[sizeof(txData)]; // буфер для приёма
Передача и приём будут осуществляться следующими строками
HAL_UART_Receive_IT(&huart3, rxData, sizeof(txData) - 1); HAL_UART_Transmit_IT(&huart2, txData, sizeof(txData) - 1);
Добавим наши массивы в Expressions нажав на них и выбрав Add Watch Expression.
Развернем и посмотрим содержимое. В массиве передачи записано наше значение.
А массив приёма пока пуст
Поставим точку останова после функций приема и передачи. Нажимаем F8. Программа дойдет до точки останова.
А наш массив заполняется значениями.
Теперь поставим точку останова на строке приёма, дойдём до неё с помощью Resume(F8) и по шагам посмотрим что происходит.
Программа стоит на 123 строке приёма. Нажимаем F5 и видим что программа входит в функцию приема UART. Если а дальше идти по шагам то видно как происходит передача и прием. Нажмём F8 и увидим что в нашем регистре приема один символ “H”. Это происходит из за того что не произошло прерывания.
Вкладка Registers.
авайте по шагам отследим изменения регистров PC и LR.
PC (Program Counter).Это счетчик команд (указатель на текущую инструкцию).Всегда указывает на адрес в памяти, где лежит следующая инструкция для выполнения.В отладчике можно видеть, где сейчас находится CPU.Если программа «зависла» — PC покажет адрес, можно понять, в каком месте (например, в бесконечном цикле или в обработчике исключения).
LR (Link Register)В отладчике LR помогает понять куда вернётся выполнение после функции.Если LR “битый” (например, память повреждена) — программа может уйти в кривой адрес и упасть в HardFault.
В режиме отладки входим в функцию HAL_Init(); Нажимаем F5 и видим что у нас меняется значение регистра PC, например 0x80013a4 <HAL_Init+16> 0x080013A4 — это физический адрес во флеше STM32, где сейчас находится выполнение.<HAL_Init+16> говорит, что сейчас PC находится внутри функции HAL_Init, но не в её начале.+16 — это смещение в байтах от начала функции.Регистр lr содержит значение 0x8000c4f и это адрес куда вернётся программа после завершения текущей функции.
Вкладка SFRs.
Теперь разберемся на примере регистров USART2 что происходит пошагово. UART имеет следующие регистры.
SR (Status Register) – флаги состояния (готовность к передаче/приёму, ошибки, переполнение и т.п.).
DR (Data Register) – регистр данных для отправки и приёма байта.
BRR (Baud Rate Register) – задаёт скорость обмена (делитель тактовой частоты).
CR1 (Control Register 1) – основные настройки: включение USART, прерывания, формат кадра (бит данных, parity и т.д.).
CR2 (Control Register 2) – дополнительные настройки: стоп-биты, синхронный режим (CK), LIN, адресация.
CR3 (Control Register 3) – расширенные опции: DMA, управление потоком (CTS/RTS), ошибки.
GTPR (Guard Time and Prescaler Register) – для SmartCard режима (guard time) и делитель для IrDA.
Нас интересует функция инициализации
MX_USART2_UART_Init();
Запускаем её в режиме отладки и видим что своё значение поменяли три регистра.
SR, BRR, CR1
Регистру SR присвоили значение 0xc0 = 1100 0000
Бит 7 TXE 1 означает что передатчик пуст
Бит 6 TC говорит о том что передача завершена
Регистру BRR присвоили значение 0x8b выставив скорость в 115200 бит/сек ( на самом деле если пересчитать по формулам из даташита скорость будет не точно такая, но очень близкая к ней, так что погрешностью можно пренебречь )
Регистру CR1 присвоили значение 0x200c=0010 0000 0000 1100 выставив биты регистра следующим образом
UE = 1 – включаем USART.
TE = 1 – включаем передачу.
RE = 1 – включаем приём.
M = 0 – длина слова 8 бит.
PCE = 0 – контроль четности выключен.
Остальные биты по нулям.
После чего происходит сам процесс передачи.
Если стать отладчиком на функцию HAL_UART_Transmit_ITто при пошаговом выполнении мы увидим что данные попадают в регистр DR UART3.
Его значение стало равно 0x48 что соответствует букве “H”из нашего массива «Hello from USART» .
В итоге мы познакомились с возможностями встроенного отладчика, который предоставляет удобный доступ как к переменным программы, так и к аппаратным регистрам микроконтроллера. Благодаря этому можно в реальном времени наблюдать изменение значений, отслеживать работу периферии и пошагово разбирать выполнение кода. Такой подход значительно упрощает поиск ошибок и лучше помогает понять, как взаимодействует программная часть с аппаратной.