【波形发生器(附源码)】基于DAC+DMA+UART的幅值、频率可调的正弦波、方波、三角波发生器

本文详细介绍了基于STM32F103芯片设计波形发生器的过程,包括采样点与精度调整、GPIO配置为模拟输入、定时器配置、DAC与DMA的配置、幅值设置及波形数据生成、波形切换和串口中断处理。通过DMA传输波形数据到DAC,实现方波、三角波和正弦波的生成,并通过串口控制波形参数。
摘要由CSDN通过智能技术生成

1.设计要求

1.输出方波、三角波、正弦波三种波形
2.各种波形幅值、频率在一定范围内可调
3.串口控制波形数据
4.基于stm32f103芯片

2.具体实现

2.1采样点及精度调整

/*采样点及精度调整*/
#define pi 3.1415926
#define POINT_NUM 128
#define angle 2*pi/POINT_NUM

2.2 GPIO配置

初学到这里一开始也很懵逼,明明是输出模拟信号凭啥配置成模拟输入模式?

后来看了CSDN几篇大神的文章终于明白了:
因为一但使能 DACx 通道之后,相应的 GPIO 引脚(PA4 或者 PA5)会自动与 DAC 的模拟输出相连。其实,模拟输入通道与模拟输出通道是一起的(本来就是一条通路嘛),其实也可以理解为这里的”输入”是相对于片上外设DAC而言,IO是作为输入,但本质上是一条通路。

那么如此配置的好处嘞

  1. 可以看到信号行走的路线上(下图红色)没有触发器、数据寄存器和上下拉电阻,所以因它而起的电平跳变噪声和相应的额外功耗就没有了。同时也降低了芯片的动态功耗。
  2. 配置在模拟输入状态的GPIO引脚属于高阻态,这点也有利于保持模拟信号的真实性(说白了,就是信号传输通道无任何信号处理装置,可以将原汁原味的模拟信号在CPU与外设之间进行传输)

在这里插入图片描述
给出配置函数:

void DAC1_GPIO_config(void)
{	
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		
	/* DAC的GPIO配置,模拟输入 */
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

2.3定时器配置

使用定时器6作为DMA搬运的触发源。
至此,采样点和定时器配置确定了,输出波形的频率也确定了。
可以由以下公式计算:
N是采样点个数,这里设为N=128
systick有系统时钟频率决定,F1这里为72M。
为了提高输出信号的带宽,一般把定时器预分频系数设为0。
在这里插入图片描述

void DAC1_TIM_Config(void)
{	
  TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
	
  /* 使能TIM6时钟,TIM2CLK 为72M */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
	
  /*定时器初始周期*/
  TIM_TimeBaseStructure.TIM_Period = 0XF;  
  /* 预分频,不分频 72M / (0+1) = 72M */      									
  TIM_TimeBaseStructure.TIM_Prescaler = 0x0; 
  /*时钟分频系数*/   				
  TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;    					
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  	
  TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);

  /* 配置TIM6触发源 */
  TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
	/* 使能TIM6 */
  TIM_Cmd(TIM6, ENABLE);
}

2.4 DAC与DMA的配置

首先是DAC配置,这里采用DAC1(32上一共有俩)
值得注意的是DAC输出缓冲的结构体成员,使能输出缓冲可以提高输出信号的驱动能力,这里不使能。
其次是DMA配置,查询手册,发现DAC1对应的DMA通道为DMA2-CH3
这里的外设数据地址要查询手册下面给出地址映射:

#define DAC_DHR12RD_ADDRESS      (DAC_BASE+0x20) //双通道输出地址
#define DAC_DHR8R1_Address      0x40007410//DAC通道1 8位输出地址
#define DAC_DHR12R1    0x40007408   //DAC通道1 12位输出地址
#define DAC_DHR12R2    0x40007414   //DAC通道2 12位输出地址

在这里插入图片描述
注意:DMA_BufferSize要和内存数组的大小相对应
DMA_MemoryDataSize和DMA_PeripheralDataSize要和定义的数据类型相对应

其他应该没啥好注意的了,下面给出配置代码:

void DAC1_DMA_Config(void)
{	
	DMA_InitTypeDef  DMA_InitStructure;	
	DAC_InitTypeDef  DAC_InitStructure;

	/* 使能DMA2时钟 */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
	
	/* 使能DAC时钟 */	
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
		
	/* 配置DAC 通道1 */
	DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;						//使用TIM6作为触发源
	DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;	//不使用波形发生器
	DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;	//不使用DAC输出缓冲
	DAC_Init(DAC_Channel_1, &DAC_InitStructure);
	
	/* 配置DMA2 */
	DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1;					//外设数据地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;				//内存数据地址 Sine12bit
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;											//数据传输方向内存至外设
	DMA_InitStructure.DMA_BufferSize = 128;									//缓存大小为POINT_NUM字节									
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;				//外设数据地址固定	
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;									//内存数据地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据以半字为单位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;					//内存数据以半字为单位	
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;													//循环模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;											//高DMA通道优先级
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;														//非内存至内存模式	
	DMA_Init(DMA2_Channel3, &DMA_InitStructure);
	
	/* 使能DMA2-14通道 */
	DMA_Cmd(DMA2_Channel3, ENABLE);
	/* 使能通道1 由PA4输出 */
	DAC_Cmd(DAC_Channel_1, ENABLE);
	/* 使能DAC的DMA请求 */
	DAC_DMACmd(DAC_Channel_1, ENABLE);
}

2.5幅值设置及波形数据生成

每次设置一次vpp就重新更新内存数组
最大采样值,2的12次=4096

2.5.1正弦波设置

void set_sine_vpp(float vpp)
{
	int i;	
	for(i=0;i<POINT_NUM;i++)
	{
		Sine12bit[i]= (uint16_t)((sin(i*angle)+1)/2*vpp*4096/3.3);		
	}	
}

2.5.2三角波设置

void set_Triangle_vpp(float vpp)
{
	int i;	
	for(i=0;i<POINT_NUM;i++)
	{
		if(i<=POINT_NUM/2 - 1)
			TriangleWave12bit[i]= (uint16_t)vpp/3.3*4096/(POINT_NUM/2)*i;
		else
			TriangleWave12bit[i]= (uint16_t)(vpp/3.3*4096-vpp/3.3*4096/(POINT_NUM/2)*(i-(POINT_NUM/2)));	
	}			
}

2.5.3方波设置

void set_Pulse_vpp(float vpp)
{
	int i;	
	for(i=0;i<POINT_NUM;i++)
	{
		if(i<=POINT_NUM/2 - 1)
			PulseWave12bit[i]= 0;
		else
			PulseWave12bit[i]= (uint16_t)(vpp/3.3*4096);	
	}			
}

2.6波形切换

主要就是改变DMA搬运的数据地址,思路简单,下面以切换到正弦波为例:
但是需要注意的是,一旦申请了结构体,其他的成员也必须赋值,否则配置缺省!

void change_to_Triangle(void)
{
	DMA_InitTypeDef  DMA_InitStructure;
		
	DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1;					//外设数据地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&TriangleWave12bit;				//内存数据地址 Sine12bit
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;											//数据传输方向内存至外设
    DMA_InitStructure.DMA_BufferSize = 128;									//缓存大小为POINT_NUM字节									
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;				//外设数据地址固定	
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;									//内存数据地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据以字为单位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;					//内存数据以字为单位	
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;													//循环模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;											//高DMA通道优先级
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

	DMA_Init(DMA2_Channel3, &DMA_InitStructure);	
}

2.7串口中断

这里需要注意数据类型的转换
上位机发送1,2000,3.0
即可输出2khz,vpp=3.0的正弦波

void DEBUG_USART_IRQHandler(void)
{	
	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
	{		
		ucTemp =USART_ReceiveData(USART1);	
		UART1_RX_BUF[rx_cnt++]=ucTemp;		
		if(ucTemp==0x0a)
		{
			sscanf((char *)UART1_RX_BUF,"%d,%d,%f",&wave_flag,&freq,&vpp);
			switch(wave_flag)
			{
				case 1:
				{
					printf("SET sine_f = %d,sine_VPP = %f\r\n",freq,vpp);
					Sine.vpp = vpp;
					Sine.freq = freq;
					Sine.period = (int)(562500/Sine.freq + 1);
					set_sine_vpp(Sine.vpp);
					change_to_sine();
					set_peroid(Sine.period);								
					break;
				}
				case 2:
				{
					printf("SET Triangle_f = %d,Triangle_VPP = %f\r\n",freq,vpp);
					Triangle.vpp = vpp;
					Triangle.freq = freq;
					Triangle.period = (int)(562500/Triangle.freq + 1);	
					set_Triangle_vpp(Triangle.vpp);
					change_to_Triangle();						
					set_peroid(Triangle.period);					
					break;					
				}
				case 3:
				{
					printf("SET Pulse_f = %d,Pulse_VPP = %f\r\n",freq,vpp);
					Pulse.vpp = vpp;
					Pulse.freq = freq;
					Pulse.period = (int)(562500/Pulse.freq + 1);	
					set_Pulse_vpp(Pulse.vpp);	
					change_to_Pulse();
					set_peroid(Pulse.period);				
					break;					
				}
				default:break;
			}
			rx_cnt = 0;
	    }	 
     }	 
}

3. 效果图及源码链接

(以下是F4的程序的效果,F1无法达到这么高的频率)
在这里插入图片描述

github源码下载

评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Greif_Hairline

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

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

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

打赏作者

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

抵扣说明:

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

余额充值