|
STM32H743 MDMA, Нет передачи в периферию |
|
|
|
Jul 19 2018, 09:41
|

Местный
  
Группа: Участник
Сообщений: 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); } Что не здесь так?
|
|
|
|
|
Jul 19 2018, 10:22
|

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

|
Цитата(__inline__ @ Jul 19 2018, 13:41)  Что не здесь так? Flush кэша данных-то Вы сделали, а invalidate после приема DMA где?
|
|
|
|
|
Jul 19 2018, 10:49
|

Местный
  
Группа: Участник
Сообщений: 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! Спасибо! По чему в случае "обычного" DMA работает без SCB_InvalidateDCache(); ? или это специфика только MDMA? Всё, понял. Это для приёмной памяти. В случае с LCD Invalidate DCache делать не нужно (его адреса портов не кешированы). Потому работало на обычном DMA. С памятью на приёме обязателен Invalidate DC.
|
|
|
|
|
Jul 19 2018, 11:26
|

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

|
Тут чисто из логики даже все следует. Есть D-Cache, он в себе хранит Ваши буферы каких-то данных. Этими данными оперирует CPU. Теперь, перед тем, как отправить подготовленный буфер данных (он сейчас в кэше) в другую память через DMA, нужно этот буфер "слить" обратно в память, поскольку DMA качает не из кэша, а из памяти. Поэтому перед тем, как запустить DMA, необходимо сделать FlushCache(). Теперь, допустим, Вам пришло прерывание по приему DMA. Вы ожидаете в приемных данных найти какой-то символ - лезете процессором по этому адресу, а он на самом деле лезет в кэш - там будут лежать старые данные. Поэтому после выдачи события "транзакция завершена" от DMA-контроллера первым делом необходимо сделать CacheInvalidate(), чтобы загрузить актуальные данные из памяти в кэш. Вот и все премудрости. Только CPU знает, какие данные на самом деле актуальны, поэтому у него и есть возможность сливать и обновлять кэш. DMA же является тупым аппаратным мастером на шине, который просто копирует данные. И он не знает о Ваших кэшах, ровным счетом, ничего
|
|
|
|
|
Jul 19 2018, 11:38
|

Местный
  
Группа: Участник
Сообщений: 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?
|
|
|
|
|
Jul 19 2018, 12:20
|

Местный
  
Группа: Участник
Сообщений: 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кБ (вроде), это связано с физическими адресуемыми подчиненными шин. Как-то так
|
|
|
|
|
Jul 19 2018, 14:54
|

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

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

Местный
  
Группа: Участник
Сообщений: 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. А это я вообще не смог понять, извините
|
|
|
|
|
Jul 19 2018, 22:28
|
Гуру
     
Группа: Свой
Сообщений: 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() для этого региона и дальше спокойно читать его.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|