本项目使用STM32F072单片机完成了信号发生器功能,能够输出可使用按键调节频率、占空比的PWM波;能够输出正弦波或者三角波,可使用按键调节波形种类、频率、幅值和直流偏移。波形信息、当前参数和控制菜单可在LCD屏上显示。项目介绍分为以下几个部分:
项目要求、选择平台及配置、完成的功能介绍、功能实现代码介绍、项目总结
一、项目要求
项目2 制作简易信号发生器
-
通过STM32F072的DAC产生正弦波、三角波等常用波形,输出到Wav管脚
-
通过STM32F072的内部定时器产生可调周期、可调占空比的PWM信号,输出到PWM管脚
-
可以通过按键改变Wav信号的波形、频率、幅度、直流偏移,改变PWM信号的频率和占空比
-
在LCD上显示波形信息以及当前的参数、控制菜单
二、选择平台及其配置
本项目使用STM32cubemx、STM32cubeprogrammer和KEIL5软件进行开发。
LCD屏幕采用SPI驱动,按键驱动使用定时器读取引脚实现,DAC+DMA+TIM输出正弦波和三角波,定时器输出PWM波,通过修改定时器的PSC、ARR、CCR寄存器来实现任意波和PWM波的频率修改,通过赋值DMA数组来实现幅值和直流偏移的修改。
三、完成的功能介绍
1、成功驱动LCD屏幕
2、输出可调频率和占空比的PWM波
3、输出可调幅值和直流偏移的正弦波和三角波
四、功能实现代码
1、驱动LCD屏幕
工程中自建了lcd_spi1_drv.c和font.c文件来存放LCD屏幕的驱动代码,可直接使用
2、TIM6定时器读取按键
通过在定时器中断回调函数中读取按键引脚状态来设置标志位,从而实现按键操作和有效消抖。代码如下
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
if(test_tim == 0) test_tim ++;
Key_Check(&Key_1);
Key_Check(&Key_2);
Key_Check(&Key_R);
Key_Check(&Key_O);
Key_Check(&Key_L);
}
}
void Key_Check(Key_TypeDef *key)
{
if(HAL_GPIO_ReadPin(key->GPIO_Port, key->Pin))
{
key->press_flag ++;
}else{
key->press_flag = 0;
}
if(key->press_flag == 1)
{
key->button_flag = 1;
key->count ++;
}
}
3、TIM2输出PWM波
(1)TIM2初始化配置
(2)修改PWM波的频率和占空比
void StdPeriph_TIM2_PWM_Update(void)
{
uint32_t temp32;
uint32_t uhTimerfrequency;
uint16_t uhTimerPeriod;
uint16_t uhTimerPrescaler;
uint16_t uhTimerPulse;
if ((TIM2_PWM_FQ_Old != TIM2_PWM_FQ) || (TIM2_PWM_Pulse_Old != TIM2_PWM_Pulse)) {
TIM2_PWM_FQ_Old = TIM2_PWM_FQ;
TIM2_PWM_Pulse_Old = TIM2_PWM_Pulse;
if (TIM2_PWM_FQ >= 4000) {
uhTimerfrequency = TIM2_PWM_FQ; /* ????????PWM???? */
uhTimerPrescaler = 1; /* ????TIM2_PWM_FQ???,TIM2?????1(???) */
} else {
uhTimerfrequency = 4000; /* ????TIM2_PWM_FQ???,?4000Hz?????,????? */
uhTimerPrescaler = 4000 / TIM2_PWM_FQ; /* ?????4000???????TIM2?????? */
uhTimerfrequency = uhTimerPrescaler * TIM2_PWM_FQ; /* TIM2???,??????,???uhTimerfrequency?? */
}
/* TIM2????????uhTimerfrequency???,uhTimerPeriod = 84MHz / uhTimerfrequency */
temp32 = (SysCoreClock / uhTimerfrequency);
if (temp32 > 65535) temp32 = 65535;
uhTimerPeriod = (uint16_t) temp32;
if (TIM2_PWM_Pulse > 100) TIM2_PWM_Pulse = 100;
uhTimerPulse = uhTimerPeriod * TIM2_PWM_Pulse / 100;
TIM2->ARR = uhTimerPeriod - 1;
TIM2->PSC = uhTimerPrescaler - 1;
TIM2->CCR3 = uhTimerPulse;
TIM2->EGR |= 0x01;
}
}
4、DAC+DMA+TIM7实现正弦波和三角波
(1)修改DMA数组dual_sine_wave_data和dual_triangle_wave_data实现正弦波和三角波,以及直流偏移和幅值。
void aw_sine_wave_data(u16 cycle)
{
u16 i = 0;
for (i = 0; i < cycle; i++) {
sine_wave_data[i] = ((int )505 * WAV_Range) * sin(1.0 * i / (cycle - 1) * 2 * PI) + 2048 + 40 - (int)(540 * Offset);
}
for (i = 0; i < cycle; i++) {
dual_sine_wave_data[i] = (sine_wave_data[i] << 16) + (sine_wave_data[i]);
}
}
void aw_triangle_wave_data(u16 cycle)
{
int i = 0;
for (i = 0; i < cycle; i++)
{
triangle_wave_data[i]=(u16)( i*( ((int )505 * WAV_Range) * 4.0 / cycle)+ 1024 + 40 - (int)(540 * Offset));
}
for (i = cycle / 2; i < cycle; i++)
{
triangle_wave_data[i]=(u16)( (cycle-i)*( ((int )505 * WAV_Range) * 4.0 / cycle) +1024 + 40 - (int)(540 * Offset));
}
for (i = 0; i < cycle; i++) {
dual_triangle_wave_data[i] = (triangle_wave_data[i] << 16) + (triangle_wave_data[i]);
}
return ;
}
(2)修改TIM7定时器实现频率修改
void StdPeriph_TIM7_WAV_Update(void)
{
uint32_t temp32;
uint32_t uhTimerfrequency;
uint16_t uhTimerPeriod;
uint16_t uhTimerPrescaler;
uint32_t temp_FQ = 0;
if ((TIM7_WAV_FQ_Old != TIM7_WAV_FQ)) {
TIM7_WAV_FQ_Old = TIM7_WAV_FQ;
temp_FQ = (uint32_t)TIM7_WAV_FQ * 1.0 * SINE_WAVE_DATA_MAX;
if (temp_FQ >= 4000) {
uhTimerfrequency = temp_FQ; /* ????????PWM???? */
uhTimerPrescaler = 1; /* ????TIM7_WAV_FQ???,TIM2?????1(???) */
} else {
uhTimerfrequency = 4000; /* ????TIM7_WAV_FQ???,?4000Hz?????,????? */
uhTimerPrescaler = 4000 / temp_FQ; /* ?????4000???????TIM2?????? */
uhTimerfrequency = uhTimerPrescaler * temp_FQ; /* TIM2???,??????,???uhTimerfrequency?? */
}
/* TIM2????????uhTimerfrequency???,uhTimerPeriod = 84MHz / uhTimerfrequency */
temp32 = (SysCoreClock / uhTimerfrequency);
if (temp32 > 65535) temp32 = 65535;
uhTimerPeriod = (uint16_t) temp32;
TIM7->ARR = uhTimerPeriod - 1;
TIM7->PSC = uhTimerPrescaler - 1;
TIM7->EGR |= 0x01;
}
}
(3)TIM7初始化配置
(4)DAC初始化配置
DMA配置使用circular以便重复读取数组值。
5、数字修改时指定位数实现
通过设置多个标志位来实现,使用switch-case语句,通过不同模式下按键按下来设置不同的标志位,标志位置1时便执行相关操作,即可实现增加不同数值。
typedef enum{
single = 0,decade,hundred,kilobit,myriabit,decimal
}ChgBit;
typedef enum{
Hz = 0,kHz
}Units;
struct{
Units Hz_units;
ChgBit Hz_bit;
ChgBit duty_bit;
}PWM_DataSet;
struct{
Units Hz_units;
ChgBit Hz_bit;
ChgBit bit;
}WAV_DataSet;
switch(Key_2.count)
{
case 0:
break;
case 1:
if(WAV_DataSet.Hz_units == kHz)
{
switch(Key_O.count)
{
case 0:
WAV_DataSet.Hz_bit = kilobit;
break;
case 1:
WAV_DataSet.Hz_bit = hundred;
break;
case 2:
WAV_DataSet.Hz_bit = myriabit;
break;
default:
Key_O.count = 0;
break;
}
}else
{
switch(Key_O.count)
{
case 0:
WAV_DataSet.Hz_bit = single;
break;
case 1:
WAV_DataSet.Hz_bit = hundred;
break;
case 2:
WAV_DataSet.Hz_bit = decade;
break;
default:
Key_O.count = 0;
break;
}
}
break;
case 2:
switch(Key_O.count)
{
case 0:
WAV_DataSet.bit = single;
break;
case 1:
WAV_DataSet.bit = decimal;
break;
default:
Key_O.count = 0;
break;
}
break;
case 3:
switch(Key_O.count)
{
case 0:
WAV_DataSet.bit = single;
break;
case 1:
WAV_DataSet.bit = decimal;
break;
default:
Key_O.count = 0;
break;
}
break;
default:
Key_2.count = 0;
break;
}
6、多种模式
通过使用标志位来实现,如果检测到标志位改变,就会更改相关的模式设定。
//当主模式改变时要进行的操作
if(mode_old != mode)
{
Key_2.count = 0;
Key_O.count = 0;
WAV_DataSet.Hz_units = kHz;
WAV_DataSet.Hz_bit = kilobit;
LCD_Clear(BLACK);
TFT_ShowString(24,30,"Random Waveform",12,24,0,BLACK,RED);
TFT_ShowString(0,60,"--------------------",12,24,0,BLACK,WHITE);
TFT_ShowString(10,100,"MODE",8,16,0,BLACK,WHITE);
TFT_ShowString(10,135,"Frequency",8,16,0,BLACK,WHITE);
TFT_ShowString(10,170,"Range",8,16,0,BLACK,WHITE);
TFT_ShowString(120+24,170,"V",8,16,0,BLACK,WHITE);
TFT_ShowString(10,205,"Offset",8,16,0,BLACK,WHITE);
TFT_ShowString(120+24,205,"V",8,16,0,BLACK,WHITE);
mode_old = mode;
change_flag = 1;
}
//当正弦波或者三角波发生变化时,修改DMA基地址
if(wav_out_old != wav_out || change_flag)
{
if(wav_out == sine_mode)
{
HAL_DAC_Stop_DMA(&hdac,DAC_CHANNEL_1);
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,(uint32_t *)dual_sine_wave_data,SINE_WAVE_DATA_MAX,DAC_ALIGN_12B_R);
TFT_ShowString(120,100,"sine_wav ",8,16,0,BLACK,WHITE);
}
else{
HAL_DAC_Stop_DMA(&hdac,DAC_CHANNEL_1);
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,(uint32_t *)dual_triangle_wave_data,SINE_WAVE_DATA_MAX,DAC_ALIGN_12B_R);
TFT_ShowString(120,100,"triangle_wav",8,16,0,BLACK,WHITE);
}
wav_out_old = wav_out;
change_flag = 0;
}
7、通过焊上去的SW引脚使用JLINK来实时DEBUG
五、项目总结
通过本次暑假一起练项目,我收获了很多,比如:可能白嫖一个板子(doge)。
这个信号发生器不大,却锻炼了我很多方面的能力。
首先是debug的能力。作为一个嵌入式菜鸟,很多错误以前都没有遇到过,本次项目编写锻炼了我寻错的能力,比如:万万想不到定时器的频率太高会导致DAC宕机;另外,也学会了一些debug的方法,比如通过观看DAC状态标志位的变化来寻找bug,一步一步慢慢试错,最终实现功能。
其次是编写代码的能力。通过本次工程,我学会了HAL库的使用,以前一直使用的是标准库,不会HAL库,以至于很多别人用HAL库写的代码我都看不懂。完成本次项目以后,我再也不会害怕HAL库,以后也会有更多的方式方法。而且我对DAC、TIM、DMA等外设的使用也更加熟练了,以后编写代码的时候也能有更多的积累。
通过本次从头到尾代码全部自行编写,我学到了很多,也发现了很多不足。最大的不足就是代码不够规范,这个短板的最直接影响就是写代码畏首畏尾、效率低,写出来的代码还特别乱,没办法分模块。以后我一定要仔细提升自己的代码规范性,养成良好的习惯,才能够提升自己。
本次活动还让我提前接触了一些数电和模电的知识,对于下面一学期即将到来的数电模电课程也会有一些帮助,希望我的数电模电能够取得理想的成绩。
最后,这次的暑假一起练活动让我把握住了这个假期,没有荒废自己,而是把握住机会提升自己、开阔眼界,对于未来的学习和成长都有着莫大的促进作用。希望今后的自己能够越来越自律。