2021暑假一起练-用STM32F072完成便携式信号发生器
使用STM32F072单片机完成了信号发生器功能,能够输出可使用按键调节频率、占空比的PWM波;能够输出正弦波或者三角波,可使用按键调节波形种类、频率、幅值和直流偏移。波形信息、当前参数和控制菜单可在LCD屏上显示。
标签
嵌入式系统
STM32
显示
DDS
小蝌蚪
更新2021-09-12
1140

本项目使用STM32F072单片机完成了信号发生器功能,能够输出可使用按键调节频率、占空比的PWM波;能够输出正弦波或者三角波,可使用按键调节波形种类、频率、幅值和直流偏移。波形信息、当前参数和控制菜单可在LCD屏上显示。项目介绍分为以下几个部分:

项目要求、选择平台及配置、完成的功能介绍、功能实现代码介绍、项目总结

 

一、项目要求

项目2 制作简易信号发生器

  1. 通过STM32F072的DAC产生正弦波、三角波等常用波形,输出到Wav管脚
  2. 通过STM32F072的内部定时器产生可调周期、可调占空比的PWM信号,输出到PWM管脚
  3. 可以通过按键改变Wav信号的波形、频率、幅度、直流偏移,改变PWM信号的频率和占空比
  4. 在LCD上显示波形信息以及当前的参数、控制菜单

二、选择平台及其配置

本项目使用STM32cubemx、STM32cubeprogrammer和KEIL5软件进行开发。

LCD屏幕采用SPI驱动,按键驱动使用定时器读取引脚实现,DAC+DMA+TIM输出正弦波和三角波,定时器输出PWM波,通过修改定时器的PSC、ARR、CCR寄存器来实现任意波和PWM波的频率修改,通过赋值DMA数组来实现幅值和直流偏移的修改。

三、完成的功能介绍

1、成功驱动LCD屏幕

Fl2A4Ids15erVjYA5y9HSfxxVJeE

2、输出可调频率和占空比的PWM波

FiSrEhBBoExsYk8jxKLdffetFp_G

Fr8SmEqp2XRLbpGq5Ezb4OxnbqTn

3、输出可调幅值和直流偏移的正弦波和三角波

FikIESIo_Nskm8DfnXBx4TzRDfAY

FtbJBpglKpPgUozJLZp53mJ09r5l

四、功能实现代码

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初始化配置

Fp852D80WZeNb-4-C09J8JTRRy6U

(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初始化配置

FiFLp0HtS9ql6V43Vy-mID5TqSXl

(4)DAC初始化配置

Frp3nk5e4MG3HrJrMdeY6coV54x7

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

Fhjrd0g0jotKApPCw3Efge_JbtEf

五、项目总结

通过本次暑假一起练项目,我收获了很多,比如:可能白嫖一个板子(doge)。

这个信号发生器不大,却锻炼了我很多方面的能力。

首先是debug的能力。作为一个嵌入式菜鸟,很多错误以前都没有遇到过,本次项目编写锻炼了我寻错的能力,比如:万万想不到定时器的频率太高会导致DAC宕机;另外,也学会了一些debug的方法,比如通过观看DAC状态标志位的变化来寻找bug,一步一步慢慢试错,最终实现功能。

其次是编写代码的能力。通过本次工程,我学会了HAL库的使用,以前一直使用的是标准库,不会HAL库,以至于很多别人用HAL库写的代码我都看不懂。完成本次项目以后,我再也不会害怕HAL库,以后也会有更多的方式方法。而且我对DAC、TIM、DMA等外设的使用也更加熟练了,以后编写代码的时候也能有更多的积累。

通过本次从头到尾代码全部自行编写,我学到了很多,也发现了很多不足。最大的不足就是代码不够规范,这个短板的最直接影响就是写代码畏首畏尾、效率低,写出来的代码还特别乱,没办法分模块。以后我一定要仔细提升自己的代码规范性,养成良好的习惯,才能够提升自己。

本次活动还让我提前接触了一些数电和模电的知识,对于下面一学期即将到来的数电模电课程也会有一些帮助,希望我的数电模电能够取得理想的成绩。

最后,这次的暑假一起练活动让我把握住了这个假期,没有荒废自己,而是把握住机会提升自己、开阔眼界,对于未来的学习和成长都有着莫大的促进作用。希望今后的自己能够越来越自律。

附件下载
HomeworkOscilloscope.hex
可执行文件
HomeworkOscilloscope.zip
源代码
团队介绍
华中科技大学电子信息与通信学院
团队成员
小蝌蚪
祝科伟
刚入大二的嵌入式菜鸟,练练手
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号