NXP S32K1 DMA module Driver

Posted by alexville on Thu, 06 Jan 2022 13:18:28 +0100

summary

By analyzing the official S32SDK of NXP S32K1, this paper analyzes its DMA code, so as to better use DMA in the future.

virtual channel

In MCU, there may be 1- multiple DMA instances, and each instance has several different numbers of DMA channels. Therefore, the concept of virtual channel is introduced into the DRV of S32SDK to isolate the underlying differences. Users only need to care about what one (virtual) channel does.

/* Get DMA instance from virtual channel */
uint8_t dmaInstance = (uint8_t)FEATURE_DMA_VCH_TO_INSTANCE(virtualChannel);

/* Get DMA channel from virtual channel*/
uint8_t dmaChannel = (uint8_t)FEATURE_DMA_VCH_TO_CH(virtualChannel);

In the DRV interface, different virtualchannels are operated.

Runtime status

Because it is implemented in C language, there is no encapsulation technology similar to the class in C + +, but NXP also cleverly uses such a concept and introduces an EDMA during initialization_ state_ t *edmaState.

typedef struct {
    edma_chn_state_t * volatile virtChnState[(uint32_t)FEATURE_DMA_VIRTUAL_CHANNELS];   /*!< Pointer array storing channel state. */
} edma_state_t;
typedef struct {
    uint8_t virtChn;                     /*!< Virtual channel number. */
    edma_callback_t callback;            /*!< Callback function pointer for the eDMA channel. It will
                                              be called at the eDMA channel complete and eDMA channel
                                              error. */
    void *parameter;                     /*!< Parameter for the callback function pointer. */
    volatile edma_chn_status_t status;   /*!< eDMA channel status. */
} edma_chn_state_t;

edmaState saves the runtime information of all channels through array pointers for peripheral drivers to use.
And edma_chn_state_t is the information of each channel, including the channel number, the callback function for the user, and the channel status (normal or error).

configuration information

Like other modules in S32SDK, XXX is used_ config_ T provides module configuration information. During initialization, config is assigned to state. This advantage is that the graphical configuration tool can be used to generate the config of the c language structure of a specific module.

typedef struct {
    edma_channel_priority_t channelPriority; /*!< eDMA channel priority - only used when channel                                                  arbitration mode is 'Fixed priority'. */
    uint8_t virtChnConfig;                   /*!< eDMA virtual channel number */ 
    dma_request_source_t source;             /*!< Selects the source of the DMA request for this channel */
    edma_callback_t callback;                /*!< Callback that will be registered for this channel */
    void * callbackParam;                    /*!< Parameter passed to the channel callback */
    bool enableTrigger;                      /*!< Enables the periodic trigger capability for the DMA channel. */			
} edma_channel_config_t;

The configuration of a channel includes channel number, priority, trigger source, callback function, whether to trigger periodically, etc.

initialization

status_t EDMA_DRV_Init(edma_state_t *edmaState,
                       const edma_user_config_t *userConfig,
                       edma_chn_state_t * const chnStateArray[],
                       const edma_channel_config_t * const chnConfigArray[],
                       uint32_t chnCount)

chnConfigArray refers to the configuration information of each chn;
chnStateArray refers to the runtime information of each chn;
edmaState commands the runtime information of all CHNS;
chnCount indicates how many CHNS are configured;
userConfig is the configuration information about the edma module.

status_t EDMA_DRV_ChannelInit(edma_chn_state_t *edmaChannelState,
                              const edma_channel_config_t *edmaChannelConfig)

EDMA_DRV_ChannelInit by EDMA_ DRV_ The init call completes the initialization of each channel.

Callback function

NXP is also a skillfully used callback function, which is used to notify the upper layer and module of another module of an event. For this purpose, the lower module provides DRV_ The installcallback interface is used by the upper layer to install callback functions.

status_t EDMA_DRV_InstallCallback(uint8_t virtualChannel,
                                  edma_callback_t callback,
                                  void *parameter)
{
    /* Check the channel number is valid */
    DEV_ASSERT(virtualChannel < FEATURE_DMA_VIRTUAL_CHANNELS);

    /* Check the channel is allocated */
    DEV_ASSERT(s_virtEdmaState->virtChnState[virtualChannel] != NULL);

    s_virtEdmaState->virtChnState[virtualChannel]->callback = callback;
    s_virtEdmaState->virtChnState[virtualChannel]->parameter = parameter;

    return STATUS_SUCCESS;
}

Interrupt function

After the DMA interrupt occurs and the DMA clears the self generated flag, there is nothing to do. What needs to be done is to use the channel to achieve the transmission purpose. Therefore, a notice is sent to the upper layer at this moment.

void EDMA_DRV_IRQHandler(uint8_t virtualChannel)
{
    const edma_chn_state_t *chnState = s_virtEdmaState->virtChnState[virtualChannel];
    EDMA_DRV_ClearIntStatus(virtualChannel);

    if (chnState != NULL)
    {
        if (chnState->callback != NULL)
        {
            chnState->callback(chnState->parameter, chnState->status);
        }
    }
}

void EDMA_DRV_ErrorIRQHandler(uint8_t virtualChannel)
{
...

    DMA_Type *edmaRegBase = s_edmaBase[dmaInstance];
    EDMA_SetDmaRequestCmd(edmaRegBase, dmaChannel, false);
    edma_chn_state_t *chnState = s_virtEdmaState->virtChnState[virtualChannel];
    if (chnState != NULL)
    {
        EDMA_DRV_ClearIntStatus(virtualChannel);
        EDMA_ClearErrorIntStatusFlag(edmaRegBase, dmaChannel);
        chnState->status = EDMA_CHN_ERROR;
        if (chnState->callback != NULL)
        {
            chnState->callback(chnState->parameter, chnState->status);
        }
    }
}

How to use DMA driver in the upper layer

UART application DMA

/* Configure the transfer control descriptor for the previously allocated channel */
(void)EDMA_DRV_ConfigMultiBlockTransfer(lpuartState->txDMAChannel, EDMA_TRANSFER_MEM2PERIPH, (uint32_t)txBuff,
										 (uint32_t)(&(base->DATA)), EDMA_TRANSFER_SIZE_1B, 1U, txSize, true);

/* Call driver function to end the transmission when the DMA transfer is done */
(void)EDMA_DRV_InstallCallback(lpuartState->txDMAChannel,
							   (edma_callback_t)(LPUART_DRV_TxDmaCallback),
							   (void*)(instance));

/* Start the DMA channel */
(void)EDMA_DRV_StartChannel(lpuartState->txDMAChannel);

Taking the serial port interrupt sending as an example, EDMA is used_ DRV_ Configmultiblocktransfer configures the transmission information, installs the callback, and starts the channel.

When DMA interrupt occurs, the callback function LPUART_DRV_TxDmaCallback trigger.

At lpuart_ DRV_ In txdmacallback, issue UART first_ EVENT_ TX_ The empty event is sent to the serial port to the application. At this time, the application can continue DMA transmission or end serial port transmission and issue LPUART_INT_TX_COMPLETE interrupt, in which UART is issued_ EVENT_ END_ The transfer event notifies the upper layer that the transmission has ended.

void LPUART_DRV_TxDmaCallback(void * parameter, edma_chn_status_t status)
{
	/* Invoke callback if there is one */
	if (lpuartState->txCallback != NULL)
	{
		/* Allow the user to provide a new buffer, for continuous transmission */
		lpuartState->txCallback(lpuartState, UART_EVENT_TX_EMPTY, lpuartState->txCallbackParam);
	}

	/* If the callback has updated the tx buffer, update the DMA descriptor to continue the transfer;
	 * otherwise, stop the current transfer.
	 */
	if (lpuartState->txSize > 0U)
	{
		/* Set the source address and the number of minor loops (bytes to be transfered) */
		EDMA_DRV_SetSrcAddr(lpuartState->txDMAChannel, (uint32_t)(lpuartState->txBuff));
		EDMA_DRV_SetMajorLoopIterationCount(lpuartState->txDMAChannel, lpuartState->txSize);

		/* Now that this tx is set up, clear remaining bytes count */
		lpuartState->txSize = 0U;

		/* Re-start the channel */
		(void)EDMA_DRV_StartChannel(lpuartState->txDMAChannel);
	}
	else
	{
		/* Enable transmission complete interrupt */
		LPUART_SetIntMode(base, LPUART_INT_TX_COMPLETE, true);
	}
}

Similarly, DMA is used for serial port reception, but the configured parameters are different. The callback function lpuart is installed_ DRV_ Rxdmacallback is different.
It is only accepted. It can also be used with IDLE interrupt, etc.

void LPUART_DRV_StartReceiveDataUsingDma()
{
//Configure DMA
EDMA_DRV_ConfigMultiBlockTransfer()
EDMA_DRV_InstallCallback()
EDMA_DRV_StartChannel()

//Configure INT
/* Enable the receiver */
LPUART_SetReceiverCmd(base, true);

/* Enable error interrupts */
LPUART_SetErrorInterrupts(base, true);

/* Enable idle interrupts */
//... Province;

/* Enable rx DMA requests for the current instance */
LPUART_SetRxDmaCmd(base, true);
}

static void LPUART_DRV_RxIdleIrqHandler(uint32_t instance)
{
    //...
	/* clear idle flag */
	LPUART_DRV_ClearIdleFlags(base);

	if(lpuartState->transferType == LPUART_USING_DMA)
	{
		lpuartState->rxSize = EDMA_DRV_GetRemainingMajorIterationsCount(lpuartState->rxDMAChannel);

		LPUART_DRV_StopRxDma(instance);

		lpuartState->rxCallback(lpuartState, UART_EVENT_RX_IDLE, lpuartState->rxCallbackParam);
	}
}

Via EDMA_DRV_GetRemainingMajorIterationsCount can obtain the remaining space of the DMA channel. The total number of bytes received can be obtained from the total number of received - the remaining number, and then stop DMA (reception may be restarted here, but it seems unnecessary to change the reception buff. The serial port cannot send so ordinary, and it is absolutely possible to restart the reception time after copying the data), Give notice.

SPI application DMA

/* Configure RX DMA channel if is used in current transfer. */
if(receiveBuffer != NULL)
{
	(void)EDMA_DRV_ConfigMultiBlockTransfer(lpspiState->rxDMAChannel, EDMA_TRANSFER_PERIPH2MEM,
						(uint32_t)(&(base->RDR)),(uint32_t)receiveBuffer, dmaTransferSize, (uint32_t)1U<<(uint8_t)(dmaTransferSize),
						(uint32_t)transferByteCount/(uint32_t)((uint32_t)1U <<(uint8_t)(dmaTransferSize)), true);
	(void)EDMA_DRV_InstallCallback(lpspiState->rxDMAChannel, (LPSPI_DRV_MasterCompleteRX),(void*)(instance));
	/* Start RX channel */
	(void)EDMA_DRV_StartChannel(lpspiState->rxDMAChannel);
}

/* Enable LPSPI DMA request */
if (receiveBuffer!=NULL)
{
	LPSPI_SetRxDmaCmd(base, true);
}

The same is to configure, install callback and start channel. Then, the peripheral enables DMA transmission.

LPSPI_DRV_MasterCompleteRX()
LPSPI_DRV_MasterCompleteDMATransfer()

How to use DMA in S32SDK

For applications that use SDK, there is no need to understand so many details like this article. It only needs to configure DMA in the configuration tool, configure the trigger source of the channel, enable the peripheral module to enable DMA mode transmission, and then simply call EDMA_ before the initialization of the peripheral. DRV_ Init is all right.

Topics: Single-Chip Microcomputer ARM NXP dma