Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32H743 MDMA
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
__inline__
Использую 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 ? Читал мануал, прямого ответа там на мой вопрос нет.
__inline__
Написал тестовый пример, пересылка память-память через 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);
}


Что не здесь так?
Arlleex
Цитата(__inline__ @ Jul 19 2018, 13:41) *
Что не здесь так?

Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?
__inline__
Всё заработало, когда включил 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);
}


Цитата(Arlleex @ Jul 19 2018, 11:22) *
Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?


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



Цитата(Arlleex @ Jul 19 2018, 11:22) *
Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?


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

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

Всё, понял. Это для приёмной памяти. В случае с LCD Invalidate DCache делать не нужно (его адреса портов не кешированы). Потому работало на обычном DMA. С памятью на приёме обязателен Invalidate DC.
Genadi Zawidowski
Инвалидэйт делать перед запуском дома на какую-то область. Немного меньше шансов испортить если cleaninvalidate.
Arlleex
Тут чисто из логики даже все следует.
Есть D-Cache, он в себе хранит Ваши буферы каких-то данных. Этими данными оперирует CPU.
Теперь, перед тем, как отправить подготовленный буфер данных (он сейчас в кэше) в другую память через DMA, нужно этот буфер "слить" обратно в память, поскольку DMA качает не из кэша, а из памяти. Поэтому перед тем, как запустить DMA, необходимо сделать FlushCache().
Теперь, допустим, Вам пришло прерывание по приему DMA. Вы ожидаете в приемных данных найти какой-то символ - лезете процессором по этому адресу, а он на самом деле лезет в кэш - там будут лежать старые данные. Поэтому после выдачи события "транзакция завершена" от DMA-контроллера первым делом необходимо сделать CacheInvalidate(), чтобы загрузить актуальные данные из памяти в кэш. Вот и все премудрости. Только CPU знает, какие данные на самом деле актуальны, поэтому у него и есть возможность сливать и обновлять кэш. DMA же является тупым аппаратным мастером на шине, который просто копирует данные. И он не знает о Ваших кэшах, ровным счетом, ничего laughing.gif
__inline__
Теперь всё ясно. Всем спасибо.

Интересно, что когда программировал 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?
Arlleex
Цитата(__inline__ @ Jul 19 2018, 15:38) *
Интересно, что когда программировал DMA звуковой карты на ПК, там не нужно было оперировать с кешем вообще. Хотя в CPU были включены кеши.

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

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

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

Цитата(__inline__ @ Jul 19 2018, 15:38) *
А для чего бурсты в 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.gif
__inline__
Цитата(Arlleex @ Jul 19 2018, 13:20) *
Это называется система с полностью когерентным кэшем. Там перед стартом DMA сам аппаратно пинает контроллер кэша, чтобы тот слил данные из нужной области памяти. Либо обновил кэш при чтении.

...

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


Спасибо за развёрнутый ответ! rolleyes.gif
Genadi Zawidowski
Цитата
Поэтому после выдачи события "транзакция завершена" от DMA-контроллера первым делом необходимо сделать CacheInvalidate(), чтобы загрузить актуальные данные из памяти в кэш


Небольшое уточнение - данные из кеша выгружаются в память когда там не хватает места или явно сказано Clean. Грузяться при обращении на чтение.
Предположим, с ранее считанными данными что-то сделали и они наъодяться в кеше готовые к выгрузке в памячть. И по Вашему сценарию запускаем DMA в надежле посде завершения транзакции сказать Invalidate. До этого момента данные могут быть выгружены в любой момент. И модем не увидеть считанное из DMA.
Так что ПЕРЕД зхапуском DMA делаем CleanInvalidate строкам, в память связанную с которыми будет читать DMA из периферии. Совмещение Clean & Invalidate не следить за отсутствием "соседей" с буфером DMA.
Arlleex
Цитата(Genadi Zawidowski @ Jul 19 2018, 17:54) *
Небольшое уточнение...

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

Цитата(Genadi Zawidowski @ Jul 19 2018, 17:54) *
Так что ПЕРЕД зхапуском DMA делаем CleanInvalidate строкам, в память связанную с которыми будет читать DMA из периферии. Совмещение Clean & Invalidate не следить за отсутствием "соседей" с буфером DMA.

А это я вообще не смог понять, извините laughing.gif
Genadi Zawidowski
Код
вообще не смог понять

Вспомните, что в данном процессоре строка кэшпамяти занимает 32 байта.
В случае, если в этих 32 (или крантых ему) байтоов кроме буфера DMA обмена есть еще что-то... присоседилось. Я об этой ситуации.
jcxz
Цитата(Genadi Zawidowski @ Jul 20 2018, 00:20) *
Вспомните, что в данном процессоре строка кэшпамяти занимает 32 байта.
В случае, если в этих 32 (или крантых ему) байтоов кроме буфера DMA обмена есть еще что-то... присоседилось. Я об этой ситуации.

Я думаю: это очень плохая практика - так делать. Перемешивать переменные, записываемые DMA-контроллером и переменные, записываемые CPU в одном регионе памяти на процессоре имеющем кеш данных.
Ну хорошо - слили Вы перед DMA-операцией эти данные в память, а дальше то что? Остановить процессор и ждать пока DMA не завершится? А какой тогда вообще смысл в DMA?
Ведь если CPU не остановить, то он может снова записать в эти переменные данные и во время DMA, тогда что делать?
Мне кажется является очевидным, что для DMA-буферов, записываемых DMA-контроллером, надо просто выделить отдельный регион памяти, и линковать туда только секции ".dmaRx". А программу так писать, чтобы процессор не писал в этот регион, а только читал его (для него секция ".dmaRx" - readonly-секция). И этот регион необходимо выровнять на размер строки кеша.
И тогда, как писал ув. Arlleex, после завершения приёмной DMA-операции, достаточно будет просто сделать CacheInvalidate() для этого региона и дальше спокойно читать его.
Genadi Zawidowski
Дело в том, что пока процессор ожидает завершения операции чтения по DMA, он может например выполнять прерывания... Кэш (может) выгрузится в память в непредсказуемый момент по отношению к операции DMA. ПОтому лучше это сделать ДО ТОГО.
Цитата
, то он может снова записать в эти переменные данные и во время DMA, тогда что делать?

Тут похоже Вы правы... совмещать не стоит. Но если ограничить рассморение случаес какой-то модификации ранее считанного буфера - его надо выгрузить ДО начала DMA READ.
Arlleex
В общем, я лишь для общей картины добавлю официальный документ от ST Microelectronics по кэш-памяти первого уровня.
esaulenka
Цитата(Arlleex @ Jul 19 2018, 15:20) *
Не забывайте только, что одна транзакция DMA Burst не должна пересекать границу 1кБ (вроде), это связано с физическими адресуемыми подчиненными шин.

Какие интересные грабли.
Это каких именно процессоров касается? И где описано?

Этак burst можно применять только в очень ограниченном числе случаев...
Arlleex
Цитата(esaulenka @ Jul 20 2018, 10:50) *
Какие интересные грабли.
Это каких именно процессоров касается? И где описано?

Это не грабли. Это документированная особенность построения ядра ARMv7M. В других версиях архитектуры аналогично, только адресная граница физических slave-подсистем (например, памяти) может отличаться от 1кБ. Поэтому, если, например, Вы запустите burst-транзакцию, пересекающую адресную границу одного подчиненного, DMA завершит транзакцию успешно (во флагах статуса), но в действительности часть данных потеряется. Причем DMA не взведет флаги Transfer Error. Ну в STM32 по крайней мере точно так. Вот Вам выдержка из Reference Manual на STM32F4xx, например:
Цитата
The burst configuration has to be selected in order to respect the AHB protocol, where
bursts must not cross the 1 KB address boundary because the minimum address space that
can be allocated to a single slave is 1 KB
. This means that the 1 KB address boundary
should not be crossed by a burst block transfer, otherwise an AHB error would be
generated, that is not reported by the DMA registers
.


Цитата(esaulenka @ Jul 20 2018, 10:50) *
Этак burst можно применять только в очень ограниченном числе случаев...

Ну нет, почему же. Если необходимо передавать данные в периферию, имеющую FIFO, то одной burst-транзакцией можно сразу заполнить в нем место, и пусть отправляет куда хочет там.
esaulenka
Да, неправ, это место просмотрел... Благо объемы у меня небольшие, и разгонять DMA не требуется - до включения burst'а руки так и не дошли...

Но это означает, что работу с буфером больше 1кБ надо обкладывать дополнительными проверками.
И маленький буфер тоже по-хорошему выравнивать надо (блин, как же в gcc это неудобно...).
Genadi Zawidowski
Цитата
И маленький буфер тоже по-хорошему выравнивать надо

На рахмер строки кэша (32 байта) да. Я так понимаю речь про килобайт? Так это можно и в динамике сделать (делить на chunks передаваемые блоки). Но это все на этапе проектирования понятно - можно менеджера памяти нагрузить требованиями по выравниванию, например. Если он есть, конечно.
Arlleex
Цитата(esaulenka @ Jul 20 2018, 16:43) *
Но это означает, что работу с буфером больше 1кБ надо обкладывать дополнительными проверками.
И маленький буфер тоже по-хорошему выравнивать надо (блин, как же в gcc это неудобно...).

Если речь про ограничение в 1кБ - то не забывайте, это только для burst-передач, которые, в общем-то, обычно ограничены десятками-сотней байт. Если это обычная транзакция DMA (не burst) - можете передавать сколько угодно, ограничение только DMA делает на количество данных, передаваемых в одном сеансе транзакции (для STM32 это 64кБ).

Цитата(Genadi Zawidowski @ Jul 20 2018, 17:26) *
Но это все на этапе проектирования понятно - можно менеджера памяти нагрузить требованиями по выравниванию, например. Если он есть, конечно.

ИМХО, лучше сразу все продумать на этапе составления программы и разлиновки памяти. Динамические диспетчеры так или иначе такты процессора тратят. И тем более было бы ради чего - а тут все буфера статически разместить можно еще на этапе обдумывания структуры проекта.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.