蓝桥杯嵌入式开发经验分享(第六届决赛——“电压测量与互补 PWM 信号输出”)

作者:马一飞                                                         QQ:791729359       

 

一、题目

                                “电压测量与互补 PWM 信号输出”设计任务书

功能简述

        设计一个电压测量与脉宽调制信号输出设备, 设备能够检测模拟信号输入,并根据检测到的电压值,计算出两路互补脉宽调制信号的占空比,输出信号频率可以通过按键调整, 设备硬件部分主要由电源部分、控制器单元、 按键部分、存储单元和显示部分组成,系统框图如图 1 所示:

                                     蓝桥杯嵌入式开发经验分享(第六届决赛——“电压测量与互补 PWM 信号输出”)_第1张图片

                                                                                              图 1 系统框图

        CT117E 考试板电路原理图、 I2C 总线驱动程序、 LCD 驱动程序及本题涉及到的芯片资料可参考计算机上的电子文档。电路原理图、程序流程图及相关工程文件请以考生准考证命名,并保存在指定文件夹中(文件夹名为考生准考证号,文件夹位于 Windows 桌面上)。

设计任务及要求

1.ADC测量

        使用 STM32 处理器片内 ADC 采集电位器 R37 输出电压, 记为 Vo,并通过 LCD 显示电压值,保留小数点后两位有效数字
2. 互补 PWM 输出
        使用 STM32 处理器 TIM1 通道(PA9、 PB14) 输出互补脉宽调制信号, PA9 输出信号占空比(P)与电位器输出电压之间的关系为 P = V/3.3。 通过按键控制信号启动、停止及调节信号输出频率。

        说明: 断开 PA9 - TXD1、 PB14 - N_SD0 上的跳线连接。

3. 按键设置
        “B1”按键设定为“启动/停止”按键,切换信号输出状态,“启动”状态下,根据 ADC采集到的电压值输出互补的脉宽调制信号, 指示灯 LD1 点亮,“停止” 状态下, 两路输出通道 PA9、 PB14 持续输出低电平,指示灯 LD1 熄灭。 LCD 实时显示采集电压、信号输出状态和信号参数,显示界面如图 2 所示

                                                               蓝桥杯嵌入式开发经验分享(第六届决赛——“电压测量与互补 PWM 信号输出”)_第2张图片

                                                                                     图 2. 液晶显示界面参考
        “B2”按键设定为“设置”按键, 按下后,进入设置界面如图 3 所示,此时通过“B3”按键调整输出信号频率, 可调范围为 1KHz~10KHz, 每次按下“B3” 按键, 频率增加 1KHz,调整完成后,再次按下“B2” 按键,保存信号输出频率参数到 E2PROM, 并退出设置界面返回图 2 所示界面。

        说明: B3 按键仅在设置界面下有效; 设备默认输出信号频率 1KHz 。

                                                              蓝桥杯嵌入式开发经验分享(第六届决赛——“电压测量与互补 PWM 信号输出”)_第3张图片

                                                                                     图 3.液晶显示界面参考

4. EEPROM 存储
        用于存储配置的输出信号频率参数, 设备重启后,能够恢复最近一次的配置

 

二、模块化分析及代码实现

由于驱动上每次编写的代码都是一样的,就不重复讲解了,我们主要是讲解各部分的逻辑

1、初始化部分

        需要使用到AT24c02,因此iic的初始化是必不可少的,同时进行初始化LED;初始化按键;初始化ADC;并且从EEPROM中读取PWM频率的存储值;设定PWM的频率并且使能PWM输出,这里还有一点要注意,我们需要输出的是互补的PWM信号,并且还是需要使用TIM1来实现,需要了解的一点是TIM1是高级定时器,它跟我们之前使用的TIM2、TIM3那种通用定时器的配置是稍微有点不一样的,不然也不能称作为高级定时器,意味着功能更加的强大。 高级定时器本身就具有输出互补方波的功能,因此我们只需要借助这个这个功能就能达到我们的需求

SysTick_Config(SystemCoreClock/1000);
i2c_init();
LED_Init();
KEY_Init();
ADC1_Init();
PWM_Frequency = _24c02_Read(0xaa) * 1000;
TIM1_PWM_Init(PWM_Frequency,P,1,PWM_Enable);

2、按键处理部分

        每50ms扫描一次按键判断按键是否有操作,同时也需要根据题目的需求设定各个按键所对应的功能,同时还得注意的一点是在主界面时KEY3、KEY4是不能操作的,这个才程序上需要留意这项操作

   KEY   //
if(KEY_Flag)
{
	KEY_Read(); //按键处理函数
	KEY_Flag = 0; //清除按键读取标志位
}
void KEY_Read(void)
{
	static u16 key1_sum = 0,key2_sum = 0,key3_sum = 0,key4_sum = 0;
	//KEY1
	if(KEY1 == 0)
	{
		key1_sum++;
		if(key1_sum == 1)
		{
			PWM_Enable ^= 1;
			TIM1_PWM_Init(PWM_Frequency,P,0,PWM_Enable);
		}
	}else
	{
		key1_sum = 0;
	}
		
	//KEY2
	if(KEY2 == 0)
	{
		key2_sum++;
		if(key2_sum == 1)
		{
			Set_Flag ^= 1;
			LCD_ClearLine(Line0);
			LCD_ClearLine(Line1);
			LCD_ClearLine(Line2);
			LCD_ClearLine(Line3);
			LCD_ClearLine(Line4);
			LCD_ClearLine(Line5);
			LCD_ClearLine(Line6);
			LCD_ClearLine(Line7);
			LCD_ClearLine(Line8);
			LCD_ClearLine(Line9);
			if(!Set_Flag)
			{
				_24c02_Write(0xaa,PWM_Frequency / 1000);
			}
		}
	}else
	{
		key2_sum = 0;
	}
		
	//KEY3
	if((KEY3 == 0) && Set_Flag)
	{
		key3_sum++;
		if(key3_sum == 1)
		{
			PWM_Frequency += 1000;
			if(PWM_Frequency > 10000) PWM_Frequency = 10000;
			TIM1_PWM_Init(PWM_Frequency,P,0,PWM_Enable);
		}
	}else
	{
		key3_sum = 0;
	}
		
	//KEY4
	if((KEY4 == 0) && Set_Flag)
	{
	    key4_sum++;
		if(key4_sum == 1)
		{
		    PWM_Frequency -= 1000;
			if(PWM_Frequency < 1000) PWM_Frequency = 1000;
			TIM1_PWM_Init(PWM_Frequency,P,0,PWM_Enable);
		}
	}else
	{
		key4_sum = 0;
	}
}

3、ADC部分

        在一定时间内,读取一次ADC的值,并计算出其电压值,同时还需要改变TIM1输出方波的频率

///  ADC  /
if(ADC_Flag)
{
	ADC_Flag = 0;
	ADC_Val = Get_Adc() * 3.3 / 4096;
	P = ADC_Val * 100 / 3.3;
	TIM1_PWM_Init(PWM_Frequency,P,0,PWM_Enable);
}

3、LCD显示部分

        在显示部分需要注意的是数值的转换吧,题目要求显示的频率单位为KHZ,那么我们也要吧我们算出来的频率HZ/1000;在显示电压值时保留小数点后两位 ,因此需要%.2f。

//   Display   //
if(Display_Flag)
{
	Display_Flag = 0;
	if(!Set_Flag)
    {
		LCD_DisplayStringLine(Line1, (u8*)"        totle     ");
		sprintf((char*)string,"ADC_Val:%.2fV         ",ADC_Val);
		LCD_DisplayStringLine(Line3, string);
		if(PWM_Enable)
		{
			LCD_DisplayStringLine(Line4, (u8*)"OutPut:Open         ");
		}else
		{
			LCD_DisplayStringLine(Line4, (u8*)"OutPut:Off          ");
		}
		sprintf((char*)string,"Signal_Val:PA9: %d            ",(u8)P);
		LCD_DisplayStringLine(Line5, string);
		sprintf((char*)string,"           PB14:%d          ",(u8)(100-P));
		LCD_DisplayStringLine(Line6, string);
		sprintf((char*)string,"           %dkHZ    ",PWM_Frequency / 1000);
		LCD_DisplayStringLine(Line7, string);
		LCD_DisplayStringLine(Line9, (u8*)"                  1");
	}else
	{
		LCD_DisplayStringLine(Line1, (u8*)"      Set_Mode       ");
		sprintf((char*)string,"  Signal_Fre:%dkHZ           ",PWM_Frequency / 1000);
		LCD_DisplayStringLine(Line5, string);
		LCD_DisplayStringLine(Line9, (u8*)"                  2");
	}
}

4、滴答定时器计时部分

        中断的话基本上就是每次都差不多的了,而滴答定时器定时部分对于每个模块的定时时间根据自己的喜好来进行调节,最重要的一点是出来的结果要看起来相对比较流畅,定时时间过短,会导致cpu资源占用过多;定时时间过短,会导致出来的结果看起来会反应迟钝的感觉。

extern u8 KEY_Flag;
extern u8 ADC_Flag;
extern u8 Display_Flag;
void SysTick_Handler(void)
{
	static u8 key_sum = 0;
	static u8 adc_sum = 0;
	static u8 display_sum = 0;
	TimingDelay--;
	if(++key_sum == 50) // 50ms
	{
		key_sum = 0;
		KEY_Flag = 1;
	}
	if(++adc_sum == 250)
	{
		adc_sum = 0;
		ADC_Flag = 1;
	}
	if(++display_sum == 200)
	{
		display_sum = 0;
		Display_Flag = 1;
	}
}

5、TIM1高级定时器初始化部分

        由于高级定时器本来就比较少考到,这么多年的赛题也只有在这一套考过TIM1高级定时器,因此有必要专门拿出来讲一下,其实在初始化部分感觉大体是一样的,只不过在配置的时候需要一起吧互补输出通道也初始化。对比一下通用定时器定时器PWM输出方式的代码也很轻易的看出不同之处。

   TIM1_PWM   
void TIM1_PWM_Init(u16 fre,u8 duty,u8 status,u8 enable)
{
	u16 frequency;
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	if(status)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA, ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
			
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOA, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOB, &GPIO_InitStructure);
	}
	frequency = 1000000 / fre;
	TIM_TimeBaseInitStructure.TIM_Period = frequency - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 71;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = 0x0;
	TIM_TimeBaseInitStructure.TIM_CounterMode = 0x0;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
		
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
	if(enable)
	{
		TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
		TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
	}else
	{
		TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
		TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
	}
	TIM_OCInitStructure.TIM_Pulse = (frequency - 1) * duty / 100;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
	TIM_OC2Init(TIM1,&TIM_OCInitStructure);
		
	TIM_CtrlPWMOutputs(TIM1, ENABLE);
		
	TIM_Cmd(TIM1, ENABLE);
}

 

三、通用初始化部分

        

u16 LED_MODE = 0XFFFF;
extern u8 PWM_Enable;
extern u16 PWM_Frequency;
u8 Set_Flag = 0;
extern float P;
/   24c02   ///
void _24c02_Write(u8 address,u8 data)
{
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(address);
	I2CWaitAck();
	I2CSendByte(data);
	I2CWaitAck();
	I2CStop();
}

u8 _24c02_Read(u8 address)
{
	u8 data;
	I2CStart();
		I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(address);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0xa1);
	I2CWaitAck();
	data = I2CReceiveByte();
	I2CWaitAck();
	I2CStop();
	return data;
}
/   ADC   ///
void ADC1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_ADC1, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel = 1;
	ADC_Init( ADC1, &ADC_InitStructure);
	
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus( ADC1));
		
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1));
}
u16 Get_Adc(void)
{
	u16 temp;
	ADC_RegularChannelConfig(ADC1,ADC_Channel_8, 1, ADC_SampleTime_239Cycles5);
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	temp = ADC_GetConversionValue(ADC1);
	ADC_SoftwareStartConvCmd(ADC1,DISABLE);
	return temp;
}
   TIM1_PWM   
void TIM1_PWM_Init(u16 fre,u8 duty,u8 status,u8 enable)
{
	u16 frequency;
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	if(status)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA, ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
			
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOA, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOB, &GPIO_InitStructure);
	}
	frequency = 1000000 / fre;
	TIM_TimeBaseInitStructure.TIM_Period = frequency - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 71;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = 0x0;
	TIM_TimeBaseInitStructure.TIM_CounterMode = 0x0;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
		
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
	if(enable)
	{
		TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
		TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
	}else
	{
		TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
		TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
	}
	TIM_OCInitStructure.TIM_Pulse = (frequency - 1) * duty / 100;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
	TIM_OC2Init(TIM1,&TIM_OCInitStructure);
		
	TIM_CtrlPWMOutputs(TIM1, ENABLE);
		
	TIM_Cmd(TIM1, ENABLE);
}

///   LED   ///
void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
		
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = 0XFF00;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	GPIOC->ODR = 0XFFFF;
	GPIOD->ODR |= (1<<2);
	GPIOD->ODR &=~(1<<2);
}

///  KEY  //
void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

 

你可能感兴趣的:(蓝桥杯)