野火学习笔记(14) —— DMA —储区访问




1. DMA 简介

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


2. DMA 功能框图

DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握功能框图中的三部分内容即可,具体见 图 DMA 框图 : DMA 控制器的框图。


图 DMA 框图 : DMA 控制器的框图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Z1XXg2k-1626833360058)(https://csdn-img-blog.oss-cn-beijing.aliyuncs.com/e555079880db4e1f8ca5c19cdd240ac5.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NtYWxsX2FzaA==,size_16,color_FFFFFF,t_70)]


2.1 ① DMA 请求

如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。

DMA 有 DMA1 和 DMA2 两个控制器, DMA1 有 7 个通道, DMA2 有 5 个通道,不同的 DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,具体见 DMA 请求映像表。


在这里插入图片描述


在这里插入图片描述


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


2.2 ② 通道

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


2.3 ③ 仲裁器

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


3. DMA 数据配置

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


3.1 从哪里来到哪里去

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

外设到存储器

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

存储器到外设

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

存储器到存储器

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


3.2 要传多少,单位是什么

当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。

以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由 DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。

要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8 位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCR 的 PSIZE[1:0] 配置,可以是 8/16/32 位,存储器的数据宽度由 DMA_CCR 的 MSIZE[1:0] 配置,可以是 8/16/32 位。

在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。


3.3 什么时候传输完成

数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个 DMA 通道在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考 DMA 中断状态寄存器 DMA_ISR 的详细描述。

传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCR 寄存器的 CIRC 循环模式位控制。


4. DMA 初始化结构体详解

标准库函数对每个外设都建立了一个初始化结构体 xxx_InitTypeDef(xxx 为外设名称),结构体成员用于设置外设工作参数,并由标准库函数 xxx_Init() 调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的。

结构体 xxx_InitTypeDef 和库函数 xxx_Init 配合使用是标准库精髓所在,理解了结构体 xxx_InitTypeDef 每个成员意义基本上就可以对该外设运用自如。结构体 xxx_InitTypeDef 定义在 stm32f10x_xxx.h(后面 xxx 为外设名称) 文件中,库函数 xxx_Init 定义在 stm32f10x_xxx.c 文件中,编程时我们可以结合这两个文件内注释使用。


DMA_ InitTypeDef 初始化结构体

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 即可启动存储器到存储器模式。

5. 利用 DMA 存储串口接收数据实验

利用 DMA 存储 串口接收数据,相当于外设到存储器功能。我们要定义一个数据缓存区用于存放串口接收到的数据。本次实验 DMA 模式是工作在正常缓存模式,不是循环缓存模式。


5.1 硬件设计

DMA 存储串口接收数据可以不需要其他硬件要求,示例中用到芯片 PA9(USART1_TX)、PA10(USART1_RX)引脚。


5.2 编程要点

1)使能 DMA 时钟;
2)配置 DMA 数据参数;
3)使能 DMA,进行传输;


5.3 代码分析

本代码用到的库有:

#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_dma.h"


uart.h

#ifndef __UART_H
#define __UART_H	


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


void UART_GPIO_Init(void);
void UART_Config(u32 bound);
void UART_NVIC_Init(void);
void UART_Init(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch);


#endif


uart.c

#include "uart.h"


/*******************************************************************************
* 函 数 名         : UART_GPIO_Init
* 函数功能		   : UART GPIO 初始化函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
void UART_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 使能PA端口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// USART1_TX  GPIOA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 
	// 复用推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	
	// IO口速度为50MHz	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	// 根据设定参数初始化
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// USART1_RX  GPIOA.10初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	// 浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	// 根据设定参数初始化
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}


/*******************************************************************************
* 函 数 名         : UART_Config
* 函数功能		   : UART 配置初始化函数
* 输    入         : 串口波特率
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
void UART_Config(u32 bound)
{
	USART_InitTypeDef USART_InitStructure;
	
	// 使能 USART1 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	// 串口波特率
	USART_InitStructure.USART_BaudRate = bound;
	// 字长为8位数据格式
	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;	
	// 初始化串口1
	USART_Init(USART1, &USART_InitStructure); 
}


/*******************************************************************************
* 函 数 名         : UART_NVIC_Init
* 函数功能		   : UART NVIC 初始化函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
void UART_NVIC_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	// Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	// 抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
	// 子优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	// IRQ通道使能
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	// 根据指定的参数初始化VIC寄存器
	NVIC_Init(&NVIC_InitStructure);	
	
	// 开启串口接受中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}


/*******************************************************************************
* 函 数 名         : UART_Init
* 函数功能		   : UART 总初始化函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
void UART_Init(void)
{
	// UART GPIO 初始化
	UART_GPIO_Init();
	// UART 配置初始化,波特率为 9600
	UART_Config(9600);
	// UART NVIC 初始化
	UART_NVIC_Init();
	
	// 使能串口1 
	USART_Cmd(USART1, ENABLE);
}



/*******************************************************************************
* 函 数 名         : Usart_SendByte
* 函数功能		   : Usart 发送一个字节
* 输    入         : 
					pUSARTx:串口
					ch:字节
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
void Usart_SendByte(USART_TypeDef * pUSARTx, uint8_t ch)
{
	// 发送一个字节数据到USART
	USART_SendData(pUSARTx,ch);
		
	// 等待发送数据寄存器为空
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}


/*******************************************************************************
* 函 数 名         : Usart_SendArray
* 函数功能		   : Usart 发送数组
* 输    入         : 
					pUSARTx:串口
					array:数组头
					num:数组个数
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
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);
}


/*******************************************************************************
* 函 数 名         : Usart_SendString
* 函数功能		   : Usart 发送字符串
* 输    入         : 
					pUSARTx:串口
					str:字符串
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
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);
}


/*******************************************************************************
* 函 数 名         : Usart_SendHalfWord
* 函数功能		   : Usart 发送一个16位数
* 输    入         : 
					pUSARTx:串口
					ch: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);	
}



/*******************************************************************************
* 函 数 名         : fputc
* 函数功能		   : 重定向c库函数printf到串口,重定向后可使用printf函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
int fputc(int ch, FILE *f)
{
	// 发送一个字节数据到串口
	USART_SendData(USART1, (uint8_t) ch);
	
	// 等待发送完毕
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);		

	return (ch);
}


/*******************************************************************************
* 函 数 名         : fgetc
* 函数功能		   : 重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
int fgetc(FILE *f)
{
	// 等待串口输入数据
	while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);

	return (int)USART_ReceiveData(USART1);
}



/*******************************************************************************
* 函 数 名         : USART1_IRQHandler
* 函数功能		   : USART1 中断函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :每接收到一个数据就发送出去
*******************************************************************************/
void USART1_IRQHandler(void)
{
	uint8_t ucTemp;
	if(USART_GetITStatus(USART1, USART_IT_RXNE)!= RESET)
	{
		// 接收数据
		ucTemp = USART_ReceiveData(USART1);
		// 发送接收到的数据
		USART_SendData(USART1,ucTemp);    
	}
}


dma.h

#ifndef __DMA_H
#define __DMA_H 	


#include "stm32f10x.h"


// 一次发送的数据量
#define RECEIVEBUFF_SIZE 8


extern DMA_InitTypeDef DMA_InitStructure;


extern unsigned char ReceiveBuff[RECEIVEBUFF_SIZE];


void DMA_Init_Config(void);
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);

#endif


dma.c

#include "dma.h"

DMA_InitTypeDef DMA_InitStructure;
// 接收缓存区
unsigned char ReceiveBuff[RECEIVEBUFF_SIZE];


/*******************************************************************************
* 函 数 名         : DMA_Init_Config
* 函数功能		   : DMA 初始化函数
* 输    入         : 无
* 输    出         : 无
* 说	明		   :无
*******************************************************************************/
void DMA_Init_Config(void)
{
	// 开启DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	//将DMA的通道1寄存器重设为缺省值
	DMA_DeInit(DMA1_Channel5);
	
	// 设置DMA源地址:串口数据寄存器地址
	DMA_InitStructure.DMA_PeripheralBaseAddr = (USART1_BASE+0x04);
	// 内存地址(要传输的变量的指针)
	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_Priority = DMA_Priority_Medium;
	// 禁止内存到内存的传输
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	// 配置DMA通道
	DMA_Init(DMA1_Channel5, &DMA_InitStructure);
	
	//清除DMA数据流传输完成标志位
    DMA_ClearFlag(DMA1_FLAG_TC5);
	// 使能DMA
	DMA_Cmd(DMA1_Channel5,ENABLE);
}


/*******************************************************************************
* 函 数 名         : MYDMA_Enable
* 函数功能		   : DMA 重新设置缓冲大小
* 输    入         : 无
* 输    出         : 无
* 说	明		   :用于接收缓存区循环存储作用
*******************************************************************************/
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
	// 关闭USART1 TX DMA1 所指示的通道 
	DMA_Cmd(DMA_CHx, DISABLE); 
	// 重新设置缓冲大小,指向数组0
	// DMA1_Channel5->CNDTR = 0x08 ,等于 DMA_SetCurrDataCounter(DMA_CHx,RECEIVEBUFF_SIZE);
	// DMA1_Channel5->CNDTR = 0x08; 
	DMA_SetCurrDataCounter(DMA_CHx,RECEIVEBUFF_SIZE);
	// 使能USART1 TX DMA1 所指示的通道 
	DMA_Cmd(DMA_CHx, ENABLE); 
} 


main.c

#include "uart.h"
#include "dma.h"


int main(void)
{	
	// 串口初始化
	UART_Init();
	// DMA 初始化
	DMA_Init_Config();
	// USART1 向 DMA发出RX请求
	USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
	
	Usart_SendString(USART1,"这个使用Usart_SendString发送的\n");
	printf("这个使用printf发送的\n\n\n\n");
	
	while(1)
	{
		// 判断通道 5 传输完成标志位
		if (DMA_GetFlagStatus(DMA1_FLAG_TC5)!=RESET)
		{
			// 清除通道4传输完成标志
			DMA_ClearFlag(DMA1_FLAG_TC5);
			// DMA 重新设置缓冲大小
			MYDMA_Enable(DMA1_Channel5);
		}
	}
}


问题点一:代码中为什么会用到 DMA1_Channel5(通道5)呢?
解答:
《STM32F1xx中文参考手册.pdf》 中 148 页有说明。因为我们要把 USART1_RX 接收到的数据放到 DMA 中,按下面表格所示 USART1_RX 对应的就是 DMA1_Channel5(通道5)。


在这里插入图片描述


在这里插入图片描述



DMA_CNDTRx 寄存器是数据传输数量。假如想知道数据传输到哪里,可通过以下公式计算:


(DMA_InitStructure.DMA_BufferSize - 1) - DMA_CNDTRx = 当前传输到缓存区的位置

例如上述代码举例,在 dma.c 里面的 DMA_Init_Config 函数,设置了 DMA_InitStructure.DMA_BufferSize = 0x0008,通过串口调试助手发送两个字节的数据给 STM32, 仿真可看到 DMA1_Channel5->CNDTR = 0x00000006。通过上述公式得出

DMA_InitStructure.DMA_BufferSize = 0x0008

DMA_CNDTRx = 0x00000006

当前传输到缓存区的位置 = (DMA_InitStructure.DMA_BufferSize - 1) - DMA_CNDTRx

所以,当前传输到缓存区的位置 = 2,即传输到 ReceiveBuff[2] 里面。


《STM32F1xx中文参考手册.pdf》 152页


在这里插入图片描述




部分摘抄自:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸道开发板》.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值