реклама на сайте
подробности

 
 
> STM32H743 MDMA, Нет передачи в периферию
__inline__
сообщение Jul 18 2018, 14:38
Сообщение #1


Местный
***

Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126



Использую 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 ? Читал мануал, прямого ответа там на мой вопрос нет.
Go to the top of the page
 
+Quote Post
2 страниц V   1 2 >  
Start new topic
Ответов (1 - 14)
__inline__
сообщение Jul 19 2018, 09:41
Сообщение #2


Местный
***

Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126



Написал тестовый пример, пересылка память-память через 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);
}


Что не здесь так?
Go to the top of the page
 
+Quote Post
Arlleex
сообщение Jul 19 2018, 10:22
Сообщение #3


Местный
***

Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264



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

Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?
Go to the top of the page
 
+Quote Post
__inline__
сообщение Jul 19 2018, 10:49
Сообщение #4


Местный
***

Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126



Всё заработало, когда включил 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.
Go to the top of the page
 
+Quote Post
Genadi Zawidowsk...
сообщение Jul 19 2018, 11:01
Сообщение #5


Профессионал
*****

Группа: Участник
Сообщений: 1 620
Регистрация: 22-06-07
Из: Санкт-Петербург, Россия
Пользователь №: 28 634



Инвалидэйт делать перед запуском дома на какую-то область. Немного меньше шансов испортить если cleaninvalidate.
Go to the top of the page
 
+Quote Post
Arlleex
сообщение Jul 19 2018, 11:26
Сообщение #6


Местный
***

Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264



Тут чисто из логики даже все следует.
Есть D-Cache, он в себе хранит Ваши буферы каких-то данных. Этими данными оперирует CPU.
Теперь, перед тем, как отправить подготовленный буфер данных (он сейчас в кэше) в другую память через DMA, нужно этот буфер "слить" обратно в память, поскольку DMA качает не из кэша, а из памяти. Поэтому перед тем, как запустить DMA, необходимо сделать FlushCache().
Теперь, допустим, Вам пришло прерывание по приему DMA. Вы ожидаете в приемных данных найти какой-то символ - лезете процессором по этому адресу, а он на самом деле лезет в кэш - там будут лежать старые данные. Поэтому после выдачи события "транзакция завершена" от DMA-контроллера первым делом необходимо сделать CacheInvalidate(), чтобы загрузить актуальные данные из памяти в кэш. Вот и все премудрости. Только CPU знает, какие данные на самом деле актуальны, поэтому у него и есть возможность сливать и обновлять кэш. DMA же является тупым аппаратным мастером на шине, который просто копирует данные. И он не знает о Ваших кэшах, ровным счетом, ничего laughing.gif
Go to the top of the page
 
+Quote Post
__inline__
сообщение Jul 19 2018, 11:38
Сообщение #7


Местный
***

Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126



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

Интересно, что когда программировал 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?
Go to the top of the page
 
+Quote Post
Arlleex
сообщение Jul 19 2018, 12:20
Сообщение #8


Местный
***

Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264



Цитата(__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
Go to the top of the page
 
+Quote Post
__inline__
сообщение Jul 19 2018, 12:38
Сообщение #9


Местный
***

Группа: Участник
Сообщений: 257
Регистрация: 5-09-17
Пользователь №: 99 126



Цитата(Arlleex @ Jul 19 2018, 13:20) *
Это называется система с полностью когерентным кэшем. Там перед стартом DMA сам аппаратно пинает контроллер кэша, чтобы тот слил данные из нужной области памяти. Либо обновил кэш при чтении.

...

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


Спасибо за развёрнутый ответ! rolleyes.gif
Go to the top of the page
 
+Quote Post
Genadi Zawidowsk...
сообщение Jul 19 2018, 14:54
Сообщение #10


Профессионал
*****

Группа: Участник
Сообщений: 1 620
Регистрация: 22-06-07
Из: Санкт-Петербург, Россия
Пользователь №: 28 634



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


Небольшое уточнение - данные из кеша выгружаются в память когда там не хватает места или явно сказано Clean. Грузяться при обращении на чтение.
Предположим, с ранее считанными данными что-то сделали и они наъодяться в кеше готовые к выгрузке в памячть. И по Вашему сценарию запускаем DMA в надежле посде завершения транзакции сказать Invalidate. До этого момента данные могут быть выгружены в любой момент. И модем не увидеть считанное из DMA.
Так что ПЕРЕД зхапуском DMA делаем CleanInvalidate строкам, в память связанную с которыми будет читать DMA из периферии. Совмещение Clean & Invalidate не следить за отсутствием "соседей" с буфером DMA.
Go to the top of the page
 
+Quote Post
Arlleex
сообщение Jul 19 2018, 17:18
Сообщение #11


Местный
***

Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264



Цитата(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
Go to the top of the page
 
+Quote Post
Genadi Zawidowsk...
сообщение Jul 19 2018, 21:20
Сообщение #12


Профессионал
*****

Группа: Участник
Сообщений: 1 620
Регистрация: 22-06-07
Из: Санкт-Петербург, Россия
Пользователь №: 28 634



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

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


Сообщение отредактировал Genadi Zawidowski - Jul 19 2018, 21:22
Go to the top of the page
 
+Quote Post
jcxz
сообщение Jul 19 2018, 22:28
Сообщение #13


Гуру
******

Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713



Цитата(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() для этого региона и дальше спокойно читать его.
Go to the top of the page
 
+Quote Post
Genadi Zawidowsk...
сообщение Jul 19 2018, 23:26
Сообщение #14


Профессионал
*****

Группа: Участник
Сообщений: 1 620
Регистрация: 22-06-07
Из: Санкт-Петербург, Россия
Пользователь №: 28 634



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

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

Сообщение отредактировал Genadi Zawidowski - Jul 19 2018, 23:31
Go to the top of the page
 
+Quote Post
Arlleex
сообщение Jul 20 2018, 06:16
Сообщение #15


Местный
***

Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264



В общем, я лишь для общей картины добавлю официальный документ от ST Microelectronics по кэш-памяти первого уровня.
Go to the top of the page
 
+Quote Post

2 страниц V   1 2 >
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 22nd June 2025 - 03:55
Рейтинг@Mail.ru


Страница сгенерированна за 0.02469 секунд с 7
ELECTRONIX ©2004-2016