本文在前两篇USART串口通信的基础上,使用DMA控制器来实现串口通信
一、DMA
1.DMA简介
DMA全称Direct Memory Access,即直接存储器访问。
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和回复现场过程,通过硬件为RAM和IO设备开辟一条直线传输数据的通道,使得CPU的效率大大提高。DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。
STM32F1最多有2个DMA控制器,DMA2仅存在于大容量产品中,DMA1有7个通道,DMA2有5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个DMA请求的优先权。在STM32中文参考手册第144页中给出了DMA的框图。
2.DMA主要特性
在STM32中文参考手册第142页比较详细的介绍了DMA的主要特性
下面以DMA1控制器为例介绍
从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。
外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。
在STM32中文参考手册第147页介绍了相关DMA1控制器的信息。
当发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先级处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。
总之,每次DMA传送由3个操作组成。
根据通道请求的优先级来启动外设/存储器的访问需要设置仲裁器。
优先权管理分2个阶段:
- 软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:最高优先级、高优先级、中等优先级、低优先级
- 硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。
注意:在大容量和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级
DMA通道每个通道都可以在固定地址的外设寄存器和存储器地址之间执行DMA传输。
关于DMA的知识资料、知识比较多,详细可以去STM32中文参考手册中查看相关信息,下面以常见的代码为例,结合之前的串口实验来实操
3.部分代码原理
还是之前的老套路,首先定义结构体
DMA_InitTypeDef DMA_InitStructure;
然后时钟使能,接下来就是结构体中的内容
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */
uint32_t DMA_MemoryBaseAddr; /*!< Specifies the memory base address for DMAy Channelx. */
uint32_t DMA_DIR; /*!< Specifies if the peripheral is the source or destination.
This parameter can be a value of @ref DMA_data_transfer_direction */
uint32_t DMA_BufferSize; /*!< Specifies the buffer size, in data unit, of the specified Channel.
The data unit is equal to the configuration set in DMA_PeripheralDataSize
or DMA_MemoryDataSize members depending in the transfer direction. */
uint32_t DMA_PeripheralInc; /*!< Specifies whether the Peripheral address register is incremented or not.
This parameter can be a value of @ref DMA_peripheral_incremented_mode */
uint32_t DMA_MemoryInc; /*!< Specifies whether the memory address register is incremented or not.
This parameter can be a value of @ref DMA_memory_incremented_mode */
uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
This parameter can be a value of @ref DMA_peripheral_data_size */
uint32_t DMA_MemoryDataSize; /*!< Specifies the Memory data width.
This parameter can be a value of @ref DMA_memory_data_size */
uint32_t DMA_Mode; /*!< Specifies the operation mode of the DMAy Channelx.
This parameter can be a value of @ref DMA_circular_normal_mode.
@note: The circular buffer mode cannot be used if the memory-to-memory
data transfer is configured on the selected Channel */
uint32_t DMA_Priority; /*!< Specifies the software priority for the DMAy Channelx.
This parameter can be a value of @ref DMA_priority_level */
uint32_t DMA_M2M; /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;
STM32固件库使用手册第89页给出了相关参数的描述
- DMA_PeripheraBaseAddr:该参数用以定义DMA外设基地址
- DMA_MemoryBaseAddr:该参数用以定义DMA内存基地址
- DMA_DIR:DMA_DIR规定了外设是作为数据传输的目的地还是来源。下面的表给出了该参数的取值范围。
- DMA_BufferSize:DMA_BufferSize用以定义指定DMA通道的DMA缓存大小,单位位数据单位。根据传输方向,数据单位等于结构中参数DMA_PeripheralDataSize或者参数DMA_MemoryDataSize的值
- DMA_PeripheralInc:DMA_PeripheralInc用来设定外设地址寄存器递增是否。下面的表给出了该参数的取值范围
- DMA_MemoryInc:DMA_MemoryInc用来设定内存地址寄存器递增与否。
- DMA_PeripheralDataSize:DMA_PeripheralDataSize设定了外设数据宽度。下面的表给出了该参数的取值范围
- DMA_MemoryDataSize:DMA_MemoryDataSize设定了外设数据宽度。下面的表给出了该参数的取值范围
- DMA_Mode:DMA_Mode设置了CAN的工作模式。下面的表给出了该参数可取的值
- DMA_Priority:DMA_Priority设定DMA通道x的软件优先级。下面的表给出了该参数可取的值
- DMA_M2M:DMA_M2M使能DMA通道的内存到内存传输。下面的表给出了该参数可取的值
所以,我们的结构体定义如下
DMA1_MEM_LEN = TEXT_LENTH+2;
DMA_InitStructure.DMA_BufferSize = TEXT_LENTH+2; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道没有设置为内存到内存传输
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; //DMA内存基地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设基地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道拥有中优先级
然后是DMA的初始化
DMA_Init(DMA1_Channel4,&DMA_InitStructure);
这里我们选择是USART1,结合上面的表59我们知道,应该选择通道4,即DMA1_Channel4
在main.c主程序中我们使能了DMA后,还需要开启DMA传输。这里我们在dma.c中添加了void MyDMA_Enable(void)函数来开启一次DMA传输
void MyDMA_Enable()
{
DMA_Cmd(DMA1_Channel4,DISABLE); //关闭USART_Tx_DMA1所指示的通道
DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN); //DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel4,ENABLE); //使能USART1_Tx_DMA1所指示的通道
}
最后不要忘了,在主程序中添加使能USART1的DMA请求
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
这个函数的使用说明如下
二、实验代码
dma.h
#ifndef __DMA_H
#define __DMA_H
#include "stm32f10x.h"
void MyDMA_Init(void);
void MyDMA_Enable(void);
#endif
dma.c
#include "dma.h"
DMA_InitTypeDef DMA_InitStructure;
const u8 TEXT_TO_SEND[]={"Hello,world!"};
#define TEXT_LENTH sizeof(TEXT_TO_SEND)-1
u8 SendBuff[TEXT_LENTH+2];
u16 DMA1_MEM_LEN;
void MyDMA_Init()
{
u16 i;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel4);
DMA1_MEM_LEN = TEXT_LENTH+2;
DMA_InitStructure.DMA_BufferSize = TEXT_LENTH+2; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道没有设置为内存到内存传输
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; //DMA内存基地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设基地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道拥有中优先级
DMA_Init(DMA1_Channel4,&DMA_InitStructure); //根据DMA_InitSturct中指定的参数初始化DMA的通道USART_Tx_DMA_Channel所标识的寄存器
//发送内容
for(i=0;i<TEXT_LENTH;i++)
{
SendBuff[i]=TEXT_TO_SEND[i];
}
SendBuff[TEXT_LENTH]=0x0d;
SendBuff[TEXT_LENTH+1]=0x0a;
}
/* 开启一次DMA传输 */
void MyDMA_Enable()
{
DMA_Cmd(DMA1_Channel4,DISABLE); //关闭USART_Tx_DMA1所指示的通道
DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN); //DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel4,ENABLE); //使能USART1_Tx_DMA1所指示的通道
}
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "dma.h"
int main(void)
{
delay_init();
MyUSART_Init();
MyDMA_Init();
while(1)
{
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //串口——DMA使能
MyDMA_Enable(); //开始MDA传输
while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //等待通道4传输完成
{
DMA_ClearFlag(DMA1_FLAG_TC4); //清除通道4传输完成标志
break;
}
}
delay_ms(1000); //延时1s
}
}
三、实验结果
四、总结
本次实验简单的运用了DMA控制器,DMA的参数比较多。由于篇幅限制(内容太多了QAQ),详细知识介绍请查看STM32中文参考手册。