对于STM32的ADC模数转换器的介绍以及配置在文章《STM32单片机(六). 传感器的使用》中已经详细介绍,在本章节中主要介绍DAC数模转换器以及DMA的使用。

1、DAC转换
1.1 数模转换器的介绍
DAC(Digital to analog converter),数字模拟转换器,可以将数字信号转换为模拟信号。DAC可以输出电压模拟信号,用来去驱动其它器件。STM32F1中的DAC模块是由12位电压输出数模转换器,可以配置为8位或12位模式,也可以与DMA控制器配合使用。在12位模式下,数据可使用左对齐或右对齐方式;8位模式下,数据只有右对齐方式。DAC含有两个输出通道,每个通道各对应一个转换器。在双通道模式下,每个通道可进行单独的转换;当两个通道组合在一起同步执行更新操作时,也可同时进行转换。DAC可通过输入参考电压引脚VREF+来提高转换后的数据精度,对于DAC通道的模块框图如下所示:
在这里插入图片描述

1.2 DAC的配置步骤
使用库函数对DAC进行配置需要使用到库文件stm32f10x_dac.h和stm32f10x_dac.c,详细的步骤如下:

1、 使能端口及DAC时钟,设置引脚为模拟输入模式;
在STM32F103中DAC两个通道对应的是PA4和PA5引脚,因此需要使能GPIOA以及DAC时钟,DAC是挂载在APB1总线上的设备,GPIOA是挂载在APB2总线上的设备。调用函数:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//使DA时钟

2、 初始化DAC,设置DAC工作模式;
调用函数:void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
第一个参数选取DAC通道(DAC_Channelx),第二个参数结构体DAC_InitTypeDef的成员变量如下:

typedef struct
{
uint32_t DAC_Trigger; 							//DAC触发选择
uint32_t DAC_WaveGeneration;					//DAC波形发生
uint32_t DAC_LFSRUnmask_TriangleAmplitude;	//屏蔽/幅值选择器
uint32_t DAC_OutputBuffer;						//DAC输出缓存 
}DAC_InitTypeDef;

3、 使能DAC输出通道;
调用函数:void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
4、 设置DAC输出值;
在使用12位数据右对齐的工作模式下,通过设置DHR12R1就可已在DAC输出引脚得到不同的电压值,调用函数:DAC_SetChannel1Data(DAC_Align_12b_R, 0);
读取DAC对应通道最后一次转换值调用函数:uint16_t DAC_GetDataOutputValue(uint32_t DAC_Channel);

1.3 应用示例
STM32使用按键控制DAC输出电压的大小,并将该数据通过串口输出,详细的代码模块如下:
dac.h

#ifndef _dac_H
#define _dac_H

#include "system.h"
void DAC1_Init(void);

#endif

dac.c

#include "dac.h"

void DAC1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	DAC_InitTypeDef DAC_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	DAC_InitStruct.DAC_Trigger=DAC_Trigger_None;	 //不使用触发
	DAC_InitStruct.DAC_WaveGeneration=DAC_WaveGeneration_None;	//不使用波形输出
	DAC_InitStruct.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
	DAC_InitStruct.DAC_OutputBuffer=DAC_OutputBuffer_Disable;	//关闭输出缓存
	DAC_Init(DAC_Channel_1,&DAC_InitStruct);
	
	DAC_Cmd(DAC_Channel_1, ENABLE);
	DAC_SetChannel1Data(DAC_Align_12b_R,0);			//初始电压值设置为0
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "key.h"
#include "dac.h"

int main()
{
	u8 i=0;
	u8 key;
	u16 dacVal;
	u16 dac_value;
	float U;
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	LED_Init();
	USART1_Init(9600);
	DAC1_Init();
  KEY_Init();
	
	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY_UP)
		{
			dacVal+=400;
			if(dacVal>=4095)
			{
				dacVal=4095;
			}
			DAC_SetChannel1Data(DAC_Align_12b_R,dacVal);		//更新电压
		}
		else if(key==KEY_DOWN)
		{
			dacVal-=400;
			if(dacVal<=0)
			{
				dacVal=0;
			}
			DAC_SetChannel1Data(DAC_Align_12b_R,dacVal);
		}
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		if(i%50==0)
		{
			dac_value=DAC_GetDataOutputValue(DAC_Channel_1);
			U=(float)dac_value*(3.3/4096);
			printf("输出电压为:%.2f\n",U);
		}
		delay_ms(10);
		
	}
}

2、DMA数据传输
2.1 DMA的介绍
DMA,Direct Memory Access,直接存储器访问。STM32中DMA可以实现外设与寄存器之间,存储器与存储器之间高效的数据传输,DMA传输数据的过程不需要CPU直接操作可以节省对CPU的占用。DMA是RAM和IO设备之间数据传输的通路,外设和存储器之间,存储器和存储器之间可以直接在通道上进行数据传输。在STM32F1中最多有2个DMA控制器(DMA2仅存在于大容量产品系列),DMA1有7个通道,DMA2有5个通道,每个通道可管理来自于一个或者多个外设对存储器访问的请求,面对这些请求通过一个仲裁器来协调其优先权。对于DMA的结构框图如下所示:
在这里插入图片描述

DMA和Cortex-M3内核共享系统数据总线,当CPU和DMA同时访问相同的目标时,DMA会请求暂停CPU访问系统总线若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线带宽。
DMA的处理过程: 当发生一个事件后,外设向DMA控制器发送一个请求信号,DMA控制器根据通道优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即给其发送一个应答信号。外设从DMA得到应答信号后,外设立即释放请求,请求一旦被释放,DMA控制器同时撤销硬打信号。当有更多的请求时,外设可以启动下一个周期。
DMA的数据配置: 外设到存储器,使用外设到存储器传输时,DMA外设寄存器的地址为外设的数据寄存器地址,DMA存储器的地址为用户自定义变量的地址;存储器到外设,使用存储器到外设的传输时,DMA外设寄存器地址对应的是其数据寄存器地址,DMA存储器地址为用户自定义变量(缓冲区,存储通过外设的数据)的地址;存储器到存储器,DMA外设寄存器地址为内部存储器(如Flash,可将其看作是外设)的地址,DMA存储器地址为用户自定义的变量(缓冲区,存储来自内部Flash的数据)的地址。DMA的传输完成分为两种模式,一次传输和循环传输。如果使用一次传输后DMA停止,若要在此传输,必须失能DMA后在重新配置方可继续传输;当使用循环传输时,一次传输完成后又再次恢复第一次传输时的配置进行数据传输,不断重复。

2.2 DMA的配置步骤
对于DMA的配置,需要使用到库函数文件stm32f10x_dma.h和stm32f10x_dma.c,详细的步骤如下所示:
1、 使能DMA控制器时钟;
使能DMA时钟,需要通过AHB1ENR寄存器控制,调用函数:void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
2、 初始化DMA通道;
需要配置DMA的通道、外设和内存地址、通道优先级以及传输数据量等,调用函数:void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
其中第一个参数用于选择哪个DMA的通道x,第二个参数是一个结构体变量包含的成员变量如下:

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_Mode; 					// 模式选择
		uint32_t DMA_M2M; 					// 存储器到存储器模式
} DMA_InitTypeDef;

3、 使能外设DMA功能;
配置好DMA后,需要使能外设的DMA功能,根据使用外设的不同调用相应的函数,如使能串口DMA的发送功能需要调用:USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
4、 开启DMA通道传输;
调用函数:void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Chammelx, FunctionalState NewState);
5、 查询DMA传输状态。
查询状态调用函数:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
获取当前传输剩余数据量大小调用函数:uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
设置传输数据量大小调用函数:void DMA_SeyCurrDatatCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);

2.3 应用示例
STM32使用按键控制DMA串口USART1的数据传送,详细的代码模块如下:
dma.h

#ifndef _dma_H
#define _dma_H

#include "system.h"
void DMAx_Init(u32 paddr,u32 maddr,u16 buffsize,DMA_Channel_TypeDef* DMAy_Channelx);
void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr);
#endif

dma.c

#include "dma.h"

void DMAx_Init(u32 paddr,u32 maddr,u16 buffsize,DMA_Channel_TypeDef* DMAy_Channelx)
{
	
	DMA_InitTypeDef DMA_InitStruct;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	DMA_InitStruct.DMA_PeripheralBaseAddr=paddr;						//外设地址
	DMA_InitStruct.DMA_MemoryBaseAddr=maddr;						//内存地址
	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralDST;					//存储器到外设
	DMA_InitStruct.DMA_BufferSize=buffsize;							//传输数据量大小
	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_Init(DMAy_Channelx,&DMA_InitStruct);
	
}
void DMAx_Enable(DMA_Channel_TypeDef* DMAy_Channelx,u16 ndtr)
{
	DMA_Cmd(DMAy_Channelx, DISABLE);
	DMA_SetCurrDataCounter(DMAy_Channelx,ndtr);
	DMA_Cmd(DMAy_Channelx, ENABLE);
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "key.h"
#include "dma.h"
#define buf_len 5000


u8 sendBuffer[buf_len];

void send_Data(u8 *p)
{
	u16 i;
	for(i=0;i<buf_len;i++)
	{
		*p='5';
		p++;
	}
}

int main()
{
	u8 i=0;
	u8 key;
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	LED_Init();
	USART1_Init(9600);
  KEY_Init();
	DMAx_Init((u32)&USART1->DR,(u32)sendBuffer,buf_len,DMA1_Channel4);
	send_Data(sendBuffer);
	
	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY_UP)
		{
			USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);	//开启外设DMA功能
			DMAx_Enable(DMA1_Channel4,buf_len);
			
			while(1)
			{
				if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)	//判断DMA数据是否发送完成
				{
					DMA_ClearFlag(DMA1_FLAG_TC4);
					break;
				}
				led2=!led2;
				delay_ms(300);
			}
		}
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		delay_ms(10);
	}
}