Embedded HandbookПриложения
Все разделы › V. Отладка и качество
STM32F103, STM32F407 · уровень средний · 29 мин чтения
Открыть в интерактивном справочнике

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 в прогоне.

Сверху — чтение IDCODE на проводе SWDIO по тактам: запрос 0xA5 от отладчика, такт поворота, ответ OK из трёх бит, 32 бита 0x1BA01477 и бит чётности от цели, снова поворот и два такта покоя. Снизу — путь запроса: отладчик, порт SW-DP, порт AHB-AP, шина и регистры SCS, FPB, флеш-память и ОЗУ

Рисунок записан с модели: на каждом фронте 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_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 «Типовые проблемы» — сводная таблица «симптом → причина → лечение» по всему справочнику.

Открыть в интерактивном справочнике