概述虚拟通道运行时状态配置信息初始化回调函数中断函数上层如何使用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应用DMAif(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即可万事大吉。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)