100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > DMA实现数据传输流程

DMA实现数据传输流程

时间:2023-12-22 14:13:10

相关推荐

DMA实现数据传输流程

1.相关概念

(1)DMA直接寄存器访问

可实现外设数据寄存器到存储器、存储器到外设数据寄存器、存储器到存储器之间的高效数据传输,无需CPU操作控制。

(2)外设与存储器

外设包括ADC、SPI、I2C、USART等等

存储器包括片内SRAM、外部存储器、片内Flash等等

(3)具体应用场合举例:

ADC采集可以利用DMA将AD转换数据转移到目标存储区,适用于多通道采集、采样频率高、连续传输的ADC采集场合;

将特定存储区的数据转移到外设数据寄存器,用于外设的对外数据传输,如存储器传输数据到串口数据寄存器,串口发送数据到PC端;

存储器到存储器的数据传输利用DMA可以达到更高的传输效率,不占用CPU,从而节约CPU资源。

2.DMA功能框图

(1)①外设通道

STM32F4系列有两个DMA控制器,每个控制器有8个数据流,每个数据流又对应8个数据通道(8个通道对应的是8个外设请求)。

DMA控制器通过DMA数据流x配置寄存器DMA_SxCR(x为0~7,对应8个DMA数据流)的CHSEL[2:0]位选择对应的通道即选择对应的外设。

DMA请求映射表如下:

每个外设请求都占用一个数据流通道,相同外设请求可以占用不同数据流通道。比如SPI3_RX请求,占用DMA1的数据流0的通道0,因此使用该请求时,需要在把DMA_S0CR寄存器的CHSEL[2:0]设置为“000”, 此时相同数据流的其他通道不被选择,处于不可用状态,比如此时不能使用数据流0的通道1即I2C1_RX请求。

(2)②仲裁器

由仲裁器判断哪个数据流优先传输

DMA_SxCR寄存器PL[1:0]位,可以设置为非常高、高、中和低四个级别;

当两个及以上数据流软件设置优先级相同时,比较数据流编号,编号小的优先级高。

(3)③FIFO

每个数据流都独立拥有四级32位FIFO(先入先出存储器缓存区,队列)。DMA传输分为FIFO模式和直接模式。

直接模式:收到外设请求会立即启动传输。

FIFO模式:在数据传输到目标地址前临时存放这些数据,通过DMA数据流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位来控制FIFO的阈值,分别为1/4、1/2、3/4和满。数据缓冲量达到阈值级别,将FIFO中的数据传输到目标地址中。

FIFO主要用于源地址和目标地址要求数据宽度不同的场合,比如源数据是源源不断的字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来4个8位字节的数据拼凑成一个32位字数据。此时使用FIFO功能先把数据缓存起来,根据需要输出数据。

FIFO还用于突发(burst)传输。

(4)④存储器端口、⑤外设端口

DMA2(DMA控制器2)的存储器端口和外设端口都是连接到AHB总线矩阵,可以使用AHB总线矩阵功能。DMA2存储器和外设端口可以访问相关的内存地址,包括有内部Flash、内部SRAM、AHB1外设、AHB2外设、APB1、APB2外设和外部存储器空间。

DMA1的存储区端口相比DMA2的要减少AHB2外设的访问权,同时DMA1外设端口是没有连接至总线矩阵的,只有连接到APB1外设,所以DMA1不能实现存储器到存储器传输。

3.DMA传输模式

DMA2支持全部三种传输模式;DMA1只支持外设到存储器、存储器到外设两种模式。

(1)模式选择:DMA_SxCR寄存器的DIR[1:0]位,“00”外设到存储器;“10”存储器到存储器;“01”存储器到外设。

(2)传输使能:DMA_SxCR寄存器的EN位置1

(3)数据宽度:DMA_SxCR寄存器的PSIZE[1:0]和MSIZE[1:0]位分别指定外设和存储器数据宽度大小,可以指定为字节(8位)、半字(16位)和字(32位)。直接模式要求外设和寄存器数据宽度一致,该模式下DMA数据流只使用PSIZE,MSIZE被忽略。

(4)地址设置

①外设地址:DMA_SxPAR寄存器(x为0~7)

②存储器地址:DMA_SxM0AR、DMA_SxM1AR,其中DMA_SxM1AR只用于双缓冲模式。

(5)循环模式与一次模式:一次模式时传输一次就停止传输,下一次传输需要手动控制;循环模式是传输一次后自动按照相同配置重新传输,直至被控制停止或传输错误。循环模式使能是DMA_SxCR寄存器的CIRC位。

(6)单次传输与突发传输:突发传输就是在传输阶段把速度瞬间提高,实现高速传输,在数据传输完成后恢复正常速度,有点类似达到数据块“秒传”效果。为达到这个效果突发传输过程要占用AHB总线,保证要求每个数据项在传输过程不被分割,这样一次性把数据全部传输完才释放AHB总线;而单次传输时必须通过AHB的总线仲裁多次控制才传输完成。

其中PBURST[1:0]和MBURST[1:0]位是位于DMA_SxCR寄存器中的, 用于分别设置外设和存储器不同节拍数的突发传输,对应为单次传输、4个节拍增量传输、8个节拍增量传输和16个节拍增量传输。

PINC位和MINC位是寄存器DMA_SxCR寄存器的第9和第10位,如果位被置1则在每次数据传输后数据地址指针自动递增, 其增量由PSIZE和MSIZE值决定,比如,设置PSIZE为半字大小,那么下一次传输地址将是前一次地址递增2。

突发传输与FIFO密切相关,突发传输需要结合FIFO使用,具体要求FIFO阈值一定要是内存突发传输数据量的整数倍。 FIFO阈值选择和存储器突发大小必须配合使用。

(7)直接模式与双缓冲模式

默认情况下,DMA工作在直接模式,不使能FIFO阈值级别。

①直接模式:每个外设请求都立即启动对存储器传输的单次传输。直接模式要求源地址和目标地址的数据宽度必须一致,所以只有PSIZE控制,而MSIZE值被忽略。突发传输是基于FIFO的所以直接模式不被支持。另外直接模式不能用于存储器到存储器传输。

②双缓冲模式:DMA_SxCR寄存器的DBM位置1可启动双缓冲传输模式,并自动激活循环模式。双缓冲不应用于存储器到存储器的传输。双缓冲模式下,两个存储器地址指针都有效,即DMA_SxM1AR寄存器将被激活使用。开始传输使用DMA_SxM0AR寄存器的地址指针所对应的存储区,当这个存储区数据传输完DMA控制器会自动切换至DMA_SxM1AR寄存器的地址指针所对应的另一块存储区,如果这一块也传输完成就再切换至DMA_SxM0AR寄存器的地址指针所对应的存储区,这样循环调用。

当其中一个存储区传输完成时都会把传输完成中断标志TCIF位置1,如果我们使能了DMA_SxCR寄存器的传输完成中断,则可以产生中断信号。

DMA_SxCR寄存器的CT位,当DMA控制器是在访问使用DMA_SxM0AR时CT=0,此时CPU不能访问DMA_SxM0AR,但可以向DMA_SxM1AR填充或者读取数据;当DMA控制器是在访问使用DMA_SxM1AR时CT=1,此时CPU不能访问DMA_SxM1AR,但可以向DMA_SxM0AR填充或者读取数据。另外在未使能DMA数据流传输时,可以直接写CT位,改变开始传输的目标存储区。

(8)流控制器

在传输之前设置DMA_SxNDTR寄存器为要传输数目值,DMA控制器在传输完这么多数目数据后就可以控制DMA停止传输。

DMA数据流x数据项数DMA_SxNDTR(x为0~7)寄存器用来记录当前仍需要传输数目,是一个16位数据有效寄存器,最大值为65535。编程时一般都会明确指定一个传输数量, 在完成一次数据传输后DMA_SxNDTR计数值就会自减,当达到零时就说明传输完成。

如果某些情况下在传输之前我们无法确定数据的数目,那DMA就无法自动控制传输停止了, 此时需要外设通过硬件通信向DMA控制器发送停止传输信号。只有SDIO可以发出停止传输信号,其他外设不具备此功能。

4.DMA中断

(1)达到半传输:DMA数据传输达到一半时HTIF标志位被置1,如果使能HTIE中断控制位将产生达到半传输中断;

(2)传输完成:DMA数据传输完成时TCIF标志位被置1,如果使能TCIE中断控制位将产生传输完成中断;

(3)传输错误:DMA访问总线发生错误或者在双缓冲模式下试图访问“受限”存储器地址寄存器时TEIF标志位被置1,如果使能TEIE中断控制位将产生传输错误中断;

(4)FIFO错误:发生FIFO下溢或者上溢时FEIF标志位被置1,如果使能FEIE中断控制位将产生FIFO错误中断;

(5)直接模式错误:在外设到存储器的直接模式下,因为存储器总线没得到授权,使得先前数据没有完成被传输到存储器空间上,此时DMEIF标志位被置1,如果使能DMEIE中断控制位将产生直接模式错误中断。

5.DMA初始化结构体

(1)DMA_InitTypeDef初始化结构体

typedef struct {uint32_t Channel; //通道选择uint32_t Direction; //传输方向uint32_t PeriphInc; //外设递增uint32_t MemInc;//存储器递增uint32_t PeriphDataAlignment; //外设数据宽度uint32_t MemDataAlignment;//存储器数据宽度uint32_t Mode; //模式选择uint32_t Priority; //优先级uint32_t FIFOMode; //FIFO模式uint32_t FIFOThreshold; //FIFO阈值uint32_t MemBurst; //存储器突发传输uint32_t PeriphBurst;//外设突发传输} DMA_InitTypeDef;

①PeriphInc:如果配置为PeriphInc_Enable,即使能外设地址自动递增(设定DMA_SxCR寄存器的PINC位的值)。一般外设只有一个数据寄存器,因此一般不会使能该位。ADC3的数据寄存器地址是固定并且只有一个所以不使能外设地址递增

②MemInc:如果配置为MemInc_Enable,使能存储器地址自动递增功能(设定DMA_SxCR寄存器的MINC位的值);我们自定义的存储区一般都是存放多个数据的,所以使能存储器地址自动递增功能。之前定义了一个包含4个元素的数组用来存放数据,使能存储区地址递增功能,自动把每个通道数据存放到对应数组元素内。

③PeriphDataAlignment:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_SxCR寄存器的PSIZE[1:0]位的值。ADC数据寄存器只有低16位数据有效,使用半字数据宽度。

④MemDataAlignment:存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_SxCR寄存器的MSIZE[1:0]位的值。保存ADC转换数据也要使用半字数据宽度,这跟我们定义的数组是相对应的。

⑤Mode:DMA传输模式选择,可选一次传输或者循环传输,它设定DMA_SxCR寄存器的CIRC位的值。我们希望ADC采集是持续循环进行的,所以使用循环传输模式。

⑥FIFOMode:FIFO模式使能,如果设置为DMA_FIFOMode_Enable表示使能FIFO模式功能(设定DMA_SxFCR寄存器的DMDIS位)。ADC采集传输使用直接传输模式即可,不需要使用FIFO模式。

⑦MemBurst:存储器突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式,它设定DMA_SxCR寄存器的MBURST[1:0]位的值。ADC采集传输是直接模式,要求使用单次模式。

⑧PeriphBurst:外设突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式,它设定DMA_SxCR寄存器的PBURST[1:0]位的值。ADC采集传输是直接模式,要求使用单次模式。

(2)DMA_HandleTypeDef初始化结构体

typedef struct __DMA_HandleTypeDef {DMA_Stream_TypeDef *Instance; //注册基地址DMA_InitTypeDef Init; //DMA通信参数HAL_LockTypeDef Lock; //DMA锁定对象__IO HAL_DMA_StateTypeDef State; //DMA传输状态void *Parent;//父类指针void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);//DMA传输完成回调函数void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);//DMA传输完成一半回调函数void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);//Memory1 DMA传输完成回调函数void (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);//Memory1 DMA传输完成一半回调函数void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);//DMA传输错误回调函数void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);//DMA传输中止回调函数__IO uint32_tErrorCode; //DMA错误码uint32_tStreamBaseAddress;//DMA数据流基地址uint32_tStreamIndex;//DMA数据流索引} DMA_HandleTypeDef;

①Instance: 指向DMA数据流基地址的指针,即指定使用哪个DMA数据流。可选数据流0至数据流7。 例如,我们使用模拟数字转换器ADC3规则采集4个输入通道的电压数据, 查表可知可以使用数据流0或者数据流1,这里支持两个数据流是为了避免多个通道使用时发生冲突,提供备选数据流可选。

②Init:这里包含上面介绍DMA_InitTypeDef结构体的所有参数的初始化。

③Lock:DMA锁定对象。DMA进程锁,通常都在DMA传输设置开始前锁上进程锁,设置完毕后释放进程锁。

④State:DMA传输状态。它包含六种状态,

复位状态,尚未初始化或者失能;

就绪状态,已经完成初始化,随时可以传输数据;

传输忙,DMA传输进程正在进行;

传输超时状态;

传输错误状态;

传输中止状态。

⑤*Parent:父类指针。只要将该指针指向一些ADC、UART等外设的handle类,就等于完成了继承。

⑥传输过程中的回调函数:包括传输完成,传输完成一半,传输错误,传输中止回调函数。这些回调函数中可以加入用户的处理代码。

⑦ErrorCode:DMA错误码,包含

无错误:HAL_DMA_ERROR_NONE,

传输错误:HAL_DMA_ERROR_TE,

FIFO错误:HAL_DMA_ERROR_FE,

直接模式错误:HAL_DMA_ERROR_DME,

超时错误:HAL_DMA_ERROR_TIMEOUT,

参数错误:HAL_DMA_ERROR_PARAM,

没有回调函数正在执行退出请求错误:HAL_DMA_ERROR_NO_XFER,

不支持模式错误:HAL_DMA_ERROR_NOT_SUPPORTED。

⑧StreamBaseAddress:DMA数据流基地址,用来根据定义句柄计算数据流的基地址。

⑨StreamIndex:DMA数据流索引,根据数据流的序号来确定数据流的偏移地址。

6.DMA_MemToMem 存储器到存储器传输

代码实现步骤:

①使能DMA数据流时钟并复位初始化DMA数据流;

②配置DMA数据参数;

③使能DMA数据流,进行传输;

④等待传输完成,并对源数据和目标地址数据进行比较;

存储器到存储器传输必须使用DMA2,但对数据流编号以及通道选择就没有硬性要求,可以自由选择。只能使用一次传输模式不能循环传输。

typedef enum {FAILED = 0,PASSED = !FAILED} TestStatus;DMA_HandleTypeDef hdma_memtomem_dma2_stream0;static const uint32_t SRC_Const_Buffer[32]= {0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};uint32_t DST_Buffer[32];__IO TestStatus TransferStatus= FAILED;int main(void){HAL_StatusTypeDef har_status;/* 复位所有外设,初始化Flash接口和系统滴答定时器 */HAL_Init();/* 配置系统时钟 */SystemClock_Config();/* 板载LED初始化 */LED_GPIO_Init();/* DMA初始化 */ MX_DMA_Init();//开始DMA传输har_status=HAL_DMA_Start(&hdma_memtomem_dma2_stream0,(uint32_t)&SRC_Const_Buffer,(uint32_t)&DST_Buffer,32);if(har_status==HAL_OK){/* 检查发送和接收的数据是否相等 */TransferStatus = Buffercmp(SRC_Const_Buffer, DST_Buffer, 32);/* 如果接收和发送的数据都是相同的,则通过 */if(TransferStatus == 1){LED1_ON;}/* 如果接收和发送的数据不同,则传输出错 */else{LED2_ON;}}else{LED3_ON;} /* 无限循环 */while (1){}}

/*** 函数功能: 判断指定长度的两个数据源是否完全相等* 输入参数: 无* 返 回 值: 无* 说 明: 如果完全相等返回1,只要其中一对数据不相等返回0*/uint8_t Buffercmp(const uint32_t* pBuffer,uint32_t* pBuffer1, uint16_t BufferLength){/* 数据长度递减 */while (BufferLength--) {/* 判断两个数据源是否对应相等 */if (*pBuffer != *pBuffer1) {/* 对应数据源不相等马上退出函数,并返回0 */return 0;}/* 递增两个数据源的地址指针 */pBuffer++;pBuffer1++;}/* 完成判断并且对应数据相对 */return 1;}

/*** 函数功能: DMA配置* 输入参数: 无* 返 回 值: 无* 说 明: 无*/void MX_DMA_Init(void) {/* 使能DMA控制器时钟 */__HAL_RCC_DMA2_CLK_ENABLE();/* 配置DMA通道工作方式 */hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;//数据流0hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;//通道0hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;//数据传输方向:存储器->存储器hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;//外设地址不变 hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;//内存地址自增hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;//传输数据半字16位 hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;//接收数据半字16位hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;//发一次开启一次hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;//优先级(设置为最高优先级)hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_DISABLE;hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;HAL_DMA_Init(&hdma_memtomem_dma2_stream0);}

7.DMA串口收发数据

//main.c #define SEND_BUF_SIZE (8800)//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区const u8 TEXT_TO_SEND[] = {"ALIENTEK Pandora STM32L4 IOT DMA 串口实验"};//显示出来是42 汉字1Byte,空格2Byte,字母数字1Byte 再加上\n\r 共44个Bytes//使用了const关键字修饰,即常量类型,使得变量存储在内部flash空间上。int main(void){u8 t = 0, j, mask;u16 i;float pro = 0; //进度HAL_Init();SystemClock_Config();//初始化系统时钟为80Mdelay_init(80); //初始化延时函数 80M系统时钟uart_init(115200);//初始化串口,波特率为115200LED_Init();//初始化LEDKEY_Init();//初始化按键LCD_Init();//初始化LCDMYDMA_Config(DMA1_Channel4, DMA_REQUEST_2); //初始化DMAj = sizeof(TEXT_TO_SEND);for(i = 0; i < SEND_BUF_SIZE; i++) //填充ASCII字符集数据 一共8800Bytes{if(t >= j) //加入换行符{if(mask){SendBuff[i] = 0x0a;t = 0;}else{SendBuff[i] = 0x0d;mask++;}}else //复制TEXT_TO_SEND语句{mask = 0;SendBuff[i] = TEXT_TO_SEND[t];t++;}}while(1){t = KEY_Scan(0);if(t == KEY0_PRES) //KEY0按下{HAL_UART_Transmit_DMA(&UART1_Handler, (u8*)SendBuff, SEND_BUF_SIZE); //启动传输//使能串口1的DMA发送 //等待DMA传输完成,此时我们来做另外一些事,点灯//实际应用中,传输数据期间,可以执行另外的任务while(1){//DMA_HandleTypeDef UART1TxDMA_Handler;//DMA句柄if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler, DMA_FLAG_TC4)) //等待DMA2_Steam7传输完成{__HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler, DMA_FLAG_TC4); //清除DMA2_Steam7传输完成标志HAL_UART_DMAStop(&UART1_Handler);//传输完成以后关闭串口DMAbreak;}pro = __HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler); //返回当前DMA通道传输中剩余数据单元的数量pro = 1 - pro / SEND_BUF_SIZE; //得到百分比pro *= 100; //扩大100倍}}delay_ms(10);}}

//dma.c/*** @briefDMAx的各通道配置,这里的传输形式是固定的,这点要根据不同的情况来修改*从存储器->外设模式/8位数据宽度/存储器增量模式** @param DMA_StreamxDMA通道选择,DMA1_Channel0~7/DMA2_Channel0~7* @param chxDMA请求选择,@ref DMA_REQUEST_0~DMA_REQUEST_7** @return void*/void MYDMA_Config(DMA_Channel_TypeDef *DMA_Channel, u32 seq){if((u32)DMA_Channel > (u32)DMA2) //得到当前stream是属于DMA2还是DMA1{__HAL_RCC_DMA2_CLK_ENABLE();//DMA2时钟使能}else{__HAL_RCC_DMA1_CLK_ENABLE();//DMA1时钟使能}__HAL_LINKDMA(&UART1_Handler, hdmatx, UART1TxDMA_Handler); //将DMA与USART1联系起来(发送DMA)//Tx DMA配置UART1TxDMA_Handler.Instance = DMA_Channel;//通道选择UART1TxDMA_Handler.Init.Request = seq;//请求选择UART1TxDMA_Handler.Init.Direction = DMA_MEMORY_TO_PERIPH; //存储器到外设UART1TxDMA_Handler.Init.PeriphInc = DMA_PINC_DISABLE;//外设非增量模式UART1TxDMA_Handler.Init.MemInc = DMA_MINC_ENABLE; //存储器增量模式UART1TxDMA_Handler.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设数据长度:8位UART1TxDMA_Handler.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;//存储器数据长度:8位UART1TxDMA_Handler.Init.Mode = DMA_NORMAL;//外设普通模式UART1TxDMA_Handler.Init.Priority = DMA_PRIORITY_HIGH; //中等优先级HAL_DMA_DeInit(&UART1TxDMA_Handler);HAL_DMA_Init(&UART1TxDMA_Handler);}

未完待续。。。。。。。。。。。。。。。。。。。。。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。