双通道相位频率幅值可调波形发生器(STM32内置DAC实现)--完整工程已开源至Github,欢迎Star!!!链接在文末
- 前言
- 器件选型
- 系统组成框图
- 硬件部分电路
- 软件设计
- 亲测可用,最终效果如下
- 有关项目的疑问大家可在下方评论,需要完整的原理图和程序也可在评论私聊我,希望对大家有所帮助。
- 完整开源Github工程链接:[点击此处](https://github.com/salute-hh/Dual-channel-phase-frequency-amplitude-adjustable-waveform-generator) https://github.com/salute-hh/Dual-channel-phase-frequency-amplitude-adjustable-waveform-generator
- CSDN下载链接:[点击此处](https://download.csdn.net/download/Emb_Salute/12838814?spm=1001.2014.3001.5503) https://download.csdn.net/download/Emb_Salute/12838814?spm=1001.2014.3001.5503
前言
由于项目需要,要设计一个双通道相位频率幅值可调波形发生器,可根据下位机的矩阵键盘和上位机串口助手对波形参数进行相应调节。
话不多说,下面开始介绍我的项目!该项目采用STM32F103RCT6为主控芯片,采用其内置DAC与DMA配合,输出不同的波形,可通过串口或者键盘进行波形参数的调节,通过改变两路波形起始点输出的时间不同进而改变两路波形的相位。
器件选型
主控
大容量的 STM32F103 具有内部 DAC,经过比对大容量的STM32F103系列中的性价比,本设计采用主控制芯片STM32F103RCT6,它具有64个引脚,通用输入输出口资源丰富,除去晶振输入、电源输入、Boot引脚,剩下的51个引脚均为GPIO。所有的GPIO引脚都可以用作外部中断源的输入,每个GPIO引脚可配置为8种模式,不同的引脚还有相应的复用功能、复用功能重映射等,足以满足波形发生器的应用。本设计采用STM32F103RCT6.
信号产生选型
采用STM32F103RCT6内置DAC与DMA
STM32的DAC模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。 DAC 可以配置为 8 位或 12 位模式,可以与 DMA 控制器配合使用。STM32的DAC模块具有两个通道,可单独进行转换,也就是说可以同时输出两个正弦波或其他波形。输出波形只要每隔一定时间向DAC的数据寄存器写入数据,然后进行数据转换,输出不同电压,然后在时间轴上显示出波形。
DMA,全称为:Direct Memory Access,即直接存储器访问,DMA 传输将数据从一个地址空间复制到另外一个地址空间。当 CPU 初始化这个传输动作,传输动作本身是由DMA 控制器来实行和完成。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。在本设计中可以采用DAC+DMA+定时器的方式产生波形,通过配置DMA,可以使得DMA每次搬运数据之后目标地址增加特定的位数。正是通过每次搬运之后的地址偏移,能够将整个波形数组中的内容依次写入到DAC寄存器当中。定时器、DAC与DMA之间有着紧密的联系。定时器在每个时钟周期后产生触发输出,该触发输出引起DAC根据寄存器内的值产生电压,同时DAC产生DMA请求使其将新的数据写入DAC的寄存器中。这种情况下,写入DAC寄存器的值并不会立即生效,而是在定时器的触发输出触发DAC后电压值才会更新。
采用STM32F103RCT6内置DAC来产生信号,实现波形信号的产生,能够快速地产生两路波形,方便调节频率、幅值及相位。
波形转换电路
由于单片机输出为单极性的信号,根据波形发生器的性质,需要将其转换为双极性,采用运放组成的反相比例加法电路,可以将DA输出的单极性信号转换成双极性信号,输出信号无衰减,同时可以将输出信号进行幅值的放大,输出电压范围增大。
显示
用TFT-LCD液晶屏显示。液晶显示器具有微功耗、体积小、显示内容丰富、超薄轻巧的诸多优点,可以显示中文以及图案,并且一个页面可以显示多内容,满足本设计两个通道的波形参数的显示。
输入
采用4*4矩阵键盘控制和串口控制
系统组成框图
硬件部分电路
由于时间比较紧这里只上传部分电路,有需要的开发者可以私聊我查看完整电路图
单极性转双极性
该电路由两级运放构成,实现了0~ +3.3V单极性信号到-3.3V~ +3.3V的双极性信号的转换。其工作原理如下:
第一级为反相比例放大电路,其电压增益为1, 实现了输入信号0+3.3V到0-3.3V的转换,即V01 = -Vi。
第二级电路是在单极性电路的基础上接入反相比例加法电路,其输出电压为
V0 = -(((R15/R14)3.3)+(R15/R13) V01)
得
V0 = -3.3+2* Vi
此电路实际的输出电压范围比单极性时扩大一倍,因此双极性电压输出的灵敏度下降为单极性的一半。
软件设计
总的软件流程框图如下:
下面讲解一部分代码,由于工程比较大,这里只贴出部分重要的子程序,完整程序开发者可以私聊我查看!感谢大家。
- 首先是GPIO的配置,STM32F103的芯片DAC输出管脚对应的是PA4,PA5,配置正确就行了
/****************引脚初始化******************/
void SineWave_GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速率
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 ; //选择引脚
GPIO_SetBits(GPIOA,GPIO_Pin_4) ; //拉高输出
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速率
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 ; //选择引脚
GPIO_SetBits(GPIOA,GPIO_Pin_5) ; //拉高输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化
}
- DAC初始化
/******************DAC初始化*************************/
void SineWave_DAC_Config()
{
DAC_InitTypeDef DAC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//开DAC时钟
/**************DAC结构初始化,很重要,否则无波形*******************/
DAC_StructInit(&DAC_InitStructure);
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;//不产生波形
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; //不使能输出缓存 DAC_OutputBuffer_Enable
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;//选择DAC触发源为TIM2
DAC_Init(DAC_Channel_1, &DAC_InitStructure);//初始化
DAC_Cmd(DAC_Channel_1, ENABLE); //使能DAC通道1
DAC_DMACmd(DAC_Channel_1, ENABLE); //使能DAC通道1的DMA
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;//选择DAC触发源为TIM6
DAC_Init(DAC_Channel_2, &DAC_InitStructure);//初始化
DAC_Cmd(DAC_Channel_2, ENABLE); //使能DAC通道2
DAC_DMACmd(DAC_Channel_2, ENABLE); //使能DAC通道2的DMA
DAC_Cmd(DAC_Channel_1, DISABLE); //使能DAC通道1
DAC_DMACmd(DAC_Channel_1, DISABLE); //使能DAC通道1的DMA
DAC_Cmd(DAC_Channel_2, DISABLE); //使能DAC通道1
DAC_DMACmd(DAC_Channel_2, DISABLE); //使能DAC通道1的DMA
}
- 定时器配置
void SineWave_TIM_Config( u32 Wave1_Fre ,u8 NewState1 ,u32 Wave2_Fre ,u8 NewState2)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
if( NewState1!=DISABLE) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//开时钟
if( NewState2!=DISABLE) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);//开时钟
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //不预分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
if( NewState1!=DISABLE)
{
TIM_TimeBaseStructure.TIM_Period = Wave1_Fre;
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);//设置TIM2输出触发为更新模式
}
if( NewState2!=DISABLE)
{
TIM_TimeBaseStructure.TIM_Period = Wave2_Fre; //设置输出频率
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //初始化
TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
}
if(NewState1==DISABLE)//恢复默认
TIM_DeInit(TIM2);
if(NewState2==DISABLE)
TIM_DeInit(TIM6);
}
- DMA配置
void SineWave_DMA_Config( u16 *Wave1_Mem ,u8 NewState1 ,u16 *Wave2_Mem ,u8 NewState2)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//开DMA2的时钟
DMA_StructInit( &DMA_InitStructure); //DMA结构初始化
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//从存储器读数据
DMA_InitStructure.DMA_BufferSize = N;//缓存大小,一般为256点
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_Priority = DMA_Priority_VeryHigh;//优先级:非常高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环发送模式
if( NewState1!=DISABLE)
{
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1;//外设地址为DAC通道1数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Wave1_Mem;//内存地址为输出波形数据数组
DMA_Init(DMA2_Channel3, &DMA_InitStructure);//初始化
DMA_Cmd(DMA2_Channel3, ENABLE); //使能DMA通道3
}
if( NewState2!=DISABLE)
{
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R2;//设置外设地址为DAC通道1数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Wave2_Mem;//内存地址为输出波形数据数组
DMA_Init(DMA2_Channel4, &DMA_InitStructure);//初始化
DMA_Cmd(DMA2_Channel4, ENABLE); //使能DMA通道4
}
if( NewState1==DISABLE)
{
DMA_Cmd(DMA2_Channel3, DISABLE); //使能DMA通道3
}
if( NewState2==DISABLE)
{
DMA_Cmd(DMA2_Channel4, DISABLE); //使能DMA通道3
}
}
- 波形初始化
void Wave_Init(u8 Wave1,u16 Wave1_Fre,float um,u8 NewState1,u8 Wave2,u16 Wave2_Fre,float um1,u8 NewState2)
{
u16 *add1,*add2;
u16 f1=(u16)(72000000/sizeof(SineWave_Value)*2/Wave1_Fre);
u16 f2=(u16)(72000000/sizeof(SineWave_Value)*2/Wave2_Fre);
SineWave_Data( N ,SineWave_Value,um); //生成波形表1
SineWave_Data1( N ,SineWave_Value1,um1); //生成波形表2
SawTooth_Data( N ,SawToothWave_Value,um);//生成波形表3
SawTooth_Data1( N ,SawToothWave_Value1,um1);//生成波形表4
SQU_Data( N ,SQU_Value,um);//生成波形表5
SQU_Data( N ,SQU_Value1,um1);//生成波形表6
TRI_Data( N ,TRI_Value,um);//生成波形表7
TRI_Data1( N ,TRI_Value1,um1);//生成波形表8
if( NewState1!=DISABLE)
{
if( Wave1==0) //sine_ware
add1=SineWave_Value;
else if(Wave1==1)
add1=SawToothWave_Value;//SawToothWave_Value;
else if(Wave1==2)
add1=TRI_Value;
else if(Wave1==3)
add1=SQU_Value;//
else
add1=SineWave_Value;
}
if( NewState2!=DISABLE)
{
if( Wave2==0) //sine_ware
add2=SineWave_Value1;
else if(Wave2==1)
add2=SawToothWave_Value1;
else if(Wave2==2)
add2=TRI_Value1;
else if(Wave2==3)
add2=SQU_Value1;
}
SineWave_GPIO_Config(); //初始化引脚
SineWave_TIM_Config( f1 , NewState1 ,f2 ,NewState2); //初始化定时器
SineWave_DAC_Config(); //初始化DAC
SineWave_DMA_Config( add1 ,NewState1 ,add2 ,NewState2); //初始化DMA
- 修改频率
void Set_WaveFre( u8 Wave_Channel ,u16 fre)
{
TIM_TypeDef* TIMX;
u16 reload;
if( Wave_Channel==0) TIMX = TIM2;
else if(Wave_Channel==1) TIMX = TIM6;
reload=(u16)(72000000/256/fre);
TIM_SetAutoreload( TIMX ,reload);
}
- 相位控制子程序
- 相位控制采用的是通过控制两路波形的触发时间不同,进而来控制其相位。简单的控制是通过延时来进行控制,该子程序通过设定一个前相位(last_frase)的变量,以及现在相位(temp_frase)的变量进行对比,现在相位(temp_frase)通过相位公式可计算出两路波形的时间差,如果last_frase< =temp_frase时,表明设置的相位有改变,所以进行延时,使两路波形输出起始时间不同。
if(CHA_state==0&&CHB_state==0)
{
if(USART_DOUBEL_FLAG==1)
{
Wave_Init(CHA_Waveform,frequency_a,volt_a/2,ENABLE,CHB_Waveform,frequency_b,volt_b/2,ENABLE);//波形相关初始化
last_frase=temp_frase;
temp_frase=(frase*1000000/(frequency_a*360));
TIM_Cmd(TIM2, ENABLE);
if((last_frase<=temp_frase)&&(frase!=0))
{
if((temp_frase-last_frase)>0)
delay_us((u32)(temp_frase-last_frase));//
else if((temp_frase-last_frase)>=0)
delay_us((u32)(temp_frase));
}
TIM_Cmd(TIM6, ENABLE);
Set_WaveFre( 0 ,frequency_a);
Set_WaveFre( 1 ,frequency_b);
}
else if(USART_DOUBEL_FLAG==0)
{
SineWave_Init(CHA_Waveform,frequency_a,volt_a/2,DISABLE,CHB_Waveform,frequency_b,volt_b/2,DISABLE);//初始化
TIM_Cmd(TIM2, DISABLE);
TIM_Cmd(TIM6, DISABLE);
}
}
- 串口接收部分
- 串口接收通过判断特定值进行标定,例如给串口发送通道A的波形参数必须带有”CHA”字符才能够进入到下面的判断,否则认为接收到了无效字符,不进行任何操作。如果发送的通道的标志字符,则采用stdio.h中内置的sscanf()函数进行字符的分割,便于对特定值进行判断,进而控制波形的相应功能。
if(p=strstr((const char*)USART_RX_BUF,"CHA"),(p!=NULL))//通道1 CHA ON SAW 5000 3
{
sscanf((const char*)USART_RX_BUF,"%s%s%s%d%f",receive_catch0,receive_catch1,receive_catch2,&receive_catch3,&receive_catch4);
printf("%s %s %s %d %f\r\n",receive_catch0,receive_catch1,receive_catch2,receive_catch3,receive_catch4);
……
}