转载:http://blog.youkuaiyun.com/gdjason/article/details/51019219
一直都没有整理STM32 DMA应用,这篇文章算是抛砖引玉吧,欢迎拍砖。
本人QQ 330952038,欢迎交流学习
什么是DMA —- Directional Memory Access, 直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作
我们通过以下几方面学习串口DMA:
一、如何理解DMA
对于DMA,打个比方就很好理解:
角色预设: 淘宝店主 —- STM32 MCU
快递员 —- 外设(如UART,SPI)
发货室 —- DMA
1、首先你是一个淘宝店主,如果每次发货收货都要跟快递沟通交涉会很浪费时间和精力。
2、然后你就自己建了一个发货室,发货室里有好多个货柜箱子,每个箱子上都写着快递名字(如果申通快递,顺丰快递等)。
3、每次发什么快递,你就找到对应的货柜箱子,把货物放进去即可,然后跟快递通知一声。
4、快递取走快件。
5、如果是收货,快递直接把快件放到对应的柜子,然后通知你一下。
6、你过来提取货物。
通过上面的方式,你可以不需要直接跟快递打交道,就可以轻松发货成功,DMA处理方式跟上面例子是一样的。
如果下图:

二、STM32 DMA 配置
那么DMA在STM32上是具体怎么实现的呢? 我们先了解一下STM32关于DMA的相关配置。
1、两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道)
ps:对应我们例子,就是有两个大的发货室,一个有7个货柜,另个有5个货柜。
2、在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)
ps: 店主可以跟每个快递公司签订协议,可以在货柜前贴上加急(很高),很急(高),急(中),一般(低), 如果同时有几个快递员过来取货,优先根据上面的优先级先取件。
3、独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
ps: 指的是货件大小
4、支持循环的缓冲器管理(会把原来的数据覆盖)
5、每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
ps: 送快递出现的异常情况(送到了一半,送完,快递出错)
解释到这里,不知道大家能不能理解呢。后面是具体的配置。
1、DMA 对应通道如下图
DMA1:

DMA2:

2、DMA配置
1)数据传输的目的地和来源
对应我的例子,就是送快递还是取快递。
2)定义DMA通道的DMA缓存的大小
ps: 即货柜大小,能存多少个快件
3)外设地址寄存器递增与否

4)内存地址寄存器递增与否

5)设定了外设数据宽度

6)设定了内存数据宽度

7)设置了DMA的工作模式

8)DMA通道的软件优先级

9)使能或关闭DMA通道的内存到内存传输

三、 编程
串口用DMA方式发送和接收,分以下几步:
1)串口初始化
2)DMA初始化
3)发送数据
4)接收数据
我们按部就班:
1) 串口初始化 — 使用串口一
#define DMASIZE 1024
static void _uart1_gpio_init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_USART1 |
RCC_APB2Periph_AFIO, ENABLE) ;
GPIOA->CRH&=0XFFFFF00F;
GPIOA->CRH|=0X000008B0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ClearFlag(USART1, USART_FLAG_TC);
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
}
static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value)
{
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate =value;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USARTx, &USART_InitStructure);
USART_Cmd(USARTx, ENABLE);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
2)初始化DMA
u8 sendbuf[1024]
u8 receivebuf[1024]
static void _uart1_dma_configuration()
{
DMA_InitTypeDef DMA_InitStructure
/* DMA1 Channel6 (triggered by USART1 Rx event) Config */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,
ENABLE)
/* DMA1 Channel5 (triggered by USART1 Rx event) Config */
DMA_DeInit(DMA1_Channel5)
DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base
DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC
DMA_InitStructure.DMA_BufferSize = DMASIZE
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_MemoryDataSize_Byte
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable
DMA_Init(DMA1_Channel5, &DMA_InitStructure)
DMA_Cmd(DMA1_Channel5, ENABLE)
/* DMA1 Channel4 (triggered by USART1 Tx event) Config */
DMA_DeInit(DMA1_Channel4)
DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base
DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST
DMA_InitStructure.DMA_BufferSize = 0
DMA_Init(DMA1_Channel4, &DMA_InitStructure)
USART_ITConfig(USART1, USART_IT_TC, ENABLE)
USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
3、数据发送
流程:串口发送数据,全部数据发送完毕后,会产生一个发送中断,所以
发送数据分为两部分,
A、发送数据
B、中断处理
A、发送数据
u16 Uart_Send_Data(void* buffer, u16 size)
{
if(!size) return 0;
while (DMA_GetCurrDataCounter(DMA1_Channel4));
if(buffer) memcpy(sendbuf, buffer,(size > 1024?1024:size));
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR = size;
DMA_Cmd(DMA1_Channel4, ENABLE);
return size;
}
B、中断处理
1)中断处理相关准备工作
typedef enum _UartEvent_
{
E_uart_0 = 0,
E_uart_tc=0x40,
E_uart_idle=0x80,
}UartEvent;
u16 receivelen = 0;
UartEvent event;
void Uart_Dma_Clr(void)
{
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR=0;
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA1_Channel5->CNDTR=DMASIZE ;
DMA_Cmd(DMA1_Channel5, ENABLE);
}
UartEvent Uart_Get_Event(void)
{
UartEvent e;
if(!DMA1_Channel5->CNDTR) Uart_Dma_Clr();
return event;
}
void Uart_Clr_Event(UartEvent event_in)
{
event&=~event_in;
}
2) 中断处理,当所有数据发送完毕,串口1产生一个发送完成中断
void Uatr1_Back_IRQHandler()
{
u8 tem;
if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)
{
tem=USART1->SR;
tem=USART1->DR;
tem=tem;
Uart_Set_Event(E_uart_idle);
receivelen =DMASIZE - DMA1_Channel5->CNDTR;
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
}
**if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_TC);
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR=0;
Uart_Set_Event(E_uart_tc);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
4、接收数据
根据上图描述,流程如下:
1、串口接收到数据
2、DMA自动取走数据
3、DMA把数据存到内存receive[1024]中
4、串口接收完毕后会产生一个空闲中断
根据上面流程,我们接收数据需要做到两步:
1)串口产生一个空闲中断后,设置一个接收完成事件
中断处理:
void Uatr1_Back_IRQHandler()
{
u8 tem;
**if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)**
{
tem=USART1->SR;
tem=USART1->DR;
tem=tem;
Uart_Set_Event(E_uart_idle);
receivelen =DMASIZE - DMA1_Channel5->CNDTR;
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
}
if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_TC);
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR=0;
Uart_Set_Event(E_uart_tc);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
2)接收数据函数检测事件,如果发现是接收完成事件,取走数据,并且做相关清除操作
u8 Uart_Receive_Data(u8*recbuf u16 *revLen)
{
u8 *str;
if( event & E_uart_idle)
{
str = Uart_Get_Data(revLen);
memcpy(recbuf,receivebuf,*revLen);
Uart_Clr_Event(E_uart_idle);
Uart_Dma_Clr();
return TRUE;
}
else
{
revLen = 0;
return FALSE;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
好了,到此DMA已经讲完了,有点长!!!
小结:
1、DMA其实就是个自动缓存器,数据来了,缓存到指定位置。发送数据则把缓存数据发送出去。
2、串口空闲中断,实测在接收完成数据后,空闲闲置时产生的,而发送数据不会产生该中断。
3、串口发送完成中断,实测在全部数据发送成功后,才会产生中断。
上面这些知识是微小见识,欢迎拍砖