STM32的DMA双缓冲模式详解

DMA双缓冲是什么?

在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?
接下来我会在下面的介绍里详细说明:

DMA全称Direct Memory Access,即直接内存访问。
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
在这里插入图片描述
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。

DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。
在这里插入图片描述
那么,双缓冲是什么意思呢?
我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)
在这里插入图片描述

而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
在这里插入图片描述

如何使用DMA双缓存?

那么我们在程序里怎么使用双缓冲呢?
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。

这里我们使用DMA的传输模式是循环模式

DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;

首先是第一种:
这里我们以大疆RoboMaster的遥控器使用为示例:
首先我们定义好双缓冲使用的两个内存区域

uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data

这里我们设置长度为18,也就是一帧遥控器数据的长度。

DMA_InitStruct.DMA_BufferSize = 18;

然后配置好双缓冲的两个内存区域地址

DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];

这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址

DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration

启动DMA双缓冲

DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);

我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
在串口中断处理函数中对数据进行处理

 
 uint16_t this_time_rx_len = 0;	//当前剩余数据长度

 if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)	//判断是否为空闲中断
    {
        USART_ReceiveData(USART3);	//清除空闲中断标志位

        if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)	//获取当前目标内存是否为 DMA_Memory_0
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);	//获取当前剩余数据量
					
					 DMA_SetCurrDataCounter(DMA1_Stream1, 18);	//重新设置数据量
					  
					 DMA_Cmd(DMA1_Stream1, ENABLE);
            if(this_time_rx_len == 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[1]);	//Memory_1
            }

        }
        else	//获取当前目标内存是否为 DMA_Memory_1
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);		//获取当前剩余数据量

						DMA_SetCurrDataCounter(DMA1_Stream1, 18);	//重新设置数据量

						DMA_Cmd(DMA1_Stream1, ENABLE);

            if( this_time_rx_len== 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[0]);	//Memory_0
            }

        }
    }

该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。

然后是第二种:
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。

DMA_InitStruct.DMA_BufferSize = 36;
 uint16_t this_time_rx_len = 0;	//当前剩余数据长度

 if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)	//判断是否为空闲中断
    {
        USART_ReceiveData(USART3);	//清除空闲中断标志位

        if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)	//获取当前目标内存是否为 DMA_Memory_0
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);	//获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
					
					  DMA_SetCurrDataCounter(DMA1_Stream1, 36);	//重新设置数据量
						DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
					
						DMA_Cmd(DMA1_Stream1, ENABLE);
            if(this_time_rx_len == 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[0]);	//Memory_0
            }

        }
        else	//获取当前目标内存是否为 DMA_Memory_1
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);		//获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18

						DMA_SetCurrDataCounter(DMA1_Stream1, 36);	//重新设置数据量
						DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0

						DMA_Cmd(DMA1_Stream1, ENABLE);

            if( this_time_rx_len == 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[1]);	//Memory_1
            }

        }
    }

两种DMA双缓冲使用方法的区别

第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。

可以看到,在第一种方法下,我们并没有使用

DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0

这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。

版权归作者(Alliance战队水木皆Ming)所有,需要转载或引用请注明来历和作者,若为其他用途或者有其他疑问则联系本文作者

版权声明:本文为CSDN博主「水木皆Ming」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhang1079528541/article/details/121049656

DMA双缓冲是什么?

在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?
接下来我会在下面的介绍里详细说明:

DMA全称Direct Memory Access,即直接内存访问。
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
在这里插入图片描述
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。

DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。
在这里插入图片描述
那么,双缓冲是什么意思呢?
我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)
在这里插入图片描述

而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
在这里插入图片描述

如何使用DMA双缓存?

那么我们在程序里怎么使用双缓冲呢?
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。

这里我们使用DMA的传输模式是循环模式

DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;

首先是第一种:
这里我们以大疆RoboMaster的遥控器使用为示例:
首先我们定义好双缓冲使用的两个内存区域

uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data

这里我们设置长度为18,也就是一帧遥控器数据的长度。

DMA_InitStruct.DMA_BufferSize = 18;

然后配置好双缓冲的两个内存区域地址

DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];

这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址

DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration

启动DMA双缓冲

DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);

我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
在串口中断处理函数中对数据进行处理

 
 uint16_t this_time_rx_len = 0;	//当前剩余数据长度

 if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)	//判断是否为空闲中断
    {
        USART_ReceiveData(USART3);	//清除空闲中断标志位

        if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)	//获取当前目标内存是否为 DMA_Memory_0
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);	//获取当前剩余数据量
					
					 DMA_SetCurrDataCounter(DMA1_Stream1, 18);	//重新设置数据量
					  
					 DMA_Cmd(DMA1_Stream1, ENABLE);
            if(this_time_rx_len == 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[1]);	//Memory_1
            }

        }
        else	//获取当前目标内存是否为 DMA_Memory_1
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);		//获取当前剩余数据量

						DMA_SetCurrDataCounter(DMA1_Stream1, 18);	//重新设置数据量

						DMA_Cmd(DMA1_Stream1, ENABLE);

            if( this_time_rx_len== 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[0]);	//Memory_0
            }

        }
    }

该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。

然后是第二种:
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。

DMA_InitStruct.DMA_BufferSize = 36;
 uint16_t this_time_rx_len = 0;	//当前剩余数据长度

 if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)	//判断是否为空闲中断
    {
        USART_ReceiveData(USART3);	//清除空闲中断标志位

        if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)	//获取当前目标内存是否为 DMA_Memory_0
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);	//获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
					
					  DMA_SetCurrDataCounter(DMA1_Stream1, 36);	//重新设置数据量
						DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
					
						DMA_Cmd(DMA1_Stream1, ENABLE);
            if(this_time_rx_len == 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[0]);	//Memory_0
            }

        }
        else	//获取当前目标内存是否为 DMA_Memory_1
        {
            //重新设置DMA
            DMA_Cmd(DMA1_Stream1, DISABLE);
					  this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);		//获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18

						DMA_SetCurrDataCounter(DMA1_Stream1, 36);	//重新设置数据量
						DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0

						DMA_Cmd(DMA1_Stream1, ENABLE);

            if( this_time_rx_len == 18)	//接收成功18个字节长度
            {
                //处理遥控器数据
                RemoteDataProcess(sbus_rx_buffer[1]);	//Memory_1
            }

        }
    }

两种DMA双缓冲使用方法的区别

第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。

可以看到,在第一种方法下,我们并没有使用

DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0

这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。

版权归作者(Alliance战队水木皆Ming)所有,需要转载或引用请注明来历和作者,若为其他用途或者有其他疑问则联系本文作者

版权声明:本文为CSDN博主「水木皆Ming」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhang1079528541/article/details/121049656

生成海报
点赞 0

水木皆Ming

我还没有学会写个人说明!

暂无评论

相关推荐

STM32的DMA双缓冲模式详解

DMA双缓冲是什么? 在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢? 接下来我会在下面的介绍里详细说明&#xff1