35д. Как отладчик управляет ядром: SWD, порты DP и AP, остановка ядра и точки останова FPB
Что умеет отладчик, рассказано в 35.1: точки останова, точки наблюдения, окно памяти. Как им пользоваться из командной строки — в 35б (OpenOCD и GDB). RTT из 21а читает память прибора на ходу, профилировщик из 75а берёт счётчик команд у DWT. Везде за кадром остаётся одно и то же: два провода SWD и несколько регистров ядра. Здесь — что именно по ним идёт.
Раздел построен на своём коде. Отладчик — swd_host.c, 217 строк на C:
он дёргает две ножки, собирает пакеты, считает чётность, переживает WAIT
и FAULT, читает память, останавливает ядро и ставит точку останова.
Для STM32F103 и STM32F407 он собирается вместе с файлом ножек pins_stm32.c.
На ПК тот же файл работает против модели цели swd_target.c. Модель
собрана по описанию из справочных руководств: провод SWD, порт DP, порт
AHB-AP, память, регистры отладки ядра, блок FPB и игрушечное ядро, которое
исполняет пять команд Thumb. Модель проверена двумя чужими отладчиками.
Первый — SW_DP.c из прошивки CMSIS-DAP (CMSIS 5.9.0,
github.com/ARM-software/CMSIS_5, Apache-2.0): это код, который стоит
в адаптерах CMSIS-DAP и гонит биты по проводу. Второй — pyocd 0.45.1
(github.com/pyocd/pyOCD, Apache-2.0): он подключился к модели, нашёл ядро,
остановил его и поставил точку останова. Настоящей платы в опытах нет.
Опоры: RM0008 (DocID13902 Rev 17, глава 31 «Debug support») и RM0090
(Rev 19, глава 38) — пакет SWD, коды ACK, переход JTAG→SWD, IDCODE,
регистры AHB-AP, число компараторов FPB и DWT. Заголовки core_cm3.h
и core_cm4.h из CMSIS 5.9.0 — адреса и биты DHCSR, DCRSR, DCRDR, DEMCR,
DFSR. Поля FP_CTRL и FP_COMP — по заголовку hw_cpu_fpb.h для Cortex-M4F
из github.com/zephyrproject-rtos/hal_ti (коммит afbcfff, BSD-3-Clause).
Биты CTRL/STAT, ABORT и CSW, номера регистров в DCRSR — по исходникам pyocd
0.45.1. Опыты на ПК — образ stm-sys:1 (gcc 12.2.0), сборка для Cortex-M —
stm-arm:1 (arm-none-eabi-gcc 12.2.1, -Os).
Как это называется в документации
Оригинал Перевод Serial Wire Debug (SWD), SWCLK, SWDIO отладка по двум проводам: такт и данные packet request запрос (заголовок пакета) turnaround поворот линии acknowledge (ACK): OK, WAIT, FAULT ответ цели line reset сброс линии Debug Port (DP), Access Port (AP) порт отладки, порт доступа MEM-AP, AHB-AP порт доступа к памяти posted read отложенное чтение sticky error залипший флаг ошибки Debug Halting Control and Status Register (DHCSR) регистр останова ядра Flash Patch and Breakpoint (FPB) блок подмены флеш-памяти и точек останова comparator компаратор (адрес, на котором срабатывает) vector catch останов на векторе (например, на сбросе)
35д.1 Провод и пакет: восемь бит запроса, поворот, ответ, данные
SWD — два провода. SWCLK всегда ведёт отладчик. SWDIO ведут по очереди: то отладчик, то цель. Каждый обмен (в документации — transaction) состоит из трёх частей (RM0008, 31.8.2):
| Часть | Бит | Кто ведёт SWDIO |
|---|---|---|
| Запрос | 8: Start=1, APnDP, RnW, A[2:3], Parity, Stop=0, Park=1 | отладчик |
| Поворот | 1 | никто, линию держит подтяжка |
| Ответ ACK | 3 | цель |
| Данные | 32 и бит чётности | цель при чтении, отладчик при записи |
Ещё один такт поворота стоит там, где линия снова меняет хозяина: после данных чтения, после ACK при записи (перед данными) и после ответа WAIT или FAULT — тогда данных нет совсем.
APnDP выбирает, к кому запрос: 0 — к порту отладки DP, 1 — к порту доступа
AP. RnW — 1 для чтения. A[2:3] — номер регистра в окне из четырёх слов.
Бит чётности запроса в RM назван «чётностью предыдущих бит»; SW_DP.c
из CMSIS-DAP считает его по четырём битам APnDP, RnW, A2, A3, без
старта. Так же считает и свой отладчик. Чётность данных — по 32 битам. Всё передаётся
младшим битом вперёд.
Ответ ACK тоже идёт младшим битом вперёд. В RM0008 это таблица: «100: OK, 010: WAIT, 001: FAULT» — там биты записаны в порядке передачи. Как число это OK = 0b001, WAIT = 0b010, FAULT = 0b100: на проводе OK начинается с единицы.
Кто когда меняет линию. Отладчик выставляет бит при низком SWCLK, цель читает его по фронту вверх. Цель выставляет свой бит по фронту вверх, отладчик читает его при низком SWCLK перед следующим фронтом. Отсюда три примитива и весь обмен:
static void bit_out(int b) /* отладчик меняет линию при низком SWCLK */
{
pin_swdio_out(b);
pin_swclk(0);
pin_swclk(1); /* цель читает по фронту вверх */
}
static int bit_in(void) /* цель выставила бит по прошлому фронту */
{
pin_swclk(0);
int b = pin_swdio_in();
pin_swclk(1);
return b;
}
static void turnaround(void) /* такт, когда линию не ведёт никто */
{
pin_swclk(0);
pin_swclk(1);
}
static int parity32(uint32_t v)
{
v ^= v >> 16; v ^= v >> 8; v ^= v >> 4; v ^= v >> 2; v ^= v >> 1;
return (int)(v & 1u);
}
uint8_t swd_header(int apndp, int rnw, int a)
{
uint32_t a23 = (uint32_t)(a >> 2) & 3u;
uint32_t body = (uint32_t)apndp | (uint32_t)rnw << 1 | a23 << 2; /* APnDP, RnW, A[2:3] */
return (uint8_t)(1u | body << 1 | (uint32_t)parity32(body) << 5 | 0u << 6 | 1u << 7);
} /* Start=1 … Parity, Stop=0, Park=1 */
int swd_xfer(int apndp, int rnw, int a, uint32_t *data)
{
uint8_t h = swd_header(apndp, rnw, a);
pin_swdio_dir(1);
for (int i = 0; i < 8; i++) bit_out(h >> i & 1);
pin_swdio_dir(0);
turnaround();
int ack = bit_in() | bit_in() << 1 | bit_in() << 2;
if (ack != ACK_OK) { /* WAIT, FAULT или тишина (линия подтянута: 0b111) */
turnaround();
pin_swdio_dir(1);
swd_bits(2, 0);
return ack;
}
if (rnw) {
uint32_t v = 0;
for (int i = 0; i < 32; i++) v |= (uint32_t)bit_in() << i;
int p = bit_in();
turnaround();
pin_swdio_dir(1);
swd_bits(2, 0);
if (p != parity32(v)) return ACK_PARITY;
*data = v;
} else {
turnaround();
pin_swdio_dir(1);
for (int i = 0; i < 32; i++) bit_out((int)(*data >> i & 1u));
bit_out(parity32(*data));
swd_bits(2, 0); /* два такта покоя: запись доходит до шины */
}
return ACK_OK;
}
Заголовок для чтения IDCODE получается 0xA5, для записи CTRL/STAT — 0xA9,
для чтения DRW порта AP — 0x9F, для чтения RDBUFF — 0xBD. Так их собрал
swd_header в прогоне.
Рисунок записан с модели: на каждом фронте SWCLK видно, кто ведёт линию.
Одно чтение — 48 тактов: 8 запрос, 1 поворот, 3 ответ, 33 данные с чётностью,
1 поворот и 2 такта покоя, которые swd_xfer добавляет после каждого обмена.
Запись тоже заняла 48 тактов. На 2 МГц, с которых OpenOCD начинает
(35б.2), это 24 мкс на обмен. За весь прогон отладчик и цель ни разу
не вели линию одновременно: 0 столкновений на 17233 тактах.
Ножки для настоящего STM32 — PB13 (SWCLK) и PB14 (SWDIO), регистрами:
/* pins_stm32.c — ножки отладчика на STM32: SWCLK — PB13, SWDIO — PB14 (раздел 35д).
* Собирается для F103 (-DSTM32F10X_MD) и F407 (-DSTM32F407xx). На плате не проверялось. */
#if defined(STM32F10X_MD)
#include "stm32f10x.h"
#else
#include "stm32f4xx.h"
#endif
#include "swd_host.h"
void pins_init(void)
{
GPIOB->BSRR = (1u << 13) | (1u << 14); /* обе линии в единицу */
#if defined(STM32F10X_MD)
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
GPIOB->CRH = (GPIOB->CRH & ~0x0FF00000u) | 0x03300000u; /* PB13, PB14: выход 50 МГц */
#else
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
GPIOB->PUPDR = (GPIOB->PUPDR & ~(3u << 28)) | (1u << 28); /* PB14: подтяжка вверх */
GPIOB->OSPEEDR |= (3u << 26) | (3u << 28);
GPIOB->MODER = (GPIOB->MODER & ~(0xFu << 26)) | (5u << 26); /* PB13, PB14: выход */
#endif
}
void pin_swclk(int level) { GPIOB->BSRR = level ? 1u << 13 : 1u << (13 + 16); }
void pin_swdio_out(int bit) { GPIOB->BSRR = bit ? 1u << 14 : 1u << (14 + 16); }
int pin_swdio_in(void) { return (int)(GPIOB->IDR >> 14 & 1u); }
void pin_swdio_dir(int out)
{
#if defined(STM32F10X_MD)
if (!out) GPIOB->BSRR = 1u << 14; /* у F1 вход с подтяжкой вверх — по ODR = 1 */
GPIOB->CRH = (GPIOB->CRH & ~0x0F000000u) | (out ? 0x03000000u : 0x08000000u);
#else
GPIOB->MODER = (GPIOB->MODER & ~(3u << 28)) | ((uint32_t)out << 28);
#endif
}
У F1 вход с подтяжкой вверх включается только при единице в ODR (RM0008,
таблица 20: «Input pull-up» — ODR = 1, «Input pull-down» — ODR = 0),
поэтому перед переключением на вход в ODR пишется единица. Без задержек
ножки дёргаются так быстро, как позволит ядро; частоту SWCLK на плате
не мерили. Код отладчика swd_host.c для Cortex-M3 и для
Cortex-M4 занял по 1248 байт, ножки — 148 байт для F103 и 144 байта для F407
(arm-none-eabi-gcc 12.2.1, -Os).
35д.2 Подключение: переход JTAG→SWD, сброс линии, IDCODE, питание
После включения у STM32 работает порт JTAG-DP. Чтобы перейти на SWD, RM0008 и RM0090 (31.3.1 и 38.3.1) велят послать на SWDIO больше 50 единиц, 16 бит 0111100111100111 (старшим битом вперёд) и снова больше 50 единиц. Младшим битом вперёд это число 0xE79E. Больше 50 единиц подряд — это ещё и сброс линии. После сброса нужно хотя бы два такта нуля (покой) и первым делом прочитать IDCODE. Иначе, по RM, порт отвечает FAULT на любой другой запрос.
void swd_bits(int n, uint32_t bits) /* до 32 бит, младший первым */
{
pin_swdio_dir(1);
for (int i = 0; i < n; i++) bit_out((int)(bits >> i & 1u));
}
void swd_line_reset(void) /* 56 единиц и 8 тактов покоя */
{
swd_bits(28, 0x0FFFFFFFu);
swd_bits(28, 0x0FFFFFFFu);
swd_bits(8, 0);
sel_cache = 0xFFFFFFFFu;
}
void swd_jtag_to_swd(void) /* RM0008/RM0090: >50 единиц, 0xE79E, >50 единиц */
{
swd_bits(28, 0x0FFFFFFFu);
swd_bits(28, 0x0FFFFFFFu);
swd_bits(16, 0xE79Eu);
swd_line_reset();
}
int swd_connect(uint32_t *idcode)
{
uint32_t cs = 0;
swd_jtag_to_swd();
int ack = dp_read(DP_IDCODE, idcode); /* после сброса линии — первым делом */
if (ack != ACK_OK) return ack;
dp_write(DP_ABORT, 0x1Eu); /* стереть залипшие флаги ошибок */
dp_write(DP_CTRL_STAT, 0x50000000u); /* CSYSPWRUPREQ | CDBGPWRUPREQ */
for (int i = 0; i < 10; i++) {
ack = dp_read(DP_CTRL_STAT, &cs);
if (ack == ACK_OK && (cs & 0xA0000000u) == 0xA0000000u) return ACK_OK;
}
return ACK_FAULT;
}
pyocd подключается той же последовательностью: в его probe/swj.py переход
на SWD — swj_sequence(16, 0xe79e). Модель распознала её и у него.
Прогон на модели:
| Что сделали | Ответ |
|---|---|
| Сброс линии без перехода JTAG→SWD, чтение IDCODE | тишина: 0b111 |
| Переход JTAG→SWD, сразу чтение CTRL/STAT | FAULT |
| Сброс линии, чтение IDCODE | OK |
| CTRL/STAT = 0x50000000, чтение CTRL/STAT | 0xF0000000 |
«Тишина» — цель не ведёт линию, и подтяжка даёт три единицы. Это не ACK, а его отсутствие; отладчик должен отличать её от FAULT.
IDCODE порта SW-DP по RM — 0x1BA01477 у F103 (Cortex-M3 r1p1) и 0x2BA01477
у F407 (Cortex-M4 r0p1); модель отвечает этими числами. Через JTAG у тех же
ядер другой код — 0x3BA00477 и 0x4BA00477, путать их не надо. Код самого
кристалла лежит в DBGMCU_IDCODE по адресу 0xE0042000; RM советует
опознавать микроконтроллер по нему. Номер устройства в младших 12 битах —
0x410 у F103 средней плотности и 0x413 у F405/F407.
Запись CTRL/STAT с битами CSYSPWRUPREQ (30) и CDBGPWRUPREQ (28) просит питание для отладки; цель подтверждает битами 31 и 29, и CTRL/STAT читается как 0xF0000000. Пока подтверждения нет, к порту AP не ходят.
35д.3 Порт AP: память через CSW, TAR и DRW
Порт DP — это только четыре регистра: IDCODE (запись по тому же адресу — ABORT), CTRL/STAT, SELECT и RDBUFF. Всё остальное делает порт доступа. У STM32F1 и F4 он один — AHB-AP (RM0008, 31.9; RM0090, 38.9): ведущий на шине, который видит всю память, кроме шины ICode. Его регистры:
| Адрес | Регистр | Что делает |
|---|---|---|
| 0x00 | CSW | размер обращения, автоинкремент адреса, атрибуты |
| 0x04 | TAR | адрес в памяти |
| 0x0C | DRW | данные: чтение или запись по адресу из TAR |
| 0x10…0x1C | BD0…BD3 | четыре слова рядом с TAR без его перезаписи |
| 0xF8 | BASE | адрес таблицы ROM |
| 0xFC | IDR | что это за порт |
Адрес регистра AP — восемь бит. Старшие четыре берутся из SELECT (поле APBANKSEL, биты 7:4), младшие — из A[2:3] запроса; номер порта — в битах 31:24 SELECT. Чтобы прочесть IDR, в SELECT пишут 0xF0, а запрос идёт с A = 0xC.
Вторая особенность — чтение AP отложено (posted). Ответ на запрос чтения AP — это результат предыдущего чтения AP. Свежий результат приходит в следующем запросе к AP или в RDBUFF порта DP (RM0008, 31.8.4). Поэтому одиночное чтение — это запрос к AP и потом чтение RDBUFF:
static int ap_select(uint32_t reg) /* APSEL = 0, банк — reg[7:4] */
{
uint32_t sel = reg & 0xF0u;
if (sel == sel_cache) return ACK_OK;
int ack = dp_write(DP_SELECT, sel);
if (ack == ACK_OK) sel_cache = sel;
return ack;
}
int ap_read(uint32_t reg, uint32_t *v) /* чтение AP отложено: результат — в RDBUFF */
{
uint32_t stale;
int ack = ap_select(reg);
if (ack == ACK_OK) ack = xfer_retry(1, 1, (int)(reg & 0xCu), &stale);
if (ack == ACK_OK) ack = dp_read(DP_RDBUFF, v);
return ack;
}
int ap_write(uint32_t reg, uint32_t v)
{
int ack = ap_select(reg);
return ack == ACK_OK ? xfer_retry(1, 0, (int)(reg & 0xCu), &v) : ack;
}
int mem_read32(uint32_t addr, uint32_t *v)
{
int ack = ap_write(AP_CSW, 0x23000002u); /* слово, без автоинкремента */
if (ack == ACK_OK) ack = ap_write(AP_TAR, addr);
if (ack == ACK_OK) ack = ap_read(AP_DRW, v);
return ack;
}
int mem_write32(uint32_t addr, uint32_t v)
{
int ack = ap_write(AP_CSW, 0x23000002u);
if (ack == ACK_OK) ack = ap_write(AP_TAR, addr);
if (ack == ACK_OK) ack = ap_write(AP_DRW, v);
return ack;
}
int mem_read_block(uint32_t addr, int n, uint32_t *buf) /* TAR сам растёт на 4 */
{
uint32_t prev;
int ack = ap_write(AP_CSW, 0x23000012u);
if (ack == ACK_OK) ack = ap_write(AP_TAR, addr);
if (ack == ACK_OK) ack = ap_select(AP_DRW);
if (ack == ACK_OK) ack = xfer_retry(1, 1, AP_DRW, &prev); /* ответ — прошлое чтение */
for (int i = 1; i < n && ack == ACK_OK; i++)
ack = xfer_retry(1, 1, AP_DRW, &buf[i - 1]); /* каждый ответ — предыдущее слово */
if (ack == ACK_OK) ack = dp_read(DP_RDBUFF, &buf[n - 1]); /* последнее — из RDBUFF */
return ack;
}
ap_select помнит, что уже лежит в SELECT, и лишний раз его не пишет.
Слово памяти в прогоне стоило 4 запроса (CSW, TAR, DRW, RDBUFF), а первое
после смены банка — 5. На 2 МГц четыре запроса по 48 тактов — 96 мкс.
Блок из четырёх слов с автоинкрементом занял 7 запросов вместо 16: CSW, TAR,
четыре чтения DRW, где каждое возвращает предыдущее слово, и RDBUFF
с последним. Прочитано 20005000, 08000009, 20004902, 60083001 — начальный
указатель стека, вектор сброса и первые команды программы.
Что прочитал отладчик через AP у модели (у F407 — во второй колонке):
| Адрес или регистр | F103 | F407 |
|---|---|---|
| IDR порта AP | 0x24770011 | 0x24770011 |
| BASE порта AP | 0xE00FF003 | 0xE00FF003 |
| CPUID, 0xE000ED00 | 0x411FC231 | 0x410FC241 |
| DBGMCU_IDCODE, 0xE0042000 | 0x20036410 | 0x10076413 |
| первая запись таблицы ROM, 0xE00FF000 | 0xFFF0F003 → SCS в 0xE000E000 | то же |
Это значения модели: так её настроили, и так их разобрал pyocd. В его
журнале — «AHB-AP#0 IDR = 0x24770011», четыре блока из таблицы ROM
(SCS, DWT, FPB, ITM) и «CPU core #0: Cortex-M3 r1p1» для F103,
«Cortex-M4 r0p1» для F407. С настоящим кристаллом эти числа не сверялись,
кроме IDCODE и кода в DBGMCU_IDCODE, которые взяты из RM.
Порт AP работает, пока ядро бежит. Программа модели в цикле прибавляет
единицу к r0 и пишет её по адресу 0x20000000. Два чтения этого адреса через
AP, между которыми прошло 100 тактов SWCLK, дали 612 и 710: ядро не
останавливали. На этом и стоит RTT из 21а.
35д.4 WAIT, FAULT и тишина
WAIT — «шина занята, повтори». Отладчик повторяет тот же запрос:
static int xfer_retry(int apndp, int rnw, int a, uint32_t *data)
{
for (int tries = 0; tries < 100; tries++) {
int ack = swd_xfer(apndp, rnw, a, data);
if (ack != ACK_WAIT) return ack;
swd_waits++; /* шина занята: тот же запрос ещё раз */
}
return ACK_WAIT;
}
int dp_read(int a, uint32_t *v) { return xfer_retry(0, 1, a, v); }
int dp_write(int a, uint32_t v) { return xfer_retry(0, 0, a, &v); }
В модели после чтения флеш-памяти шина занята ещё на три запроса. Чтение
слова 0x08000004 прошло с тремя ответами WAIT: 7 запросов вместо 4,
результат верный — 0x08000009. SW_DP.c из CMSIS-DAP на той же модели тоже
получил три WAIT и то же слово. CMSIS-DAP повторяет запрос столько раз,
сколько ему задали (retry_count в DAP_Data); в своём отладчике это
xfer_retry выше.
FAULT — ошибка, которая залипла. Чтение по адресу 0x60000000, где у модели ничего нет, само прошло с OK: чтение отложено. Ошибка шины поставила STICKYERR (бит 5 CTRL/STAT), и FAULT получил следующий запрос — чтение RDBUFF. Дальше FAULT получают все запросы к AP, даже к исправной памяти:
| Шаг | Результат |
|---|---|
| Чтение 0x60000000 | FAULT, CTRL/STAT = 0xF0000020 |
| Чтение 0x20000000 | FAULT |
| ABORT = 0x04 (STKERRCLR) | CTRL/STAT = 0xF0000000 |
| Чтение 0x20000000 | OK |
pyocd на тех же шагах получил TransferFaultError и сам снял флаг: его
следующее чтение 0x20000000 прошло.
Тишина — ответ 0b111. Так бывает, когда цель не поняла запрос. В прогоне отладчик послал заголовок 0x85 — это 0xA5 с перевёрнутым битом чётности. Модель замолчала и молчала и на следующий правильный запрос IDCODE; после сброса линии IDCODE прочитался. Это поведение модели; для настоящего порта такие случаи RM0008 и RM0090 не описывают.
35д.5 Остановка ядра: DHCSR, DCRSR, DCRDR, DEMCR
Через AP отладчик пишет в обычные адреса памяти. Ядром он управляет
четырьмя регистрами в области SCS (CoreDebug_BASE = 0xE000EDF0
в core_cm3.h и core_cm4.h):
| Адрес | Регистр | Главные биты |
|---|---|---|
| 0xE000EDF0 | DHCSR | 0 C_DEBUGEN, 1 C_HALT, 2 C_STEP; 16 S_REGRDY, 17 S_HALT, 24 S_RETIRE_ST, 25 S_RESET_ST; запись — только с DBGKEY в битах 31:16 |
| 0xE000EDF4 | DCRSR | 4:0 REGSEL — номер регистра ядра, 16 REGWnR — запись |
| 0xE000EDF8 | DCRDR | значение регистра ядра |
| 0xE000EDFC | DEMCR | 0 VC_CORERESET — останов на сбросе, 24 TRCENA — включить DWT и ITM |
Ключ DBGKEY — 0xA05F (так в pyocd и в сценариях OpenOCD). Запись в DHCSR
без него не проходит. В прогоне
запись 0x00000003 (C_HALT и C_DEBUGEN без ключа) ядро не остановила:
DHCSR остался 0x01010000, ядро бежит. Запись 0xA05F0003 остановила:
DHCSR = 0x00030003 (S_HALT, S_REGRDY, C_HALT, C_DEBUGEN), в DFSR — бит
HALTED. Ту же запись делает OpenOCD в своём сценарии для MAX32xxx
(target/max32xxx_common.cfg, OpenOCD 0.12.0, GPL-2.0):
mww 0xE000EDF0 [expr {$DBGKEY | $C_HALT | $C_DEBUGEN}] при DBGKEY = 0xA05F0000.
Регистр ядра читается в три шага: номер в DCRSR, ожидание S_REGRDY, значение из DCRDR. Номера: 0…12 — R0…R12, 13 — SP, 14 — LR, 15 — адрес возврата из останова (то есть PC), 16 — xPSR, 17 — MSP, 18 — PSP.
static int wait_dhcsr(uint32_t mask)
{
uint32_t v = 0;
for (int i = 0; i < 100; i++)
if (mem_read32(DHCSR, &v) == ACK_OK && (v & mask)) return ACK_OK;
return ACK_FAULT;
}
int core_halt(void)
{
int ack = mem_write32(DHCSR, DBGKEY | 0x3u); /* C_HALT | C_DEBUGEN */
return ack == ACK_OK ? wait_dhcsr(1u << 17) : ack; /* S_HALT */
}
int core_run(void) { return mem_write32(DHCSR, DBGKEY | 0x1u); }
int core_step(void)
{
int ack = mem_write32(DHCSR, DBGKEY | 0x5u); /* C_STEP | C_DEBUGEN, C_HALT = 0 */
return ack == ACK_OK ? wait_dhcsr(1u << 17) : ack;
}
int core_reg_read(int n, uint32_t *v) /* n: 0…12 R0…R12, 13 SP, 14 LR, 15 PC, 16 xPSR */
{
int ack = mem_write32(DCRSR, (uint32_t)n); /* REGWnR = 0: чтение */
if (ack == ACK_OK) ack = wait_dhcsr(1u << 16); /* S_REGRDY */
if (ack == ACK_OK) ack = mem_read32(DCRDR, v);
return ack;
}
После останова отладчик прочитал R0 = 962, R1 = 0x20000000, SP = 0x20005000, PC = 0x0800000C, xPSR = 0x01000000 и в памяти по 0x20000000 — тоже 962. За следующие 200 тактов SWCLK остановленное ядро не выполнило ни одной команды.
Останов на сбросе. RM0008 (31.10) описывает его так: VC_CORERESET
в DEMCR и C_DEBUGEN в DHCSR. Регистры отладки системный сброс не трогают.
Отладчик записал DEMCR = 0x01000001 и сбросил ядро записью 0x05FA0004
в AIRCR (SYSRESETREQ с ключом 0x05FA). Ядро модели встало на первой команде:
PC = 0x08000008, SP = 0x20005000 из вектора 0, DFSR = 0x00000008 (VCATCH),
DHCSR = 0x02030003 — с флагом S_RESET_ST. Команда pyocd reset_and_halt
на модели дала тот же PC.
35д.6 Точки останова FPB
Те шесть аппаратных точек из 35.1 — это блок FPB по адресу 0xE0002000. RM0008 (31.12) и RM0090 (38.12) пишут: 6 компараторов команд и 2 компаратора литералов. Компараторы команд либо подменяют команду, либо дают ядру команду останова; одно из двух.
| Регистр | Поля |
|---|---|
| FP_CTRL, 0xE0002000 | 0 ENABLE, 1 KEY — без него запись не проходит, 7:4 NUM_CODE1, 11:8 NUM_LIT, 13:12 NUM_CODE2 |
| FP_COMP0…7, 0xE0002008…0xE0002024 | 0 ENABLE, 28:2 COMP — адрес слова, 31:30 REPLACE: 01 — младшее полуслово, 10 — старшее, 11 — оба |
Модель отвечает FP_CTRL = 0x00000260: 6 компараторов команд, 2 литерала,
в битах 31:28 (REV) — 0; по pyocd это FPB первой версии. Отсюда главное
ограничение. В COMP помещаются только биты 28:2, значит, точка ставится лишь
в область кода 0x00000000…0x1FFFFFFF. Флеш-память STM32 с адреса 0x08000000
туда входит, ОЗУ с 0x20000000 — нет. Для кода в ОЗУ отладчик пишет
в память команду BKPT: это программная точка, и FPB для неё не нужен.
fpb_set на адрес 0x20000100 отказывает сразу.
int fpb_set(int n, uint32_t addr) /* FPB v1: только 0x00000000…0x1FFFFFFF */
{
if (addr >= 0x20000000u) return ACK_FAULT;
uint32_t rep = (addr & 2u) ? 2u : 1u; /* 10 — старшее полуслово, 01 — младшее */
int ack = mem_write32(FP_CTRL, 0x3u); /* KEY | ENABLE */
if (ack == ACK_OK) ack = mem_write32(FP_COMP(n), rep << 30 | (addr & 0x1FFFFFFCu) | 1u);
return ack;
}
int fpb_clear(int n) { return mem_write32(FP_COMP(n), 0); }
Программа ядра модели — пять команд. Собрана arm-none-eabi-as, байты
совпали с теми, что лежат в модели:
@ prog35f.s — программа, которую исполняет ядро модели (раздел 35д).
.syntax unified
.cpu cortex-m3
.thumb
.section .text
.p2align 2
.word 0x20005000 @ 0x08000000: начальный SP
.word reset + 1 @ 0x08000004: вектор сброса (Thumb)
reset: ldr r1, ram @ 0x08000008: литерал из флеш-памяти
movs r0, #0 @ 0x0800000A
loop: adds r0, #1 @ 0x0800000C
str r0, [r1] @ 0x0800000E
b loop @ 0x08000010
nop @ 0x08000012
ram: .word 0x20000000 @ 0x08000014
Сработавший компаратор модель проверяет так — до исполнения команды:
static int fpb_hit(uint32_t pc) /* FPB v1: только код 0…0x1FFFFFFF */
{
if (!(fp_ctrl & 1u)) return 0;
for (int i = 0; i < 6; i++) {
uint32_t c = fp_comp[i];
if (!(c & 1u) || (c & 0x1FFFFFFCu) != (pc & 0x1FFFFFFCu)) continue;
uint32_t rep = c >> 30;
if (rep == 3 || (rep == 1 && !(pc & 2u)) || (rep == 2 && (pc & 2u))) return 1;
}
return 0;
}
Опыт: точка на str r0, [r1] по адресу 0x0800000E. Адрес лежит в старшем
полуслове слова 0x0800000C, поэтому REPLACE = 10, и в FP_COMP0 ушло
0x8800000D. pyocd на той же точке записал то же 0x8800000D.
| Шаг | PC | R0 | Память 0x20000000 | DFSR |
|---|---|---|---|---|
| Точка поставлена, ядро пущено | 0x0800000E | 963 | 962 | 0x00000002, BKPT |
| Пустили снова, не сняв точку | 0x0800000E | 963 | 962 | команд выполнено: 0 |
| Сняли, шаг, вернули, пустили | 0x0800000E | 964 | 963 | команд выполнено: 3 |
Ядро встаёт до команды на точке: R0 уже 963, а в память ещё не записано.
Если просто продолжить, ядро снова попадает на ту же команду и встаёт, не
сделав ни шага. Поэтому отладчик перед продолжением снимает точку, делает
шаг (C_STEP), ставит точку обратно и только потом пускает ядро. После шага
PC = 0x08000010, в DFSR — HALTED. pyocd на модели делает так же: после
remove_breakpoint и step PC стал 0x08000010.
Чтение флеш-памяти через AP точку не видит: по адресу 0x0800000C отладчик
прочитал 0x60083001 — настоящие команды adds и str. В RM0008 так
и сказано: обращения AHB-AP идут мимо FPB.
Четыре точки наблюдения из 35.1 — это компараторы DWT; в модели поле NUMCOMP регистра DWT_CTRL (0xE0001000) равно 4, как пишут RM0008 и RM0090. Сам DWT разобран в 75а.6.
35д.7 Грабли
ACK читается задом наперёд. В таблице RM0008 OK записан как «100». Это порядок на проводе, а как число OK = 0b001. Отладчик, который сравнивает с 0b100, принимает OK за FAULT. В JTAG коды другие: там «010» — OK или FAULT, «001» — WAIT.
Чётность запроса со стартовым битом. Старт всегда 1. Если включить его в чётность, бит чётности всегда будет неверным. Цель молчит (0b111), и кажется, что она не подключена.
После сброса линии — сначала IDCODE. Любой другой запрос получает FAULT (RM0008, 31.8.3). Отладчик, который сразу пишет ABORT или CTRL/STAT, видит FAULT на исправной плате.
Тишину путают с FAULT. Три единицы — это подтяжка, а не ответ. Тишина бывает, когда цель не на SWD (не было перехода JTAG→SWD), когда нет питания или провода, когда отладчик ошибся в заголовке. Лечится сбросом линии и чтением IDCODE, а не ABORT.
Первое чтение AP возвращает мусор. Ответ на чтение AP — результат прошлого чтения. Кто берёт данные прямо из ответа, получает предыдущее слово. Одиночное чтение заканчивают чтением RDBUFF.
Ошибка «из ниоткуда». FAULT приходит не на тот запрос, который его вызвал, а на следующий запрос к AP. Пока не записать ABORT со STKERRCLR, FAULT получают все, даже к исправной памяти.
DHCSR без ключа. Запись без 0xA05F в старших битах молча пропадает: ни ошибки, ни останова. FP_CTRL так же требует бит KEY.
Точка в ОЗУ. FPB первой версии видит только адреса до 0x1FFFFFFF. Код, перенесённый в ОЗУ, останавливают командой BKPT в памяти — и её надо убрать до того, как отладчик отключится: без отладчика BKPT ведёт к HardFault (35а.3).
Продолжить с точки — встать на ней же. Ядро останавливается до команды. Сначала снять точку, шаг, вернуть точку, потом пускать.
pyocd 0.45.1 считает литералы неверно. Его журнал на модели — «6 hardware
breakpoints, 4 literal comparators» при FP_CTRL = 0x00000260, где NUM_LIT
(биты 11:8) равно 2. В pyocd/coresight/fpb.py поле берётся сдвигом на 7,
а не на 8. На работу точек это не влияет: литералы pyocd не использует.
35д.8 Итоги
- Обмен SWD — 8 бит запроса, поворот, 3 бита ответа, 32 бита данных с чётностью. С двумя тактами покоя — 48 тактов, на 2 МГц 24 мкс.
- Заголовки: чтение IDCODE 0xA5, запись CTRL/STAT 0xA9, чтение DRW 0x9F, чтение RDBUFF 0xBD. Чётность — по четырём битам без старта; ACK OK на проводе начинается с единицы.
- Подключение: больше 50 единиц, 0xE79E, больше 50 единиц, покой, IDCODE (0x1BA01477 у F103, 0x2BA01477 у F407), питание через CTRL/STAT 0x50000000 → 0xF0000000.
- Память — через AHB-AP: CSW, TAR, DRW. Чтение AP отложено. Слово — 4 запроса, блок из 4 слов с автоинкрементом — 7. Читать можно на ходу: 612 → 710.
- WAIT — повторить; FAULT залипает до ABORT; тишина 0b111 лечится сбросом линии.
- Ядро: DHCSR с ключом 0xA05F останавливает (0x00030003), DCRSR и DCRDR читают регистры, DEMCR.VC_CORERESET и AIRCR дают останов на сбросе (PC = 0x08000008).
- FPB v1: 6 компараторов команд и 2 литерала, только адреса до 0x1FFFFFFF; точка на 0x0800000E — FP_COMP0 = 0x8800000D, то же записал pyocd.
- Свой отладчик — 1248 байт на Cortex-M3 и M4. Модель проверена SW_DP.c из CMSIS-DAP и pyocd 0.45.1; на плате ничего не запускалось.
Полные исходники
swd_host.c — отладчик SWD на двух ножках: пакеты, порты DP и AP, память, останов, FPB.
/* swd_host.c — отладчик SWD на двух ножках (раздел 35д): пакеты, чётность,
* WAIT, порты DP и AP, память, DHCSR/DCRSR и точки останова FPB. */
#include "swd_host.h"
uint32_t swd_waits;
static uint32_t sel_cache = 0xFFFFFFFFu; /* что сейчас в DP SELECT */
static void bit_out(int b) /* отладчик меняет линию при низком SWCLK */
{
pin_swdio_out(b);
pin_swclk(0);
pin_swclk(1); /* цель читает по фронту вверх */
}
static int bit_in(void) /* цель выставила бит по прошлому фронту */
{
pin_swclk(0);
int b = pin_swdio_in();
pin_swclk(1);
return b;
}
static void turnaround(void) /* такт, когда линию не ведёт никто */
{
pin_swclk(0);
pin_swclk(1);
}
static int parity32(uint32_t v)
{
v ^= v >> 16; v ^= v >> 8; v ^= v >> 4; v ^= v >> 2; v ^= v >> 1;
return (int)(v & 1u);
}
uint8_t swd_header(int apndp, int rnw, int a)
{
uint32_t a23 = (uint32_t)(a >> 2) & 3u;
uint32_t body = (uint32_t)apndp | (uint32_t)rnw << 1 | a23 << 2; /* APnDP, RnW, A[2:3] */
return (uint8_t)(1u | body << 1 | (uint32_t)parity32(body) << 5 | 0u << 6 | 1u << 7);
} /* Start=1 … Parity, Stop=0, Park=1 */
void swd_bits(int n, uint32_t bits) /* до 32 бит, младший первым */
{
pin_swdio_dir(1);
for (int i = 0; i < n; i++) bit_out((int)(bits >> i & 1u));
}
void swd_line_reset(void) /* 56 единиц и 8 тактов покоя */
{
swd_bits(28, 0x0FFFFFFFu);
swd_bits(28, 0x0FFFFFFFu);
swd_bits(8, 0);
sel_cache = 0xFFFFFFFFu;
}
void swd_jtag_to_swd(void) /* RM0008/RM0090: >50 единиц, 0xE79E, >50 единиц */
{
swd_bits(28, 0x0FFFFFFFu);
swd_bits(28, 0x0FFFFFFFu);
swd_bits(16, 0xE79Eu);
swd_line_reset();
}
int swd_xfer(int apndp, int rnw, int a, uint32_t *data)
{
uint8_t h = swd_header(apndp, rnw, a);
pin_swdio_dir(1);
for (int i = 0; i < 8; i++) bit_out(h >> i & 1);
pin_swdio_dir(0);
turnaround();
int ack = bit_in() | bit_in() << 1 | bit_in() << 2;
if (ack != ACK_OK) { /* WAIT, FAULT или тишина (линия подтянута: 0b111) */
turnaround();
pin_swdio_dir(1);
swd_bits(2, 0);
return ack;
}
if (rnw) {
uint32_t v = 0;
for (int i = 0; i < 32; i++) v |= (uint32_t)bit_in() << i;
int p = bit_in();
turnaround();
pin_swdio_dir(1);
swd_bits(2, 0);
if (p != parity32(v)) return ACK_PARITY;
*data = v;
} else {
turnaround();
pin_swdio_dir(1);
for (int i = 0; i < 32; i++) bit_out((int)(*data >> i & 1u));
bit_out(parity32(*data));
swd_bits(2, 0); /* два такта покоя: запись доходит до шины */
}
return ACK_OK;
}
static int xfer_retry(int apndp, int rnw, int a, uint32_t *data)
{
for (int tries = 0; tries < 100; tries++) {
int ack = swd_xfer(apndp, rnw, a, data);
if (ack != ACK_WAIT) return ack;
swd_waits++; /* шина занята: тот же запрос ещё раз */
}
return ACK_WAIT;
}
int dp_read(int a, uint32_t *v) { return xfer_retry(0, 1, a, v); }
int dp_write(int a, uint32_t v) { return xfer_retry(0, 0, a, &v); }
static int ap_select(uint32_t reg) /* APSEL = 0, банк — reg[7:4] */
{
uint32_t sel = reg & 0xF0u;
if (sel == sel_cache) return ACK_OK;
int ack = dp_write(DP_SELECT, sel);
if (ack == ACK_OK) sel_cache = sel;
return ack;
}
int ap_read(uint32_t reg, uint32_t *v) /* чтение AP отложено: результат — в RDBUFF */
{
uint32_t stale;
int ack = ap_select(reg);
if (ack == ACK_OK) ack = xfer_retry(1, 1, (int)(reg & 0xCu), &stale);
if (ack == ACK_OK) ack = dp_read(DP_RDBUFF, v);
return ack;
}
int ap_write(uint32_t reg, uint32_t v)
{
int ack = ap_select(reg);
return ack == ACK_OK ? xfer_retry(1, 0, (int)(reg & 0xCu), &v) : ack;
}
int swd_connect(uint32_t *idcode)
{
uint32_t cs = 0;
swd_jtag_to_swd();
int ack = dp_read(DP_IDCODE, idcode); /* после сброса линии — первым делом */
if (ack != ACK_OK) return ack;
dp_write(DP_ABORT, 0x1Eu); /* стереть залипшие флаги ошибок */
dp_write(DP_CTRL_STAT, 0x50000000u); /* CSYSPWRUPREQ | CDBGPWRUPREQ */
for (int i = 0; i < 10; i++) {
ack = dp_read(DP_CTRL_STAT, &cs);
if (ack == ACK_OK && (cs & 0xA0000000u) == 0xA0000000u) return ACK_OK;
}
return ACK_FAULT;
}
int mem_read32(uint32_t addr, uint32_t *v)
{
int ack = ap_write(AP_CSW, 0x23000002u); /* слово, без автоинкремента */
if (ack == ACK_OK) ack = ap_write(AP_TAR, addr);
if (ack == ACK_OK) ack = ap_read(AP_DRW, v);
return ack;
}
int mem_write32(uint32_t addr, uint32_t v)
{
int ack = ap_write(AP_CSW, 0x23000002u);
if (ack == ACK_OK) ack = ap_write(AP_TAR, addr);
if (ack == ACK_OK) ack = ap_write(AP_DRW, v);
return ack;
}
int mem_read_block(uint32_t addr, int n, uint32_t *buf) /* TAR сам растёт на 4 */
{
uint32_t prev;
int ack = ap_write(AP_CSW, 0x23000012u);
if (ack == ACK_OK) ack = ap_write(AP_TAR, addr);
if (ack == ACK_OK) ack = ap_select(AP_DRW);
if (ack == ACK_OK) ack = xfer_retry(1, 1, AP_DRW, &prev); /* ответ — прошлое чтение */
for (int i = 1; i < n && ack == ACK_OK; i++)
ack = xfer_retry(1, 1, AP_DRW, &buf[i - 1]); /* каждый ответ — предыдущее слово */
if (ack == ACK_OK) ack = dp_read(DP_RDBUFF, &buf[n - 1]); /* последнее — из RDBUFF */
return ack;
}
static int wait_dhcsr(uint32_t mask)
{
uint32_t v = 0;
for (int i = 0; i < 100; i++)
if (mem_read32(DHCSR, &v) == ACK_OK && (v & mask)) return ACK_OK;
return ACK_FAULT;
}
int core_halt(void)
{
int ack = mem_write32(DHCSR, DBGKEY | 0x3u); /* C_HALT | C_DEBUGEN */
return ack == ACK_OK ? wait_dhcsr(1u << 17) : ack; /* S_HALT */
}
int core_run(void) { return mem_write32(DHCSR, DBGKEY | 0x1u); }
int core_step(void)
{
int ack = mem_write32(DHCSR, DBGKEY | 0x5u); /* C_STEP | C_DEBUGEN, C_HALT = 0 */
return ack == ACK_OK ? wait_dhcsr(1u << 17) : ack;
}
int core_reg_read(int n, uint32_t *v) /* n: 0…12 R0…R12, 13 SP, 14 LR, 15 PC, 16 xPSR */
{
int ack = mem_write32(DCRSR, (uint32_t)n); /* REGWnR = 0: чтение */
if (ack == ACK_OK) ack = wait_dhcsr(1u << 16); /* S_REGRDY */
if (ack == ACK_OK) ack = mem_read32(DCRDR, v);
return ack;
}
int fpb_set(int n, uint32_t addr) /* FPB v1: только 0x00000000…0x1FFFFFFF */
{
if (addr >= 0x20000000u) return ACK_FAULT;
uint32_t rep = (addr & 2u) ? 2u : 1u; /* 10 — старшее полуслово, 01 — младшее */
int ack = mem_write32(FP_CTRL, 0x3u); /* KEY | ENABLE */
if (ack == ACK_OK) ack = mem_write32(FP_COMP(n), rep << 30 | (addr & 0x1FFFFFFCu) | 1u);
return ack;
}
int fpb_clear(int n) { return mem_write32(FP_COMP(n), 0); }
Дальше: раздел 36 «Типовые проблемы» — сводная таблица «симптом → причина → лечение» по всему справочнику.