« 上一篇下一篇 »

简易函数信号发生器——STM32F407学习(二)

写一篇文章,真的没那么容易,起个头都好难。

花了几天时间,利用STM32F407这个平台,外加简单的外围设备(按键、液晶),做了个比较粗糙的信号发生器,功能如下:

1、波形发生:正弦波、三角波、矩形波,以及噪声。

2、频率可调:能够按键调节频率,且可变换档位。

3、幅度可调:能够按键调节幅度。

4、界面显示:使用TFT液晶进行操作显示。

5、按键长按与短按:实现了按键长按与短按功能,长按为返回主界面,短按为幅度调节。

下面介绍我的思路与方法:

一、工具介绍

为什么首先介绍需要用到的工具呢?因为在这个程序的编写过程中,这些工具的确非常重要。那么,到底是什么工具呢?

1、Matlab应用软件

    这个程序的编写必须要Matlab的协助,因为我们所需要的波形的数据都要由它来产生(噪声除外),所以必不可少。

2、示波器

    程序调试过程中示波器是少不了的, 不然你怎么知道有没有波形。

二、思路介绍

因为本程序的代码相对而言较多,所以我只讲它的设计方法与一些需要注意的点,代码尽量少讲。

1、波形产生

上面已经提到,波形数据基本由Matlab提供,既然如此,那我们自然会用到数组,用以存储波形数据。即:我们用Matlab产生正弦、三角、矩形三种波形的数字信号,然后通过F407内部的DAC把这些数字信号转换为模拟信号,从而产生所需要的波形。

噪声的产生是利用了内部DAC已有的功能,对内部DAC进行配置,从而产生噪声

2、频率调节

这里的DAC以定时器作为触发源。调频的机理便是改变对应定时器计数值,即改变触发时间来达到频率调节的目的。

3、调频档位

档位变换其实就是使用不同的数组。数组里面存放的是定时器的计数值,不同的档位存放数值的步进不同。

4、幅度调节

万变不离其宗。幅度的调节也是对对应数组的修改。因为我们输出的信号的幅值大小就波形数组里面数据的大小,当我们改变数据的大小,就达到了改变信号的幅度的目的,而且,不会使信号的频率发生改变。幅度调节时要先判断当前波形的类型,再对对应波形数组的值进行修改。幅度的调节我放在了中断服务程序里面,根据我的调试来看,这样效果是最好的,按键响应速度快,而且稳定。

5、按键的长按与短按

采用状态机的思想,使用定时器来完成功能。当检测到对应按键按下时,使能定时器,每隔一段时间(200ms),在定时器中断里面检测键盘状态,如果键盘仍为按下,则计数器自加,如果键盘松开,则执行短按功能。如果计数器加到一定值,即按键按下持续到一定的时间,则执行长按功能。这里还需要一个标志位,短按功能不仅要按键松开,而且要同一时刻标志位为1才会执行。检测到按键按下后,我们把标志位置1,使能定时器,在中断服务程序里,如果为短按,执行完短按功能后将标志清0,避免下次进入中断时未按键却执行功能;当为长按时,执行完长按功能后,同样将标志清0,因为短按的判断代码放在长按之后,如果长按后未将标志清0,在按键松开后还会执行短按功能,显然,这不是我们想要的。

三、具体实现

由于DAC与定时器是本程序的重点,所以这里主要介绍DAC与定时器的配置。具体的代码文件将会在文章结尾奉上,以飨读者。

/**
  * @brief  使能DAC的时钟,初始化GPIO
  * @param  无
  * @retval 无
  */
static void DAC_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef  DAC_InitStructure;

  /* 使能GPIOA时钟 */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);							
	
	/* 使能DAC时钟 */	
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
	
  /* DAC的GPIO配置,模拟 */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5;	//使能引脚4、5
 
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* 配置DAC 通道1 */
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;		//使用TIM2作为触发源
  
  
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;	//这个使能与否对波形产生没太大关系,但是对电压输出有关系
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);			//DAC通道1初始化

  /* 配置DAC 通道2 */
  DAC_Init(DAC_Channel_2, &DAC_InitStructure);

  /* 使能通道1 由PA4输出 */
  DAC_Cmd(DAC_Channel_1, ENABLE);
	
  /* 使能通道2 由PA5输出 */
  DAC_Cmd(DAC_Channel_2, ENABLE);

  /* 使能DAC通道2的DMA请求 */
  DAC_DMACmd(DAC_Channel_2, ENABLE);												
}


/**
  * @brief  配置TIM
  * @param  无
  * @retval 无
  */

static void DAC_TIM_Config(void)
{
	
  TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
	
  /* 使能TIM2时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
  /* TIM2基本定时器配置 */
   // TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); 
    
  TIM_TimeBaseStructure.TIM_Prescaler = 2;       		//预分频
  TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;    		//时钟分频系数
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  	//向上计数模式
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

  /* 配置TIM2触发源 */
  TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

    /* 使能TIM2 */
  TIM_Cmd(TIM2, ENABLE);
}

/**
  * @brief  配置DMA
  * @param  无
  * @retval 无
  */
static void DAC_DMA_Config(void)
{	
  DMA_InitTypeDef  DMA_InitStructure;
  //NVIC_InitTypeDef   NVIC_InitStructure;

  /* 使能DMA2时钟 */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
	
  /* 配置DMA2 */
  DMA_InitStructure.DMA_Channel = DMA_Channel_7;
  DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_Address;		//外设数据地址
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&DualSine12bit ;//内存数据地址 DualSine12bit
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;		//数据传输方向内存至外设
  DMA_InitStructure.DMA_BufferSize = 512;				//缓存大小为32字节	
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;	//外设数据地址固定	
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;		//内存数据地址自增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据以字为单位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;	//内存数据以字为单位	
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;				//循环模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;			//高DMA通道优先级
  //DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;				//非内存至内存模式	
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

  DMA_Init(DMA1_Stream6, &DMA_InitStructure);
	
  /* 使能DMA2-14通道 */
  DMA_Cmd(DMA1_Stream6, ENABLE);
}

DAC由TIM2来触发,调频也是对TIM2配置的修改,然后用DMA来传输数据。

/**
  * @brief  配置嵌套向量中断控制器NVIC
  * @param  无
  * @retval 无
  */
static void NVIC_Config(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;                  //中断初始化结构体定义
  
  /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);        //配置为优先级组1
  
  /* 配置TIM5_IRQ中断为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);                        //中断初始化
}
/**
  * @brief  配置通用定时器TIM5,每隔一定时间在中断里面读取按键状态并做相应处理
  * @param  无
  * @retval 无
  */
void TIM5_Init(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;        //时基结构体定义
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);    //使能TIM5时钟
    /* Time Base configuration */
  TIM_TimeBaseStructure.TIM_Prescaler = 0;                //84M
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;    //向上计数
  TIM_TimeBaseStructure.TIM_Period = (84000000/5);                //200ms定时
  TIM_TimeBaseStructure.TIM_ClockDivision = 1;                    //时钟分频系数
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);                  //时基初始化:TIM5
  //TIM_ARRPreloadConfig(TIM5, ENABLE);                 //使能TIM5重载寄存器ARR,普通定时可不加
  /* TIM1 counter enable */
  
    TIM_ITConfig(TIM5, TIM_IT_Update, ENABLE);            //使能TIM5更新事件中断
    
    NVIC_Config();                                        //中断配置
}

TIM5主要用于一个按键的两种功能:长按与短按。

TIM5设置中断触发时间,在它的中断服务程序里面来判断执行长按功能还是短按功能,短按就是幅度的调节,即幅度调节在中断里完成;长按即返回主界面,这里的应用重点主要就是为了实现用一个按键来实现两种功能,很有趣,这就是电子表里用到的功能,可以按一下,换档;也可以一直按着,然后不停的加数。

四、细节难点

1、波形数据哪里来?

如果没有用过Matlab,这就有可能成为一个坎,但是不要怕,其实我们不需要怎样去学它,只要会些基本的函数就可以了。这些函数我们可以去网上找,也可以在一些类似的例程里面找,应该也会有的,只要它是用数组来产生的信号,那就一定会用到Matlab。当然,在后面我会把我这几天用过的一些Matlab代码一起上传。

2、如何在调幅时不会改变频率?

其实在我们用的这种方法,调幅是不会改变频率的。只是我有听到别人写的程序,调幅时频率会发生改变,当然,他用的不是这种方法,我猜的话应该是直接控制DA输出的幅值,这样有可能会使得频率也改变,而本文的方法是改变数组元素的大小,从而达到幅度调节的目的,所以,不用担心频率会改变。

3、如何使步进档位提示语与波形提示语同时显示且一直显示?

这也是个问题。我的做法是,在每一个波形标志的if语句里都进行步进档位标志的判断,不同的档位显示不同的提示语,同时也显示对应的波形提示语。然后在波形选择键与步进档位选择键按下时都进行一次清屏。这样就可以和谐显示了,另外,主界面的显示语不要和波形与档位提示语在同一行,这样就可以同时显示所有提示语而不混乱。

4、如何调节频率?如何调节幅度?如何实现按键长按与短按?

这三个问题请参见上文的“思路介绍”,支持连按的按键程序请参见本站前面的文章。

五、可以改进的地方

1、把DA直接调节幅度加进去试试看效果会是怎样?

2、幅度值显示。

3、频率值显示。

4、频率调节精度不高,范围太小。这个也确实和硬件有关,因为DAC毕竟不是专门的信号产生器件,如果用DDS专用芯片或许会好很多。

5、更人性化的界面与操作。

感悟

1、每当我要写文章之前,我总以为我将会写出一篇水平很高的文章出来,可是,在写完之后才发现其实我又想错了,写文章永远是一门很大的学问,有时心里知道是怎么回事,但是说出来,就没那个味道了,不过,我还是尽量用我所认为好的方式来表达,以便能让读者和以后的自己看这篇文章时,能够读起来易于理解。

2、做技术就应该要脚踏实地,不要畏难,不要退缩。这几天我就是碰到问题,解决问题,想着它一定能解决的,果然,只要肯下功夫钻研,就一定能找到解决的办法。做技术只有沉住气,一步一步来,才能学到东西,才能增加信心,不然,如果一碰到问题就只想着退缩或者是幻想着一下子就能解决,那干脆转行算了。其实,当你苦苦思索的一个问题被自己解决后,那种成就感是无与伦比的。

3、今天才知道这个博客自带代码高亮功能,亏的我以前都是一张一张的截图,又或者是粘贴过来,然后又把注释的颜色一条条的更改,啊,生活真的是难。

附件:

M4_DAC_多波形发生(还可以改进).zip

波形数据生成.txt