写一篇文章,真的没那么容易,起个头都好难。
花了几天时间,利用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、今天才知道这个博客自带代码高亮功能,亏的我以前都是一张一张的截图,又或者是粘贴过来,然后又把注释的颜色一条条的更改,啊,生活真的是难。
附件: