代码改变世界

stm32 DMA

2022-01-28 22:15  jym蒟蒻  阅读(298)  评论(0编辑  收藏  举报

stm32 DMA

  • 介绍的定义
  • 存储器到存储器
    • main.c
  • 存储器到外设
    • main.c
    • usart_dma.c
    • usart_dma.h
  • 外设到存储器
    • main.c
    • usart_dma.c
    • usart_dma.h
    • 中断函数
  • 对比理解

 

介绍的定义

存储器:用来存储程序代码和数据。

易失性存储器:存储器断电后,它存储的数据内容丢失。易失性存储器存取速度快,如内存。

非易失性存储器:存储器断电后,它存储的数据内容不丢失。非易失性存储器可长期保存数据,如硬盘。

RAM(Random Access Memory):随机存储器。当存储器中的消息被读取或写入时,所需要的时间与这段信息所在的位置无关。也就是说,RAM 读取其内部任意地址的数据,时间都是相同的。根据RAM 的存储机制,分为动态随机存储器 DRAM;静态随机存储器 SRAM。

DRAM(Dynamic RAM):动态随机存储器。DRAM 的存储单元以电容的电荷来表示数据,有电荷代表 1,无电荷代表 0。由于代表 1 的电容会放电,代表 0 的电容会吸收电荷,因此需要定期刷新操作,刷新操作会对电容进行检查,若电量大于满电量的 1/2,则认为其代表 1,并把电容充满电;若电量小于 1/2, 则认为其代表 0,并把电容放电。 DRAM 的结构简单,所以生产相同容量的存储器,DRAM 的成本更低,集成度更高。外部扩展的内存一般使用 DRAM。

SRAM(Static RAM):静态随机存储器。 SRAM 的存储单元以锁存器来存储数据,这种电路结构不需要定时刷新充电,就能保持状态。SRAM 一般用于 CPU 内部的高速缓存(Cache)。

SDRAM(Synchronous DRAM):同步通讯方式的DRAM,使用时钟同步的通讯速度更快。SDRAM 只在上升沿表示有效数据,在 1 个时钟周期内,只能表示 1 个有数据。

DDR SDRAM(Double Data Rate SDRAM):在时钟的上升沿及下降沿各表示一个数据,也就是说在 1 个时钟周期内可以表示 2 位数据,在时钟频率同样的情况下, 提高了一倍的速度。

ROM(Read Only Memory):英文是只能读的存储器,后来人们设计出可以写入数据的 ROM。用于指代非易失性半导体存储器。

MASK ROM:存储在它内部的数据是在出厂时使用特殊工艺固化的,生产后就不可修改,用在生产量大,数据不需要修改的场合。

EEPROM(Electrically Erasable Programmable ROM):电可擦除存储器。可重复擦写,擦除和写入都是直接使用电路控制,不需要使用外部设备。而且可以以字节为单位修改数据,无需整个芯片擦除。现在主要使用的 ROM 芯片都是 EEPROM。

FLASH:闪存,有人称为flash ROM,也是可重复擦写的储器,容量一般比 EEPROM 大得多;在擦除时,一般以多个字节为单位。有的 FLASH 存储器以 4096 个字节为扇区,最小的擦除单位为一个扇区。FLASH 存储器又分为 NOR FLASH 和 NAND FLASH。NOR 与 NAND 的共性是在数据写入前都需要有擦除操作。FLASH 的擦除次数都是有限的(10 万次左右),当使用接近寿命的时候,可能会出现写操作失败。

NOR FLASH: NOR 的地址线和数据线分开,可以按“字节”读写数据;假如 NOR 上存储了代码指令,CPU 给 NOR 一个地址,NOR 就能向 CPU 返回一个数据让 CPU 执行,中间不需要额外的处理操作。功能上可以认为 NOR 是一种断电后数据不丢失的 RAM,但他的读写速度比 RAM 要慢得多。NOR FLASH 一般应用在代码存储的场合,如嵌入式控制器内部的程序存储空间。

NAND FLASH:NAND 的数据和地址线共用,只能按“块”来读写数据;若代码存储在 NAND 上,可以把它先加载到 RAM 存储器上,再由 CPU 执行。NAND FLASH 一般应用在大数据量存储的场合,包括 SD 卡、U 盘以及固态硬盘等。

DMA:直接存储器存取,是单片机的一个外设,主要功能是搬数据,但是不需要占用 CPU(传输数据的时候,CPU 可以干其他的事情)。数据传输支持从外设到存储器、存储器到外设、存储器到存储器,这里的存储器可以是 SRAM 或者是 FLASH。DMA 控制器独立于内核,属于一个单独的外设。

存储器到存储器

main.c

先定义一个静态的源数据,存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM),最后对比源数据和目标地址的数据,看看是否传输准确。RGB 彩色灯用于指示程序状态,如果 DMA 传输成功设置 RGB 彩色灯为蓝色,如果 DMA 传输出错设置 RGB 彩色灯为红色。

void DMA_Config(void)函数是根据DMA_InitTypeDef这个结构体来编写的,整体上,关于DMA的函数和结构体定义,都在stm32f10x_dma.c和stm32f10x_dma.h文件里面,利用库函数编程其实说白了就是结合外设对应的那两个文件来编程。

main整体流程:使能 DMA 时钟;配置 DMA 数据参数;使能 DMA,进行传输;等待传输完成,并对源数据和目标地址数据进行比较。

main里面DMA_GetFlagStatus函数是获取DMA事件标志位的当前状态,参数是DMA_FLAG_TC意味着获取DMA 数据传输完成这个标志位,DMA传输完成后,退出循环,运行之后程序。

main里面Buffercmp函数,aSRC_Const_Buffer是FLASH中存储的数据,aDST_Buffer是内部的SRAM中存储的数据,BUFFER_SIZE是数据大小。

TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
#include "stm32f10x.h"
#include "./led/bsp_led.h"


// 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
#define DMA_CHANNEL     DMA1_Channel6
#define DMA_CLOCK       RCC_AHBPeriph_DMA1

// 传输完成标志
#define DMA_FLAG_TC     DMA1_FLAG_TC6

// 要发送的数据大小
#define BUFFER_SIZE     32

/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
 * const关键字将aSRC_Const_Buffer数组变量定义为常量类型
 * 表示数据存储在内部的FLASH中
 */
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
                                    0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
                                    0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
                                    0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
                                    0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
                                    0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
                                    0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
                                    0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
                                    0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
/* 定义DMA传输目标存储器
 * 存储在内部的SRAM中																		
 */
uint32_t aDST_Buffer[BUFFER_SIZE];

#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount); 
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength);
void DMA_Config(void);																	
																		
/**
  * @brief  主函数
  * @param  无  
  * @retval 无
  */
int main(void)
{
  /* 定义存放比较结果变量 */
  uint8_t TransferStatus;
  
	/* LED 端口初始化 */
	LED_GPIO_Config();
    
  /* 设置RGB彩色灯为紫色 */
  LED_PURPLE;  
  
  /* 简单延时函数 */
  Delay(0xFFFFFF);  
  
  /* DMA传输配置 */
  DMA_Config(); 
  
  /* 等待DMA传输完成 */
  while(DMA_GetFlagStatus(DMA_FLAG_TC)==RESET)
  {
    
  }   
  
  /* 比较源数据与传输后数据 */
  TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
  
  /* 判断源数据与传输后数据比较结果*/
  if(TransferStatus==0)  
  {
    /* 源数据与传输后数据不相等时RGB彩色灯显示红色 */
    LED_RED;
  }
  else
  { 
    /* 源数据与传输后数据相等时RGB彩色灯显示蓝色 */
    LED_BLUE;
  }

	while (1)
	{		
	}
}

void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

void DMA_Config(void)
{
	  DMA_InitTypeDef DMA_InitStructure;
	
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
		// 外设
    	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
		// 存储器
		DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
		// 方向:外设到存储器(这里的外设是内部的FLASH)	
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
		// 传输大小	
		DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
		// 外设(内部的FLASH)地址递增	    
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
		// 内存地址递增
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
		// 外设数据单位	
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
		// 内存数据单位
		DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;	 
		// DMA模式,一次或者循环模式
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
		//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  
		// 优先级:高	
		DMA_InitStructure.DMA_Priority = DMA_Priority_High;
		// 使能内存到内存的传输
		DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
		// 配置DMA通道		   
		DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
    	//清除DMA数据流传输完成标志位
    	DMA_ClearFlag(DMA_FLAG_TC);
		// 使能DMA
		DMA_Cmd(DMA_CHANNEL,ENABLE);
}

/**
  * 判断指定长度的两个数据源是否完全相等,
  * 如果完全相等返回1,只要其中一对数据不相等返回0
  */
uint8_t Buffercmp(const uint32_t* pBuffer, 
                  uint32_t* pBuffer1, uint16_t BufferLength)
{
  /* 数据长度递减 */
  while(BufferLength--)
  {
    /* 判断两个数据源是否对应相等 */
    if(*pBuffer != *pBuffer1)
    {
      /* 对应数据源不相等马上退出函数,并返回0 */
      return 0;
    }
    /* 递增两个数据源的地址指针 */
    pBuffer++;
    pBuffer1++;
  }
  /* 完成判断并且对应数据相对 */
  return 1;  
}

//typedef struct
//{
//  uint32_t DMA_PeripheralBaseAddr;   // 外设地址
//  uint32_t DMA_MemoryBaseAddr;       // 存储器地址
//  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_M2M;                  // 存储器到存储器模式
//}DMA_InitTypeDef;

/*********************************************END OF FILE**********************/

存储器到外设

main.c

先定义一个数据变量,存于 SRAM 中,然后通过 DMA 的方式传输到串口的数据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来。结果就是串口一直收到P,然后小灯一直闪,这也就说明,DMA 传输过程不占用 CPU 资源,可以一边传输一边运行其他任务。

在这里插入图片描述

// DMA 存储器到外设(串口)数据传输实验

#include "stm32f10x.h"
#include "bsp_usart_dma.h"
#include "bsp_led.h"

extern uint8_t SendBuff[SENDBUFF_SIZE];
static void Delay(__IO u32 nCount); 

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{
  uint16_t i;
  /* 初始化USART */
  USART_Config(); 

  /* 配置使用DMA模式 */
  USARTx_DMA_Config();
  
  /* 配置RGB彩色灯 */
  LED_GPIO_Config();

  //printf("\r\n USART1 DMA TX 测试 \r\n");
  
  /*填充将要发送的数据*/
  for(i=0;i<SENDBUFF_SIZE;i++)
  {
    SendBuff[i]	 = 'P';
    
  }

  /*为演示DMA持续运行而CPU还能处理其它事情,持续使用DMA发送数据,量非常大,
  *长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,
  *或把DMA配置中的循环模式改为单次模式*/		
  
  /* USART1 向 DMA发出TX请求 */
  USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);

  /* 此时CPU是空闲的,可以干其他的事情 */  
  //例如同时控制LED
  while(1)
  {
    LED1_TOGGLE
    Delay(0xFFFFF);
  }
}

static void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/

usart_dma.c

对于USARTx_DMA_Config函数,数据是从存储器到串口,所以设置存储器为源地址,串口的数据寄存器为目标地 址,要发送的数据有很多且都先存储在存储器中,则存储器地址指针递增,串口数据寄存器只有一个,则外设地址不变。

#include "bsp_usart_dma.h"

uint8_t SendBuff[SENDBUFF_SIZE];

/**
  * @brief  USART GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置 针数据字长
	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(DEBUG_USARTx, &USART_InitStructure);	
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}


/*****************  发送一个字节 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

/****************** 发送8位的数组 ************************/
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)
{
  uint8_t i;
	
	for(i=0; i<num; i++)
  {
	    /* 发送一个字节数据到USART */
	    Usart_SendByte(pUSARTx,array[i]);	
  
  }
	/* 等待发送完成 */
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}

/*****************  发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
	unsigned int k=0;
  do 
  {
      Usart_SendByte( pUSARTx, *(str + k) );
      k++;
  } while(*(str + k)!='\0');
  
  /* 等待发送完成 */
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
  {}
}

/*****************  发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
	uint8_t temp_h, temp_l;
	
	/* 取出高八位 */
	temp_h = (ch&0XFF00)>>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送高八位 */
	USART_SendData(pUSARTx,temp_h);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	/* 发送低八位 */
	USART_SendData(pUSARTx,temp_l);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

/**
  * @brief  USARTx TX DMA 配置,内存到外设(USART1->DR)
  * @param  无
  * @retval 无
  */
void USARTx_DMA_Config(void)
{
		DMA_InitTypeDef DMA_InitStructure;
	
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
		// 外设:串口数据寄存器地址*/
    	DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
		// 内存地址(要传输的变量的指针)
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
		// 方向:从内存到外设	
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
		// 传输大小	
		DMA_InitStructure.DMA_BufferSize = SENDBUFF_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_MemoryDataSize_Byte;	 
		// DMA模式,一次或者循环模式
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
		//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
		// 优先级:中	
		DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
		// 禁止内存到内存的传输
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
		// 配置DMA通道		   
		DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);		
		// 使能DMA
		DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
}


usart_dma.h

这里面是USART 和 DMA 的宏定义。

#ifndef __USARTDMA_H
#define	__USARTDMA_H


#include "stm32f10x.h"
#include <stdio.h>


// 串口工作参数宏定义
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

// 串口对应的DMA请求通道
#define  USART_TX_DMA_CHANNEL     DMA1_Channel4
// 外设寄存器地址
#define  USART_DR_ADDRESS        (USART1_BASE+0x04)
// 一次发送的数据量
#define  SENDBUFF_SIZE            5000


void USART_Config(void);
void USARTx_DMA_Config(void);

#endif /* __USARTDMA_H */

外设到存储器

main.c

实验结果就是,用电脑向开发板串口发送数据,数据会返回到电脑。

在这里插入图片描述

// DMA 外设(串口)到存储器数据传输实验

#include "stm32f10x.h"
#include "bsp_usart_dma.h"
#include "bsp_led.h"
static void Delay(__IO u32 nCount);
/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{
  /* 初始化USART */
  USART_Config(); 

  /* 配置使用DMA模式 */
  USARTx_DMA_Config();
  
  /* 配置RGB彩色灯 */
  LED_GPIO_Config();
  
  printf("\r\nDMA外设到存储器模式,用电脑向开发板串口发送数据,数据会返回到电脑。\r\n");
  
  /* USART1 向 DMA发出RX请求 */
  USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
 //用电脑向开发板串口发送数据,数据会返回到电脑。
  while(1)
  {
    LED1_TOGGLE
    Delay(0xFFFFF);
  }
}
static void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/

usart_dma.c

#include "bsp_usart_dma.h"

uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];

/**
  * @brief  USART GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStruct;
	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
 	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStruct.NVIC_IRQChannel = DEBUG_USART_IRQ;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
	NVIC_Init(&NVIC_InitStruct);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置 针数据字长
	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(DEBUG_USARTx, &USART_InitStructure);	
	//使能空闲中断
	USART_ITConfig(DEBUG_USARTx,USART_IT_IDLE,ENABLE);
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}


/*****************  发送一个字节 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

/****************** 发送8位的数组 ************************/
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)
{
  uint8_t i;
	
	for(i=0; i<num; i++)
  {
	    /* 发送一个字节数据到USART */
	    Usart_SendByte(pUSARTx,array[i]);	
  
  }
	/* 等待发送完成 */
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}

/*****************  发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
	unsigned int k=0;
  do 
  {
      Usart_SendByte( pUSARTx, *(str + k) );
      k++;
  } while(*(str + k)!='\0');
  
  /* 等待发送完成 */
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
  {}
}

/*****************  发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
	uint8_t temp_h, temp_l;
	
	/* 取出高八位 */
	temp_h = (ch&0XFF00)>>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送高八位 */
	USART_SendData(pUSARTx,temp_h);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	/* 发送低八位 */
	USART_SendData(pUSARTx,temp_l);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

/**
  * @brief  USARTx TX DMA 配置,内存到外设(USART1->DR)
  * @param  无
  * @retval 无
  */
void USARTx_DMA_Config(void)
{
		DMA_InitTypeDef DMA_InitStructure;
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	    
		// 外设:串口数据寄存器地址*/
        DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
		// 内存地址(要传输的变量的指针)
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)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_MemoryDataSize_Byte;	 
		// DMA模式,一次或者循环模式
//		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
		DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
		// 优先级:中	
		DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
		// 禁止内存到内存的传输
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
		// 配置DMA通道		   
		DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);		
		// 使能DMA
		DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
}


usart_dma.h

#ifndef __USARTDMA_H
#define	__USARTDMA_H


#include "stm32f10x.h"
#include <stdio.h>


// 串口工作参数宏定义
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler

// 串口对应的DMA请求通道
#define  USART_TX_DMA_CHANNEL     DMA1_Channel5
// 外设寄存器地址
#define  USART_DR_ADDRESS        (USART1_BASE+0x04)
// 一次发送的数据量
#define  RECEIVEBUFF_SIZE            5000


void USART_Config(void);
void USARTx_DMA_Config(void);
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num);
#endif /* __USARTDMA_H */

中断函数

由于在usart_dma.c的USART_Config函数中使能了 USART 接收中断,当 USART 接收到数据就会执行 DEBUG_USART_IRQHandler函数。里面有USART_GetITStatus函数:

USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET

这使jym想到,存储器到存储器也有个类似的DMA_GetFlagStatus函数:

DMA_GetFlagStatus(DMA_FLAG_TC)==RESET

USART_GetITStatus()和USART_GetFlagStatus()的区别:

USART_GetITStatus常在串口中断函数中使用,USART_GetFlagStatus常在做串口轮询时使用。

USART_GetITStatus在判断相应位是否置1(读SR寄存器)前会先判断相应位的中断是否使能(读CR寄存器);

在void USART_Config(void)函数里可以看到使能了空闲中断:

	//使能空闲中断
	USART_ITConfig(DEBUG_USARTx,USART_IT_IDLE,ENABLE);

USART_GetFlagStatus直接判断相应位是否置1(读SR寄存器),而不会判断相应中断是否开启,通常可用于没开启相应中断时进行判断

串口数据是通过DMA传到ReceiveBuff(在SRAM里),然后由于开了USART 的IDLE接收中断(当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断),中断里面通过Usart_SendArray函数向电脑传ReceiveBuff里面的数据,这样就达到了串口接收数据,串口数据通过DMA传到ReceiveBuff(在SRAM里),ReceiveBuff里面的数据返回到电脑这样一个过程。

void DEBUG_USART_IRQHandler(void)
{
	uint16_t t;
	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET)          //检查中断是否发生
	{	
		DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE);                         //关闭DMA传输

		t = DMA_GetCurrDataCounter(USART_TX_DMA_CHANNEL);              //获取剩余的数据数量
		
        Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t);       //向电脑返回数据(接收数据数量 = SENDBUFF_SIZE - 剩余未传输的数据数量)
		
		DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,RECEIVEBUFF_SIZE);    //重新设置传输的数据数量

		DMA_Cmd(USART_TX_DMA_CHANNEL,ENABLE);                          //开启DMA传输
		
		USART_ReceiveData(DEBUG_USARTx);                              //读取一次数据,不然会一直进中断
		USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE);                //清除串口空闲中断标志位
	}
	
}

对比理解

jym对USART_DMACmd函数的理解:

USART_DMACmd函数用于控制 USART 的 DMA 请求的启动和关闭。它接收三个参数,第一个参数用于设置串口外设;第二个参数设置串口的具体 DMA 请求,包括串口发送请求USART_DMAReq_Tx和接收请求 USART_DMAReq_Rx;第三个参数用于设置启动请求 ENABLE 或者关闭请求 DISABLE;

运行该函数后 USART 的 DMA 发送传输就开始了,如果是串口发送请求(请求发送到串口),即USART_DMAReq_Tx,存储器的数据会发送到串口,如下面的存储器到外设中的代码;如果是串口接收请求(请求存储器接收串口数据),即USART_DMAReq_Rx,串口数据会传给存储器。

存储器到外设:

/* USART1 向 DMA发出TX请求 */
  USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);

外设到存储器:

/* USART1 向 DMA发出RX请求 */
  USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);

其实对于DMA设置,首要理解的就是这个DMA_InitTypeDef结构体。jym采用三种方式定义了这个结构体,下面把每个参数单独摘出来,通过类比来理解。

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr;   // 外设地址
  uint32_t DMA_MemoryBaseAddr;       // 存储器地址
  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_M2M;                  // 存储器到存储器模式
}DMA_InitTypeDef;

DMA_PeripheralBaseAddr:一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。

DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;//外设到存储器
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;//存储器到外设
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;//存储器到存储器

DMA_MemoryBaseAddr:一般设置为自定义存储区的首地址。

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ReceiveBuff;//外设到存储器
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;//存储器到外设
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;//存储器到存储器

DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设;并没有存储器到存储器的方向选择, 当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设到存储器
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//存储器到存储器
#define DMA_DIR_PeripheralDST              ((uint32_t)0x00000010)
#define DMA_DIR_PeripheralSRC              ((uint32_t)0x00000000)

剩下的jym不类比了,因为太多了。其实要想知道DMA把数据从哪传到哪,关键就是上面这三个参数的设置。