openlang

STM32H7 UART TX DMA отправляет старые байты при включённом D-cache

STM32H7, arm-none-eabi-gcc. DMA TX кадра в 64 байта. С выключенным кешем всё нормально. С -DUSE_DCACHE анализатор показывает неверные кадры. Первый кадр после сброса правильный. Следующие несут байты предыдущего кадра, блоками по 32 байта. Иногда начало новое, а хвост старый.

В итоге компилятор переносит цикл копирования за запись в enable. Но с -O0 то же самое. С tx_buf volatile то же самое. С DSB после цикла то же самое. В дизассемблере записи идут до записи в CR.

for (size_t i = 0; i < n; i++)
    tx_buf[i] = data[i];

__asm volatile ("dsb 0xF" ::: "memory");

DMA1_S3_M0AR = (uint32_t)(uintptr_t)tx_buf;
DMA1_S3_NDTR = (uint32_t)n;
DMA1_S3_CR  |= DMA_EN;

Кеш включается в board_init():

SystemInit();
#ifdef USE_DCACHE
    SCB_EnableICache();
    SCB_EnableDCache();
#endif

Сборка:

make clean && make OPT=-O0 DEFS=-DUSE_DCACHE -DTRACE_ITM

Порядок, по-моему, правильный, а данные всё равно старые. Что ещё может быть?

Переведено с английскогоПоказать оригинал

Разговор продолжается 8

Следуя доводам
  1. Ссылка на ответ

    Нет, clean я нигде не вызываю. Думал, что DSB достаточно, а он только упорядочивает записи. Это объясняет блоки по 32 байта, ведь это размер линии.

    tx_buf — обычный статический массив, без атрибута aligned. В конце концов он делит линию с другой переменной.

    Два года я делаю так на своём курсе: буфер выровнен по 32, размер кратен 32, потом clean по адресу перед записью в EN. На F7 один раз переменная рядом с моим RX-буфером оказалась испорчена из-за invalidate. Сейчас попробую.

    static uint8_t tx_buf[64] __attribute__((aligned(32)));
    SCB_CleanDCache_by_Addr((uint32_t *)tx_buf, sizeof tx_buf);
    Переведено с английскогоПоказать оригинал
  2. Ссылка на ответ

    Теперь работает. Все кадры на анализаторе правильные, включая хвостовые блоки.

    anselm был прав. DSB упорядочивает записи, но линии остаются грязными в кэше, и DMA читает устаревшую RAM. Функция clean из CMSIS сама делает DSB, так что мой лишний не нужен.

    Clean идёт после цикла копирования и перед записью в M0AR. Clean перед циклом ничего не даёт.

    Для RX всё наоборот. Invalidate нужно делать до старта DMA, иначе в конце концов CPU вытеснит грязную линию поверх ваших новых данных. Один мой студент потерял на этом целый вечер.

    Переведено с английскогоПоказать оригинал
  3. Ссылка на ответ

    Оговорка по TX: clean линии, которую делят с другой переменной, безвреден, он только записывает обратно. Опасен именно invalidate.

    Альтернатива: положить DMA-буферы в отдельную секцию и пометить этот регион в MPU как non-cacheable. Тогда никаких вызовов clean и invalidate не нужно. Я один раз так сделал для SPI-буфера, и баг больше не возвращался.

    Переведено с английскогоПоказать оригинал
  4. Ссылка на ответ

    Это D-cache. DSB только упорядочивает записи, линии остаются грязными, и DMA читает устаревшую RAM. Сделайте clean для tx_buf по адресу после цикла копирования, перед записью в EN, и выровняйте его по 32. Или положите буфер в DTCM (он некэшируемый, читал где-то в блоге).

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. В колбэке из C пропадает арена, которую я настроил

    C-библиотека вызывает колбэк в моём коде на Odin, и колбэк не видит контекст, который я настроил перед регистрацией. У proc "…

    Ответы 5Последняя активность odinc
  2. TMR3 ISR теряет capture-события, хотя флаги w1c и сбрасываются

    Контроллер reflow-печи, клон Cortex-M0, arm-none-eabi-gcc -O2, Fedora. TMR3 работает на 1 МГц. Update event — тик 1 мс, CH1 з…

    Ответы 5Последняя активность c
  3. Разворот C-указателя через .? - это утверждение

    .? на указателе из C - это утверждение, а не проверка. Оно говорит, что null здесь невозможен. В сборке с проверками безопасн…

    Ответы 3Последняя активность zig