1.项目需求
我选择的项目是暑期一起练中基于STM32F072的多功能掌中仪器的项目2,项目要求如下:
-
通过STM32F072的DAC产生正弦波、三角波等常用波形,输出到Wav管脚;
-
通过STM32F072的内部定时器产生可调周期、可调占空比的PWM信号,输出到PWM管脚;
-
可以通过按键改变Wav信号的波形、频率、幅度、直流偏移,改变PWM信号的频率和占空比;
- 在LCD上显示波形信息以及当前的参数、控制菜单。
2.硬件介绍
本次项目的芯片是STM32F072CBT6,,是主流ARM Cortex-M0 USB系列MCU,具有128 KB Flash、48 MHz CPU、USB、CAN和CEC功能。开发板上集成了一个波轮按键和两个用户按键以及一个240*240的LCD显示屏幕。
3.已完成的功能
- 能够产生可调频率,幅值和直流偏移的正弦波,三角波,方波;
- 能够产生可调周期,占空比的PWM波;
- 产生波形实际的最大幅值大致为3.6V;
- 屏幕上显示当前输出波形的各个参数以及可以通过按键修改波形的频率、幅值、直流偏移以及PWM波的频率和占空比。
4.主要实现思路
我是2021年7月份开始接触单片机的开发的。所以在本次项目中我算是边做边学,并且利用STM32的标准库进行开发。首先一拿到开发板,首先是将屏幕点亮。关于LCD显示的代码我是从网上移植的。一开始选择的是模拟SPI刷新屏幕,但是十分缓慢。于是查阅资料以后更换硬件SPI并且使用DMA传输来实现。点亮屏幕以后编写DAC和定时器相关的代码,来产生波形和pwm波。考虑的波形数据传输很快,所以在DAC中增加了DMA传输。
5.主要实现步骤说明
1.点亮LCD屏幕
从网上移植关于LCD显示的代码,并按照下图的引脚来修改GPIO的设置。根据f072的编程手册可以知道,DMA_channel3为spi1的专用的通道,选择好通道并完成DMA的其他设置即可完成硬件spi驱动LCD屏幕。
2.功能实现-用户按键读取
用户按键的读取主要是外部中断的相关配置。根据下图可以知道相应按键对应的GPIO口。使能相应的GPIO口并拉低。主要代码如下:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC|RCC_AHBPeriph_GPIOF, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15|GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
这里需要注意的是PF0和PF1默认作为外部晶振引脚。所以使用前需要关闭。在GPIO初始化之前加入下列代码
RCC->CR &= ~((uint32_t)RCC_CR_HSEON);
配置好了GPIO以后就是相应的中断设置。
需要注意的是首先需要使能SYSCFG时钟,然后是中断的触发方式选择上升沿触发(与前面拉低GPIO的电平相对应)以PC13为例:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,GPIO_PinSource13);
EXTI_InitStructure.EXTI_Line=EXTI_Line13;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //上升沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
最后编写相应的中断服务函数来实现按键的相应的功能。
本次项目,我的key1是用来切换信号发生器和PWM的产生。key2用来切换参数,波轮按键用来选择波形以及对参数的增加和减少。
3.功能实现-DAC产生模拟信号
DAC主要用来产生模拟信号,根据原理图可以知道PA4为DAC的引脚,使能PA4,并设置成GPIO_IN。这里不设置成输出的主要是因为一但使能 DAC1通道之后, PA4 会自动与 DAC 的模拟输出相连,设置为输入,是为了避免额外的干扰。触发方式选择定时器TIM3触发,并配置好DMA。重要代码如下:
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T3_TRGO;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
DAC_Cmd(DAC_Channel_1,ENABLE);
DAC_DMACmd(DAC_Channel_1,ENABLE);
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
TIM_Cmd(TIM3,ENABLE);
dma_struct.DMA_PeripheralBaseAddr = (u32)&(DAC->DHR12R1);
dma_struct.DMA_MemoryBaseAddr = (u32)wave;
dma_struct.DMA_DIR = DMA_DIR_PeripheralDST;
4.功能实现-波形的设置
首先是利用电路知识将放大器的比例关系找到
利用直流分析可以知道,U2(输出)= 4-2.4*U1(输入),以及DAC_OUTx = VREF+ * 3.3 / 4095可以对相应的波形进行配置。其中正弦波的产生主要用math.h的sin函数来计算。
用一个数组来存储波形的各个点的值其公式为(以正弦波为例):wave_out[i]=2048+amplitude*512*wave_sin[i]-DC_Offset*512.
关于波形的频率的计算,本次实验采集240个点输出,和F072的系统时钟频率为48MHz根据定时器的公式可以知道信号的频率=48MHz/240/(arr+1)/(psc+1)
5.按键消抖
这次做的项目中,我主要采用延时消抖,但是这种方式会占用CPU。可以采用状态机来进行消抖。可以参考这个链接 状态机消抖 。
6.功能实现-pwm波的输出
pwm波的输出主要就是定时器的相关配置,根据编程手册,TIM2的channel3为作为PWM的通道
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_TimeBaseStruct.TIM_Prescaler= TIM2_pre-1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = TIM2_period;
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStruct.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStruct.TIM_Pulse = TIM2_pulse;
TIM_OC3Init(TIM2,&TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
TIM2_Pulse控制占空比,TIM2_pre和TIM2_period控制PWM波的频率。
7.功能实现-各种参数的更改
在外部中断回调函数中更改频率、波幅、直流偏移的大小。其中频率的改变可以通过TIM_PrescalerConfig()函数来更改定时器的触发频率从而更改信号输出的频率。
关于波幅和直流偏移的更改可以参考这个公式:wave_out[i]=2048+amplitude*512*wave_sin[i]-DC_Offset*512。
6.遇到的问题
1.屏幕刷新太慢了,速度远不及官方例程的刷新速度。这次实验我是利用DMA+硬件spi驱动LCD屏幕,但是可能由于代码移值的问题,优化程度不够,导致屏幕刷新速度缓慢。到时候我会好好学习官方例程的代码以及其他同学的优秀案例。
2.DMA通道冲突的处理。由于本次实验的spi和DAC均使用到了DMA的通道3。一开始我每次刷新屏幕后都会卡死,经过查阅资料后发现,只需要在初始化DMA之前,DISABLE DMA 即可解决。
3.每次烧录程序都需要重新插电才能下载程序,通过询问学长和其他同学,可以用杜邦线把jlink的线和开发板后面对应的线焊起来。即可通过jlink来下载程序和debug。
4.代码编写不太规范,还需要继续在这一方面学习。
5.信号输出的频率最多达到25khz,再往上增加会出现BUG。
7.总结
我是今年七月份开始接触单片机的开发,在这次项目中算是边做边学,所以本次我做的信号发生器还有很多不足以及不理解的地方,希望能通过官方例程的代码以及其他同学的优秀代码学习提高自己的水平。暑假的大部分的时间我是在做双通道示波器,但由于我缺少信号与系统的相关知识,最后卡在离散傅里叶变化。最后几天才开始做信号发生器。本来想自己设置一个有个性的用户界面,但是由于时间问题,我只能参考官方例程的用户界面。总而言之,这次活动让我看到了我的一些不足,锻炼了我的实践能力,丰富了我的知识储备。非常感谢硬禾学堂提供的这个平台!
8.实物图片