Перейти к содержанию

Почему после приёма по DMA STM32F7 читает старые данные?

Обновлено Редакция GAW

У Cortex-M7 в STM32F7 и STM32H7 есть кэш данных L1. Пока он выключен, DMA и ядро работают с одной и той же памятью. После SCB_EnableDCache() ядро читает и пишет в основном через кэш, а DMA — напрямую в память, и ST в AN4839 подчёркивает: аппаратной когерентности нет, её обеспечивает программа.

Что происходит при приёме

  1. Ядро когда-то читало буфер — его строки лежат в кэше.
  2. DMA принимает пакет и записывает новые байты в память.
  3. Ядро читает буфер и получает строки из кэша — старые данные.

Лекарство — отбросить строки буфера из кэша перед чтением:

#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 читает (передача)до запуска DMASCB_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 отправит из памяти старое содержимое.

Источники

  1. 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 ядра
  2. 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()

Нашли ошибку?