(六)ASCLIN_UART模块串口DMA模式
文章目录
- DMA简介
- 采用DMA完成串口收发
- mydma.c文件:
- 简单分析:
- 大致流程:
- 注意事项:
- mydma.h文件:
- 主函数调用:
- 测试现象1:
- 测试现象2:
- 测试现象3:
- 简单补充几个其它配置的测试
- 修改发送的测试
- 修改接收的测试
DMA简介
DMA(Direct Memory Access,直接存储器访问)是一种在不需要CPU干预的情况下,实现外设与存储器之间 或 存储器与存储器之间高速数据传输的技术。DMA的主要目的是减轻CPU的负担,使其能够专注于更复杂的计算和控制任务
简单理解:俩地址之间进行数据搬运,不需要CPU干涉
可以有软件触发DMA进行搬运,也可以硬件触发DMA搬运,取决于配置。
比如:(五)ASCLIN_UART模块串口中断模式 中采用的是借助串口的接收发送中断进行数据传输的。这里可以配置DMA硬件触发,将串口的接收/发送中断,路由到DMA上,每一次触发串口的中断,就触发一次DMA搬运即可。
当然DMA配置很复杂,可以直接借助iLLD库函数进行配置,应用为王。直接分析代码。
采用DMA完成串口收发
mydma.c文件:
#include "IfxAsclin_Asc.h"
#include "IfxDma_Dma.h"
#include "IfxDma.h"
#include "Bsp.h"
#include "string.h"
/*********************************************************************************************************************/
/*------------------------------------------------------Macros-------------------------------------------------------*/
/*********************************************************************************************************************/
#define UART_BAUDRATE 460800 /* UART baud rate in bit/s */
#define UART_PIN_RX IfxAsclin2_RXE_P33_8_IN /* UART receive port pin */
#define UART_PIN_TX IfxAsclin2_TX_P33_9_OUT /* UART transmit port pin */
#define DMA_CHANNEL_RX INTPRIO_ASCLIN2_RX
#define DMA_CHANNEL_TX INTPRIO_ASCLIN2_TX
IfxDma_Dma_Channel g_rxchn; /* DMA channel handle */
IfxDma_Dma_Channel g_txchn;
/* Definition of the interrupt priorities */
#define INTPRIO_ASCLIN2_TX 11 /* Triggered when AscLin transmits */
#define INTPRIO_ASCLIN2_RX 12 /* Triggered when AscLin receives */
#define INTPRIO_DMA_TX 13 /* Triggered when a DMA transaction is finished */
#define INTPRIO_DMA_RX 14 /* Triggered when a DMA transaction is finished */
#define UART_RX_BUFFER_SIZE 16 /* Definition of the receive buffer size */
#define UART_TX_BUFFER_SIZE 16 /* Definition of the transmit buffer size */
/* Declaration of the ASC handle */
static IfxAsclin_Asc g_ascHandle;
/* Declaration of the FIFOs parameters */
_Alignas(16) static uint8 g_ascTxBuffer[UART_TX_BUFFER_SIZE];//只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!
_Alignas(16) static uint8 g_ascRxBuffer[UART_RX_BUFFER_SIZE];
/* Definition of txData */
_Alignas(16) uint8 g_txData[] = "Hello World!";//只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!
/* Size of the message */
Ifx_UReg_32Bit g_count = sizeof(g_txData)-1;
int rxcnt = 0;
int txcnt = 0;
IFX_INTERRUPT(prio_DMA_RX, 0, INTPRIO_DMA_RX);
void prio_DMA_RX(void)
{
rxcnt++;
IfxDma_clearChannelInterrupt(&MODULE_DMA, g_rxchn.channelId);
}
/* Interrupt triggered when the DMA finishes a transaction*/
IFX_INTERRUPT(prio_DMA_TX, 0, INTPRIO_DMA_TX);
void prio_DMA_TX(void)
{
txcnt++;
IfxDma_clearChannelInterrupt(&MODULE_DMA, g_txchn.channelId);
}
/* This function initializes the ASCLIN UART module */
void init_asclin_uart(void)
{
/* Initialize an instance of IfxAsclin_Asc_Config with default values */
IfxAsclin_Asc_Config ascConfig;
IfxAsclin_Asc_initModuleConfig(&ascConfig, &MODULE_ASCLIN2);
/* Set the desired baud rate */
ascConfig.baudrate.baudrate = UART_BAUDRATE;
/* ISR priorities and interrupt target */
ascConfig.interrupt.txPriority = INTPRIO_ASCLIN2_TX;
ascConfig.interrupt.rxPriority = INTPRIO_ASCLIN2_RX;
ascConfig.interrupt.typeOfService = IfxSrc_Tos_cpu0 ;
/* FIFO configuration */ //没用到串口接收/发送的缓存可以不绑定,直接删除即可(如出现问题,可以直接删除)
ascConfig.txBuffer = &g_ascTxBuffer;
ascConfig.txBufferSize = UART_TX_BUFFER_SIZE;
ascConfig.rxBuffer = &g_ascRxBuffer;
ascConfig.rxBufferSize = UART_RX_BUFFER_SIZE;
/* Pin configuration */
const IfxAsclin_Asc_Pins pins =
{
NULL_PTR, IfxPort_InputMode_pullUp, /* CTS pin not used */
&UART_PIN_RX, IfxPort_InputMode_pullUp, /* RX pin */
NULL_PTR, IfxPort_OutputMode_pushPull, /* RTS pin not used */
&UART_PIN_TX, IfxPort_OutputMode_pushPull, /* TX pin */
IfxPort_PadDriver_cmosAutomotiveSpeed1
};
ascConfig.pins = &pins;
IfxAsclin_Asc_initModule(&g_ascHandle, &ascConfig); /* Initialize module with above parameters */
/* Modification of the TOS for the Rx related interruption */
/* Change from CPU0 (previously defined above) to DMA */
volatile Ifx_SRC_SRCR *src;
src = IfxAsclin_getSrcPointerTx(ascConfig.asclin);
/* Assign DMA as Service Provider when INTPRIO_ASCLIN2_TX is triggered */
IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_TX);
IfxAsclin_enableTxFifoFillLevelFlag(ascConfig.asclin, TRUE);
IfxSrc_enable(src);
/* Modification of the TOS for the Rx related interruption */
/* Change from CPU0 (previously defined above) to DMA */
src = IfxAsclin_getSrcPointerRx(ascConfig.asclin);
/* Assign DMA as Service Provider when INTPRIO_ASCLIN2_RX is triggered */
IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_RX);
IfxAsclin_enableRxFifoFillLevelFlag(ascConfig.asclin, TRUE);
IfxSrc_enable(src);
}
/* This function is called from main in order to initialize the DMA module */
void init_dma(void)
{
/* Initialize an instance of IfxDma_Dma_Config with default values */
IfxDma_Dma_Config dmaConfig;
IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);
/* Initialize module */
IfxDma_Dma dma;
IfxDma_Dma_initModule(&dma, &dmaConfig);
/* Initial configuration for all channels */
IfxDma_Dma_ChannelConfig cfg;
IfxDma_Dma_initChannelConfig(&cfg, &dma);
/* Following configuration is used by the DMA channel */
cfg.moveSize = IfxDma_ChannelMoveSize_8bit;
cfg.blockMode = IfxDma_ChannelMove_1;
/*********************************************** TX部分**************************************************************/
cfg.transferCount = 0;//一次触发搬运多少个字节数据(后面写入的时候会修改)
/* DMA completes a full transaction on requests */
cfg.requestMode = IfxDma_ChannelRequestMode_oneTransferPerRequest; //一个请求触发一个单一DMA传输 即请求启动单个事务
/* DMA as Interrupt Service Provider */
cfg.hardwareRequestEnabled = TRUE; // 使能UART发送中断触发Dma(硬件触发)
/* DMA channel stays enabled after one request */
cfg.operationMode = IfxDma_ChannelOperationMode_single; //DMA单次搬运
/*************** Source and destination addresses ***************/
cfg.sourceCircularBufferEnabled = TRUE; //开启源地址自增
cfg.sourceAddressIncrementStep = IfxDma_ChannelIncrementStep_1; //源地址自增加+1
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_16; //源地址16字节循环
cfg.destinationCircularBufferEnabled = TRUE; //开启目的地址自增
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_none; //目的地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE
/*************** Channel specific configurations ***************/
/* Select the Channel 11, related to the interruption on AscLin TX */
cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_TX;
/* Address of the UART TX FIFO */
cfg.sourceAddress = (uint32)&g_ascTxBuffer;
cfg.destinationAddress = (uint32) &g_ascHandle.asclin->TXDATA.U;
/* DMA中断部分(如果采用环形缓存区,就没必要开启了哈) */
cfg.channelInterruptEnabled = TRUE;
/* DMA triggers an interrupt once the full transaction is done */
cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
/* Priority of the channel interrupt trigger */
cfg.channelInterruptPriority = INTPRIO_DMA_TX;
/* Interrupt service provider */
cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;
IfxDma_Dma_initChannel(&g_txchn, &cfg);
/********************************************************* RX部分***********************************************************/
cfg.transferCount = 1; //一次触发搬运多少个字节数据
/* DMA completes a full transaction on requests */
cfg.requestMode = IfxDma_ChannelRequestMode_completeTransactionPerRequest; //一个请求触发一个完整事务 即请求启动完整的事务
/* DMA as Interrupt Service Provider */
cfg.hardwareRequestEnabled = TRUE; // UART接收中断触发Dma
/* DMA channel stays enabled after one request */
cfg.operationMode = IfxDma_ChannelOperationMode_continuous; // DMA连续搬运模式
/*************** Source and destination addresses ***************/
cfg.sourceCircularBufferEnabled = TRUE; //源地址自增
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_none;源地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE
cfg.destinationCircularBufferEnabled = TRUE;
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_16;
/*************** Channel specific configurations ***************/
/* Select the Channel 12, related to the interruption on AscLin RX */
cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_RX;
/* Address of the UART RX FIFO */
cfg.sourceAddress = (uint32) &g_ascHandle.asclin->RXDATA.U;
cfg.destinationAddress = (uint32)&g_ascRxBuffer;
/* DMA中断部分(如果采用环形缓存区,就没必要开启了哈) */
cfg.channelInterruptEnabled = TRUE;
/* DMA triggers an interrupt once the full transaction is done */
cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
/* Priority of the channel interrupt trigger */
cfg.channelInterruptPriority = INTPRIO_DMA_RX;
/* Interrupt service provider */
cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;
IfxDma_Dma_initChannel(&g_rxchn, &cfg);
}
void send_data(char* g_txData,Ifx_UReg_32Bit len) //发送数据--指定长度大小
{
Ifx_DMA_CH *channel = g_txchn.channel;
IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
channel->SADR.U = (uint32)g_txData;
channel->CHCFGR.B.TREL = len;
IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
IfxDma_Dma_startChannelTransaction(&g_txchn);
//g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
}
void send_1s_pack(void) //对上面 send_data封装而已
{
send_data((char *)g_txData,g_count);
waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}
void recive_data(void) //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)
{
Ifx_DMA_CH *channel = g_txchn.channel;
IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
channel->SADR.U = (uint32)&g_ascRxBuffer;
channel->CHCFGR.B.TREL = 16;
IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
IfxDma_Dma_startChannelTransaction(&g_txchn);
//g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}
简单分析:
编写DMA俩个通道的中断:
int rxcnt = 0;
int txcnt = 0;
IFX_INTERRUPT(prio_DMA_RX, 0, INTPRIO_DMA_RX);
void prio_DMA_RX(void)
{
rxcnt++;
IfxDma_clearChannelInterrupt(&MODULE_DMA, g_rxchn.channelId);
}
/* Interrupt triggered when the DMA finishes a transaction*/
IFX_INTERRUPT(prio_DMA_TX, 0, INTPRIO_DMA_TX);
void prio_DMA_TX(void)
{
txcnt++;
IfxDma_clearChannelInterrupt(&MODULE_DMA, g_txchn.channelId);
}
初始化ASCLIN的UART模块代码和之前的一模一样,多加了串口中断路由到DMA请求触发上面,也就是本来串口中断CPU0来提供服务,现在变成了DMA(就是串口触发DMA搬运,):
/* Modification of the TOS for the Rx related interruption */
/* Change from CPU0 (previously defined above) to DMA */
volatile Ifx_SRC_SRCR *src;
src = IfxAsclin_getSrcPointerTx(ascConfig.asclin);
/* Assign DMA as Service Provider when INTPRIO_ASCLIN2_TX is triggered */
IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_TX);
IfxAsclin_enableTxFifoFillLevelFlag(ascConfig.asclin, TRUE);
IfxSrc_enable(src);
/* Modification of the TOS for the Rx related interruption */
/* Change from CPU0 (previously defined above) to DMA */
src = IfxAsclin_getSrcPointerRx(ascConfig.asclin);
/* Assign DMA as Service Provider when INTPRIO_ASCLIN2_RX is triggered */
IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_RX);
IfxAsclin_enableRxFifoFillLevelFlag(ascConfig.asclin, TRUE);
IfxSrc_enable(src);
接着初始化DMA,里面采用的还是,初始化DMA配置参数的结构体,然后初始化DMA模块,再初始化配置DMA通道的结构体,根据需求,修改对应的结构体成员配置,然后调用DMA通道初始化函数进行初始化(这里配置很多哈)。当然DMA也可以开启搬运完成的中断。
void init_dma(void)
{
/* Initialize an instance of IfxDma_Dma_Config with default values */
IfxDma_Dma_Config dmaConfig;
IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);
/* Initialize module */
IfxDma_Dma dma;
IfxDma_Dma_initModule(&dma, &dmaConfig);
/* Initial configuration for all channels */
IfxDma_Dma_ChannelConfig cfg;
IfxDma_Dma_initChannelConfig(&cfg, &dma);
/* Following configuration is used by the DMA channel */
cfg.moveSize = IfxDma_ChannelMoveSize_8bit;
cfg.blockMode = IfxDma_ChannelMove_1;
cfg.transferCount = 0;
//..........
IfxDma_Dma_initChannel(&g_rxchn, &cfg);
}
封装的发送函数,就是将源地址设为字节要发送的数组,然后根据数组个数,设置DMA要传输多少次数据,然后触发DMA即可。
void send_data(char* g_txData,Ifx_UReg_32Bit len) //发送数据--指定长度大小
{
Ifx_DMA_CH *channel = g_txchn.channel;
IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
channel->SADR.U = (uint32)g_txData;
channel->CHCFGR.B.TREL = len;
IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
IfxDma_Dma_startChannelTransaction(&g_txchn);
//g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
}
void send_1s_pack(void) //对上面 send_data封装而已
{
send_data((char *)g_txData,g_count);
waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}
编写接收并发送的回环(16字节接收缓存数组,就发送16字节):
void recive_data(void) //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)
{
Ifx_DMA_CH *channel = g_txchn.channel;
IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
channel->SADR.U = (uint32)&g_ascRxBuffer;
channel->CHCFGR.B.TREL = 16;
IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
IfxDma_Dma_startChannelTransaction(&g_txchn);
//g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}
大致流程:
1.初始化ASCLIN的UART模块:
/* Initialize an instance of IfxAsclin_Asc_Config with default values */
IfxAsclin_Asc_Config ascConfig;
IfxAsclin_Asc_initModuleConfig(&ascConfig, &MODULE_ASCLIN2);
/* Set the desired baud rate */
ascConfig.baudrate.baudrate = UART_BAUDRATE;
/* ISR priorities and interrupt target */
ascConfig.interrupt.txPriority = INTPRIO_ASCLIN2_TX;
ascConfig.interrupt.rxPriority = INTPRIO_ASCLIN2_RX;
ascConfig.interrupt.typeOfService = IfxSrc_Tos_cpu0 ;
/* FIFO configuration */
ascConfig.txBuffer = &g_ascTxBuffer;
ascConfig.txBufferSize = UART_TX_BUFFER_SIZE;
ascConfig.rxBuffer = &g_ascRxBuffer;
ascConfig.rxBufferSize = UART_RX_BUFFER_SIZE;
/* Pin configuration */
const IfxAsclin_Asc_Pins pins =
{
NULL_PTR, IfxPort_InputMode_pullUp, /* CTS pin not used */
&UART_PIN_RX, IfxPort_InputMode_pullUp, /* RX pin */
NULL_PTR, IfxPort_OutputMode_pushPull, /* RTS pin not used */
&UART_PIN_TX, IfxPort_OutputMode_pushPull, /* TX pin */
IfxPort_PadDriver_cmosAutomotiveSpeed1
};
ascConfig.pins = &pins;
IfxAsclin_Asc_initModule(&g_ascHandle, &ascConfig); /* Initialize module with above parameters */
2.,再初始化完成串口后,需将CPU0服务串口中断,改成DMA服务串口中断(即将中断路由到DMA上):
/* Modification of the TOS for the Rx related interruption */
/* Change from CPU0 (previously defined above) to DMA */
volatile Ifx_SRC_SRCR *src;
src = IfxAsclin_getSrcPointerTx(ascConfig.asclin);
/* Assign DMA as Service Provider when INTPRIO_ASCLIN2_TX is triggered */
IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_TX);
IfxAsclin_enableTxFifoFillLevelFlag(ascConfig.asclin, TRUE);
IfxSrc_enable(src);
/* Modification of the TOS for the Rx related interruption */
/* Change from CPU0 (previously defined above) to DMA */
src = IfxAsclin_getSrcPointerRx(ascConfig.asclin);
/* Assign DMA as Service Provider when INTPRIO_ASCLIN2_RX is triggered */
IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_RX);
IfxAsclin_enableRxFifoFillLevelFlag(ascConfig.asclin, TRUE);
IfxSrc_enable(src);
3.初始化DMA模块(先初始化参数,直接用默认参数初始化DMA模块):
/* Initialize an instance of IfxDma_Dma_Config with default values */
IfxDma_Dma_Config dmaConfig;
IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);
/* Initialize module */
IfxDma_Dma dma;
IfxDma_Dma_initModule(&dma, &dmaConfig);
4.配置DMA通道,先初始化配置参数,以及配置有点公共参数(当然公共参数,也可以分别给TX/RX通道各自,自己再配置一遍哈),这里配置DMA一次搬运8位也就是一个字节,只使用一个DMA进行搬运
/* Initial configuration for all channels */
IfxDma_Dma_ChannelConfig cfg;
IfxDma_Dma_initChannelConfig(&cfg, &dma);
/* Following configuration is used by the DMA channel */
cfg.moveSize = IfxDma_ChannelMoveSize_8bit;
cfg.blockMode = IfxDma_ChannelMove_1;
接下里配置TX/RX各自通道,这里配置参数,还有一样的配置,也被单独拎出来了,只是为了方便理解,甚至可以定义俩变量配置,当然也完全可以不重复配置,
5.配置TX的DMA通道部分:
-
每配置一步细节
-
(1)配置一次触发DMA搬运多少个数据,以及配置每次 DMA 请求只触发一次数据传输。每次接收到一个请求信号时,DMA 控制器只会执行单次的数据传输。一般配合DMA单次搬运模式使用(这个就是串口发送中断,触发一个字节发送,DMA搬运一个)。
(2)使能DMA硬件触发(这里就是串口的发送中断触发DMA)
(3)DMA单次搬运模式(就是触发一次DMA,搬运一次)
(4)开启源地址自增模式,设置源地址自增+1,设置源地址16字节循环(这三个是配套使用,这里可以理解在DMA在每搬运一个数据后,DMA源地址指针,自动往后+1,然后达到16字节后,又回到第一个字节位置)这里注意, 一旦开启了循环模式,定义的数组一定要对齐到相应的循环范围,比如16字节循环,那定义的数组要16字节对齐,比如代码里的:(_Alignas(16) uint8 g_txData[] = “Hello World!”; //只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!)
(5)开启目的地地址自增模式,设置目的地址自增范围为0字节循环(这里虽然开启了自增+循环模式,但是也是在原地不动,因为范围是0,圈住了,好似不开启自增模式也就是FALSE模式, 其实不然,即使不开启自增模式,也就是FALSE模式,那么地址依然还是会自增的,但是每一次启动DMA搬运,DMA指针得重新指向头,要不然就是自增后的地址往后了,这样会造成指针越界问题,后面会验证,说白了这个循环模式,就是用来防止指针越界的,可以这么理解)
(6)设置DMA发送的通道号,设置源地址,以及目的地地址
(7)开启DMA中断==(如果开启,一定要编写中断服务函数,否则程序崩溃)==
(8)最后初始化通道配置。
/*********************************************** TX部分**************************************************************/
cfg.transferCount = 0;//一次触发搬运多少个字节数据(后面写入的时候会修改)
/* DMA completes a full transaction on requests */
cfg.requestMode = IfxDma_ChannelRequestMode_oneTransferPerRequest; //单一传输
/* DMA as Interrupt Service Provider */
cfg.hardwareRequestEnabled = TRUE; // 使能UART发送中断触发Dma(硬件触发)
/* DMA channel stays enabled after one request */
cfg.operationMode = IfxDma_ChannelOperationMode_single; //DMA单次搬运
/*************** Source and destination addresses ***************/
cfg.sourceCircularBufferEnabled = TRUE; //开启源地址自增
cfg.sourceAddressIncrementStep = IfxDma_ChannelIncrementStep_1; //源地址自增加+1
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_16; //源地址16字节循环
cfg.destinationCircularBufferEnabled = TRUE; //开启目的地址自增
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_none; //目的地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE
/*************** Channel specific configurations ***************/
/* Select the Channel 11, related to the interruption on AscLin TX */
cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_TX;
/* Address of the UART TX FIFO */
cfg.sourceAddress = (uint32)&g_ascTxBuffer;
cfg.destinationAddress = (uint32) &g_ascHandle.asclin->TXDATA.U;
/* DMA中断部分(如果采用环形缓存区,就没必要开启了哈) */
cfg.channelInterruptEnabled = TRUE;
/* DMA triggers an interrupt once the full transaction is done */
cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
/* Priority of the channel interrupt trigger */
cfg.channelInterruptPriority = INTPRIO_DMA_TX;
/* Interrupt service provider */
cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;
IfxDma_Dma_initChannel(&g_txchn, &cfg);
6.配置RX的DMA通道部分:
-
每配置一步细节)
-
(1)配置一次触发DMA搬运多少个数据,以及配置DMA 请求触发一整笔事务的传输,理解就是接收到一个请求信号后,DMA 控制器会自动完成整个事务(例如,传输一个完整的数据块或一组数据,接收串口数据一般都是大量涌入的,不可能是一次发1个或者几个字节把,像那种mavlink数据包,都是很多的,因此这里配置了一次触发,一直搬运), 这里有个思考,明明串口的接收,就是一个字节触发一个中断,为什么DMA和它可以不保持一致,个人理解,没错串口触发一直在触发,DMA只要有数据也是一直在搬运的(好比这样一种场景:串口中断告诉DMA来了一个数据,然后DMA就一直在干活了,串口接收中断呢,一直在那边喊话,又来了一个数据,又来了一个数据)
(2)使能DMA硬件触发(这里就是串口的接收中断触发DMA)
(3)DMA连续搬运模式(就是触发一次DMA,一直搬运)
(4)开启源地址自增,设置地址循环范围为0字节(这里虽然开启了自增+循环模式,但是也是在原地不动,因为范围是0,圈住了,好似不开启自增模式也就是FALSE模式, 其实不然,即使不开启自增模式,也就是FALSE模式,那么地址依然还是会自增的,但是每一次启动DMA搬运,DMA指针得重新指向头,要不然就是自增后的地址往后了,这样会造成指针越界问题,后面会验证)
(5)开启目的地地址自增模式,设置目的地地址自增+1,设置目的地地址16字节循环(这三个是配套使用,这里可以理解在DMA在每搬运一个数据后,DMA目的地地址指针,自动往后+1,然后达到16字节后,又回到第一个字节位置)这里注意,==一旦开启了循环模式,定义的数组一定要对齐到相应的循环范围,比如16字节循环,那定义的数组要16字节对齐,使用关键字!!!!!! Alignas(16) C99版本还支持__attribute_ ((aligned (16)))) 目前英飞凌官方ide只支持_Alignas(16) ==
(6)设置DMA接收的通道号,设置源地址,以及目的地地址
(7)开启DMA中断==(如果开启,一定要编写中断服务函数,否则程序崩溃)==
(8)最后初始化通道配置。
/********************************************************* RX部分***********************************************************/
cfg.transferCount = 1; //一次触发搬运多少个字节数据
/* DMA completes a full transaction on requests */
cfg.requestMode = IfxDma_ChannelRequestMode_oneTransferPerRequest; //一个请求触发一个完整事务 即请求启动完整的事务
/* DMA as Interrupt Service Provider */
cfg.hardwareRequestEnabled = TRUE; // UART接收中断触发Dma
/* DMA channel stays enabled after one request */
cfg.operationMode = IfxDma_ChannelOperationMode_continuous; // DMA连续搬运模式
/*************** Source and destination addresses ***************/
cfg.sourceCircularBufferEnabled = TRUE; //源地址自增
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_none;源地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE
cfg.destinationCircularBufferEnabled = TRUE;
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_16;
/*************** Channel specific configurations ***************/
/* Select the Channel 12, related to the interruption on AscLin RX */
cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_RX;
/* Address of the UART RX FIFO */
cfg.sourceAddress = (uint32) &g_ascHandle.asclin->RXDATA.U;
cfg.destinationAddress = (uint32)&g_ascRxBuffer;
/* DMA中断部分(如果采用环形缓存区,就没必要开启了哈) */
cfg.channelInterruptEnabled = TRUE;
/* DMA triggers an interrupt once the full transaction is done */
cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
/* Priority of the channel interrupt trigger */
cfg.channelInterruptPriority = INTPRIO_DMA_RX;
/* Interrupt service provider */
cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;
IfxDma_Dma_initChannel(&g_rxchn, &cfg);
然后就是编写了一个发送函数,以及接收并发送的函数,这里发送使用g_txData数组,当然也是可以使用g_ascTxBuffer数组的哈,这里只是防止和上一章混淆,不使用同一个数组而已。
注意事项:
1.只要DMA通道开启了循环模式,一定要相应的字节对齐:
比如代码DMA发送通道的配置和发送缓存区字节对齐
cfg.sourceCircularBufferEnabled = TRUE; //开启源地址自增
cfg.sourceAddressIncrementStep = IfxDma_ChannelIncrementStep_1; //源地址自增加+1
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_16; //源地址16字节循环
cfg.destinationCircularBufferEnabled = TRUE; //开启目的地址自增
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_none; //目的地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE
_Alignas(16) uint8 g_txData[] = "Hello World!";//只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!
同理DMA接收通道的配置和定义接收缓存区对齐:
cfg.sourceCircularBufferEnabled = TRUE; //源地址自增
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_none;源地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE
cfg.destinationCircularBufferEnabled = TRUE;
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_16;
_Alignas(16) static uint8 g_ascRxBuffer[UART_RX_BUFFER_SIZE];
2.如果启动了DMA中断,一定要编写相应的中断服务函数(可以为空,但不能不写);
3.设置DMA源地址,目标地址配置时,一定要检查好,否则会出现问题。
4.配置串口时,默认参数不可以,一定要改,比如过采因子,采样点等。
mydma.h文件:
#ifndef __MYDMA_H_
#define __MYDMA_H_
void init_asclin_uart(void); /* Initialization function */
void init_dma(void);
void send_receive_ASCLIN_UART_message(void); /* Send and receive function */
void read2send(void);
void send_data(char* g_txData,Ifx_UReg_32Bit len); //发送数据--指定长度大小
void send_1s_pack(void);
void recive_data(void) ; //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)
#endif
主函数调用:
调用send_1s_pack();是1s的“Hello world”的心跳包;
调用:recive_data();1s发送16字节的接收缓存区的数据;
#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "Blinky_LED.h"
#include "interrupt.h"
#include "mydma.h"
IFX_ALIGN(4) IfxCpu_syncEvent g_cpuSyncEvent = 0;
void core0_main(void)
{
IfxCpu_enableInterrupts();
/* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
* Enable the watchdogs and service them periodically if it is required
*/
IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());
/* Wait for CPU sync event */
IfxCpu_emitEvent(&g_cpuSyncEvent);
IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
initLED();
initGtmTom();
init_asclin_uart();
init_dma();
while(1)
{
send_1s_pack();
//recive_data();
}
}
测试现象1:
调用send_1s_pack();
这里发送是每一次发送,都将DMA的指针,重新指向发送数组头部,并且设置一次搬运多少个数据。(因此这里可以不使用循环,就是上面说的FALSE模式,这个模式也是自增的,因为每次都初始化了指针指向头,不会造成指针越界问题,解决了指针越界问题,因此可以取消循环,现象也是一样的),推荐还是用循环模式,后面采用环形数组就很完美。
cfg.sourceCircularBufferEnabled = FALSE; //开启源地址自增
然后验证,一次DMA传输个数,可以关闭send_data里面每次设置的每次DMA传输多少个数据,然后将默认参数分别改成2或5试试
void send_data(char* g_txData,Ifx_UReg_32Bit len) //发送数据--指定长度大小
{
Ifx_DMA_CH *channel = g_txchn.channel;
IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
channel->SADR.U = (uint32)g_txData;
//channel->CHCFGR.B.TREL = len; //这里屏蔽掉
IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
IfxDma_Dma_startChannelTransaction(&g_txchn);
//g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
}
cfg.transferCount = 2; //一次触发搬运多少个字节数据(后面写入的时候会修改)
改成2,现象:
txcnt是DMA中断次数哈
改成5,现象:
验证成功。
测试现象2:
调用:recive_data();
第一次发送123456:
第二次发送123456,可以看到依次往后了:
第三次发送123456,可以看到依次往后,DMA接收指针到了最后,循环回来指到头,然后覆盖了12,变成了56:
第四次发送123456,继续往后覆盖:
至此能感受到DMA循环模式的魅力,后面会使用环形换缓存区对接收发;
测试现象3:
两个同时调用,也是没问题的哈,已实测。
简单补充几个其它配置的测试
修改发送的测试
调用的是send_1s_pack();以发送为例
1.修改DMA发送通道的源地址循环范围4字节,则必须修改发送缓存数组4字节对齐:
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_4;
_Alignas(4) uint8 g_txData[] = "Hello World!";//
可以看到重复发送了发送数组前四个字节,就是源地址指针只在前四个字节循环了,这就是循环模式。
2.还可以再修改发送DMA通道的循环范围0字节,则修改数组不对齐即可,当然对齐也没关系:
cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_none;
可以看到,发送的12个字节全是H,那是因为DMA发送指针,一直原地不动,发送了12个字节。
修改接收的测试
调用的是recive_data();以接收为例
修改DMA接收通道的目的地地址循环范围4字节,则必须修改接收缓存数组4字节对齐
cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_4;
_Alignas(4) static uint8 g_ascRxBuffer[UART_RX_BUFFER_SIZE];
用debug看数组数值,可以看到我发送12345,然后接收数组g_ascRxBuffer里面 只有5234,那是因为目的地地址设置了4字节范围循环,其实12345都接收到了,只是5把1给覆盖了,只是接收了1234指针又回到了头部:
此时指针在第二个位置,然后再发一次12345,123把234覆盖了 ,然后指针又回到了头部,45又把51给覆盖,所以发送回来的是4523