STM32单片机学习资料(十一)-DMA直接存储器存取(一)

写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节包括前几节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火B站账号:野火官方B站账号链接

参考资料

《STM32F10x芯片参考手册-中文版》、《STM32F10x芯片数据手册-中文版》、《STM32F10x Cortex-M3编程手册-英文版》、《CM3权威指南-中文版》、《野火开发板相关资料》等;
本文中理论知识的梳理主要参考《野火开发板相关资料》,当然芯片参考手册中也有关于USART的介绍,不过理论性较强,在本文中可能不会大量引用,主要引用野火的资料;

本文仅为个人笔记,不严谨处还望指正,另外本文仅为初稿,细节问题后续修改。

学习目标

1.简单了解DMA,并完成从存储器到存储器、从外设到存储器和从存储器到外设实验。

一、DMA简介及功能框图

1.DMA简介

  DMA(Direct Memory Access)—直接存储器存取,属于单片机的一个外设,它可以用来管理存储器到存储器(M→M)、外设到存储器(P→M)和存储器到外设(M→P)中间的数据传输,传输数据的过程中不需要CPU干预,即可以在DMA传输数据的同时,利用CPU进行其他的操作(多线程)。
  DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,,DMA2有5个通道,每个通道专门来管理来自于一个或多个外设对存储器访问的请求,另外还有一个仲裁器来协调各个DMA请求的优先权。要注意的是DMA2只存在于大容量的单片机中,如STM32F103单片机。

2.DMA主要特性

该部分内容来自《STM32F10x中文参考手册》;
● 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过 软件来配置。
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、 中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目 标地址必须按数据传输宽度对齐。
● 支持循环的缓冲器管理
● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志 逻辑或成为一个单独的中断请求。
● 存储器和存储器间的传输
● 外设和存储器、存储器和外设之间的传输
● 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
● 可编程的数据传输数目:最大为65535

3.DMA框图

图11-1:DMA框图
在这里插入图片描述

(1)DMA请求和通道

  如果外设想通过DMA来传输数据,必须先向DMA控制器发送DMA请求,DMA收到请求信号后,控制器会给外设一个应答信号,当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。
  DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置。
  虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
  表11-1和表11-2是DMA请求映像表:
表11-1:DMA1请求映像表

外设通道1通道2通道3通道4通道5通道6通道7
ADC1ADC1
SPI/I2SSPI1_RXSPI1_TXSPI/I2S2_RXSPI/I2S2_TX
USARTUSART3_TXUSART3_RXUSART1_TXUSART1_RXUSART2_RXUSART2_TX
I2CI2C2_TXI2C2_RXI2C1_TXI2C1_RX
TIM1TIM1_CH1TIM1_CH2TIM1_TX4
TIM1_TRIG
TIM1_COM
TIM1_UPTIM1_CH3
TIM2TIM2_CH3TIM2_UPTIM2_CH1TIM2_CH2
TIM2_CH4
TIM3TIM3_CH3TIM3_CH4
TIM3_UP
TIM3_CH1
TIM3_TRIG
TIM4TIM4_CH1TIM4_CH2TIM4_CH3TIM4_UP

表11-2:DMA2请求映像表

外设通道1通道2通道3通道4通道5
ADC3ADC3
SPI1/I2S3SPI/I2S3_RXSPI/I2S3_TX
UASRT4USART4_RXUSART4_TX
SDIOSDIO
TIM5TIM5_CH4
TIM5_TRIG
TIM5_CH3
TIM5_UP
TIM5_CH2TIM5_CH1
TIM6/DAC通道1TIM6_UP/
DAC通道1
TIM7/DAC通道2TIM7_UP/
DAC通道2
TIM8TIM8_CH3
TIM8_UP
TIM8_CH4
TIM8_TRIG
TIM8_COM
TIM8_CH1TIM8_CH2

注:ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。

(2)仲裁器

  仲裁器根据通道请求的优先级来启动外设/存储器的访问,优先级的管理分2个阶段:
   ●软件阶段:每个通道的优先级可以在DMA_CCRx寄存器的PL[1:0]位设置,有4个等级:
    —最高优先级
    —高优先级
    —中等优先级
    —低优先级
   ●硬件阶段:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道优先级高,如,通道2的优先级高于通道4。同时,在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。

二、DMA数据配置(初始化结构体注释)

  这部分内容将结合以下程序进行学习:

//来源:#include "stm32f10x_dma.h"
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.数据从哪里来到哪里去

  uint32_t DMA_PeripheralBaseAddr;	//外设地址
  uint32_t DMA_MemoryBaseAddr;		//存储器地址
  uint32_t DMA_DIR;					//传输方向
  //涉及到的寄存器
  //外设地址:DMA_CPAR
  //存储器地址:DMA_CMAR
  //传输方向:DMA_CCR:DIR

  DMA的数据传输方向有三个:从存储器到存储器、从外设到存储器、从存储器到外设。具体的方向可通过DMA_CCR寄存器的位4:DIR配置:0表示从外设到存储器,1表示从存储器到外设。这里涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。

(1)存储器到外设

  存储器到外设,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储将要通过串口发送到电脑的数据)的地址,方向我们设置外设为目标地址。

(2)外设到存储器

  外设到存储器,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址,DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。

(3)存储器到存储器

  存储器到存储器,以内部FLASH向内部SRAM复制数据为例。DMA外设寄存器的地址对应的就是内部FLASH(我们这里需要将内部FLASH当作一个外设来看)的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。

2.数据要传多少,传的单位是什么

  uint32_t DMA_BufferSize;			//传输数目
  uint32_t DMA_PeripheralInc;		//外设地址增量模式
  uint32_t DMA_MemoryInc;			//存储器地址增量模式
  uint32_t DMA_PeripheralDataSize;	//外设数据宽度
  uint32_t DMA_MemoryDataSize;		//外设数据宽度
  //涉及到的寄存器
  //传输数目:DMA_CNDTR
  //外设地址是否递增:DMA_CCRx:PINC
  //存储器地址事都递增:DMA_CCRx:MINC
  //外设数据宽度:DMA_CCRx:PSIZE
  //存储器数据宽度:DMA_CCRx:MSIZE

  以串口向电脑发送数据为例,我们一次性可以向电脑发送数据的数量,由DMA_CNDTR寄存器配置,该寄存器是32为寄存器,一次最多传输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.什么时候传输完成

  uint32_t DMA_Mode;				//模式选择
  //模式选择:DMA_CCRx:CIRC
  //传输过半、传输完成、传输出错:DMA_ISR

  数据什么时候传输完成,可以通过查询标志位或通过中断的方式来鉴别。每个DMA通道在DMA传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。
  传输完成还分两种模式:一次传输和循环传输。

三、程序

  本次实验会用到LED灯,关于LED灯的程序会放在本文最后,另外本次实验会用到USART,USART的程序也会放在最后,程序仅供参考。

1.DMA存储器到存储器模拟实验

实验目标:实现数据在两个内存的快速拷贝,先定义一个静态的源数据,存放在内部FLASH,然后使用DMA传输把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和目标地址的数据,看传输是否准确,本实验通过LED灯指示传输是否准确。
编程要点:
   (1)使能DMA时钟;
   (2)配置DMA数据参数;
   (3)使能DMA,进行传输;
   (4)等待传输完成,并对源数据和目标地址数据进行比较。
程序11-1:bsp_dma_mtm.c

#include "bsp_dma_mtm.h"

/* 定义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];
																		
#if 0		//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;
#endif

void MTM_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	//打开DMA时钟
	RCC_AHBPeriphClockCmd(MTM_DMA_CLK, ENABLE);
	//设置源数据地址
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
	//设置目标源地址
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
	//设置传输方向,外设地址为发送方
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
	//设置传输数据的大小,32位数据
	DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE;
	//设置外设地址增量模式,递增
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	//设置内存地址增量模式,递增
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//设置外设地址宽度,32位
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
	//设置内存地址宽度,32位
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
	//设置传输模式,只发送一次
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	//设置通道优先级,设置为高
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;
	//设置为存储器到存储器
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;
	//配置DMA通道
	DMA_Init(MTM_DMA_CHANNEL, &DMA_InitStruct);
	//清除DMA传输完成标志位
	DMA_ClearFlag(MTM_DMA_FLAG_TC);
	//使能DMA
	DMA_Cmd(MTM_DMA_CHANNEL, ENABLE);
}

uint8_t Buffercmp(const uint32_t *pBuffer,uint32_t *pBuffer1,uint16_t Bufferlength)
{
	while(Bufferlength--)
	{
		if(*pBuffer1 != *pBuffer)//指针的比较
		{
			return 0;
		}
		pBuffer++;
		pBuffer1++;
	}
	return 1;
}


程序11-2:bsp_dma_mtm.h

#ifndef __BSP_DMA_MTM_H
#define __BSP_DMA_MTM_H

#include "stm32f10x.h"

#define BUFFER_SIZE			32

#define MTM_DMA_CHANNEL		DMA1_Channel6
#define MTM_DMA_CLK			RCC_AHBPeriph_DMA1
#define MTM_DMA_FLAG_TC		DMA1_FLAG_TC6


void MTM_DMA_Config(void);
uint8_t Buffercmp(const uint32_t *pBuffer,uint32_t *pBuffer1,uint16_t Bufferlength);

#endif /*__BSP_DMA_MTM_H*/


程序11-3:main.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_dma_mtm.h"

extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];

void Delay(uint32_t count)
{
	for(;count != 0;count--);
}

int main(void)
{
	uint8_t transferstatu = 0;
	
	LED_GPIO_Config();
	MTM_DMA_Config();
	LED_YELLOW;
	Delay(0xfffff);
	while(DMA_GetFlagStatus(MTM_DMA_FLAG_TC) == RESET);//等待传输完成
	transferstatu = Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE);//判断是否传输完成
	if(transferstatu == 0)
	{
		LED_RED;
	}
	else
	{
		LED_GREEN;
	}
	while(1)
	{
		
	}
}


2.DMA存储器到外设模拟实验

实验目标:预设一个数组,利用DMA,通过串口将数组的数据发送到电脑,并用串口接收器接收数据。
编程要点:
   (1) 配置 USART 通信功能;
   (2) 设置串口 DMA 工作参数;
   (3) 使能 DMA;
   (4) DMA 传输同时 CPU 可以运行其他任务。
程序11-4:bsp_dma_mtp.c

#include "bsp_dma_mtp.h"

uint8_t SendBuff[SENDBUFF_SIZE];

#if 0
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;
#endif

void MTP_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	//打开DMA时钟
	RCC_AHBPeriphClockCmd(MTP_DMA_TX_CLK, ENABLE);
	//设置DMA源地址,串口数据寄存器地址
	DMA_InitStruct.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
	//设置内存地址
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
	//设置传输方向,内存发送到外设
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
	//设置传输数据的大小
	DMA_InitStruct.DMA_BufferSize = SENDBUFF_SIZE;
	//设置外设地址增量模式,不递增
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	//设置内存地址增量模式,递增
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//设置外设地址宽度
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	//设置内存地址宽度
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	//设置传输模式,只发送一次
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	//设置通道优先级,设置为中
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
	//关闭存储器到存储器
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
	//配置DMA通道
	DMA_Init(MTP_DMA_TX_CHANNEL, &DMA_InitStruct);
	//清除DMA传输完成标志位
	DMA_ClearFlag(MTP_DMA_TX_FLAG_TC);
	//使能DMA
	DMA_Cmd(MTP_DMA_TX_CHANNEL, ENABLE);
}


程序11-5:bsp_dma_mtp.h

#ifndef __BSP_DMA_MTP_H
#define __BSP_DMA_MTP_H

#include "stm32f10x.h"

#define SENDBUFF_SIZE			5000
#define USART_DR_ADDRESS		(USART1_BASE+0X04)

#define MTP_DMA_TX_CHANNEL		DMA1_Channel4
#define MTP_DMA_TX_CLK			RCC_AHBPeriph_DMA1
#define MTP_DMA_TX_FLAG_TC		DMA1_FLAG_TC4

void MTP_DMA_Config(void);

#endif /*__BSP_DMA_MTP_H*/


程序11-6:main.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_dma_mtp.h"

extern uint8_t SendBuff[SENDBUFF_SIZE];

void Delay(uint32_t count)
{
	for(;count != 0;count--);
}

int main(void)
{
	uint16_t i;
	
	LED_GPIO_Config();
	USART_Config();
	MTP_DMA_Config();
	
	for(i=0;i<SENDBUFF_SIZE;i++)
	{
		SendBuff[i] = 'p';
	}
	
	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
	
	while(1)
	{
		LED_G_TOGGLE;
		Delay(0xfffff);
	}
}


3.DMA外设到存储器(这部分仅作记录,不做实验)

  这部分内容在AD部分会用到,这里只简单对程序做个小实验记录,后续在AD部分(如果我更新到的话)再详细学习。
程序11-7:bsp_dma_ptm.c

#include "bsp_dma_ptm.h"

uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];

#if 0
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;
#endif

void PTM_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	//打开DMA时钟
	RCC_AHBPeriphClockCmd(PTM_DMA_RX_CLK, ENABLE);
	//设置DMA源地址,串口数据寄存器地址
	DMA_InitStruct.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
	//设置内存地址
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)ReceiveBuff;
	//设置传输方向,内存发送到外设
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
	//设置传输数据的大小
	DMA_InitStruct.DMA_BufferSize = RECEIVEBUFF_SIZE;
	//设置外设地址增量模式,不递增
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	//设置内存地址增量模式,递增
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//设置外设地址宽度
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	//设置内存地址宽度
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	//设置传输模式,只发送一次
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
	//设置通道优先级,设置为中
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
	//关闭存储器到存储器
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
	//配置DMA通道
	DMA_Init(PTM_DMA_RX_CHANNEL, &DMA_InitStruct);
	//清除DMA传输完成标志位
	DMA_ClearFlag(PTM_DMA_RX_FLAG_TC);
	//使能DMA
	DMA_Cmd(PTM_DMA_RX_CHANNEL, ENABLE);
}


程序11-8:bsp_dma_ptm.h

#ifndef __BSP_DMA_PTM_H
#define __BSP_DMA_PTM_H

#include "stm32f10x.h"

#define RECEIVEBUFF_SIZE		5000
#define USART_DR_ADDRESS		(USART1_BASE+0x04)

#define PTM_DMA_RX_CHANNEL		DMA1_Channel5
#define PTM_DMA_RX_CLK			RCC_AHBPeriph_DMA1
#define PTM_DMA_RX_FLAG_TC		DMA1_FLAG_TC5

void PTM_DMA_Config(void);

#endif /*__BSP_DMA_PTM_H*/


程序11-9:stm32f10x_it.c(在该文件中加入下面的程序)

#include "bsp_usart.h"
#include "bsp_dma_ptm.h"

#if 1

void DEBUG_USART_IRQHandler(void)
{
	uint16_t t;
	if(USART_GetITStatus(DEBUG_USARTx, USART_IT_IDLE) == SET)	//如果产生中断
	{
		DMA_Cmd(PTM_DMA_RX_CHANNEL, DISABLE);	//关闭DMA传输
		
		t = DMA_GetCurrDataCounter(PTM_DMA_RX_CHANNEL);	//获取剩余的数据量
		
		Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t);	//向电脑返回接收到的数据
		
		DMA_SetCurrDataCounter(PTM_DMA_RX_CHANNEL, RECEIVEBUFF_SIZE);	//重新设置传输的数据量
		
		DMA_Cmd(PTM_DMA_RX_CHANNEL, ENABLE);	//打开DMA传输
		
		USART_ReceiveData(DEBUG_USARTx);	//接收一次数据,不然会一直进中断
		USART_ClearFlag(DEBUG_USARTx, USART_FLAG_IDLE);	//清除串口空闲标志位,结束中断
	}
}

#endif

程序11-10:main.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_dma_ptm.h"

extern uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];

void Delay(uint32_t count)
{
	for(;count != 0;count--);
}

int main(void)
{
	LED_GPIO_Config();
	USART_Config();
	PTM_DMA_Config();
	printf("\r\nDMA外设到存储器模式,用电脑向开发板串口发送数据,数据会返回到电脑。\r\n");
	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
	while(1)
	{
		LED_G_TOGGLE;
		Delay(0xfffff);
	}
}


4.LED和USART相关程序

程序11-11:bsp_usart.c

#include "bsp_usart.h"

void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	//复位USART
	USART_DeInit(DEBUG_USARTx);
	//打开时钟
	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);
	
	//初始化TX端口GPIO,复用推挽输出
	GPIO_InitStruct.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStruct);
	//初始化RX端口GPIO,浮空输入
	GPIO_InitStruct.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStruct);
	//初始化串口
	USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
	USART_Init(DEBUG_USARTx, &USART_InitStruct);

	//使能空闲中断,接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
	//使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);
	
}
//发送一个字节
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data)
{
	USART_SendData(DEBUG_USARTx, data);
	while(USART_GetFlagStatus(DEBUG_USARTx, 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_SendByte(pUSARTx,array[i]);
	}
	while(USART_GetFlagStatus(DEBUG_USARTx, 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);
}


程序11-12:bsp_usart.h

#ifndef __BSP_USART_H
#define __BSP_USART_H

#include "stm32f10x.h"
#include <stdio.h>
//USART—GPIO定义
#define DEBUG_USART_TX_GPIO_PIN				GPIO_Pin_9		//TX
#define DEBUG_USART_TX_GPIO_PORT			GPIOA					//TX
#define DEBUG_USART_RX_GPIO_PIN				GPIO_Pin_10		//RX
#define DEBUG_USART_RX_GPIO_PORT			GPIOA					//RX
#define DEBUG_USART_GPIO_CLK				RCC_APB2Periph_GPIOA	//定义GPIO时钟
#define DEBUG_USART_GPIO_APBxClkCmd			RCC_APB2PeriphClockCmd
//USART
#define DEBUG_USARTx						USART1
#define DEBUG_USART_BAUDRATE				115200	//波特率
#define DEBUG_USART_CLK						RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd				RCC_APB2PeriphClockCmd
//NVIC
#define DEBUG_USART_IRQ						USART1_IRQn
#define DEBUG_USART_IRQHandler				USART1_IRQHandler


void USART_Config(void);
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data);
void Usart_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint16_t num);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);

#endif	/* __BSP_USART_H */


程序11-13:bsp_led.c

#include "bsp_led.h"

void LED_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;		//定义变量,方便赋值
	
	RCC_APB2PeriphClockCmd(LED_GPIO_CLK,ENABLE);	//打开APB2时钟,GPIO挂载在APB2
	//GPIO_PIN的部分用或将用到的管脚初始化为一个十六进制数据,原本是三个十六进制
	GPIO_InitStruct.GPIO_Pin = (LED_G_GPIO_PIN);	//设置需要用到的管脚,LED_G_GPIO_PIN看.h文件
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;	//设置输出模式为推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	//设置输出速率为50MHz
	
	GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStruct);	//加上&,方便取值	//初始化GPIO
	//初始化红灯和蓝灯,主要是管脚和GPIO
	GPIO_InitStruct.GPIO_Pin = (LED_B_GPIO_PIN);
	GPIO_Init(LED_B_GPIO_PORT, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Pin = (LED_R_GPIO_PIN);
	GPIO_Init(LED_R_GPIO_PORT, &GPIO_InitStruct);
	//初始化上电后关闭所有灯
	GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
	GPIO_SetBits(LED_B_GPIO_PORT,LED_B_GPIO_PIN);
	GPIO_SetBits(LED_R_GPIO_PORT,LED_R_GPIO_PIN);
}

程序11-14:bsp_led.h

#ifndef _BSP_LED_H
#define _BSP_LED_H

#include "stm32f10x.h"		//要包含固件库的.h文件

#define LED_G_GPIO_PIN			GPIO_Pin_0		//定义绿灯管脚号
#define LED_G_GPIO_PORT			GPIOB							//定义用到的GPIO
#define LED_G_GPIO_CLK			RCC_APB2Periph_GPIOB	//定义GPIO时钟

#define LED_B_GPIO_PIN			GPIO_Pin_1		//定义蓝灯管脚号
#define LED_B_GPIO_PORT			GPIOB							//定义用到的GPIO
#define LED_B_GPIO_CLK			RCC_APB2Periph_GPIOB	//定义GPIO时钟

#define LED_R_GPIO_PIN			GPIO_Pin_5		//定义红灯管脚号
#define LED_R_GPIO_PORT			GPIOB							//定义用到的GPIO
#define LED_R_GPIO_CLK			RCC_APB2Periph_GPIOB	//定义GPIO时钟

#define LED_GPIO_CLK			LED_G_GPIO_CLK|LED_B_GPIO_CLK|LED_R_GPIO_CLK

//下面几个声明可以不管,直接按照main中屏蔽掉的部分写也行
//下面用到的“\”符号为续行符,其后面不能由任何东西,意为这行下面的一行跟这行是一起的,分行写看起来比较清晰
#define ON						1
#define OFF						0		
#define LED(a)					if(a) \
									GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); \
								else \
									GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);

//定义控制LED灯的宏
#define digitalHi(p,i)			{p->BSRR=i;}//输出为高电平
#define digitalLo(p,i)			{p->BRR=i;}//输出为低电平
#define digitalToggle(p,i)		{p->ODR^=i;}//输出反转

#define LED_G_ON				digitalLo(LED_G_GPIO_PORT,LED_G_GPIO_PIN)
#define LED_G_OFF				digitalHi(LED_G_GPIO_PORT,LED_G_GPIO_PIN)
#define LED_G_TOGGLE			digitalToggle(LED_G_GPIO_PORT,LED_G_GPIO_PIN)

#define LED_B_ON				digitalLo(LED_B_GPIO_PORT,LED_B_GPIO_PIN)
#define LED_B_OFF				digitalHi(LED_B_GPIO_PORT,LED_B_GPIO_PIN)
#define LED_B_TOGGLE			digitalToggle(LED_B_GPIO_PORT,LED_B_GPIO_PIN)

#define LED_R_ON				digitalLo(LED_R_GPIO_PORT,LED_R_GPIO_PIN)
#define LED_R_OFF				digitalHi(LED_R_GPIO_PORT,LED_R_GPIO_PIN)
#define LED_R_TOGGLE			digitalToggle(LED_R_GPIO_PORT,LED_R_GPIO_PIN)

//绿灯
#define LED_GREEN				LED_G_ON;\
								LED_B_OFF;\
								LED_R_OFF;
//红灯
#define LED_RED					LED_G_OFF;\
								LED_B_OFF;\
								LED_R_ON;
//蓝灯
#define LED_BLUE				LED_G_OFF;\
								LED_B_ON;\
								LED_R_OFF;
//黄灯
#define LED_YELLOW				LED_G_ON;\
								LED_B_OFF;\
								LED_R_ON;
//全关
#define LED_OFF					LED_G_OFF;\
								LED_B_OFF;\
								LED_R_OFF;


void LED_GPIO_Config(void);		//.c文件中的函数声明

#endif	/*_BSP_LED_H*/


以上内容还有很多不足之处,笔者会进行进一步的学习,等再有新的心得会再次更新相关内容,同时如果笔者发现上述内容有错误,也会及时进行更改,感谢观看。

以上仅为笔记记录,不作教学等用途,感谢观看。

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值