Почему после приёма по DMA STM32F7 читает старые данные?
Обновлено Редакция GAW
У Cortex-M7 в STM32F7 и STM32H7 есть кэш данных L1. Пока он выключен, DMA и ядро работают с одной и той же памятью. После SCB_EnableDCache() ядро читает и пишет в основном через кэш, а DMA — напрямую в память, и ST в AN4839 подчёркивает: аппаратной когерентности нет, её обеспечивает программа.
Что происходит при приёме
- Ядро когда-то читало буфер — его строки лежат в кэше.
- DMA принимает пакет и записывает новые байты в память.
- Ядро читает буфер и получает строки из кэша — старые данные.
Лекарство — отбросить строки буфера из кэша перед чтением:
#define RX_LEN 64 // multiple of the 32-byte cache line
__attribute__((aligned(32))) static uint8_t rx_buf[RX_LEN];
void start_rx(void) {
SCB_InvalidateDCache_by_Addr(rx_buf, RX_LEN); // drop stale or dirty lines first
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_LEN);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *h) {
SCB_InvalidateDCache_by_Addr(rx_buf, RX_LEN); // CPU now reads what DMA wrote
process(rx_buf, RX_LEN);
}
Направление решает операцию
| Обмен | Когда | Операция CMSIS |
|---|---|---|
| Ядро пишет → DMA читает (передача) | до запуска DMA | SCB_CleanDCache_by_Addr |
| DMA пишет → ядро читает (приём) | после окончания DMA, до чтения | SCB_InvalidateDCache_by_Addr |
| Буфер в некэшируемой области | — | ничего, но доступ ядра медленнее |
Функции CMSIS обходят область строками по 32 байта — __SCB_DCACHE_LINE_SIZE в cachel1_armv7.h, — поэтому выравнивание и кратный 32 размер обязательны. Как работают write-back и write-through, разобрано в термине «Политика записи кэша».
Вместо обслуживания кэша буферы можно положить в некэшируемую область — её настраивают через MPU, как показано в ответе «Некэшируемая область для DMA через MPU». У STM32F7 есть и третий путь из AN4839: DTCM-RAM, которая не кэшируется и доступна DMA через порт AHBS ядра; у STM32H743 так нельзя — там к DTCM ходит только MDMA. Память и тактирование STM32F7 — на странице STM32F7.
Похожие вопросы
Почему после invalidate испортилась соседняя переменная?
Операции по адресу работают целыми строками по 32 байта. Если буфер начинается не с границы строки или его размер не кратен 32, в ту же строку попадает соседняя переменная, и invalidate отбрасывает её свежее значение, которое ещё не было записано в память. Поэтому буфер выравнивают атрибутом aligned(32) и округляют его размер вверх до кратного 32.
Нужно ли делать invalidate и перед запуском приёма?
Полезно: если в строках буфера остались грязные данные от прежней записи ядром, кэш может вытеснить их в память уже после того, как DMA туда записал, и затереть принятое. Invalidate или clean-and-invalidate буфера перед запуском DMA убирает такие строки заранее.
А при передаче по DMA что вызывать?
Clean: SCB_CleanDCache_by_Addr(buf, size) после того, как ядро заполнило буфер, и до запуска DMA. Иначе при политике write-back часть данных останется в кэше, и DMA отправит из памяти старое содержимое.
Источники
- STMicroelectronics. AN4839 Level 1 cache on STM32F7 Series and STM32H7 Series, Rev 2 (March 2018) — введение: в системах с кэшем программа обеспечивает когерентность данных ядра и основной памяти при работе с общими данными; табл. 1: clean записывает грязные строки в память, invalidate отбрасывает содержимое кэша; разд. 3.2: пример — DMA копирует массив из SRAM1 с атрибутом write-back в DTCM, при включённом D-cache данные расходятся; разд. 4: перед запуском DMA — clean, перед чтением результата DMA — invalidate, либо некэшируемая область для обмена с DMA; рис. 1: у STM32F7 DTCM-RAM доступна шинной матрице через порт AHBS ядра
- Arm. CMSIS 5.9.0, CMSIS/Core/Include/cachel1_armv7.h (V1.0.1, 19 April 2021) — __SCB_DCACHE_LINE_SIZE = 32 — строка кэша Cortex-M7 фиксирована, 32 байта (8 слов); SCB_InvalidateDCache_by_Addr, SCB_CleanDCache_by_Addr и SCB_CleanInvalidateDCache_by_Addr(addr, dsize) обходят область строками по 32 байта, регистры DCIMVAC и DCCMVAC принимают только биты 31..5 адреса, цикл обрамлён __DSB() и __ISB()
Нашли ошибку?
Смотрите также
- Когерентность кэша (cache coherency)Согласование кэша ядра с памятью, в которую пишет DMA
- Кэш L1 Cortex-M7 (I-cache, D-cache)Раздельные кэши команд и данных ядра Cortex-M7: строка 32 байта
- Политика записи кэша (write-through, write-back)Сквозная и отложенная запись кэша данных и выделение строки при записи
- Как сделать некэшируемую область для DMA через MPU на STM32H7?Регион MPU: TEX = 001, C = 0, B = 0, размер — степень двойки, база выровнена по размеру
- STM32F7Cortex-M7 до 216 МГц: F72x/F73x, F74x/F75x, F76x/F77x — линейки, память, загрузка и распиновки