NXP S32K1 DMA模块Driver使用

NXP S32K1 DMA模块Driver使用,第1张

NXP S32K1 DMA模块Driver使用

目录

概述虚拟通道运行时状态配置信息初始化回调函数中断函数上层如何使用DMA驱动

UART应用DMASPI应用DMA 应用如何使用S32SDK中DMA

概述

本文通过分析NXP S32K1的官方S32SDK,剖析其DMA代码,以便后续更好的使用DMA。

虚拟通道

在MCU中,可能由1-多个DMA实例,每个实例又有若干不同数目的DMA通道,为此,在S32SDK的DRV中引入virtualChannel的概念,隔绝底层的差异,用户只需要关心一个(虚拟)通道完成什么样的事情。

uint8_t dmaInstance = (uint8_t)FEATURE_DMA_VCH_TO_INSTANCE(virtualChannel);


uint8_t dmaChannel = (uint8_t)FEATURE_DMA_VCH_TO_CH(virtualChannel);

在DRV接口中, *** 作的便是不同的virtualChannel。

运行时状态

由于是使用C语言实现,没有类似C++中类这样的封装技术,但NXP也巧妙的使用了这样的概念,在初始化时引入一个edma_state_t *edmaState。

typedef struct {
    edma_chn_state_t * volatile virtChnState[(uint32_t)FEATURE_DMA_VIRTUAL_CHANNELS];   
} edma_state_t;
typedef struct {
    uint8_t virtChn;                     
    edma_callback_t callback;            
    void *parameter;                     
    volatile edma_chn_status_t status;   
} edma_chn_state_t;

edmaState通过数组指针的方式保存有所有通道的运行时信息,供外设驱动使用。
而edma_chn_state_t便是每个通道的信息,有通道号,供用户使用的回调函数,以及通道状态(正常or错误)。

配置信息

同S32SDK中其他模块一样,使用xxx_config_t的方式,提供模块的配置信息,在初始化时,将config赋给state,这样的好处就是可以使用图形化的配置工具生成特定模块的c语言结构体的config。

typedef struct {
    edma_channel_priority_t channelPriority; 
    uint8_t virtChnConfig;                    
    dma_request_source_t source;             
    edma_callback_t callback;                
    void * callbackParam;                    
    bool enableTrigger;                      			
} edma_channel_config_t;

对于一个通道的配置,有通道号、优先级、触发源、回调函数、是否周期触发等。

初始化
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指向每个chn的配置信息;
chnStateArray指向每个chn的运行时信息;
edmaState统领所有chn的运行时信息;
chnCount指明配置了多少路chn;
userConfig是关于edma模组的配置信息。

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

EDMA_DRV_ChannelInit被EDMA_DRV_Init调用完成每个通道的初始化。

回调函数

NXP也是巧妙的使用的回调函数,用与下层通知上层、模块通知另一个模块某个事件发生。为此,下层模块提供DRV_InstallCallback接口供上层安装回调函数。

status_t EDMA_DRV_InstallCallback(uint8_t virtualChannel,
                                  edma_callback_t callback,
                                  void *parameter)
{
    
    DEV_ASSERT(virtualChannel < FEATURE_DMA_VIRTUAL_CHANNELS);

    
    DEV_ASSERT(s_virtEdmaState->virtChnState[virtualChannel] != NULL);

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

    return STATUS_SUCCESS;
}
中断函数

DMA中断发生后,DMA清除自生标志后,其实并没有什么需要做的,需要做的是使用该通道实现传输目的上层上层,于是,此刻发出通知给上层。

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);
        }
    }
}
上层如何使用DMA驱动 UART应用DMA
(void)EDMA_DRV_ConfigMultiBlockTransfer(lpuartState->txDMAChannel, EDMA_TRANSFER_MEM2PERIPH, (uint32_t)txBuff,
										 (uint32_t)(&(base->DATA)), EDMA_TRANSFER_SIZE_1B, 1U, txSize, true);


(void)EDMA_DRV_InstallCallback(lpuartState->txDMAChannel,
							   (edma_callback_t)(LPUART_DRV_TxDmaCallback),
							   (void*)(instance));


(void)EDMA_DRV_StartChannel(lpuartState->txDMAChannel);

以串口中断发送为例,使用EDMA_DRV_ConfigMultiBlockTransfer配置传输信息、安装回调,启动通道。

在DMA中断发生时,回调函数LPUART_DRV_TxDmaCallback触发。

在LPUART_DRV_TxDmaCallback中,首先发出UART_EVENT_TX_EMPTY事件给串口发送应用,应用此时可以继续DMA传输,也可以结束串口发送,发出LPUART_INT_TX_COMPLETE中断,在中断中又发出UART_EVENT_END_TRANSFER事件,通知上层传输结束了。

void LPUART_DRV_TxDmaCallback(void * parameter, edma_chn_status_t status)
{
	
	if (lpuartState->txCallback != NULL)
	{
		
		lpuartState->txCallback(lpuartState, UART_EVENT_TX_EMPTY, lpuartState->txCallbackParam);
	}

	
	if (lpuartState->txSize > 0U)
	{
		
		EDMA_DRV_SetSrcAddr(lpuartState->txDMAChannel, (uint32_t)(lpuartState->txBuff));
		EDMA_DRV_SetMajorLoopIterationCount(lpuartState->txDMAChannel, lpuartState->txSize);

		
		lpuartState->txSize = 0U;

		
		(void)EDMA_DRV_StartChannel(lpuartState->txDMAChannel);
	}
	else
	{
		
		LPUART_SetIntMode(base, LPUART_INT_TX_COMPLETE, true);
	}
}

同样的,对于串口接收使用DMA的方式,只是配置的参数不同,安装的回调函数LPUART_DRV_RxDmaCallback不同。
只是接受中,还可以配合IDLE中断等使用。

void LPUART_DRV_StartReceiveDataUsingDma()
{
//配置DMA
EDMA_DRV_ConfigMultiBlockTransfer()
EDMA_DRV_InstallCallback()
EDMA_DRV_StartChannel()

//配置INT

LPUART_SetReceiverCmd(base, true);


LPUART_SetErrorInterrupts(base, true);


//...省;


LPUART_SetRxDmaCmd(base, true);
}

static void LPUART_DRV_RxIdleIrqHandler(uint32_t instance)
{
    //...
	
	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);
	}
}

通过EDMA_DRV_GetRemainingMajorIterationsCount可以获得DMA通道剩余空间,由接收总数-剩余数可以得到接收了多少字节数,然后停止DMA(这里或许可以重启接收,不过要更换接收buff,但似乎也没必要,串口不可能发的那么平凡,在COPY了数据之后再重启接收时间上是绝对可以的),发出通知。

SPI应用DMA
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));
	
	(void)EDMA_DRV_StartChannel(lpspiState->rxDMAChannel);
}


if (receiveBuffer!=NULL)
{
	LPSPI_SetRxDmaCmd(base, true);
}

同样是配置、安装回调、启动通道。然后,外设启用DMA传输。

LPSPI_DRV_MasterCompleteRX()
LPSPI_DRV_MasterCompleteDMATransfer()
应用如何使用S32SDK中DMA

对于使用SDK的应用,无须像本文这样了解这么多细节,只需要再配置工具中启用DMA,配置通道的触发源,外设模块启用DMA方式传输,然后简单的在外设初始化前调用EDMA_DRV_Init即可万事大吉。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5702362.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存