Jump to content

    
Sign in to follow this  
jenya7

Посылка по UART с DMA

Recommended Posts

Настройка

void DMA_Init(void)
{
	//enable clock for DMAMUX and DMA
	SIM_SCGC6 |= SIM_SCGC6_DMAMUX0_MASK;
	SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;

	//enable channel 0 and set the source - 3 = UART0 Transmit ($3.3.9.1 Table 3-26)
	DMAMUX0_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(3);

	//set memory address for source and destination
	DMA_TCD0_SADDR = (uint32_t)&log_buffer_data;  //global scope buffer
	DMA_TCD0_DADDR = (uint32_t)&UART0_D;

    //set an offset
	DMA_TCD0_SOFF = 1;  //???
	DMA_TCD0_DOFF = 0;  //???

	//set data transfer size - byte
	DMA_TCD0_ATTR = DMA_ATTR_SSIZE(0) | DMA_ATTR_DSIZE(0);  //???

	//number of bytes to be transfered in each service request
	DMA_TCD0_NBYTES_MLNO = glob_data_chank;  //???

	//current major iteration count (a single iteration)
	DMA_TCD0_CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(1);  //???
	DMA_TCD0_BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(1);  //???

	DMA_TCD0_SLAST = 0;   //???
	DMA_TCD0_DLASTSGA = 0; //???

	//setup control and status register
	//DMA_TCD0_CSR = 0;

	//interrupt when tx/rx buffer is full. stop DMA on single transfer.
	DMA_TCD0_CSR = DMA_CSR_INTMAJOR_MASK | DMA_CSR_DREQ_MASK;
	//enable request signal for channel 0
	DMA_ERQ = DMA_ERQ_ERQ0_MASK;
}

Возникает несколько вопросов.

1. Правильно ли настроены все регистры?

2. размер посылаемого масива (glob_data_chank) может изменится. Нужно изменять DMA_TCD0_NBYTES_MLNO каждый service request?

3. Нужно прерывание? Или просто опрашивать флаг

while (DMA_TCD0_CSR & DMA_CSR_DONE_MASK)

4. На стороне  UART нужны какие то дополнительные настройки? Нужны прерывания?

UART0_C2 |= UART_C2_TIE_MASK;
UART0_C5 |= UART_C5_TDMAS_MASK;

5. Как я начинаю каждую посылку? Так?

DMA_TCD0_CSR |= DMA_CSR_START_MASK;

 

Edited by jenya7

Share this post


Link to post
Share on other sites

Делаете отправку через HAL (5 минут)

Смотрите код (час)

Получаете знания и даёте советы таким же гуру программирования (можно за деньги)

Share this post


Link to post
Share on other sites
4 minutes ago, x893 said:

Делаете отправку через HAL (5 минут)

Смотрите код (час)

Получаете знания и даёте советы таким же гуру программирования (можно за деньги)

из HAL спустится в регистры это ещё тот фильм ужасов. я надеюсь не я первый на планете земля делает связку UART-DMA. Хотя часто возникает мысль - я  таки Гагарин первый.

Share this post


Link to post
Share on other sites

C NXP не работал (только STM, Миландр), но общие соображения:

1. DMA при обмене данными используют в том числе для минимизации прерываний, в идеале никаких прерываний от UART обрабатывать не нужно, только прерывание от окончания DMA.

2. При приеме я использую DMA только, если длина посылки заранее известна. В моих случаях с UART - практически никогда, веду прием побайтно по прерыванию или по опросу флага.

3. При передаче заполняю буфер, настраиваю DMA на запуск от события пустого передатчика и передачу заданного числа байт. Запуск передачи - разрешением передачи UART: после все делает DMA. Это в STM. В Миландре в передатчике UART собственный буфер, стараюсь не превышать его длину и не использовать ни DMA, ни прерывания.

Share this post


Link to post
Share on other sites
38 minutes ago, V_G said:

C NXP не работал (только STM, Миландр), но общие соображения:

1. DMA при обмене данными используют в том числе для минимизации прерываний, в идеале никаких прерываний от UART обрабатывать не нужно, только прерывание от окончания DMA.

2. При приеме я использую DMA только, если длина посылки заранее известна. В моих случаях с UART - практически никогда, веду прием побайтно по прерыванию или по опросу флага.

3. При передаче заполняю буфер, настраиваю DMA на запуск от события пустого передатчика и передачу заданного числа байт. Запуск передачи - разрешением передачи UART: после все делает DMA. Это в STM. В Миландре в передатчике UART собственный буфер, стараюсь не превышать его длину и не использовать ни DMA, ни прерывания.

прерывание от окончания DMA - это тот который в vector table?

(tIsrFunc)&Cpu_Interrupt,          /* 0x10  0x00000040   -   ivINT_DMA0_DMA16               unused by PE */
(tIsrFunc)&Cpu_Interrupt,          /* 0x11  0x00000044   -   ivINT_DMA1_DMA17               unused by PE */
(tIsrFunc)&Cpu_Interrupt,          /* 0x12  0x00000048   -   ivINT_DMA2_DMA18               unused by PE */
(tIsrFunc)&Cpu_Interrupt,          /* 0x13  0x0000004C   -   ivINT_DMA3_DMA19               unused by PE */
   

там нет разделения по какому ивенту произошло прерывание. нужно проверять флаг - (DMA_TCD0_CSR & DMA_CSR_DONE_MASK) в прерывании?

 

Edited by jenya7

Share this post


Link to post
Share on other sites
7 часов назад, jenya7 сказал:

нужно проверять флаг - (DMA_TCD0_CSR & DMA_CSR_DONE_MASK) в прерывании?

Флаг проверять, конечно, нужно, но главное - запретить ВСЕ прерывания от DMA, кроме прерывания от окончания. Если есть такая возможность. Или по крайней мере, стараться запретить и не обрабатывать наиболее частые прерывания, загружающие проц.

Кстати, про оптимальность и про HAL (хотя его в NXP может не быть - своя библиотека). И не про UART, а про высокоскоростной I2S. Я поначалу написал прием данных с АЦП на HAL, скорость не удовлетворила. Потом просто прошелся по шагам внутри функций HAL и исключил всю избыточность для моего конкретного случая. Производительность возросла в разы...

Share this post


Link to post
Share on other sites

я нашел иакой пример

int UART1_dma_complete(void){
	//Channel Done wait
	return ((DMA0->TCD[1].CSR) & DMA_CSR_DONE_MASK);
}

void UART1_dma_complete_wait(void){
	while(!((DMA0->TCD[1].CSR) & DMA_CSR_DONE_MASK));
}

void UART1_txBulk(uint8_t *data, uint16_t len){
	//Wait until dma is complete
	while(!(UART1_dma_complete()));
	DMAMUX0->CHCFG[1]=0; //set to zero during configuration

	//Enable request signal for channel 1
	DMA0->ERQ|=DMA_ERQ_ERQ1_MASK;

	//Set memory address
	DMA0->TCD[1].SADDR=(uint32_t) (data);
	DMA0->TCD[1].DADDR=(uint32_t) &(UART->D);

	//Source and destination data transfer size
	DMA0->TCD[1].ATTR=DMA_ATTR_SSIZE(0)|DMA_ATTR_DSIZE(0);
	//Set number of minor loops
	DMA0->TCD[1].CITER_ELINKNO=DMA_CITER_ELINKNO_CITER(len);
	DMA0->TCD[1].BITER_ELINKNO=DMA_BITER_ELINKNO_BITER(len);

	DMAMUX0->CHCFG[1]=DMAMUX_CHCFG_ENBL_MASK|DMAMUX_CHCFG_SOURCE(5); //always enabled
	DMA0->TCD[1].CSR=DMA_CSR_DREQ_MASK;
}

окончание транзакции делается полингом флага. а зачем переписывать все регистры заново? а почему DMA_CSR_DONE_MASK не сбрасывается? а где тригер новой транзакции?

и почему

 

DMA0->TCD[1].CITER_ELINKNO=DMA_CITER_ELINKNO_CITER(len);
DMA0->TCD[1].BITER_ELINKNO=DMA_BITER_ELINKNO_BITER(len);

это ведь minor loop - 1 байт. вся длина в major loop - DMA_TCD0_NBYTES_MLNO.

Edited by jenya7

Share this post


Link to post
Share on other sites

Не-не-не, я ж говорил, с NXP не работал, там все не так, как в STM, тут никаких советов по конкретному коду. Но переписывать тучу регистров при реинициализации DMA - обычное дело. Особенно в Миландре, где DMA особенно кривой

Share this post


Link to post
Share on other sites

я смотрю в HAL

status_t UART_SendEDMA(UART_Type *base, uart_edma_handle_t *handle, uart_transfer_t *xfer)
{
    assert(handle);
    assert(handle->txEdmaHandle);
    assert(xfer);
    assert(xfer->data);
    assert(xfer->dataSize);

    edma_transfer_config_t xferConfig;
    status_t status;

    /* If previous TX not finished. */
    if (kUART_TxBusy == handle->txState)
    {
        status = kStatus_UART_TxBusy;
    }
    else
    {
        handle->txState = kUART_TxBusy;
        handle->txDataSizeAll = xfer->dataSize;

        /* Prepare transfer. */
        EDMA_PrepareTransfer(&xferConfig, xfer->data, sizeof(uint8_t), (void *)UART_GetDataRegisterAddress(base),
                             sizeof(uint8_t), sizeof(uint8_t), xfer->dataSize, kEDMA_MemoryToPeripheral);

        /* Store the initially configured eDMA minor byte transfer count into the UART handle */
        handle->nbytes = sizeof(uint8_t);

        /* Submit transfer. */
        EDMA_SubmitTransfer(handle->txEdmaHandle, &xferConfig);
        EDMA_StartTransfer(handle->txEdmaHandle);

        /* Enable UART TX EDMA. */
        UART_EnableTxDMA(base, true);

        status = kStatus_Success;
    }

    return status;
}

на каждую посылку переписываются ВСЕ регистры. вот лишь малая часть кода

   /* source address */
    tcd->SADDR = config->srcAddr;
    /* destination address */
    tcd->DADDR = config->destAddr;
    /* Source data and destination data transfer size */
    tcd->ATTR = DMA_ATTR_SSIZE(config->srcTransferSize) | DMA_ATTR_DSIZE(config->destTransferSize);
    /* Source address signed offset */
    tcd->SOFF = config->srcOffset;
    /* Destination address signed offset */
    tcd->DOFF = config->destOffset;
    /* Minor byte transfer count */
    tcd->NBYTES = config->minorLoopBytes;
    /* Current major iteration count */
    tcd->CITER = config->majorLoopCounts;
    /* Starting major iteration count */
    tcd->BITER = config->majorLoopCounts;
    /* Enable scatter/gather processing */

и я не понимаю это обязательные действия или нет.

потом

Quote

/* Minor byte transfer count */
    tcd->NBYTES = config->minorLoopBytes;  //minorLoopBytes = sizeof(uin8_t)

в описании регистра
 

Quote

DMA_TCDn_NBYTES_MLNO

This register, or one of the next two registers (TCD_NBYTES_MLOFFNO,
TCD_NBYTES_MLOFFYES), defines the number of bytes to transfer per request.

что тогда - number of bytes to transfer per request. трансфер это ж вся посылка, весь буфер.

 

 

Share this post


Link to post
Share on other sites
19 минут назад, V_G сказал:

Но переписывать тучу регистров при реинициализации DMA - обычное дело.

Никакой "тучи" в NXP переписывать не надо (так же как и в большинстве других известных мне семейств МК). На приём: один раз запустил DMA в "linked-list" режиме и больше его не трогаешь (только периодически читаешь текущее принятое число символов, да обрабатываешь прерывания завершения очередного блока). На передачу, тоже негусто: всего несколько записей в регистры (меняются только адрес и размер пересылки).

Да и не нужно DMA в NXP для UART как правило: UART там имеет нормальный FIFO. Это не STM32 с его примитивными UART.

Share this post


Link to post
Share on other sites
1 hour ago, jcxz said:

Никакой "тучи" в NXP переписывать не надо (так же как и в большинстве других известных мне семейств МК). На приём: один раз запустил DMA в "linked-list" режиме и больше его не трогаешь (только периодически читаешь текущее принятое число символов, да обрабатываешь прерывания завершения очередного блока). На передачу, тоже негусто: всего несколько записей в регистры (меняются только адрес и размер пересылки).

Да и не нужно DMA в NXP для UART как правило: UART там имеет нормальный FIFO. Это не STM32 с его примитивными UART.

а что значит в "linked-list" режиме? какой регистр?

Quote

На передачу, тоже негусто: всего несколько записей в регистры (меняются только адрес и размер пересылки).

а почему адрес меняется? я всегда посылаю статический буфер.

Quote

Да и не нужно DMA в NXP для UART как правило

то есть достаточно проверять (DMA0->TCD[1].CSR) & DMA_CSR_DONE_MASK) ?

это не надо?

UART0_C5 |= UART_C5_TDMAS_MASK;

 

а что там с minor loop и major loop? minor loop - 1 байт, major loop - вся длина? я видел и так и наоборот.

 

а офсеты? я так понял memory to peripherial

DMA0->TCD[1].SOFF=1;
DMA0->TCD[1].DOFF=0;

 

Quote

Да и не нужно DMA в NXP для UART как правило: UART там имеет нормальный FIFO.

у меня посылка 4096 байт. и пока DMA их шлет я должен вычитать следующий сектор из флэш памяти по SPI. так я экономлю время.

Edited by jenya7

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this