参考:bilibili:江协科技,看着视频写了个文字版的,相当于总结笔记,也方便以后查看。
简介:
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传 输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
下面是DMA的框图:
我们来看框图中有关DMA的部分,首先可以看出有两个DMA,其中DMA2仅存在于大容量产品和互联型产品,所以我们在这里主要看DMA1,在DMA1中有很多通道,还有一个仲裁器,我们可以选择其中的通道进行传输数据,当发生多个通道请求时,仲裁其要根据通道的优先级来选择是哪个通道。
我们也可以看到DMA连接的是AHB总线,CPU可以通过总线矩阵走AHB总线去配置DMA,而DMA的通道也可以从DMA总线到总线矩阵去访问外设。
然后我们可以看到在APB1和APB2总线上的外设会向DMA发送请求,当外设想要让DMA来运输数据时,必须发送相对应的请求。
知道了DMA的作用后,我们这次的实验就选择DMA来运输外设和存储器的数据,当我们向串口发送数据后,DMA会将串口接收的数据运输到我们的存储器,当空闲中断发生时,在中断服务函数向电脑返回存储器的数据。
我们选择的是串口1,这个外设对应DMA的哪个通道呢,可以来看下面的DMA1的对应表:
我们要选择的是串口1接收,所以对应DMA1的通道5。之后,我们可以尝试编写函数了。
DMA初始化配置函数,一共有11个参数,这里依次来看一下,DMA_PeripheralBaseAddr,DMA外设基地址,这里我们选择串口1的DR寄存器,也就是数据寄存器的地址,因为这个寄存器存放了串口的发送和接收数据。
DMA_MemoryBaseAddr,DMA内存基地址,就是存储器基地址,我们可以定义一个数组当作存储器,将数组名放入这参数即可。
DMA_DIR,这个参数规定了运输的方向,是外设到存储器还是存储器到外设,这里我们选择DMA_DIR_PeripheralSRC,外设作为数据传输的来源,方向为外设到存储器。
DMA_BufferSize,DMA通道的大小,我们选择之前定义的数组大小即可。
DMA_PeripheralInc,决定外设寄存器地址是否递增,我们选择disable,不递增,外设寄存器的地址一直是USART的DR寄存器地址。如果递增,就变为别的寄存器了。
DMA_MemoryInc,决定存储器地址是否递增,我们选择enable,递增,例如数组 [ 1 ] 受到数据,下一个应该数组 [ 2 ] 来收,所以这里要递增。
DMA_PeripheralDataSize,外设数据的数据宽度,我们选择8位即可。
DMA_MemoryDataSize,同理,存储器的数据宽度我们也选择8位。
DMA_Mode,决定DMA是循环运输模式还是单次运输模式,这里我们选择循环模式,其实做实验,两种模式都可以。
DMA_Priority,DMA通道的优先级,我们只用到一个通道,所以什么优先级都可以,我们在这里选择中,
DMA_M2M,这个参数为enable,代表存储器到存储器,参数为disable,代表外设到存储器,这里我们选择disable,即外设到存储器。
最后调用DMA_Init 即可。
之后,我们可以调用DMA_Cmd来开启DMA。整体代码如下:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ReceiveBuff;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = RECEIVEBUFF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, ENABLE);
这是DMA的配置函数,这篇不是串口的博客,不讲串口的配置函数。
我们想要运输外设的数据的前提还有外设要向DMA发送请求,串口对应的函数是USART_DMACmd,配置通道,串口接收的请求,使能三个参数即可。代码如下:
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
我们还需要配置中断函数,当我们在电脑上发送数据后,串口接收数据,DMA开始运输,当没有接收数据了之后串口会产生空闲中断,在中断服务函数里我们首先要关闭DMA的运输,接着我们要关闭DMA的运输,然后读取在通道中剩余还没运输的数据量,再将已经运输到存储器的数据量发送给电脑,之后将重新设置传输的数据量,设置传输的大小依然是数据的大小,最后开启DMA。在重新开启DMA后,我们要读取一次数据,其实就是读取一次DR寄存器,不读取的话会一直进入中断,之后要清除中断标志位,然后就退出中断服务程序了,代码如下:
void DEBUG_USART_IRQHandler(void)
{
uint16_t t;
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET) //检查中断是否发生
{
DMA_Cmd(DMA1_Channel5,DISABLE); //关闭DMA传输
t = DMA_GetCurrDataCounter(DMA1_Channel5); //获取剩余的数据数量
Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t); //向电脑返回数据(接收数据数量 = SENDBUFF_SIZE - 剩余未传输的数据数量)
DMA_SetCurrDataCounter(DMA1_Channel5,RECEIVEBUFF_SIZE); //重新设置传输的数据数量
DMA_Cmd(DMA1_Channel5,ENABLE); //开启DMA传输
USART_ReceiveData(DEBUG_USARTx); //读取一次数据,不然会一直进中断
USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE); //清除串口空闲中断标志位
}
}
这样,我们关于DMA的部分就完成了,为了表现出DMA不干预CPU,CPU可以处理其他事情的现象,这里while(1)循环中延时翻转led灯状态,使led闪烁。代码如下:
while(1)
{
LED1_TOGGLE;
Delay(0xFFFFF);
}
最终现象: