stm32学习第十二天

本文详细介绍了STM32的DMA(直接存储器存取)功能,包括DMA的请求、通道、仲裁器的运作,以及数据配置、初始化结构体详解。通过示例展示了如何配置DMA进行存储器到存储器、外设到存储器和存储器到外设的数据传输。文中还涵盖了DMA编程流程,并提供了实际的代码示例,帮助读者理解和应用STM32的DMA功能。
摘要由CSDN通过智能技术生成


记录DMA学习的笔记

一、DMA简介

DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用 CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是 SRAM或者是 FLASH。DMA控制器包含了 DMA1和 DMA2,其中 DMA1有 7个通道,DMA2 有 5 个通道,这里的通道可以理解为传输数据的一种管道。要注意的是 DMA2 只存在于大容量的单片机中。

二、DMA功能框图

在这里插入图片描述

2.1 DMA请求

如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。
DMA1 有 7 个通道,DMA2 有 5 个通道,不同的 DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,具体见 DMA 请求映像表。

在这里插入图片描述

2.2 DMA通道

DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

2.3 DMA仲裁器

当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。

三、数据配置

使用 DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。

3.1 传输来去

1)外设到存储器
当我们使用从外设到存储器传输时,以 ADC 采集为例。DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD采集的数据)的地址。方向我们设置外设为源地址。
2)存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
3)存储器到存储器
当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。

3.2 传输数据和单位

传输数据具体多少由DMA_CNDTR 配置,这是一个 32位的寄存器,一次最多只能传输 65535 个数据。(保留16位,传输数据位2的16次方就是0-65535)
要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCR 的PSIZE[1:0]配置,可以是 8/16/32位,存储器的数据宽由 DMA_CCR 的 MSIZE[1:0]配置,可以是 8/16/32 位。
数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。

3.3 传输完成标志位

传输完成还分两种模式,是一次传输还是循环传输。
**一次传输:**即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。

**循环传输:**是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCR寄存器的 CIRC 循环模式位控制。

四、DMA 初始化结构体详解

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;
  1. DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
  2. DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。
  3. DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的 DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
  4. DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值。
  5. DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
  6. DMA_MemoryInc:如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
  7. DMA_PeripheralDataSize:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定 DMA_CCR寄存器的 PSIZE[1:0]位的值。
  8. DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
  9. DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR 寄存器的 CIRC 位的值。例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。
  10. DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0]位的值。DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
  11. DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。

五、DMA编程

5.1 编程思路

  1. 使能 DMA时钟;
  2. 配置 DMA数据参数;
  3. 使能 DMA,进行传输;
  4. 等待传输完成,并对源数据和目标地址数据进行比较。

5.2 DMA数据储存器到储存器

bsp_dma.h

#ifndef _BSP__LED_H
#define	_BSP__LED_H

#include "stm32f10x.h"

//不想让编译器报错
#define BUFFER_SIZE  32

#define DMA_CHANNEL     DMA1_Channel6 		//这里是channel   最后一个是L小写 
#define DMA_CLOCK       RCC_AHBPeriph_DMA1
#define DMA_FLAG_TC     DMA1_FLAG_TC6

void DMA_Config(void);
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength);
#endif /*_BSP__LED_H*/

bsp_dma.c

#include "bsp_dma.h"

//定义数据传输源 const表示放在内部的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,};
//定义DMA目标传输储存器,内部的SRAM中
uint32_t aDST_Buffer[BUFFER_SIZE];

//原型																		
//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;		
//按照这个套路来编写就好了
																		
void DMA_Config()
{
	DMA_InitTypeDef DMA_CONFIG;
	//开时钟之前原理图可以看出是挂载到AHB上的 
	RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
	// 外设地址 理解为传输数据的地址
	DMA_CONFIG.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
	// 目标地址 
	DMA_CONFIG.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
	// 方向:外设到存储器(这里的外设是内部的FLASH)	
		DMA_CONFIG.DMA_DIR = DMA_DIR_PeripheralSRC;
		// 传输大小	
		DMA_CONFIG.DMA_BufferSize = BUFFER_SIZE;
		// 外设(内部的FLASH)地址递增	    
		DMA_CONFIG.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
		// 内存地址递增
		DMA_CONFIG.DMA_MemoryInc = DMA_MemoryInc_Enable;
		// 外设数据单位	
		DMA_CONFIG.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
		// 内存数据单位
		DMA_CONFIG.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;	 
		// DMA模式,一次或者循环模式
		DMA_CONFIG.DMA_Mode = DMA_Mode_Normal ;
		// 优先级:高	
		DMA_CONFIG.DMA_Priority = DMA_Priority_High;
		// 使能内存到内存的传输
		DMA_CONFIG.DMA_M2M = DMA_M2M_Enable;
		// 配置DMA通道		   
		DMA_Init(DMA_CHANNEL, &DMA_CONFIG);
    //清除DMA数据流传输完成标志位
    DMA_ClearFlag(DMA_FLAG_TC);
		// 使能DMA
		DMA_Cmd(DMA_CHANNEL,ENABLE);
	
}
//比较
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;  
}


bsp_led.h

#ifndef __LED_H
#define	__LED_H


#include "stm32f10x.h"


/* 定义LED连接的GPIO端口, 用户只需要修改下面的代码即可改变控制的LED引脚 */
// R-红色
#define LED1_GPIO_PORT    	GPIOB			              /* GPIO端口 */
#define LED1_GPIO_CLK 	    RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
#define LED1_GPIO_PIN		GPIO_Pin_5			        /* 连接到SCL时钟线的GPIO */

// G-绿色
#define LED2_GPIO_PORT    	GPIOB			              /* GPIO端口 */
#define LED2_GPIO_CLK 	    RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
#define LED2_GPIO_PIN		GPIO_Pin_0			        /* 连接到SCL时钟线的GPIO */

// B-蓝色
#define LED3_GPIO_PORT    	GPIOB			              /* GPIO端口 */
#define LED3_GPIO_CLK 	    RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
#define LED3_GPIO_PIN		GPIO_Pin_1			        /* 连接到SCL时钟线的GPIO */


/** the macro definition to trigger the led on or off 
  * 1 - off
  *0 - on
  */
#define ON  0
#define OFF 1

/* 使用标准的固件库控制IO*/
#define LED1(a)	if (a)	\
					GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2(a)	if (a)	\
					GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3(a)	if (a)	\
					GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN)


/* 直接操作寄存器的方法控制IO */
#define	digitalHi(p,i)		 {p->BSRR=i;}	 //输出为高电平		
#define digitalLo(p,i)		 {p->BRR=i;}	 //输出低电平
#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态


/* 定义控制IO的宏 */
#define LED1_TOGGLE		 digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF		   digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON			   digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2_TOGGLE		 digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF		   digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON			   digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3_TOGGLE		 digitalToggle(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF		   digitalHi(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON			   digitalLo(LED3_GPIO_PORT,LED3_GPIO_PIN)

/* 基本混色,后面高级用法使用PWM可混出全彩颜色,且效果更好 */

//红
#define LED_RED  \
					LED1_ON;\
					LED2_OFF\
					LED3_OFF

//绿
#define LED_GREEN		\
					LED1_OFF;\
					LED2_ON\
					LED3_OFF

//蓝
#define LED_BLUE	\
					LED1_OFF;\
					LED2_OFF\
					LED3_ON

					
//黄(红+绿)					
#define LED_YELLOW	\
					LED1_ON;\
					LED2_ON\
					LED3_OFF
//紫(红+蓝)
#define LED_PURPLE	\
					LED1_ON;\
					LED2_OFF\
					LED3_ON

//青(绿+蓝)
#define LED_CYAN \
					LED1_OFF;\
					LED2_ON\
					LED3_ON
					
//白(红+绿+蓝)
#define LED_WHITE	\
					LED1_ON;\
					LED2_ON\
					LED3_ON
					
//黑(全部关闭)
#define LED_RGBOFF	\
					LED1_OFF;\
					LED2_OFF\
					LED3_OFF

void LED_GPIO_Config(void);

#endif /* __LED_H */

bsp_led.c

  
#include "bsp_led.h"   

void LED_GPIO_Config(void)
{		
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;

		/*开启LED相关的GPIO外设时钟*/
		RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK, ENABLE);
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;	

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;

		/*调用库函数,初始化GPIOF*/
		GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);

		/* 关闭所有led灯	*/
		GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
		
		/* 关闭所有led灯	*/
		GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);	 
    
    /* 关闭所有led灯	*/
		GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
}



main.c


#include 	"bsp_led.h"
#include 	"bsp_dma.h" 
#include 	"stm32f10x.h"

#define SOFT_DELAY 	Delay(0x0FFFFF);
void Delay(__IO u32 nCount); 

/* extern 修饰我们调用函数的来源*/
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];

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

																		
int main(void)
{	
	/* 定义存放比较结果变量 */
	uint8_t TransferStatus;
	/* LED 端口初始化 */
	LED_GPIO_Config();	 
	/* 设置RGB彩色灯为紫色 */
  LED_PURPLE; 
	/* 简单延时函数 */
  Delay(0xFFFFFF);  
	/* DMA传输配置 */
  DMA_Config(); 
	/* 等待DMA传输完成 */
  while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==RESET)
  { 
  }  
	/* 比较源数据与传输后数据 */
  TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
	/* 判断源数据与传输后数据比较结果*/
  if(TransferStatus==0)  
  {
  /* 源数据与传输后数据不相等时RGB彩色灯显示红色 */
    LED_RED;
  }
  else
  { 
   /* 源数据与传输后数据相等时RGB彩色灯显示蓝色 */
    LED_BLUE;
  }
	while (1)
	{
		
	}
}

这样就可以看到我们的灯从紫色等待数据传输完成过后,变成蓝色。

六、DMA储存器到外设

储存器到外设需要调用我们之前写的USART的相关代码。
bsp_dma.h

#ifndef __USARTDMA_H
#define	__USARTDMA_H

#include "stm32f10x.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            20


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

#endif /* __USARTDMA_H */


#include "bsp_dma.h"

uint8_t SendBuff[SENDBUFF_SIZE];

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 USARTx_DMA_Config(void)
{
		DMA_InitTypeDef DMA_InitStructure;
	
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
		// 设置DMA源地址:串口数据寄存器地址*/
    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);
}


#include 	"bsp_led.h"
#include 	"bsp_dma.h" 
#include 	"stm32f10x.h"

#define SOFT_DELAY 	Delay(0x0FFFFF);
void Delay(__IO u32 nCount); 

/* extern 修饰我们调用函数的来源*/

extern uint8_t SendBuff[SENDBUFF_SIZE];

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

																		
int main(void)
{
  uint16_t i;
  /* 初始化USART */
  USART_Config(); 

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

  /*填充将要发送的数据*/
  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);
  }
}


bsp_led的代码都没动
打开我们的串口调试助手
在这里插入图片描述
我们看到对应的数据已经发送到串口助手上了额,此时我们的灯也在同时闪烁。

七、外设发送到储存器

外设发送到储存器需要中断函数
还是调用我们之前的中断函数
首先在stm32f10x_it.c里面添加中断服务函数

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);               			 	//清除串口空闲中断标志位
	}
	
}

bsp_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 */

bsp_dma.c


#include "bsp_dma.h"

uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];


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 USARTx_DMA_Config(void)
{
		DMA_InitTypeDef DMA_InitStructure;
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	    
		// 设置DMA源地址:串口数据寄存器地址*/
        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);
}
//需要数组来判断传输数据的多少。
/*****************  发送一个字节 **********************/
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);
}


main.c


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

#include "stm32f10x.h"
#include "bsp_dma.h"
#include "bsp_led.h"
static void Delay(__IO u32 nCount);

int main(void)
{
  /* 初始化USART */
  USART_Config(); 

  /* 配置使用DMA模式 */
  USARTx_DMA_Config();
  
  /* 配置RGB彩色灯 */
  LED_GPIO_Config();
	
  /* 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--);
}

在这里插入图片描述
这个时候我们即能控制灯的闪烁,还可以同时传输数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永不秃头的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值