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

Использую H743 на отладочной плате Nucleo.

 

Работал с DMA, DMA2D - всё чудесно и превосходно: с AXI RAM отправляю буфер в LCD-дисплей по шине FMC 8-бит (дисплей со своим контроллером и видеопамятью).

 

Но не хватает опции перестановки байтов, потому что LCD требует передавать старший, затем младший байт. Пока использую REV16 per pixel.

 

Хочу избавиться от REV16, применив MDMA в котором можно включить перестановку байтов (слов и полу-слов).

 

Проблема: MDMA не работает: инит сделал Кубом , вызов через HAL. Код стандартный и нет нужды его приводить здесь.

 

Буфер кадра в AXI SRAM, дисплей на FMC, кеширование портов дисплея отключено (адреса 0xC0000000...0xC0010000). Дисплей чёрный - ничего не отрисовывает.

 

Почему?

 

Волнует специфика - может ли MDMA отрабатывать транзакции AXI SRAM => FMC ? Читал мануал, прямого ответа там на мой вопрос нет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Написал тестовый пример, пересылка память-память через MDMA.

 

Инит МДМА:

 

static void MX_MDMA_Init(void) 
{
  /* MDMA controller clock enable */
  __HAL_RCC_MDMA_CLK_ENABLE();

  /* Configure MDMA channel MDMA_Channel7 */
  /* Configure MDMA request hmdma_mdma_channel7_sw_0 on MDMA_Channel7 */
  hmdma_mdma_channel7_sw_0.Instance = MDMA_Channel7;
  hmdma_mdma_channel7_sw_0.Init.Request = MDMA_REQUEST_SW;
  hmdma_mdma_channel7_sw_0.Init.TransferTriggerMode = MDMA_BLOCK_TRANSFER;
  hmdma_mdma_channel7_sw_0.Init.Priority = MDMA_PRIORITY_LOW;
  hmdma_mdma_channel7_sw_0.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
  hmdma_mdma_channel7_sw_0.Init.SourceInc = MDMA_SRC_INC_BYTE; //HALFWORD;
  hmdma_mdma_channel7_sw_0.Init.DestinationInc = MDMA_DEST_INC_BYTE; //HALFWORD;
  hmdma_mdma_channel7_sw_0.Init.SourceDataSize = MDMA_SRC_DATASIZE_BYTE; //HALFWORD;
  hmdma_mdma_channel7_sw_0.Init.DestDataSize = MDMA_DEST_DATASIZE_BYTE; //HALFWORD;
  hmdma_mdma_channel7_sw_0.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
  hmdma_mdma_channel7_sw_0.Init.BufferTransferLength = 8; //32; //1;
  hmdma_mdma_channel7_sw_0.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;
  hmdma_mdma_channel7_sw_0.Init.DestBurst = MDMA_DEST_BURST_SINGLE;
  hmdma_mdma_channel7_sw_0.Init.SourceBlockAddressOffset = 0;
  hmdma_mdma_channel7_sw_0.Init.DestBlockAddressOffset = 0;
  if (HAL_MDMA_Init(&hmdma_mdma_channel7_sw_0) != HAL_OK)
  {
    Error_Handler();
  }

}

 

Main:

u8 Buffer0[32] __attribute__((at(0x30000000)));  //__attribute__((aligned(32)));
volatile u8 Buffer1[32] /*__attribute__((aligned(32)));*/   __attribute__((at(0x30040000)));        //__attribute__((aligned(32)));

int main(void)
{
SCB_EnableICache();
SCB_EnableDCache();

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

MX_MDMA_Init();

LED(RESET);

memset(       Buffer0,0,sizeof(Buffer0));
memset((void*)Buffer1,0,sizeof(Buffer1));

for(u32 i=0;i<32;i++)Buffer0[i]=0xAA;

SCB_CleanDCache(); //сбрасываем содержимое кэша данных в память

if(HAL_MDMA_Start(&hmdma_mdma_channel7_sw_0,(u32)Buffer0,(u32)Buffer1,1,1)         !=HAL_OK)while(1);

if(HAL_MDMA_PollForTransfer(&hmdma_mdma_channel7_sw_0,HAL_MDMA_BLOCK_TRANSFER,1000)!=HAL_OK)while(1);

delay_ms(500);

if(Buffer1[0]!=0xAA)
{
LED(SET);
while(1);
}

while(1)
{
LED(SET);
delay_ms(100);
LED(RESET);
delay_ms(100);
}



while(1);

 

Старт и опрос проходят без проблем. Проверяю первый байт приёмного буфера Buffer1, он не равен 0xAA как это в источнике - светодиод загорается и горит вечно:

 

if(Buffer1[0]!=0xAA)
{
LED(SET);
while(1);
}

 

Что не здесь так?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Всё заработало, когда включил MPU. Протестил на разной памяти - вот что вышло:

 

1) С выключенным DCache работают все комбинации

 

С включенным DCache:

 

2) DTCM => DTCM - работает без включения MPU

 

3) любой регион RAM => DTCM - работает без включения MPU

 

4) любой регион RAM => любой регион RAM кроме DTCM - требует включения MPU на адрес приёмника.

 

static void MPU_Conf(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;

HAL_MPU_Disable();

MPU_InitStruct.Enable=MPU_REGION_ENABLE;

MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;

MPU_InitStruct.AccessPermission=MPU_REGION_FULL_ACCESS;

MPU_InitStruct.TypeExtField=MPU_TEX_LEVEL0;

MPU_InitStruct.IsCacheable=MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsBufferable=MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsShareable=MPU_ACCESS_SHAREABLE;

MPU_InitStruct.Number=MPU_REGION_NUMBER0;
MPU_InitStruct.SubRegionDisable=0x00;
MPU_InitStruct.DisableExec=MPU_INSTRUCTION_ACCESS_DISABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);

HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

 

Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?

 

Попробую Вашу идею. Так invalidate D-cache надо вызывать после приёма любого DMA (DMA, MDMA, DMA2D) ? Или это касается только MDMA?

 

 

 

Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?

 

SCB_InvalidateDCache(); - сделал, тоже заработало и без включения MPU! Спасибо! :yeah:

 

По чему в случае "обычного" DMA работает без SCB_InvalidateDCache(); ? или это специфика только MDMA?

 

Всё, понял. Это для приёмной памяти. В случае с LCD Invalidate DCache делать не нужно (его адреса портов не кешированы). Потому работало на обычном DMA. С памятью на приёме обязателен Invalidate DC.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Инвалидэйт делать перед запуском дома на какую-то область. Немного меньше шансов испортить если cleaninvalidate.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Тут чисто из логики даже все следует.

Есть D-Cache, он в себе хранит Ваши буферы каких-то данных. Этими данными оперирует CPU.

Теперь, перед тем, как отправить подготовленный буфер данных (он сейчас в кэше) в другую память через DMA, нужно этот буфер "слить" обратно в память, поскольку DMA качает не из кэша, а из памяти. Поэтому перед тем, как запустить DMA, необходимо сделать FlushCache().

Теперь, допустим, Вам пришло прерывание по приему DMA. Вы ожидаете в приемных данных найти какой-то символ - лезете процессором по этому адресу, а он на самом деле лезет в кэш - там будут лежать старые данные. Поэтому после выдачи события "транзакция завершена" от DMA-контроллера первым делом необходимо сделать CacheInvalidate(), чтобы загрузить актуальные данные из памяти в кэш. Вот и все премудрости. Только CPU знает, какие данные на самом деле актуальны, поэтому у него и есть возможность сливать и обновлять кэш. DMA же является тупым аппаратным мастером на шине, который просто копирует данные. И он не знает о Ваших кэшах, ровным счетом, ничего :laughing:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Теперь всё ясно. Всем спасибо.

 

Интересно, что когда программировал DMA звуковой карты на ПК, там не нужно было оперировать с кешем вообще. Хотя в CPU были включены кеши.

 

Немного огорчило, что максимальный блок для передачи MDMA - 64 кБ. Пришлось буфер LCD резать на кусочки. Сделал 3 куска (320x240x2 /3 <64kB). Всё работает.

 

Свопинг байтов тоже сделан, что и нужно было!

 

u16 Buffer[240*320] __attribute__((aligned(32)));

int main(void)
{
SCB_EnableICache();
SCB_EnableDCache();

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();
MX_FMC_Init();
MX_DMA_Init();

MX_MDMA_Init();

LED(RESET);

LCD_Reset();
LCD_Init();
LCD_Position(0,0,W-1,H-1);

for(u32 i=0;i<(240*320);i++)
{
  u32 c=i/(240*40);
  Buffer[i]=  (((c/4)*0x1F)<<11) | ((((c/2)&1)*0x3F)<<5) | ((c&1)*0x1F);
}

SCB_CleanDCache();

if(HAL_MDMA_Start(&hmdma_mdma_channel7_sw_0,(u32)Buffer,(u32)&LCD_DAT32,(240*320*2)/3,3)!=HAL_OK)while(1);

if(HAL_MDMA_PollForTransfer(&hmdma_mdma_channel7_sw_0,HAL_MDMA_FULL_TRANSFER,1000)!=HAL_OK)while(1);

// SCB_InvalidateDCache();

while(1)
{
LED(SET);
delay_ms(100);
LED(RESET);
delay_ms(100);
}

 

Инит МДМА:

static void MX_MDMA_Init(void) 
{
  /* MDMA controller clock enable */
  __HAL_RCC_MDMA_CLK_ENABLE();
  /* Local variables */

  /* Configure MDMA channel MDMA_Channel7 */
  /* Configure MDMA request hmdma_mdma_channel7_sw_0 on MDMA_Channel7 */
  hmdma_mdma_channel7_sw_0.Instance = MDMA_Channel7;
  hmdma_mdma_channel7_sw_0.Init.Request = MDMA_REQUEST_SW;
  hmdma_mdma_channel7_sw_0.Init.TransferTriggerMode = MDMA_FULL_TRANSFER;
  hmdma_mdma_channel7_sw_0.Init.Priority = MDMA_PRIORITY_LOW;

  hmdma_mdma_channel7_sw_0.Init.Endianness = MDMA_LITTLE_BYTE_ENDIANNESS_EXCHANGE; //Меняем местами байты (для LCD)

  hmdma_mdma_channel7_sw_0.Init.SourceInc      = MDMA_SRC_INC_HALFWORD;
  hmdma_mdma_channel7_sw_0.Init.DestinationInc = MDMA_DEST_INC_DISABLE;

  hmdma_mdma_channel7_sw_0.Init.SourceDataSize = MDMA_SRC_DATASIZE_HALFWORD;
  hmdma_mdma_channel7_sw_0.Init.DestDataSize = MDMA_DEST_DATASIZE_HALFWORD;

  hmdma_mdma_channel7_sw_0.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;

  hmdma_mdma_channel7_sw_0.Init.BufferTransferLength = 2;                     //1 для BYTE, 2 для HALFWORD, 4 для WORD

  hmdma_mdma_channel7_sw_0.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;
  hmdma_mdma_channel7_sw_0.Init.DestBurst = MDMA_DEST_BURST_SINGLE;
  hmdma_mdma_channel7_sw_0.Init.SourceBlockAddressOffset = 0;
  hmdma_mdma_channel7_sw_0.Init.DestBlockAddressOffset = 0;
  if (HAL_MDMA_Init(&hmdma_mdma_channel7_sw_0) != HAL_OK)
  {
    Error_Handler();
  }

}

 

А для чего бурсты в DMA ? Они ускорят обмен с LCD?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Интересно, что когда программировал DMA звуковой карты на ПК, там не нужно было оперировать с кешем вообще. Хотя в CPU были включены кеши.

Это называется система с полностью когерентным кэшем. Там перед стартом DMA сам аппаратно пинает контроллер кэша, чтобы тот слил данные из нужной области памяти. Либо обновил кэш при чтении.

 

Немного огорчило, что максимальный блок для передачи MDMA - 64 кБ. Пришлось буфер LCD резать на кусочки. Сделал 3 куска (320x240x2 /3 <64kB). Всё работает.

Ну да, так сделано во всех STM32.

 

А для чего бурсты в DMA ? Они ускорят обмен с LCD?

Burst-режим работы DMA - это режим, когда DMA безразрывно занимает шину, то есть транзакцию никакой мастер шины больше прервать не сможет. Например, если надо разом заполнить все регистры таймера.

Я могу ошибаться, но вроде отличие от одиночного режима в том, что в случае одиночных пересылок на каждую транзакцию контроллер DMA выделяет еще стадию адресации памяти:

ADDR1 | DATA1 | ADDR2 | DATA2 | CPU ADDR1 | CPU DATA1 | ADDR3 | DATA3 |... Как видно, при этом CPU может разорвать атомарность пересылки DMA.

В burst-режиме используется один цикл адресации с дальнейшей передачей всего пакета (beat):

| ADDR | DATA1 | DATA2 | DATA3 | DATA4 |...

Это из какой-то Интернет-байки статьи, не относительно к конкретной архитектуре.

Не забывайте только, что одна транзакция DMA Burst не должна пересекать границу 1кБ (вроде), это связано с физическими адресуемыми подчиненными шин. Как-то так :rolleyes:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Это называется система с полностью когерентным кэшем. Там перед стартом DMA сам аппаратно пинает контроллер кэша, чтобы тот слил данные из нужной области памяти. Либо обновил кэш при чтении.

 

...

 

Не забывайте только, что одна транзакция DMA Burst не должна пересекать границу 1кБ (вроде), это связано с физическими адресуемыми подчиненными шин. Как-то так :rolleyes:

 

Спасибо за развёрнутый ответ! :rolleyes:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Поэтому после выдачи события "транзакция завершена" от DMA-контроллера первым делом необходимо сделать CacheInvalidate(), чтобы загрузить актуальные данные из памяти в кэш

 

Небольшое уточнение - данные из кеша выгружаются в память когда там не хватает места или явно сказано Clean. Грузяться при обращении на чтение.

Предположим, с ранее считанными данными что-то сделали и они наъодяться в кеше готовые к выгрузке в памячть. И по Вашему сценарию запускаем DMA в надежле посде завершения транзакции сказать Invalidate. До этого момента данные могут быть выгружены в любой момент. И модем не увидеть считанное из DMA.

Так что ПЕРЕД зхапуском DMA делаем CleanInvalidate строкам, в память связанную с которыми будет читать DMA из периферии. Совмещение Clean & Invalidate не следить за отсутствием "соседей" с буфером DMA.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Небольшое уточнение...

Пытался раз пять вдумчиво прочитать Ваше сообщение - но ничего не понял, что Вы хотели сказать.

То, что при чтении данных из памяти, в случае их отсутствия в кэше, это понятно - контроллер кэш-памяти формирует запрос на вычитку этих данных из памяти в кэш, причем полностью аппаратно. Программист этого не видит (даже низкоуровневый ассемблерщик). Логично также, что при обращении к данным, которых нет в кэше, контроллер кэш-памяти сольет давно не использованные данные в память, а на их место подгрузит новые - запрашиваемые. Это тоже все полностью аппаратно и прозрачно для программиста. Программист может явно указать Flush-шить (или Clean, кому как удобнее) или Invalidate-ить кэш.

Также я указал, что перед запуском DMA все строки кэша, в которых содержатся данные буфера-источника транзакции, должны быть слиты в память, после чего можно запускать DMA. После того, как DMA выдаст сигнал о завершении транзакции, пользователь должен обновить кэш (сделать Invalidate), прежде чем читать память-приемник, указанную в DMA-транзакции, поскольку эти данные могу частично (или полностью) быть в кэше (а может и не быть вовсе, тогда не нужно ничего Invalidate-ить).

 

Так что ПЕРЕД зхапуском DMA делаем CleanInvalidate строкам, в память связанную с которыми будет читать DMA из периферии. Совмещение Clean & Invalidate не следить за отсутствием "соседей" с буфером DMA.

А это я вообще не смог понять, извините :laughing:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

вообще не смог понять

Вспомните, что в данном процессоре строка кэшпамяти занимает 32 байта.

В случае, если в этих 32 (или крантых ему) байтоов кроме буфера DMA обмена есть еще что-то... присоседилось. Я об этой ситуации.

 

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вспомните, что в данном процессоре строка кэшпамяти занимает 32 байта.

В случае, если в этих 32 (или крантых ему) байтоов кроме буфера DMA обмена есть еще что-то... присоседилось. Я об этой ситуации.

Я думаю: это очень плохая практика - так делать. Перемешивать переменные, записываемые DMA-контроллером и переменные, записываемые CPU в одном регионе памяти на процессоре имеющем кеш данных.

Ну хорошо - слили Вы перед DMA-операцией эти данные в память, а дальше то что? Остановить процессор и ждать пока DMA не завершится? А какой тогда вообще смысл в DMA?

Ведь если CPU не остановить, то он может снова записать в эти переменные данные и во время DMA, тогда что делать?

Мне кажется является очевидным, что для DMA-буферов, записываемых DMA-контроллером, надо просто выделить отдельный регион памяти, и линковать туда только секции ".dmaRx". А программу так писать, чтобы процессор не писал в этот регион, а только читал его (для него секция ".dmaRx" - readonly-секция). И этот регион необходимо выровнять на размер строки кеша.

И тогда, как писал ув. Arlleex, после завершения приёмной DMA-операции, достаточно будет просто сделать CacheInvalidate() для этого региона и дальше спокойно читать его.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Дело в том, что пока процессор ожидает завершения операции чтения по DMA, он может например выполнять прерывания... Кэш (может) выгрузится в память в непредсказуемый момент по отношению к операции DMA. ПОтому лучше это сделать ДО ТОГО.

, то он может снова записать в эти переменные данные и во время DMA, тогда что делать?

Тут похоже Вы правы... совмещать не стоит. Но если ограничить рассморение случаес какой-то модификации ранее считанного буфера - его надо выгрузить ДО начала DMA READ.

Изменено пользователем Genadi Zawidowski

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В общем, я лишь для общей картины добавлю официальный документ от ST Microelectronics по кэш-памяти первого уровня.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...