STM32中的DMA

概念

全称是Direct Memory Access,中文意思为直接存储器访问。

DMA可用于实现外设与存储器之间或者存储器与存储器之间数据传输的高效性。之所以称为高效,是因为DMA传输数据移动过程无需CPU直接操作,这样节省的 CPU 资源就可供其它操作使用。从硬件层面来理解,DMA就好像是RAM与I/O设备间数据传输的通路,外设与存储器之间或者存储器与存储器之间可以直接在这条通路上进行数据传输。这里说的外设一般指外设的数据寄存器,比如ADC、 SPI、 I2C等外设的数据寄存器,存储器一般是指片内SRAM、外部存储器、片内 Flash 等。

利用DMA获取数据的流程

  • UART控制器读取外设发送的数据
  • DMA硬件上自动读取UART数据寄存器的数据
  • DMA硬件上自动将获取的数据搬移到内存中
  • UART控制器给CPU发送中断信号通知CPU数据读取完毕
  • CPU可以访问内存中的数据

总结:CPU无需频繁的访问数据寄存器,CPU只关心内存

利用DMA发送数据的流程

  • CPU将数据写入指定的内存中
  • DMA硬件上自动从指定的内存中获取要发送的数据
  • DMA硬件上自动将数据搬移到数据寄存器中
  • UART控制器硬件上自动将数据发送出去
  • DMA给CPU发送中断信号通知数据发送完毕

结论:CPU无需频繁的访问数据寄存器,CPU只关心内存

DMA的特性

  • STM32F103有 2 个 DMA 控制器,分别是DMA1和DMA2
  • DMA1 有 7 个通道
  • DMA2 有 5个通道
  • 每个通道专门用来管理来自于一个或多个外设对存储器访问的请求

在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4

代码实现

标准库函数和结构体

复制代码
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发送数据完成后,使用中断发送给CPU结束信号,需要匹配中断和DMA已经NVIC控制器

objectivec 复制代码
void My_DMA_Init(void)
{
	// 1.打开DMA1控制器时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	// 2.配置DMA1通道4作为串口1发送 : 内存 -> 寄存器
	DMA_InitTypeDef DMA_Config;
	DMA_Config.DMA_MemoryBaseAddr = (u32)UART1DMA_TxBuff; // 内存缓冲区首地址
	DMA_Config.DMA_PeripheralBaseAddr = (u32)&USART1->DR; // 寄存器首地址
	DMA_Config.DMA_BufferSize = UART1DMA_TXBUFF_SIZE; // 内存缓冲区大小
	DMA_Config.DMA_DIR = DMA_DIR_PeripheralDST; // 内存->寄存器
	DMA_Config.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 寄存器不自增
	DMA_Config.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存自增
	DMA_Config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存搬移字节为单位
	DMA_Config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 寄存器字节位单位
	DMA_Config.DMA_Mode = DMA_Mode_Normal; // 普通模式
	DMA_Config.DMA_Priority = DMA_Priority_Medium; // 中等
	DMA_Config.DMA_M2M = DMA_M2M_Disable; // 禁止内存之间拷贝
	DMA_Init(DMA1_Channel4, &DMA_Config);
	
	// 3.配置DMA1通道4支持DMA_IT_TC中断 
	DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
	
	// 4.配置NVIC支持DMA1通道4中断 
	NVIC_InitTypeDef NVIC_Config;
	NVIC_Config.NVIC_IRQChannel = DMA1_Channel4_IRQn; // DMA1通道4中断 
	NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Config.NVIC_IRQChannelSubPriority = 2;
	NVIC_Config.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_Config);
}

测试函数

objectivec 复制代码
// 串口1发送测试函数 
void UART1_DMA_Tx_Test(void)
{
	// 1.初始化内存缓冲区 
	u32 i;
	for(i = 0; i < UART1DMA_TXBUFF_SIZE; i++)
		UART1DMA_TxBuff[i] = 'A';
	// 2.关闭DMA1通道4
	DMA_Cmd(DMA1_Channel4, DISABLE);
	// 3.配置串口1支持DMA的发送 
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
	// 4.设置搬移数据的长度
	DMA_SetCurrDataCounter(DMA1_Channel4, UART1DMA_TXBUFF_SIZE);
	
	// 5.打开DMA1通道4
	DMA_Cmd(DMA1_Channel4, ENABLE);
	// 打开该功能后, DMA1通道4就会自动从内存缓冲区中搬移数据到串口1的DR中
	// 搬移完成会触发DMA1通道4 TC中断
	
	DMA_TcFlag = 0; // 标识数据还没发完
	
	// 6.采用中断的方式判断数据是否搬移完毕 ??
	while(1){
		if(DMA_TcFlag){ // 
			printf("\n DMA TX SUCCESS\n");
			break;
		}
		// 做其它业务 ...
		LED0 = !LED0;
		delay_ms(200);
	}
}

中断处理函数

objectivec 复制代码
// DMA1通道4触发的中断 
// 发送完成触发中断
void DMA1_Channel4_IRQHandler(void){
	// 1.判断是哪个中断 
	if(DMA_GetITStatus(DMA1_IT_TC4) != RESET){
		// 2.清除中断到来位 
		DMA_ClearITPendingBit(DMA1_IT_TC4);
		// 3.关闭DMA1通道4
		DMA_Cmd(DMA1_Channel4, DISABLE);
		// 4.标志数据发送完毕
		DMA_TcFlag = 1;
	}
}

初始化函数和cmd函数中需要将DMA相关添加进来

objectivec 复制代码
init.c中
static PINIT_T init_func[] = {
	LED_Init,			// led灯初始化
	BEEP_Init,		// beep初始化 
	Systick_init,	// 滴答定时器初始化
	KEY_Init,			// 按键初始化
	My_EXTI_Init,	// 中断初始化 
	UART_Init,		// 串口初始化
	AT24C02_Init, // AT24C02初始化
	DS18B20_Init, // 温度传感器的初始化
	My_DMA_Init,  // DMA初始化
	0
};


cmd.c中
cmd_t cmd[] = {
	{"led on", LED_On},
	{"led off", LED_Off},
	{"beep on", BEEP_On},
	{"beep off", BEEP_Off},
	{"EEPROM R", AT24C02_ReadOne}, // 读取单字节
	{"EEPROM W", AT24C02_WriteOne}, //写入单字节
	{"EEPROM RS", AT24C02_ReadMul}, // 读取多字节
	{"EEPROM WS", AT24C02_WriteMul},	// 写入多字节
	{"temp", DS18B20_Test},  // 获取温度命令
	{"rom", DS18B20_ReadRom},	// 读取ROM值命令
	{"dma tx", UART1_DMA_Tx_Test} // DMA发送数据命令
};

实验结果

在串口工具中发送dma tx,串口工具能够显示通过内存写入到寄存器中的数据,本文中是写了1024和'A'

相关推荐
Miuney_MAX8 小时前
【单片机】之HC32F460中断向量选择
单片机·嵌入式硬件
猫猫的小茶馆13 小时前
【ARM】ARM的介绍
c语言·开发语言·arm开发·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆13 小时前
【PCB工艺】数模电及射频电路基础
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·pcb工艺
点灯小铭13 小时前
基于单片机的智能药物盒设计与实现
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
梓德原14 小时前
【基础】详细分析带隙型稳压电路的工作原理
单片机·嵌入式硬件·物联网
国科安芯15 小时前
航天医疗领域AS32S601芯片的性能分析与适配性探讨
大数据·网络·人工智能·单片机·嵌入式硬件·fpga开发·性能优化
小李做物联网16 小时前
【物联网毕业设计】60.1基于单片机物联网嵌入式项目程序开发之图像厨房监测系统
stm32·单片机·嵌入式硬件·物联网
贝塔实验室16 小时前
新手如何使用Altium Designer创建第一张原理图(三)
arm开发·单片机·嵌入式硬件·fpga开发·射频工程·基带工程·嵌入式实时数据库
@good_good_study17 小时前
STM32 ADC多通道采样实验
stm32·单片机·嵌入式硬件
Darken0317 小时前
什么是“位带”?;在STM32单片机中有什么作用?
stm32·单片机·嵌入式硬件