9а. GPDMA на STM32U5: связные списки, триггеры и приём в кольцо без прерываний
Раздел 9 разобрал DMA у F1 и F4: каналы и потоки, бит CIRC, счётчик
NDTR. У STM32U5 этого контроллера нет. На его месте — GPDMA: у него нет
ни бита CIRC, ни двойной буферизации, ни таблицы привязок. Всё это
заменяет один механизм — связный список: цепочка описаний пересылок
в памяти, которую канал проходит сам. Здесь — как список устроен в памяти,
как из него собрать кольцо для приёма UART (сравнение с 16б), два буфера
для АЦП и вывод таблицы по триггеру таймера; чем отличается LPDMA и что
добавляет TrustZone.
Опоры: справочное руководство RM0456 (ред. 4), главы 17 «GPDMA» и 18
«LPDMA», заголовок stm32u585xx.h и HAL 1.3.0 из пака Keil STM32U5xx_DFP
2.2.1, пакет платы B-U585I-IOT02A_BSP 1.1.0. Свой код собран armclang и
arm-none-eabi-gcc под Cortex-M33. Тот же код прогнан на компьютере над
моделью канала, написанной по тексту RM0456, а сборка элементов списка
сверена слово в слово с кодом HAL. QEMU U5 не моделирует, на плате
не запускалось.
Как это называется в документации
Оригинал Перевод general purpose DMA (GPDMA) DMA общего назначения low-power DMA (LPDMA) DMA домена малого потребления linked-list item (LLI) элемент связного списка linked-list register file регистры канала, которые обновляет список link transfer загрузка следующего элемента из памяти update bits ( UT1,UT2,UB1,USA,UDA,ULL)биты обновления: какие регистры лежат в элементе run-to-completion mode список выполняется до конца сам link step mode ( LSM)один элемент за одно включение канала block / burst / beat блок / пакет / одна пересылка request ( REQSEL) / trigger (TRIGSEL)запрос периферии / триггер transfer complete event mode ( TCEM)на каком уровне поднимать «передача завершена» padding/alignment mode ( PAM)дополнение и выравнивание при разной ширине user setting error ( USEF)ошибка настройки update link error ( ULEF)ошибка шины при чтении элемента trigger overrun ( TOF)потерянный триггер 2D addressing двумерная адресация
9а.1 Что изменилось против F1 и F4
| F4 (раздел 9) | STM32U585 | |
|---|---|---|
| контроллеры | DMA1, DMA2 по 8 потоков | GPDMA1 — 16 каналов, LPDMA1 — 4 канала |
| выбор источника | поток + поле CHSEL, таблица привязок |
любой запрос на любой канал: REQSEL |
| что задаёт пересылку | периферия, память, направление | источник и приёмник, каждый со своей шириной и приращением |
| счётчик | NDTR, в элементах |
BNDT, в байтах |
| по кругу | бит CIRC |
связный список |
| два буфера | бит DBM |
связный список из двух элементов |
| старт по событию | нет | триггер: TRIGSEL, TRIGPOL, TRIGM |
| флаги | LISR/HISR на четыре потока |
свой CxSR у каждого канала, сброс в CxFCR |
| выключить | сбросить EN и ждать |
SUSP, ждать SUSPF, RESET |
| защита | нет | каналы делятся на защищённые и привилегированные |
Запросы. Таблицы привязок нет. Номер запроса пишут в поле REQSEL
регистра CxTR2 — семь бит, в таблице 136 руководства номера от 0 до 125:
adc1_dma — 0, usart1_rx_dma — 24, usart1_tx_dma — 25. Конфликтов
«два блока на одном канале» больше нет, но появился обратный: один запрос
нельзя отдавать двум включённым каналам, и ошибку настройки за это
контроллер не поднимает.
Каналы неравные (2д.4): у каналов 0…11 очередь на 8 байт и линейная адресация, у 12…15 — 32 байта и двумерная. Очередь есть всегда: пересылка идёт в два приёма — из источника в очередь и из очереди в приёмник. Режима «без FIFO», как у F4, у GPDMA нет.
Приоритет — поле PRIO в CxCR, четыре значения. Значение 3 — отдельный
класс: его обслуживают раньше всех. Значения 0, 1, 2 делят оставшееся
время шины с весами: по примеру из RM0456, запрос с приоритетом 0 рядом
с запросом приоритета 1 получает 1/4 оставшейся полосы против 3/4,
рядом с приоритетом 2 — 1/6 против 5/6.
Имена. В RM0456 регистры зовут GPDMA_CxCR, GPDMA_CxTR1; в заголовке
это поля CCR, CTR1, CTR2, CBR1, CSAR, CDAR, CTR3, CBR2,
CLLR, CLBAR, CSR, CFCR структуры DMA_Channel_TypeDef, а биты —
DMA_CCR_EN, DMA_CTR2_REQSEL без номера канала. Канал x лежит по смещению
0x50 + 0x80 · x от начала контроллера. У LPDMA1 структура та же.
9а.2 Связный список: CxLBAR, CxLLR и элемент в памяти
Шесть регистров канала описывают одну пересылку блока: CxTR1, CxTR2,
CxBR1, CxSAR, CxDAR и CxLLR. У каналов 12…15 к ним добавлены
CxTR3 и CxBR2. Когда блок закончен, канал смотрит в CxLLR:
CxLLR= 0 — список кончился: поднимаетсяTCF, битENсбрасывается сам;- иначе канал читает из памяти следующий элемент и продолжает.
Адрес элемента — старшие 16 бит из CxLBAR (поле LBA) плюс младшие 16
из CxLLR (поле LA, биты 15:2). Отсюда два правила RM0456: элемент
выровнен на 4 байта, а весь список одного канала лежит в одном окне
64 КБ. Остальные биты CxLLR — биты обновления: какие регистры лежат
в следующем элементе.
Бит CxLLR |
Номер | Что обновить из памяти |
|---|---|---|
UT1 |
31 | CxTR1 |
UT2 |
30 | CxTR2 |
UB1 |
29 | CxBR1 |
USA |
28 | CxSAR |
UDA |
27 | CxDAR |
UT3 |
26 | CxTR3 — только каналы 12…15 |
UB2 |
25 | CxBR2 — только каналы 12…15 |
ULL |
16 | сам CxLLR |
Элемент — не структура с фиксированными полями. Это слова подряд, без
пропусков: по одному на каждый взведённый бит, в порядке таблицы. Все
шесть бит — элемент в 6 слов (24 байта), у канала 12…15 со всеми восемью —
8 слов (32 байта). Один бит — одно слово. Важно, чей это CxLLR: раскладку
элемента описывают биты предыдущего элемента (для первого — самого
регистра). Слово LLR внутри элемента описывает уже следующий.
Типы и константы, на которые опираются все листинги раздела (gpdma.h):
/* Значения регистров одного элемента списка — в том порядке, в каком GPDMA читает их из памяти */
typedef struct {
uint32_t tr1, tr2, br1, sar, dar; /* есть у всех каналов */
uint32_t tr3, br2; /* только каналы 12…15: двумерная адресация */
} lli_regs_t;
/* Биты обновления CxLLR: какие регистры лежат в следующем элементе */
#define LLI_UPD_LINEAR (DMA_CLLR_UT1 | DMA_CLLR_UT2 | DMA_CLLR_UB1 | DMA_CLLR_USA | DMA_CLLR_UDA | DMA_CLLR_ULL)
#define LLI_UPD_2D (LLI_UPD_LINEAR | DMA_CLLR_UT3 | DMA_CLLR_UB2)
#define GPDMA_FLAGS (DMA_CFCR_TCF | DMA_CFCR_HTF | DMA_CFCR_DTEF | DMA_CFCR_ULEF | DMA_CFCR_USEF \
| DMA_CFCR_SUSPF | DMA_CFCR_TOF)
#define GPDMA_ERRORS (DMA_CSR_DTEF | DMA_CSR_ULEF | DMA_CSR_USEF)
#define GPDMA_W8 0u /* SDW_LOG2 / DDW_LOG2: двоичный логарифм ширины в байтах */
#define GPDMA_W16 1u
#define GPDMA_W32 2u
#define GPDMA_TRIG_RISING 1u /* TRIGPOL */
#define GPDMA_TRIG_BLOCK 0u /* TRIGM: триггер разрешает блок */
#define GPDMA_TRIG_LINK 2u /* загрузку следующего элемента */
#define GPDMA_TRIG_EACH 3u /* каждую пересылку */
Сборка элемента и значения CxLLR (gpdma.c):
/* Сколько 32-битных слов занимает элемент, который описывают биты обновления upd */
uint32_t lli_words(uint32_t upd)
{
uint32_t n = 0;
for (upd &= LLI_UPD_2D; upd != 0u; upd &= upd - 1u)
n++;
return n;
}
/* Собрать элемент в памяти. upd — биты Uxx из CxLLR ПРЕДЫДУЩЕГО элемента (для первого — из самого
регистра CxLLR): они говорят, какие регистры лежат в этом элементе. Слова идут подряд, без
пропусков, в порядке TR1, TR2, BR1, SAR, DAR, TR3, BR2, LLR. llr — то, что попадёт в CxLLR:
описание следующего элемента. Возвращает число записанных слов. */
uint32_t lli_pack(uint32_t *mem, const lli_regs_t *r, uint32_t upd, uint32_t llr)
{
uint32_t n = 0;
if (upd & DMA_CLLR_UT1) mem[n++] = r->tr1;
if (upd & DMA_CLLR_UT2) mem[n++] = r->tr2;
if (upd & DMA_CLLR_UB1) mem[n++] = r->br1;
if (upd & DMA_CLLR_USA) mem[n++] = r->sar;
if (upd & DMA_CLLR_UDA) mem[n++] = r->dar;
if (upd & DMA_CLLR_UT3) mem[n++] = r->tr3;
if (upd & DMA_CLLR_UB2) mem[n++] = r->br2;
if (upd & DMA_CLLR_ULL) mem[n++] = llr;
return n;
}
/* Значение CxLLR: какие регистры обновить (upd) и где лежит элемент — младшие 16 бит адреса.
Старшие 16 бит держит CxLBAR, один на весь список: список целиком лежит в одном окне 64 КБ. */
uint32_t lli_link(uint32_t upd, uint32_t addr)
{
return (upd & LLI_UPD_2D) | (addr & DMA_CLLR_LA);
}
Что с CxBR1, если его нет в элементе. Счётчик BNDT к концу блока
дошёл до нуля. Если UB1 = 0, а хоть один другой бит обновления взведён,
контроллер сам возвращает в BNDT запрограммированное значение. На этом
и держится кольцо (9а.4): длину блока в элементе повторять не нужно.
Два режима прохода. LSM = 0 в CxCR — список идёт сам до CxLLR = 0.
Если последний элемент указывает на себя или назад, канал не остановится
никогда — это и есть «по кругу». LSM = 1 — режим одного шага: канал
выполняет один элемент, загружает следующий и выключается; программа
включает его снова. За это время она может дописать или подменить
следующий элемент в памяти.
Прогон на модели. Список из трёх блоков: первый — в регистрах, второй —
полный элемент в 6 слов, третий — сжатый, как в примере RM0456 (UT2,
UDA, ULL: 3 слова), с CxLLR = 0. Копирование 64 байт в три разных
места: 3 блока, 2 загрузки, 9 слов прочитано, в конце EN = 0 и BNDT = 0.
В режиме одного шага те же 64 байта потребовали 3 включения канала.
Сверка с HAL. HAL_DMAEx_List_BuildNode кладёт регистры в массив
LinkRegisters[8] в том же порядке: для линейного канала CxLLR — слово
с индексом 5, для двумерного TR3 и BR2 — 5 и 6, CxLLR — 7. Пять слов
линейного элемента и семь слов двумерного, собранные lli_pack, совпали
с HAL побайтно. Структура DMA_NodeTypeDef у HAL занимает 36 байт:
8 слов регистров и слово служебных сведений, которое в канал не уходит.
9а.3 Регистры пересылки: CxTR1, CxTR2, CxBR1
CxTR1 — как переносить. Две одинаковые половины: младшая про
источник, старшая про приёмник.
| Поле | Биты | Смысл |
|---|---|---|
SDW_LOG2 / DDW_LOG2 |
1:0 / 17:16 | ширина: 0 — байт, 1 — полуслово, 2 — слово; 3 — ошибка настройки |
SINC / DINC |
3 / 19 | адрес растёт после каждой пересылки |
SBL_1 / DBL_1 |
9:4 / 25:20 | длина пакета минус один: 0…63 |
PAM |
12:11 | что делать при разной ширине |
SBX, DBX, DHX |
13, 26, 27 | перестановка байтов и полуслов |
SAP / DAP |
14 / 30 | какой из двух портов AHB использовать |
SSEC / DSEC |
15 / 31 | пересылка защищённая — только у защищённого канала |
Направления «периферия → память» больше нет: есть источник и приёмник. Растёт адрес той стороны, где память.
PAM заменяет сразу несколько приёмов F4. Приёмник шире источника: 00 —
значение в младших битах, старшие — нули; 01 — расширить знаком; 1x —
собрать несколько единиц источника в одну единицу приёмника.
Приёмник уже источника: 00 — оставить младшую часть, 01 — старшую,
1x — разложить на несколько записей. Так 16-битная таблица ложится
в 32-битный регистр без промежуточного массива (9а.5).
Пакет программа задаёт длиной до 64 пересылок, но контроллер сам режет его на части: если пакет больше половины очереди канала, если он переходит границу 1 КБ адресов или длиннее 16 пересылок. Целостность данных при этом сохраняется — в отличие от F4, где за границу 1 КБ отвечал программист (9.6).
CxTR2 — когда переносить.
| Поле | Биты | Смысл |
|---|---|---|
REQSEL |
6:0 | номер запроса периферии |
SWREQ |
9 | 1 — запрос программный, REQSEL не смотрят: память → память |
DREQ |
10 | 0 — запрос даёт источник, 1 — приёмник |
BREQ |
11 | 1 — один запрос на целый блок |
TRIGM |
15:14 | что разрешает триггер |
TRIGSEL |
22:16 | номер триггера |
TRIGPOL |
25:24 | 01 — фронт, 10 — спад, 00 и 11 — триггера нет |
TCEM |
31:30 | уровень события «передача завершена» |
DREQ — то, что осталось от бита направления: при передаче в периферию
запрос даёт приёмник, и DREQ = 1. BREQ нужен двум запросам из таблицы
137: lptim1_ue_dma и lptim3_ue_dma.
Запрос и триггер — разные вещи. Запрос говорит «данные готовы» и
приходит от того блока, с которым идёт обмен. Триггер говорит «можно
начинать» и приходит со стороны: линии EXTI (номера 0…7 в таблице 138),
завершение другого канала (gpdma1_ch0_tc — 22 … gpdma1_ch15_tc — 37),
таймеры (tim2_trgo — 42, tim15_trgo — 43). TRIGM выбирает, что именно
ждёт триггера: 00 — начало каждого блока, 10 — загрузку следующего
элемента, 11 — каждую пересылку. Один сработавший триггер контроллер
запоминает; второй, пришедший до конца пересылки, теряется, и поднимается
TOF. Канал при этом не выключается.
⚠️ В заголовке U585 поле
TRIGSEL— 6 бит (маска0x3F), а в RM0456 ред. 4 — 7 бит, и таблица триггеров идёт до номера 67. Старшие номера — у других камней серии;tim3_trgo,tim4_trgoиtim5_trgo(44…46) по сноске к таблице есть только у U59x/5Ax и U5Fx/5Gx.
TCEM — на каком уровне поднимать TCF и HTF: 00 — каждый блок, 01 —
повторённый блок целиком (каналы 12…15), 10 — каждый элемент вместе
с загрузкой следующего, 11 — только конец всего списка. У бесконечного
списка с TCEM = 11 событий не будет вовсе.
Два построителя полей (gpdma.c):
/* CxTR1: ширина и приращение адреса источника и приёмника; пакет — одна пересылка, PAM = 00 */
uint32_t gpdma_tr1(uint32_t sdw_log2, uint32_t sinc, uint32_t ddw_log2, uint32_t dinc)
{
return ((sdw_log2 << DMA_CTR1_SDW_LOG2_Pos) & DMA_CTR1_SDW_LOG2) | (sinc ? DMA_CTR1_SINC : 0u)
| ((ddw_log2 << DMA_CTR1_DDW_LOG2_Pos) & DMA_CTR1_DDW_LOG2) | (dinc ? DMA_CTR1_DINC : 0u);
}
/* Поля триггера в CxTR2: номер входа, фронт, что именно триггер разрешает */
uint32_t gpdma_trig(uint32_t trigsel, uint32_t pol, uint32_t mode)
{
return ((trigsel << DMA_CTR2_TRIGSEL_Pos) & DMA_CTR2_TRIGSEL)
| ((pol << DMA_CTR2_TRIGPOL_Pos) & DMA_CTR2_TRIGPOL)
| ((mode << DMA_CTR2_TRIGM_Pos) & DMA_CTR2_TRIGM);
}
CxBR1 — сколько. BNDT (биты 15:0) — длина блока в байтах, до 65 535;
пока канал включён, поле читается и убывает. Длина обязана делиться
на ширину источника, иначе — USEF. Половина блока для HTF — BNDT/2
с округлением вверх.
Двумерная адресация есть только у каналов 12…15. BRC (биты 26:16
в CxBR1) — сколько раз повторить блок, 0…2047. CxTR3 — прибавка
к адресу после каждого пакета (SAO, DAO, по 13 бит), CxBR2 — после
каждого блока (BRSAO, BRDAO, по 16 бит); знак задают биты SDEC,
DDEC, BRSDEC, BRDDEC в CxBR1. Так читают прямоугольник из кадра
или раскладывают поток по столбцам. Кольцо на таком канале RM0456
предлагает делать иначе: отрицательная прибавка после блока сама
возвращает адрес к началу.
9а.4 🔧 Приём UART в кольцо: список из одного слова
Задача та же, что в 16б: USART1 принимает на 921 600 бод, DMA кладёт байты
в кольцо 512 байт, разбор зовут три события. Отличается только то, как
сделано «по кругу». RM0456 (17.4.13) описывает это так: первый блок задают
регистры, а единственный элемент списка возвращает адрес памяти в начало;
длину блока контроллер восстанавливает сам. Для приёма это элемент
с одним битом UDA — одно слово. ULL = 0: CxLLR не обновляется и
продолжает указывать на тот же элемент.
/* Приём в кольцо: периферия -> память, байт на запрос, по кругу без участия программы.
Список — один элемент в одно слово: после каждого круга GPDMA читает из node новый CxDAR,
начало кольца. CxBR1 он восстанавливает сам, а CxLLR не обновляется (ULL = 0) и продолжает
указывать на тот же элемент. Это замена биту CIRC у F1 и F4. */
void gpdma_ring_rx(DMA_Channel_TypeDef *ch, uint32_t reqsel, uint32_t src_reg,
uint32_t buf_addr, uint16_t size, uint32_t *node, uint32_t node_addr)
{
lli_regs_t r = {0};
(void)gpdma_abort(ch);
r.dar = buf_addr;
(void)lli_pack(node, &r, DMA_CLLR_UDA, 0u); /* в элементе одно слово — CxDAR */
ch->CTR1 = gpdma_tr1(GPDMA_W8, 0u, GPDMA_W8, 1u); /* байт -> байт, растёт адрес приёмника */
ch->CTR2 = reqsel & DMA_CTR2_REQSEL; /* запрос даёт источник: DREQ = 0; TCEM = 00 */
ch->CBR1 = size; /* длина блока — в БАЙТАХ */
ch->CSAR = src_reg;
ch->CDAR = buf_addr;
ch->CLBAR = node_addr & DMA_CLBAR_LBA; /* старшие 16 бит адреса списка */
ch->CLLR = lli_link(DMA_CLLR_UDA, node_addr); /* обновлять только CxDAR, брать по node_addr */
ch->CCR = DMA_CCR_HTIE | DMA_CCR_TCIE | DMA_CCR_DTEIE | DMA_CCR_ULEIE | DMA_CCR_USEIE
| DMA_CCR_PRIO_1; /* LSM = 0: список идёт сам */
ch->CCR |= DMA_CCR_EN;
}
/* Куда GPDMA положит следующий байт кольца: BNDT — сколько байт блока осталось, как NDTR у F4 */
uint16_t gpdma_ring_wr(const DMA_Channel_TypeDef *ch, uint16_t size)
{
uint16_t left = (uint16_t)(ch->CBR1 & DMA_CBR1_BNDT);
if (left == 0u || left > size) /* BNDT = 0 — миг между концом круга и загрузкой элемента */
return 0u;
return (uint16_t)(size - left);
}
Позиция записи считается как в 16б.1: wr = size − BNDT. Разбор кадров —
тот же uring.c из 16б.4 без единой правки: ему вместо NDTR передают
BNDT.
Константы примеров (gpdma_u585.c):
#define PCLK2_HZ 160000000u
#define BAUD 921600u
#define RX_SIZE 512u
#define ADC_N 256u /* отсчётов в одном буфере */
#define PAT_N 8u
#define REQ_ADC1 0u /* GPDMA1: adc1_dma */
#define REQ_USART1_RX 24u /* GPDMA1: usart1_rx_dma */
#define TRIG_TIM2_TRGO 42u /* GPDMA1: tim2_trgo */
#define LPREQ_LPUART1_RX 0u /* LPDMA1: lpuart1_rx_dma */
#define ADC_EXT_TIM2_TRGO 11u /* ADC1: adc_ext_trg11 = tim2_trgo */
Буферы и элементы списков — статические: канал читает элемент после каждого круга, пока включён.
static uint8_t rx_buf[RX_SIZE];
static uint32_t rx_node[1]; /* элемент списка кольца: одно слово */
static uring_t rx;
static uint16_t adc_a[ADC_N], adc_b[ADC_N];
static uint32_t adc_nodes[4]; /* два элемента по два слова */
static const uint16_t pattern[PAT_N] = {0x0001u, 0x0003u, 0x0007u, 0x000Fu, 0x000Eu, 0x000Cu, 0x0008u, 0x0000u};
static uint32_t pat_node[1];
static uint8_t lp_buf[64];
static uint32_t lp_node[1];
volatile uint32_t rx_frames, dma_errors, uart_ore, adc_blocks;
volatile const uint16_t *adc_ready; /* буфер, который можно обрабатывать */
static void app_frame(const uint8_t *a, uint16_t na, const uint8_t *b, uint16_t nb)
{
(void)a; (void)na; (void)b; (void)nb;
rx_frames++;
}
Привязка к USART1:
static void uart_ring_arm(void)
{
gpdma_ring_rx(GPDMA1_Channel0, REQ_USART1_RX, (uint32_t)&USART1->RDR,
(uint32_t)rx_buf, RX_SIZE, rx_node, (uint32_t)rx_node);
}
void uart_ring_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPDMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
(void)RCC->APB2ENR;
uring_init(&rx, rx_buf, RX_SIZE, app_frame);
uart_ring_arm(); /* сначала канал, потом запросы */
USART1->BRR = (PCLK2_HZ + BAUD / 2u) / BAUD; /* 174: 919 540 бод */
USART1->CR3 = USART_CR3_DMAR | USART_CR3_EIE;
USART1->CR1 = USART_CR1_UE | USART_CR1_RE | USART_CR1_IDLEIE;
NVIC_SetPriority(GPDMA1_Channel0_IRQn, 5); /* один приоритет: разбор не прервёт сам себя */
NVIC_SetPriority(USART1_IRQn, 5);
NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
NVIC_EnableIRQ(USART1_IRQn);
}
static void rx_poll(void)
{
uring_poll(&rx, (uint16_t)(GPDMA1_Channel0->CBR1 & DMA_CBR1_BNDT));
}
void GPDMA1_Channel0_IRQHandler(void) /* HT, TC или ошибка */
{
uint32_t sr = GPDMA1_Channel0->CSR;
GPDMA1_Channel0->CFCR = sr & GPDMA_FLAGS; /* сброс — записью единиц */
if (sr & GPDMA_ERRORS) { /* канал уже выключен железом */
dma_errors++;
rx.rd = 0;
uart_ring_arm(); /* сброс канала и запуск с начала кольца */
return;
}
rx_poll();
}
void USART1_IRQHandler(void)
{
uint32_t isr = USART1->ISR;
if (isr & USART_ISR_ORE)
uart_ore++;
/* у U5 флаги снимают записью в ICR: регистр данных не читают, байт у GPDMA не отнять */
USART1->ICR = USART_ICR_IDLECF | USART_ICR_ORECF | USART_ICR_NECF | USART_ICR_FECF;
if (isr & USART_ISR_IDLE)
rx_poll();
}
При такте 160 МГц (2д.5) делитель BRR = 174 даёт 919 540 бод, ошибка
−0,22 %. Что получилось в регистрах, показала модель: CxTR1 =
0x00080000 (один бит DINC), CxTR2 = 0x00000018 (запрос 24), CxBR1
= 512, CxCR = 0x00801F01; в CxLLR — бит 27 и младшие 16 бит адреса
элемента.
Против 16б:
| F407 (16б.3) | U585 | |
|---|---|---|
| «по кругу» | бит CIRC |
элемент списка в одно слово |
| счётчик | SxNDTR |
BNDT в CxBR1 |
| перед настройкой | сбросить EN, ждать, сбросить флаги в LIFCR |
gpdma_abort: сброс канала и флагов |
| FIFO | выключить: прямой режим | очередь есть всегда |
снять IDLE |
чтение SR, затем DR — и только при RXNE = 0 |
запись IDLECF в ICR |
Ловушка 16б.7 «снятие IDLE отнимает байт» у U5 исчезла: флаг снимают отдельным регистром.
«Без прерываний» — про само кольцо: перезапускать канал не нужно, он
идёт сам, пока есть питание. Прерывания здесь только зовут разбор. Можно
обойтись и без них: опрашивать BNDT по таймеру, как в 16б.5.
Прогон на модели: 200 кадров формата 16б, 7478 байт подряд через
кольцо 512 — 14 полных кругов. После каждого круга CxDAR вернулся
в начало кольца, BNDT — к 512; из списка прочитано 14 слов, по одному
на круг. HTF поднялся 15 раз, TCF — 14. Позиция size − BNDT совпала
с числом принятых байт после каждого из 7478. Разбор получил все 200
кадров; занятость кольца не превысила 306 байт.
А HAL? Пакет платы B-U585I-IOT02A_BSP делает такое же кольцо из одного
элемента (звук с микрофона), но средствами HAL. HAL_DMAEx_List_SetCircularMode
оставляет в элементе все шесть регистров — 6 слов на круг. После
HAL_DMAEx_List_ConvertQToDynamic в CxLLR элемента остаётся ULL = 0,
а пять остальных бит взведены — 5 слов на круг. Свой элемент — 1 слово.
Разница — четыре или пять лишних чтений памяти раз в 512 байт: на скорость
не влияет, но показывает, что именно делает список.
9а.5 🔧 АЦП по таймеру и таблица по триггеру
Два буфера по очереди. У F4 для этого был режим двойной буферизации.
Здесь — два элемента по два слова, CxDAR и CxLLR, которые указывают
друг на друга:
/* Два буфера по очереди — замена двойной буферизации F4. Два элемента по два слова: CxDAR и
CxLLR. Первый блок идёт по регистрам в буфер A, дальше B, A, B ... Событие TC — после каждого
блока: заполненный буфер можно обрабатывать, пока пишется второй. */
void gpdma_pingpong(DMA_Channel_TypeDef *ch, uint32_t tr1, uint32_t tr2, uint32_t src_reg,
uint32_t a_addr, uint32_t b_addr, uint16_t bytes, uint32_t *nodes, uint32_t nodes_addr)
{
const uint32_t upd = DMA_CLLR_UDA | DMA_CLLR_ULL;
lli_regs_t r = {0};
uint32_t n;
(void)gpdma_abort(ch);
r.dar = b_addr; /* элемент 0: писать в B, за ним — элемент 1 */
n = lli_pack(&nodes[0], &r, upd, lli_link(upd, nodes_addr + 4u * lli_words(upd)));
r.dar = a_addr; /* элемент 1: писать в A, за ним — элемент 0 */
(void)lli_pack(&nodes[n], &r, upd, lli_link(upd, nodes_addr));
ch->CTR1 = tr1;
ch->CTR2 = tr2;
ch->CBR1 = bytes;
ch->CSAR = src_reg;
ch->CDAR = a_addr;
ch->CLBAR = nodes_addr & DMA_CLBAR_LBA;
ch->CLLR = lli_link(upd, nodes_addr);
ch->CCR = DMA_CCR_TCIE | DMA_CCR_DTEIE | DMA_CCR_ULEIE | DMA_CCR_USEIE | DMA_CCR_PRIO_1;
ch->CCR |= DMA_CCR_EN;
}
АЦП запускает таймер, а не GPDMA. Запрос adc1_dma (номер 0) приходит
на каждый готовый отсчёт; темп отсчётов задаёт внешний запуск АЦП — для
ADC1 tim2_trgo стоит под номером 11 (adc_ext_trg11). В CFGR1 у ADC1
поле DMNGT = 11 — «DMA по кругу», EXTEN = 01 — запуск по фронту.
void adc_dma_arm(uint32_t psc, uint32_t arr) /* АЦП уже включён, канал выбран */
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPDMA1EN;
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM2EN;
(void)RCC->APB1ENR1;
adc_ready = 0;
gpdma_pingpong(GPDMA1_Channel1, gpdma_tr1(GPDMA_W16, 0u, GPDMA_W16, 1u), REQ_ADC1,
(uint32_t)&ADC1->DR, (uint32_t)adc_a, (uint32_t)adc_b,
(uint16_t)(2u * ADC_N), adc_nodes, (uint32_t)adc_nodes);
NVIC_SetPriority(GPDMA1_Channel1_IRQn, 6);
NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
/* запрос GPDMA даёт АЦП на каждый отсчёт; темп отсчётов — фронт TRGO таймера */
ADC1->CFGR1 = (ADC1->CFGR1 & ~(ADC_CFGR1_DMNGT | ADC_CFGR1_EXTSEL | ADC_CFGR1_EXTEN | ADC_CFGR1_CONT))
| (3u << ADC_CFGR1_DMNGT_Pos) /* 11: DMA по кругу */
| (ADC_EXT_TIM2_TRGO << ADC_CFGR1_EXTSEL_Pos)
| ADC_CFGR1_EXTEN_0; /* 01: по фронту */
ADC1->CR |= ADC_CR_ADSTART; /* ждать запуска от таймера */
TIM2->PSC = psc;
TIM2->ARR = arr;
TIM2->CR2 = TIM_CR2_MMS_1; /* TRGO по событию обновления */
TIM2->EGR = TIM_EGR_UG;
TIM2->CR1 = TIM_CR1_CEN;
}
void GPDMA1_Channel1_IRQHandler(void) /* TC после каждого блока */
{
uint32_t sr = GPDMA1_Channel1->CSR;
GPDMA1_Channel1->CFCR = sr & GPDMA_FLAGS;
if (sr & GPDMA_ERRORS) {
dma_errors++;
return;
}
adc_ready = (adc_blocks & 1u) ? adc_b : adc_a; /* чётные блоки — в A, нечётные — в B */
adc_blocks++;
}
Буфер на 256 отсчётов по 16 бит — это BNDT = 512: счёт в байтах. Модель
прогнала 1024 отсчёта: 4 блока, 4 загрузки по 2 слова — 8 слов; блоки
легли в A, B, A, B без единой ошибки. HAL для той же пары буферов после
сжатия оставляет первый элемент полным (6 слов), второй — в 3 слова
(CxSAR, CxDAR, CxLLR): адреса HAL из элемента не убирает никогда,
даже одинаковые. Второй элемент HAL и lli_pack с теми же битами
совпали побайтно.
Таблица по триггеру. В 9.7 таблицу в регистр таймера переносил запрос
TIMx_UP. У GPDMA это делается без запроса периферии: SWREQ = 1 и
триггер на каждую пересылку.
/* Таблица -> регистр по триггеру, по кругу. Запроса периферии нет (SWREQ = 1): каждую пересылку
разрешает один фронт триггера (TRIGM = 11). Полуслово таблицы дополняется нулями до слова
регистра (PAM = 00). Элемент списка — одно слово: CxSAR, начало таблицы. */
void gpdma_table_out(DMA_Channel_TypeDef *ch, uint32_t trigsel, uint32_t dst_reg,
uint32_t tab_addr, uint16_t bytes, uint32_t *node, uint32_t node_addr)
{
lli_regs_t r = {0};
(void)gpdma_abort(ch);
r.sar = tab_addr;
(void)lli_pack(node, &r, DMA_CLLR_USA, 0u);
ch->CTR1 = gpdma_tr1(GPDMA_W16, 1u, GPDMA_W32, 0u);
ch->CTR2 = DMA_CTR2_SWREQ | gpdma_trig(trigsel, GPDMA_TRIG_RISING, GPDMA_TRIG_EACH);
ch->CBR1 = bytes;
ch->CSAR = tab_addr;
ch->CDAR = dst_reg;
ch->CLBAR = node_addr & DMA_CLBAR_LBA;
ch->CLLR = lli_link(DMA_CLLR_USA, node_addr);
ch->CCR = DMA_CCR_DTEIE | DMA_CCR_ULEIE | DMA_CCR_USEIE | DMA_CCR_TOIE;
ch->CCR |= DMA_CCR_EN;
}
Здесь кольцо «в другую сторону»: элемент возвращает на начало адрес
источника, бит USA. Вызов — таблица pattern из восьми полуслов
в регистр ODR порта B по tim2_trgo:
/* ── Таблица -> GPIOB->ODR по TRGO таймера TIM2: канал 2 GPDMA1 ───────── */
void pattern_start(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPDMA1EN;
RCC->AHB2ENR1 |= RCC_AHB2ENR1_GPIOBEN;
(void)RCC->AHB2ENR1;
gpdma_table_out(GPDMA1_Channel2, TRIG_TIM2_TRGO, (uint32_t)&GPIOB->ODR,
(uint32_t)pattern, (uint16_t)sizeof pattern, pat_node, (uint32_t)pat_node);
}
Выводы порта B к этому моменту настроены на выход, а TIM2 запущен, как
в adc_dma_arm: без фронтов TRGO канал стоит. В модели таблица
из 8 полуслов и 20 фронтов триггера
tim2_trgo (номер 42) дали 20 записей в регистр, 2 загрузки элемента;
CxTR2 = 0x012AC200. Старшая половина каждого записанного слова — нули,
хотя память вокруг таблицы была забита единицами.
9а.6 Флаги, остановка, LPDMA и TrustZone
Флаги — в CxSR своего канала, сброс — записью единицы в тот же бит
CxFCR. Прерывание у каждого канала своё.
| Флаг | Бит | Что случилось | Канал после этого |
|---|---|---|---|
IDLEF |
0 | канал стоит; после сброса — 1 | — |
TCF |
8 | передача завершена на уровне TCEM |
работает, если список не кончился |
HTF |
9 | половина блока | работает |
DTEF |
10 | ошибка шины на данных | выключен, нужен RESET |
ULEF |
11 | ошибка шины при чтении элемента | выключен, регистры списка писать заново |
USEF |
12 | недопустимая настройка | выключен, не начав |
SUSPF |
13 | пауза наступила | стоит, EN = 1 |
TOF |
14 | триггер потерян | работает |
USEF поднимают, например: BNDT = 0 без UB1; длина блока, не кратная
ширине источника; адрес, не выровненный на ширину; ширина 3 (8 байт).
Выключить канал нельзя сбросом EN: запись нуля в этот бит RM0456
называет игнорируемой. EN сбрасывает только само железо — по концу
списка и по ошибке — или сброс канала. Последовательность остановки
(17.4.4):
/* Остановить канал — RM0456, 17.4.4. 0 — канал остановлен и сброшен, -1 — пауза не наступила. */
int gpdma_abort(DMA_Channel_TypeDef *ch)
{
if (ch->CCR & DMA_CCR_EN) {
uint32_t guard = 100000u;
ch->CCR |= DMA_CCR_SUSP; /* 1. попросить паузу: EN сам не сбрасывается */
while (!(ch->CSR & DMA_CSR_SUSPF)) { /* 2. дождаться конца идущей пересылки */
if (--guard == 0u)
return -1;
}
}
ch->CCR = DMA_CCR_RESET; /* 3. сброс: очередь, состояние, EN и SUSP */
ch->CFCR = GPDMA_FLAGS; /* флаги снимают записью единиц в CxFCR */
return 0; /* 4. дальше — заново CxBR1, CxSAR, CxDAR и EN */
}
Пауза без сброса — те же шаги 1 и 2, а продолжение — запись нуля в SUSP.
На паузе можно спокойно читать BNDT и адреса: они не меняются.
Запись RESET действует только на остановленный или стоящий на паузе
канал, поэтому шаг 2 пропускать нельзя.
LPDMA1 — второй контроллер, в домене, который живёт в Stop 2 (10а). Регистры канала те же, код 9а.4 работает без правок:
void lpuart_ring_init(void)
{
RCC->AHB3ENR |= RCC_AHB3ENR_LPDMA1EN;
RCC->SRDAMR |= RCC_SRDAMR_LPDMA1AMEN; /* самостоятельная работа в Stop 0, 1 и 2 */
(void)RCC->AHB3ENR;
gpdma_ring_rx(LPDMA1_Channel0, LPREQ_LPUART1_RX, (uint32_t)&LPUART1->RDR,
(uint32_t)lp_buf, (uint16_t)sizeof lp_buf, lp_node, (uint32_t)lp_node);
}
/* ── Привилегии и TrustZone: только привилегированный код, до включения канала ── */
void gpdma_channel_privileged(uint32_t n)
{
GPDMA1->PRIVCFGR |= 1u << n; /* PRIVn: канал виден только привилегированному коду */
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
GPDMA1->SECCFGR |= 1u << n; /* SECn: канал принадлежит защищённому миру */
GPDMA1->RCFGLOCKR |= 1u << n; /* LOCKn: до сброса атрибуты не изменить */
#endif
}
| GPDMA1 | LPDMA1 | |
|---|---|---|
| каналов | 16 | 4 |
| порты AHB | два | один |
| очередь | 8 или 32 байта | нет |
| пакеты | до 64 пересылок | только одиночные |
| двумерная адресация | каналы 12…15 | нет |
PAM |
с упаковкой | без упаковки |
REQSEL / TRIGSEL |
7 бит | 5 бит |
| запросы | таблица 136 | 17 запросов: LPUART1, SPI3, I2C3, ADC4, DAC1, ADF1, LPTIM1, LPTIM3 |
| сам работает в | Sleep, Stop 0, Stop 1 | Sleep, Stop 0, Stop 1, Stop 2 |
Номера запросов у LPDMA1 свои: lpuart1_rx_dma там 0, а не 34, как
у GPDMA1; adc4_dma — 7. В Stop канал сам просит такт у RCC, когда пришёл
запрос или триггер, и отпускает его после пересылки; будит ядро обычное
прерывание канала.
TrustZone и привилегии. Три общих регистра по биту на канал:
SECCFGR — канал защищённый, PRIVCFGR — привилегированный,
RCFGLOCKR — заморозить оба атрибута до сброса. Чужое чтение регистров
такого канала возвращает ноль, чужая запись ничего не делает. Менять
атрибуты можно только при выключенном канале. У защищённого канала биты
SSEC и DSEC в CxTR1 отдельно задают, защищённым ли обращением читать
источник и писать приёмник, а элементы списка он читает защищённым
обращением всегда. Запросы прерываний разведены по двум регистрам:
MISR — незащищённые каналы, SMISR — защищённые. Если TrustZone
выключена (TZEN = 0, раздел 2б), RCFGLOCKR не пишется.
9а.7 Грабли
Канал включён и молчит, USEF = 1. Чаще всего — длина в BNDT
посчитана в элементах, а не в байтах, и не делится на ширину; или адрес
буфера uint8_t отдан под 16-битные пересылки. У F4 то же самое давало
сдвинутые данные, здесь — отказ начинать.
Принята половина ожидаемого. BNDT = числу отсчётов при 16-битной
ширине: 256 отсчётов — это 512 байт.
Кольцо остановилось после первого круга. В CxLLR ноль или элемент
собран не по тем битам: CxLLR говорит «в элементе CxDAR», а в памяти
лежит полный элемент — в CxDAR попало значение CxTR1. Число слов
в элементе обязано равняться числу взведённых бит.
ULEF сразу после первого блока. Элемент лежит не там: CxLBAR не
записан или элементы списка в разных окнах 64 КБ.
Список в стеке. Элемент объявлен локальной переменной. Канал читает его после каждого круга — через секунду там другие данные. Элементы списка — статические, как и буферы (9.9).
EN не сбрасывается. Запись нуля в EN игнорируется. Только
SUSP → SUSPF → RESET.
После RESET канал перезапущен, а пишет не туда. CxBR1, CxSAR
и CxDAR железо меняло по ходу работы; после сброса их пишут заново.
Один запрос на двух каналах. Ошибки настройки не будет — будут пропавшие данные. Следить самому.
Триггеры теряются. TOF поднимается, когда второй триггер пришёл,
пока первая пересылка не кончилась. Канал при этом работает дальше,
и без TOIE потерю не видно.
Номер запроса от GPDMA записан в LPDMA. Таблицы разные, а поле
одинаковое. lpuart1_rx_dma: 34 у GPDMA1 и 0 у LPDMA1.
Позиция по BNDT на миг впереди памяти. BNDT считает прочитанное
из источника; байт может ещё лежать в очереди канала. К моменту
прерывания IDLE — через целый символ — он давно в памяти. HAL считает
позицию так же, по BNDT. На железе не проверялось.
9а.8 Итоги
- У U585 два контроллера: GPDMA1 на 16 каналов и LPDMA1 на 4. Любой
запрос — на любой канал, номером в
REQSEL:usart1_rx_dma— 24. - Пересылку описывают шесть регистров; у каналов 12…15 — восемь.
Следующую читает из памяти сам канал: адрес —
CxLBARплюсLA, состав — биты обновленияCxLLR. - Элемент списка — слова подряд по числу взведённых бит: от 1 до 6,
у каналов 12…15 до 8 (32 байта).
CxLLR= 0 — конец списка. - Кольцо для приёма — элемент в одно слово с битом
UDA,ULL= 0;BNDTвосстанавливается сам. Позиция записи — size −BNDT. - Прогон: 7478 байт, 14 кругов, 14 слов из списка, 200 кадров из 200; HAL для того же кольца читает 5 или 6 слов на круг.
- Два буфера по очереди — два элемента по 2 слова; таблица по триггеру —
SWREQ= 1,TRIGM= 11 и элемент с битомUSA. BNDT— в байтах, до 65 535. Запись нуля вENигнорируется: остановка —SUSP,SUSPF,RESET.- Флаги — в
CxSRканала, сброс — вCxFCR. ПослеDTEF,ULEF,USEFканал выключен. - LPDMA1 — те же регистры без очереди, пакетов и двумерной адресации, зато работает в Stop 2.
- Код:
gpdma_ring_rx— 94 байта,gpdma_abort— 62,lli_pack— 152 (armclang,-Os).
Полные исходники
gpdma.h — типы и константы GPDMA и LPDMA: элемент связного списка, кольцо, остановка канала.
/* Раздел 9а: GPDMA и LPDMA STM32U5 — элемент связного списка, кольцевой приём, остановка канала.
Регистры канала — DMA_Channel_TypeDef из stm32u585xx.h; раскладка элемента в памяти — RM0456, 17.4.5.
Адреса передаются числами uint32_t: тот же код собирается и для модели на компьютере. */
#ifndef GPDMA_H
#define GPDMA_H
#include <stdint.h>
#include "stm32u5xx.h"
/* Значения регистров одного элемента списка — в том порядке, в каком GPDMA читает их из памяти */
typedef struct {
uint32_t tr1, tr2, br1, sar, dar; /* есть у всех каналов */
uint32_t tr3, br2; /* только каналы 12…15: двумерная адресация */
} lli_regs_t;
/* Биты обновления CxLLR: какие регистры лежат в следующем элементе */
#define LLI_UPD_LINEAR (DMA_CLLR_UT1 | DMA_CLLR_UT2 | DMA_CLLR_UB1 | DMA_CLLR_USA | DMA_CLLR_UDA | DMA_CLLR_ULL)
#define LLI_UPD_2D (LLI_UPD_LINEAR | DMA_CLLR_UT3 | DMA_CLLR_UB2)
#define GPDMA_FLAGS (DMA_CFCR_TCF | DMA_CFCR_HTF | DMA_CFCR_DTEF | DMA_CFCR_ULEF | DMA_CFCR_USEF \
| DMA_CFCR_SUSPF | DMA_CFCR_TOF)
#define GPDMA_ERRORS (DMA_CSR_DTEF | DMA_CSR_ULEF | DMA_CSR_USEF)
#define GPDMA_W8 0u /* SDW_LOG2 / DDW_LOG2: двоичный логарифм ширины в байтах */
#define GPDMA_W16 1u
#define GPDMA_W32 2u
#define GPDMA_TRIG_RISING 1u /* TRIGPOL */
#define GPDMA_TRIG_BLOCK 0u /* TRIGM: триггер разрешает блок */
#define GPDMA_TRIG_LINK 2u /* загрузку следующего элемента */
#define GPDMA_TRIG_EACH 3u /* каждую пересылку */
uint32_t lli_words(uint32_t upd);
uint32_t lli_pack(uint32_t *mem, const lli_regs_t *r, uint32_t upd, uint32_t llr);
uint32_t lli_link(uint32_t upd, uint32_t addr);
uint32_t gpdma_tr1(uint32_t sdw_log2, uint32_t sinc, uint32_t ddw_log2, uint32_t dinc);
uint32_t gpdma_trig(uint32_t trigsel, uint32_t pol, uint32_t mode);
int gpdma_abort(DMA_Channel_TypeDef *ch);
void gpdma_ring_rx(DMA_Channel_TypeDef *ch, uint32_t reqsel, uint32_t src_reg,
uint32_t buf_addr, uint16_t size, uint32_t *node, uint32_t node_addr);
uint16_t gpdma_ring_wr(const DMA_Channel_TypeDef *ch, uint16_t size);
void gpdma_pingpong(DMA_Channel_TypeDef *ch, uint32_t tr1, uint32_t tr2, uint32_t src_reg,
uint32_t a_addr, uint32_t b_addr, uint16_t bytes, uint32_t *nodes, uint32_t nodes_addr);
void gpdma_table_out(DMA_Channel_TypeDef *ch, uint32_t trigsel, uint32_t dst_reg,
uint32_t tab_addr, uint16_t bytes, uint32_t *node, uint32_t node_addr);
#endif
gpdma.c — построители элементов списка, полей CxTR1/CxTR2 и запуск и остановка канала.
/* Раздел 9а: GPDMA и LPDMA STM32U5 регистрами. Один файл на оба контроллера: каналы LPDMA1 —
тот же DMA_Channel_TypeDef. Порядок действий — RM0456, 17.4.4, 17.4.5 и 17.4.13. */
#include "gpdma.h"
/* Сколько 32-битных слов занимает элемент, который описывают биты обновления upd */
uint32_t lli_words(uint32_t upd)
{
uint32_t n = 0;
for (upd &= LLI_UPD_2D; upd != 0u; upd &= upd - 1u)
n++;
return n;
}
/* Собрать элемент в памяти. upd — биты Uxx из CxLLR ПРЕДЫДУЩЕГО элемента (для первого — из самого
регистра CxLLR): они говорят, какие регистры лежат в этом элементе. Слова идут подряд, без
пропусков, в порядке TR1, TR2, BR1, SAR, DAR, TR3, BR2, LLR. llr — то, что попадёт в CxLLR:
описание следующего элемента. Возвращает число записанных слов. */
uint32_t lli_pack(uint32_t *mem, const lli_regs_t *r, uint32_t upd, uint32_t llr)
{
uint32_t n = 0;
if (upd & DMA_CLLR_UT1) mem[n++] = r->tr1;
if (upd & DMA_CLLR_UT2) mem[n++] = r->tr2;
if (upd & DMA_CLLR_UB1) mem[n++] = r->br1;
if (upd & DMA_CLLR_USA) mem[n++] = r->sar;
if (upd & DMA_CLLR_UDA) mem[n++] = r->dar;
if (upd & DMA_CLLR_UT3) mem[n++] = r->tr3;
if (upd & DMA_CLLR_UB2) mem[n++] = r->br2;
if (upd & DMA_CLLR_ULL) mem[n++] = llr;
return n;
}
/* Значение CxLLR: какие регистры обновить (upd) и где лежит элемент — младшие 16 бит адреса.
Старшие 16 бит держит CxLBAR, один на весь список: список целиком лежит в одном окне 64 КБ. */
uint32_t lli_link(uint32_t upd, uint32_t addr)
{
return (upd & LLI_UPD_2D) | (addr & DMA_CLLR_LA);
}
/* CxTR1: ширина и приращение адреса источника и приёмника; пакет — одна пересылка, PAM = 00 */
uint32_t gpdma_tr1(uint32_t sdw_log2, uint32_t sinc, uint32_t ddw_log2, uint32_t dinc)
{
return ((sdw_log2 << DMA_CTR1_SDW_LOG2_Pos) & DMA_CTR1_SDW_LOG2) | (sinc ? DMA_CTR1_SINC : 0u)
| ((ddw_log2 << DMA_CTR1_DDW_LOG2_Pos) & DMA_CTR1_DDW_LOG2) | (dinc ? DMA_CTR1_DINC : 0u);
}
/* Поля триггера в CxTR2: номер входа, фронт, что именно триггер разрешает */
uint32_t gpdma_trig(uint32_t trigsel, uint32_t pol, uint32_t mode)
{
return ((trigsel << DMA_CTR2_TRIGSEL_Pos) & DMA_CTR2_TRIGSEL)
| ((pol << DMA_CTR2_TRIGPOL_Pos) & DMA_CTR2_TRIGPOL)
| ((mode << DMA_CTR2_TRIGM_Pos) & DMA_CTR2_TRIGM);
}
/* Остановить канал — RM0456, 17.4.4. 0 — канал остановлен и сброшен, -1 — пауза не наступила. */
int gpdma_abort(DMA_Channel_TypeDef *ch)
{
if (ch->CCR & DMA_CCR_EN) {
uint32_t guard = 100000u;
ch->CCR |= DMA_CCR_SUSP; /* 1. попросить паузу: EN сам не сбрасывается */
while (!(ch->CSR & DMA_CSR_SUSPF)) { /* 2. дождаться конца идущей пересылки */
if (--guard == 0u)
return -1;
}
}
ch->CCR = DMA_CCR_RESET; /* 3. сброс: очередь, состояние, EN и SUSP */
ch->CFCR = GPDMA_FLAGS; /* флаги снимают записью единиц в CxFCR */
return 0; /* 4. дальше — заново CxBR1, CxSAR, CxDAR и EN */
}
/* Приём в кольцо: периферия -> память, байт на запрос, по кругу без участия программы.
Список — один элемент в одно слово: после каждого круга GPDMA читает из node новый CxDAR,
начало кольца. CxBR1 он восстанавливает сам, а CxLLR не обновляется (ULL = 0) и продолжает
указывать на тот же элемент. Это замена биту CIRC у F1 и F4. */
void gpdma_ring_rx(DMA_Channel_TypeDef *ch, uint32_t reqsel, uint32_t src_reg,
uint32_t buf_addr, uint16_t size, uint32_t *node, uint32_t node_addr)
{
lli_regs_t r = {0};
(void)gpdma_abort(ch);
r.dar = buf_addr;
(void)lli_pack(node, &r, DMA_CLLR_UDA, 0u); /* в элементе одно слово — CxDAR */
ch->CTR1 = gpdma_tr1(GPDMA_W8, 0u, GPDMA_W8, 1u); /* байт -> байт, растёт адрес приёмника */
ch->CTR2 = reqsel & DMA_CTR2_REQSEL; /* запрос даёт источник: DREQ = 0; TCEM = 00 */
ch->CBR1 = size; /* длина блока — в БАЙТАХ */
ch->CSAR = src_reg;
ch->CDAR = buf_addr;
ch->CLBAR = node_addr & DMA_CLBAR_LBA; /* старшие 16 бит адреса списка */
ch->CLLR = lli_link(DMA_CLLR_UDA, node_addr); /* обновлять только CxDAR, брать по node_addr */
ch->CCR = DMA_CCR_HTIE | DMA_CCR_TCIE | DMA_CCR_DTEIE | DMA_CCR_ULEIE | DMA_CCR_USEIE
| DMA_CCR_PRIO_1; /* LSM = 0: список идёт сам */
ch->CCR |= DMA_CCR_EN;
}
/* Куда GPDMA положит следующий байт кольца: BNDT — сколько байт блока осталось, как NDTR у F4 */
uint16_t gpdma_ring_wr(const DMA_Channel_TypeDef *ch, uint16_t size)
{
uint16_t left = (uint16_t)(ch->CBR1 & DMA_CBR1_BNDT);
if (left == 0u || left > size) /* BNDT = 0 — миг между концом круга и загрузкой элемента */
return 0u;
return (uint16_t)(size - left);
}
/* Два буфера по очереди — замена двойной буферизации F4. Два элемента по два слова: CxDAR и
CxLLR. Первый блок идёт по регистрам в буфер A, дальше B, A, B ... Событие TC — после каждого
блока: заполненный буфер можно обрабатывать, пока пишется второй. */
void gpdma_pingpong(DMA_Channel_TypeDef *ch, uint32_t tr1, uint32_t tr2, uint32_t src_reg,
uint32_t a_addr, uint32_t b_addr, uint16_t bytes, uint32_t *nodes, uint32_t nodes_addr)
{
const uint32_t upd = DMA_CLLR_UDA | DMA_CLLR_ULL;
lli_regs_t r = {0};
uint32_t n;
(void)gpdma_abort(ch);
r.dar = b_addr; /* элемент 0: писать в B, за ним — элемент 1 */
n = lli_pack(&nodes[0], &r, upd, lli_link(upd, nodes_addr + 4u * lli_words(upd)));
r.dar = a_addr; /* элемент 1: писать в A, за ним — элемент 0 */
(void)lli_pack(&nodes[n], &r, upd, lli_link(upd, nodes_addr));
ch->CTR1 = tr1;
ch->CTR2 = tr2;
ch->CBR1 = bytes;
ch->CSAR = src_reg;
ch->CDAR = a_addr;
ch->CLBAR = nodes_addr & DMA_CLBAR_LBA;
ch->CLLR = lli_link(upd, nodes_addr);
ch->CCR = DMA_CCR_TCIE | DMA_CCR_DTEIE | DMA_CCR_ULEIE | DMA_CCR_USEIE | DMA_CCR_PRIO_1;
ch->CCR |= DMA_CCR_EN;
}
/* Таблица -> регистр по триггеру, по кругу. Запроса периферии нет (SWREQ = 1): каждую пересылку
разрешает один фронт триггера (TRIGM = 11). Полуслово таблицы дополняется нулями до слова
регистра (PAM = 00). Элемент списка — одно слово: CxSAR, начало таблицы. */
void gpdma_table_out(DMA_Channel_TypeDef *ch, uint32_t trigsel, uint32_t dst_reg,
uint32_t tab_addr, uint16_t bytes, uint32_t *node, uint32_t node_addr)
{
lli_regs_t r = {0};
(void)gpdma_abort(ch);
r.sar = tab_addr;
(void)lli_pack(node, &r, DMA_CLLR_USA, 0u);
ch->CTR1 = gpdma_tr1(GPDMA_W16, 1u, GPDMA_W32, 0u);
ch->CTR2 = DMA_CTR2_SWREQ | gpdma_trig(trigsel, GPDMA_TRIG_RISING, GPDMA_TRIG_EACH);
ch->CBR1 = bytes;
ch->CSAR = tab_addr;
ch->CDAR = dst_reg;
ch->CLBAR = node_addr & DMA_CLBAR_LBA;
ch->CLLR = lli_link(DMA_CLLR_USA, node_addr);
ch->CCR = DMA_CCR_DTEIE | DMA_CCR_ULEIE | DMA_CCR_USEIE | DMA_CCR_TOIE;
ch->CCR |= DMA_CCR_EN;
}
gpdma_u585.c — примеры на STM32U585: приём USART1 в кольцо, АЦП по таймеру, таблица в порт, LPUART1 через LPDMA1.
/* Раздел 9а: STM32U585 — приём USART1 в кольцо через GPDMA1, АЦП по таймеру в два буфера,
таблица в порт по триггеру, LPUART1 через LPDMA1. Такт 160 МГц и выводы — как в разделе 2д;
включение и калибровка АЦП здесь не показаны. Номера запросов и триггеров — RM0456,
таблицы 136, 138 и 146; внешний запуск ADC1 от TIM2 — таблица внешних запусков ADC12. */
#include "gpdma.h"
#include "uring.h"
#define PCLK2_HZ 160000000u
#define BAUD 921600u
#define RX_SIZE 512u
#define ADC_N 256u /* отсчётов в одном буфере */
#define PAT_N 8u
#define REQ_ADC1 0u /* GPDMA1: adc1_dma */
#define REQ_USART1_RX 24u /* GPDMA1: usart1_rx_dma */
#define TRIG_TIM2_TRGO 42u /* GPDMA1: tim2_trgo */
#define LPREQ_LPUART1_RX 0u /* LPDMA1: lpuart1_rx_dma */
#define ADC_EXT_TIM2_TRGO 11u /* ADC1: adc_ext_trg11 = tim2_trgo */
_Static_assert(DMA_CTR2_REQSEL_Msk == 0x7Fu, "REQSEL — биты 6:0");
_Static_assert(DMA_CTR2_SWREQ_Pos == 9 && DMA_CTR2_DREQ_Pos == 10 && DMA_CTR2_BREQ_Pos == 11, "SWREQ, DREQ, BREQ");
_Static_assert(DMA_CTR2_TRIGM_Pos == 14 && DMA_CTR2_TRIGSEL_Pos == 16 && DMA_CTR2_TRIGPOL_Pos == 24, "поля триггера");
_Static_assert(DMA_CTR2_TRIGSEL_Msk == (0x3Fu << 16), "в заголовке U585 TRIGSEL — 6 бит");
_Static_assert(DMA_CTR2_TCEM_Pos == 30, "TCEM — биты 31:30");
_Static_assert(DMA_CTR1_SDW_LOG2_Pos == 0 && DMA_CTR1_SINC_Pos == 3 && DMA_CTR1_SBL_1_Pos == 4, "источник в CxTR1");
_Static_assert(DMA_CTR1_PAM_Pos == 11 && DMA_CTR1_DDW_LOG2_Pos == 16 && DMA_CTR1_DINC_Pos == 19, "PAM и приёмник");
_Static_assert(DMA_CTR1_DBL_1_Pos == 20 && DMA_CTR1_SSEC_Pos == 15 && DMA_CTR1_DSEC_Pos == 31, "DBL_1, SSEC, DSEC");
_Static_assert(DMA_CBR1_BNDT_Msk == 0xFFFFu && DMA_CBR1_BRC_Msk == (0x7FFu << 16), "BNDT 16 бит, BRC 11 бит");
_Static_assert(DMA_CLLR_UT1_Pos == 31 && DMA_CLLR_UT2_Pos == 30 && DMA_CLLR_UB1_Pos == 29, "UT1, UT2, UB1");
_Static_assert(DMA_CLLR_USA_Pos == 28 && DMA_CLLR_UDA_Pos == 27 && DMA_CLLR_ULL_Pos == 16, "USA, UDA, ULL");
_Static_assert(DMA_CLLR_UT3_Pos == 26 && DMA_CLLR_UB2_Pos == 25, "UT3, UB2 — каналы 12…15");
_Static_assert(DMA_CLLR_LA_Msk == 0xFFFCu && DMA_CLBAR_LBA_Msk == 0xFFFF0000u, "LA 15:2, LBA 31:16");
_Static_assert(DMA_CSR_IDLEF_Pos == 0 && DMA_CSR_TCF_Pos == 8 && DMA_CSR_HTF_Pos == 9 && DMA_CSR_DTEF_Pos == 10, "флаги");
_Static_assert(DMA_CSR_ULEF_Pos == 11 && DMA_CSR_USEF_Pos == 12 && DMA_CSR_SUSPF_Pos == 13 && DMA_CSR_TOF_Pos == 14, "флаги");
_Static_assert(DMA_CCR_EN_Pos == 0 && DMA_CCR_RESET_Pos == 1 && DMA_CCR_SUSP_Pos == 2, "EN, RESET, SUSP");
_Static_assert(DMA_CCR_LSM_Pos == 16 && DMA_CCR_LAP_Pos == 17 && DMA_CCR_PRIO_Pos == 22, "LSM, LAP, PRIO");
_Static_assert(RX_SIZE <= 0xFFFFu && 2u * ADC_N <= 0xFFFFu, "блок — не больше 65 535 байт");
_Static_assert((PCLK2_HZ + BAUD / 2u) / BAUD == 174u, "BRR на 921 600 бод при 160 МГц");
static uint8_t rx_buf[RX_SIZE];
static uint32_t rx_node[1]; /* элемент списка кольца: одно слово */
static uring_t rx;
static uint16_t adc_a[ADC_N], adc_b[ADC_N];
static uint32_t adc_nodes[4]; /* два элемента по два слова */
static const uint16_t pattern[PAT_N] = {0x0001u, 0x0003u, 0x0007u, 0x000Fu, 0x000Eu, 0x000Cu, 0x0008u, 0x0000u};
static uint32_t pat_node[1];
static uint8_t lp_buf[64];
static uint32_t lp_node[1];
volatile uint32_t rx_frames, dma_errors, uart_ore, adc_blocks;
volatile const uint16_t *adc_ready; /* буфер, который можно обрабатывать */
static void app_frame(const uint8_t *a, uint16_t na, const uint8_t *b, uint16_t nb)
{
(void)a; (void)na; (void)b; (void)nb;
rx_frames++;
}
/* ── USART1 -> кольцо: канал 0 GPDMA1 ─────────────────────────────────── */
static void uart_ring_arm(void)
{
gpdma_ring_rx(GPDMA1_Channel0, REQ_USART1_RX, (uint32_t)&USART1->RDR,
(uint32_t)rx_buf, RX_SIZE, rx_node, (uint32_t)rx_node);
}
void uart_ring_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPDMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
(void)RCC->APB2ENR;
uring_init(&rx, rx_buf, RX_SIZE, app_frame);
uart_ring_arm(); /* сначала канал, потом запросы */
USART1->BRR = (PCLK2_HZ + BAUD / 2u) / BAUD; /* 174: 919 540 бод */
USART1->CR3 = USART_CR3_DMAR | USART_CR3_EIE;
USART1->CR1 = USART_CR1_UE | USART_CR1_RE | USART_CR1_IDLEIE;
NVIC_SetPriority(GPDMA1_Channel0_IRQn, 5); /* один приоритет: разбор не прервёт сам себя */
NVIC_SetPriority(USART1_IRQn, 5);
NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
NVIC_EnableIRQ(USART1_IRQn);
}
static void rx_poll(void)
{
uring_poll(&rx, (uint16_t)(GPDMA1_Channel0->CBR1 & DMA_CBR1_BNDT));
}
void GPDMA1_Channel0_IRQHandler(void) /* HT, TC или ошибка */
{
uint32_t sr = GPDMA1_Channel0->CSR;
GPDMA1_Channel0->CFCR = sr & GPDMA_FLAGS; /* сброс — записью единиц */
if (sr & GPDMA_ERRORS) { /* канал уже выключен железом */
dma_errors++;
rx.rd = 0;
uart_ring_arm(); /* сброс канала и запуск с начала кольца */
return;
}
rx_poll();
}
void USART1_IRQHandler(void)
{
uint32_t isr = USART1->ISR;
if (isr & USART_ISR_ORE)
uart_ore++;
/* у U5 флаги снимают записью в ICR: регистр данных не читают, байт у GPDMA не отнять */
USART1->ICR = USART_ICR_IDLECF | USART_ICR_ORECF | USART_ICR_NECF | USART_ICR_FECF;
if (isr & USART_ISR_IDLE)
rx_poll();
}
/* ── ADC1 по TIM2 -> два буфера: канал 1 GPDMA1 ───────────────────────── */
void adc_dma_arm(uint32_t psc, uint32_t arr) /* АЦП уже включён, канал выбран */
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPDMA1EN;
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM2EN;
(void)RCC->APB1ENR1;
adc_ready = 0;
gpdma_pingpong(GPDMA1_Channel1, gpdma_tr1(GPDMA_W16, 0u, GPDMA_W16, 1u), REQ_ADC1,
(uint32_t)&ADC1->DR, (uint32_t)adc_a, (uint32_t)adc_b,
(uint16_t)(2u * ADC_N), adc_nodes, (uint32_t)adc_nodes);
NVIC_SetPriority(GPDMA1_Channel1_IRQn, 6);
NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
/* запрос GPDMA даёт АЦП на каждый отсчёт; темп отсчётов — фронт TRGO таймера */
ADC1->CFGR1 = (ADC1->CFGR1 & ~(ADC_CFGR1_DMNGT | ADC_CFGR1_EXTSEL | ADC_CFGR1_EXTEN | ADC_CFGR1_CONT))
| (3u << ADC_CFGR1_DMNGT_Pos) /* 11: DMA по кругу */
| (ADC_EXT_TIM2_TRGO << ADC_CFGR1_EXTSEL_Pos)
| ADC_CFGR1_EXTEN_0; /* 01: по фронту */
ADC1->CR |= ADC_CR_ADSTART; /* ждать запуска от таймера */
TIM2->PSC = psc;
TIM2->ARR = arr;
TIM2->CR2 = TIM_CR2_MMS_1; /* TRGO по событию обновления */
TIM2->EGR = TIM_EGR_UG;
TIM2->CR1 = TIM_CR1_CEN;
}
void GPDMA1_Channel1_IRQHandler(void) /* TC после каждого блока */
{
uint32_t sr = GPDMA1_Channel1->CSR;
GPDMA1_Channel1->CFCR = sr & GPDMA_FLAGS;
if (sr & GPDMA_ERRORS) {
dma_errors++;
return;
}
adc_ready = (adc_blocks & 1u) ? adc_b : adc_a; /* чётные блоки — в A, нечётные — в B */
adc_blocks++;
}
/* ── Таблица -> GPIOB->ODR по TRGO таймера TIM2: канал 2 GPDMA1 ───────── */
void pattern_start(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPDMA1EN;
RCC->AHB2ENR1 |= RCC_AHB2ENR1_GPIOBEN;
(void)RCC->AHB2ENR1;
gpdma_table_out(GPDMA1_Channel2, TRIG_TIM2_TRGO, (uint32_t)&GPIOB->ODR,
(uint32_t)pattern, (uint16_t)sizeof pattern, pat_node, (uint32_t)pat_node);
}
/* ── LPUART1 -> кольцо через LPDMA1: тот же код, другой контроллер ────── */
void lpuart_ring_init(void)
{
RCC->AHB3ENR |= RCC_AHB3ENR_LPDMA1EN;
RCC->SRDAMR |= RCC_SRDAMR_LPDMA1AMEN; /* самостоятельная работа в Stop 0, 1 и 2 */
(void)RCC->AHB3ENR;
gpdma_ring_rx(LPDMA1_Channel0, LPREQ_LPUART1_RX, (uint32_t)&LPUART1->RDR,
(uint32_t)lp_buf, (uint16_t)sizeof lp_buf, lp_node, (uint32_t)lp_node);
}
/* ── Привилегии и TrustZone: только привилегированный код, до включения канала ── */
void gpdma_channel_privileged(uint32_t n)
{
GPDMA1->PRIVCFGR |= 1u << n; /* PRIVn: канал виден только привилегированному коду */
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
GPDMA1->SECCFGR |= 1u << n; /* SECn: канал принадлежит защищённому миру */
GPDMA1->RCFGLOCKR |= 1u << n; /* LOCKn: до сброса атрибуты не изменить */
#endif
}
Дальше: раздел 10 «Питание и энергосбережение» — режимы сна, пробуждение и что происходит с периферией, пока камень спит.