在上一讲,我们讲过CKS32F4xx系列的6个串口都支持DMA传输。因此本节我们对CKS32F4xx系列的DMA进行介绍,同时利用DMA对串口数据进行传输。
DMA介绍
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
CKS32F4xx系列最多有2个DMA控制器(DMA1和DMA2),共16个数据流(每个控制器8个),每个数据流总共可以有多达8个通道(或称请求)。CKS32F4xx系列的DMA支持外设到存储器传输、存储器到外设传输和存储器到存储器传输三种传输模式。,存储器一般是指片内SRAM、外部存储器、片内Flash等等。
外设到存储器:把外设数据寄存器内容转移到指定的内存空间。比如进行ADC采集时我们可以利用DMA传输把AD转换的数据转移到我们定义的存储区中。
存储器到外设:把特定存储区内容转移至外设的数据寄存器中,这种多用于外设的发送通信,比如SPI、I2C和串口等。
存储器到存储器:就是把一个指定的存储区内容拷贝到另一个存储区空间。功能类似于C语言内存拷贝函数memcpy,不过利用DMA传输可以达到更高的传输效率。
注意:只有DMA2控制器支持存储器到存储器的传输,DMA1不支持。
DMA功能框图
标号1处是外设通道,每一个数据流(标号2)对应着8个外设通道,外设通道选择要解决的主要问题是决定哪一个外设作为该数据流的源地址或者目标地址。DMA1各个通道的请求映像如下表所示:
DMA2各个通道的请求映像如下表所示:
比如我们利用DMA将存储器数据传输到串口3,然后发送出去,那么根据上表可知,我们可以选择DMA1的数据流3通道4,或者DMA1的数据流4通道7。而将串口3的数据传输到存储器则可以选择DMA1的数据流1通道4。
标号2处是数据流和仲裁器,一个DMA控制器对应8个数据流,数据流包含要传输数据的源地址、目标地址、数据等等信息。如果我们需要同时使用同一个DMA控制器 (DMA1 或DMA2) 多个外设请求时,那必然需要同时使用多个数据流,这时就需要仲裁器对每一个数据流的优先级进行划分。数据流的优先级可以通过配置DMA_SxCR寄存器 PL[1:0]位,可以设置为非常高、高、中和 低四个级别。如果两个或以上数据流软件设置优先级一样,则他们优先级取决于数据流编号,编号越低越具有优先权,比如数据流2优先级高于数据流 3。
标号3处是FIFO,用于在源数据传输到目标地址之前临时存放数据用的。可以通过DMA数据流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位来控制FIFO的阈值,分别为1/4、1/2、3/4和满。如果数据存储量达到阈值级别时,FIFO内容将传输到目标中。
标号4和5处是存储器端口、外设端口。DMA控制器通过存储器端口和外设端口与存储器和外设进行数据传输。DMA2(DMA控制器2)的存储器端口和外设端口都是连接到AHB总线矩阵,可以使用AHB总线矩阵功能。而DMA1的存储区端口相比DMA2的要减少AHB2外设的访问权,同时DMA1外设端口是没有连接至总线矩阵的,只有连接到APB1外设,所以 DMA1不能实现存储器到存储器传输。
标号6处是编程端口,AHB从器件编程端口是连接至AHB2外设的,AHB2外设在使用 DMA传输时需要相关控制信号。
DMA软件配置
在标准库函数中对DMA进行初始化主要是对结构体DMA_InitTypeDef内的各类参数进行初始化。DMA_InitTypeDef结构体参数及介绍如下:
typedef struct { uint32_t DMA_Channel; uint32_t DMA_PeripheralBaseAddr; uint32_t DMA_Memory0BaseAddr; uint32_t DMA_DIR; uint32_t DMA_BufferSize; uint32_t DMA_PeripheralInc; uint32_t DMA_MemoryInc; uint32_t DMA_PeripheralDataSize; uint32_t DMA_MemoryDataSize; uint32_t DMA_Mode; uint32_t DMA_Priority; uint32_t DMA_FIFOMode; uint32_t DMA_FIFOThreshold; uint32_t DMA_MemoryBurst; uint32_t DMA_PeripheralBurst; }DMA_InitTypeDef;
1)DMA_Channel:DMA请求通道的选择,每个外设对应固定的通道,具体设置值需要查25.2节中的DMA1和DMA2各个通道的请求映像表。该值在标准库中可供选择的参数值如下:
#define DMA_Channel_0 ((uint32_t)0x00000000) #define DMA_Channel_1 ((uint32_t)0x02000000) #define DMA_Channel_2 ((uint32_t)0x04000000) #define DMA_Channel_3 ((uint32_t)0x06000000) #define DMA_Channel_4 ((uint32_t)0x08000000) #define DMA_Channel_5 ((uint32_t)0x0A000000) #define DMA_Channel_6 ((uint32_t)0x0C000000) #define DMA_Channel_7 ((uint32_t)0x0E000000)
2)DMA_PeripheralBaseAddr:外设地址,设定DMA_SxPAR寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。比如要进行串口DMA传输,那么外设基地址为串口接收发送数据存储器USART1->DR的地址,表示方法为&USART1->DR。
3)DMA_Memory0BaseAddr:存储器0地址,设定DMA_SxM0AR寄存器值;一般设置为我们自定义存储区的首地址。比如我们程序中自己定义的用来存放数据的数组,此时这个值设置为数组名即可。
4) DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器。该值在标准库中可供选择的参数值如下:
DMA_DIR_PeripheralToMemory //外设到存储器 DMA_DIR_MemoryToPeripheral //存储器到外设 DMA_DIR_MemoryToMemory //存储器到存储器
5)DMA_BufferSize:设定待传输数据数目,初始化设定DMA_SxNDTR寄存器的值。
6)DMA_PeripheralInc:是否使能外设地址自动递增功能,它设定DMA_SxCR寄存器的PINC位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。该值在标准库中可供选择的参数值如下:
DMA_PeripheralInc_Enable //使能 DMA_PeripheralInc_Disable //不使能
7)DMA_MemoryInc:是否使能存储器地址自动递增功能,它设定DMA_SxCR寄存器的MINC位的值;我们自定义的存储区一般都是存放多个数据的,所以一般是使能存储器地址自动递增功能。该值在标准库中可供选择的参数值如下:
DMA_MemoryInc_Enable //使能 DMA_MemoryInc_Disable //不使能
8)DMA_PeripheralDataSize:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),根据外设数据长度进行选择。该值在标准库中可供选择的参数值如下:
DMA_PeripheralDataSize_Byte //字节 DMA_PeripheralDataSize_HalfWord //半字 DMA_PeripheralDataSize_Word //字
9) DMA_MemoryDataSize:存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),和外设数据宽度相对应。该值在标准库中可供选择的参数值如下:
DMA_MemoryDataSize_Byte //字节 DMA_MemoryDataSize_HalfWord //半字 DMA_MemoryDataSize_Word //字
10) DMA_Mode:DMA传输模式选择,可选一次传输或者循环传输。该值在标准库中可供选择的参数值如下:
DMA_Mode_Normal //一次传输 DMA_Mode_Circular //循环传输
11) DMA_Priority:软件设置数据流的优先级,有4个可选优先级分别为非常高、高、中和低。DMA优先级只有在多个DMA数据流同时使用时才有意义,如果只有一个数据流的话,设置成非常高优先级就可以了。该值在标准库中可供选择的参数值如下:
DMA_Priority_Low //低 DMA_Priority_Medium //中 DMA_Priority_High //高 DMA_Priority_VeryHigh //非常高
12) DMA_FIFOMode:FIFO模式使能,如果设置为DMA_FIFOMode_Enable 表示使能FIFO模式功能;如果采用直接传输模式,则不需要使用FIFO模式。直接模式下,DMA直接进行数据从源地址到目的地址的传输。而FIFO模式下,可以将要传输的多个数据(或字节)累计存储在FIFO缓冲器中,然后在FIFO缓冲器中设置存储阈值,当到达阈值时,FIFO会自动把所有存储的数据一次性的发送到目标地址。该值在标准库中可供选择的参数值如下:
DMA_FIFOMode_Disable //不使能 DMA_FIFOMode_Enable //使能
13) DMA_FIFOThreshold:FIFO阈值选择,可选4种状态分别为FIFO容量的1/4、1/2、3/4和满;不使用FIFO模式时,该设置改值无效。该值在标准库中可供选择的参数值如下:
DMA_FIFOThreshold_1QuarterFull //1/4 DMA_FIFOThreshold_HalfFull //1/2 DMA_FIFOThreshold_3QuartersFull //3/4 DMA_FIFOThreshold_Full //满
14) DMA_MemoryBurst:存储器突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式。单次传输模式下,一次操作(软件)只能传输一次,突发传输模式下,一次操作可以传输多次,如4次,8次,16次。该值在标准库中可供选择的参数值如下:
DMA_MemoryBurst_Single //单次 DMA_MemoryBurst_INC4 //4节拍 DMA_MemoryBurst_INC8 //8节拍 DMA_MemoryBurst_INC16 //16节拍
15) DMA_PeripheralBurst:外设突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式。该值在标准库中可供选择的参数值如下:
DMA_PeripheralBurst_Single //单次 DMA_PeripheralBurst_INC4 //4节拍 DMA_PeripheralBurst_INC8 //8节拍 DMA_PeripheralBurst_INC16 //16节拍
串口DMA接发通信实验
串口的DMA接发通信实验是存储器到外设和外设到存储器的数据传输。在第24课串口通信的基础上编写而成。
1.编程要点
1)配置USART通信功能;
2)配置DMA通信功能,存储器到外设和外设到存储器两种模式的配置。
3)使能指定的DMA数据流中断;
4)使能USART3的DMA发送和接收请求;
5)开始一次DMA传输。
2.代码分析
代码中有关串口的配置的程序在第24课已经详细讲过了,这里就不再讲述。主要是对DMA相关的代码进行分析,相关程序在dma.c文件里。
代码清单1:DMA初始化配置
void MYDMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){} //存储器到外设的DMA配置 DMA_DeInit(DMA1_Stream3); DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);//DMA外设地址 DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)USART3_TX_BUF;//DMA 存储器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式 DMA_InitStructure.DMA_BufferSize = USART3_MAX_TX_LEN;//数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输 DMA_Init(DMA1_Stream3, &DMA_InitStructure);//初始化DMA Stream DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); //清除标志位 DMA_Cmd(DMA1_Stream3, DISABLE); //关闭DMA DMA_ITConfig(DMA1_Stream3,DMA_IT_TC,ENABLE); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); //外设到存储器的DMA配置 DMA_DeInit(DMA1_Stream1); while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE){}//等待DMA可配置 DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);;//DMA外设地址 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)USART3_RX_BUF;//DMA 存储器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//存储器到外设模式 DMA_InitStructure.DMA_BufferSize = USART3_MAX_RX_LEN;//数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//最高优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输 DMA_Init(DMA1_Stream1, &DMA_InitStructure);//初始化DMA Stream DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除标志位 DMA_Cmd(DMA1_Stream1, ENABLE); //关闭DMA DMA_ITConfig(DMA1_Stream1,DMA_IT_TC,ENABLE); //使能DMA1数据流1的传输完成中断 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE); }
这段代码主要是根据第25.3节中讲解的内容对DMA进行配置的。串口发送数据我们利用的是DMA数据流3通道4,存储器是我们定义的一个数组USART3_TX_BUF,外设是串口的数据寄存器USART_DR,表示方法为&USART3->DR。串口接收数据我们利用的是DMA数据流1通道4,存储器是我们定义的一个数组USART3_RX_BUF,外设是串口的数据寄存器USART_DR,表示方法为&USART3->DR。
代码清单2:DMA1_Stream3_IRQHandler函数
void DMA1_Stream3_IRQHandler(void) //DMA发送数据流中断服务函数 { if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3) != RESET) { DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); DMA_Cmd(DMA1_Stream3,DISABLE); //关闭DMA USART3_DMA_Tx_flag = 0; //发送完成 } }
该函数是DMA1数据流3中断服务函数,在函数里我们通过对DMA_IT_TCIF3标志位的判断,可以知道数据流3的数据是否传输完成,然后清除相应的标志位和关闭DMA传输。
代码清单3:DMA1_Stream1_IRQHandler函数
void DMA1_Stream1_IRQHandler(void) //DMA接收数据流中断服务函数 { if(DMA_GetITStatus(DMA1_Stream1,DMA_IT_TCIF1) != RESET) { DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除标志位 DMA_Cmd(DMA1_Stream1,DISABLE); //关闭DMA } }
该函数的功能和DMA1数据流3中断服务函数的功能是一样的。
代码清单4:USART3_IRQHandler函数
void USART3_IRQHandler(void) { if(USART_GetITStatus(USART3 , USART_IT_IDLE) != RESET) { USART_ReceiveData(USART3);//清除空闲中断标志 DMA_Cmd(DMA1_Stream1,DISABLE);//关闭DMA ,防止干扰 USART3_RX_STA = USART3_MAX_RX_LEN - DMA_GetCurrDataCounter(DMA1_Stream1);//获得接收到的字节数 DMA_SetCurrDataCounter(DMA1_Stream1,USART3_MAX_RX_LEN); DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1);//比f103多的一句 DMA_Cmd(DMA1_Stream1,ENABLE);// DMA 开启,等待数据。 USART3_RX_BUF[USART3_RX_STA&0X7FFF]='\0'; USART3_RX_STA|=0x8000; //最高位置1,标记接收完成了 } if(USART_GetFlagStatus(USART3,USART_FLAG_ORE) == SET) // 检查 ORE 标志,防止开关总中断死机,放在接收中断前面 { USART_ClearFlag(USART3,USART_FLAG_ORE); USART_ReceiveData(USART3); } }
该函数是串口3的中断服务函数。在函数里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。然后在中断里依次做了以下事情:处理接收buff的数据;关闭串口接收DMA通道,防止后面接收到数据产生串扰;计算接收到的字节数存储在USART3_RX_STA变量中;重新设置DMA下次要接收的数据字节数;清除DMA1数据流1接收完成标志位;开启DMA通道,等待下一次的数据接收;置位标志位USART3_RX_STA,表示接收完成了。同时为了防止串口溢出错误,程序卡死在串口中断里,在中断服务函数里增加了溢出错误的处理。
代码清单5:主函数
int main(void) { GPIO_Configuration(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2 USART_Configuration(); printf("start\r\n"); while (1) { if(USART3_RX_STA&0x8000) { //printf("USART3_Read:%s",USART3_RX_BUF); printf("USART3_Read:%s",USART3_RX_BUF); USART3_RX_STA=0; memset(USART3_RX_BUF,0,sizeof(USART3_RX_BUF)); } } }
主函数的编写逻辑比较简单,首先是各类外设的初始化,包括GPIO初始化、NVIC中断初始化、串口初始化。然后在while循环里等待接收完成标志置位,将接收到的数据再发送到串口调试助手,并利用memset函数将存储数据的数组USART3_RX_BUF清零。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。