【蓝桥杯嵌入式】六、真题演练(一)-2研究篇:第 14 届真题

温馨提示:

        真题演练分为模拟篇和研究篇。本专栏的主要作用是记录我的备赛过程,我打算先自己做一遍,把遇到的问题和不同之处记录到演练篇,然后再返回来仔细研究一下,找到最佳的解题方法记录到研究篇。题目在:https://pan.quark.cn/s/fc121e80dbff

本文可能不会重复之前提到的内容,如需查看,请参考附录

【蓝桥杯嵌入式】附录

目录

演练时发现的问题:

题目分析与计划:

设计规范 

浏览题目

详细阅读与分析

硬件要求:

功能要求:

整体流程设计

全局变量定义:

一、外设配置与代码框分析

二、软件配置

按照分析配置外设:

三、程序功能实现

主循环代码

各个按键功能代码

功能函数代码:

stm32g4xx_it.c中TIM4、TIM6、TIM7代码

四、保存结果


演练时发现的问题:

  1. 不能总是清屏,应该改成在按下B1后清屏
  2.  每次都要关LED。而且不可以离点亮LED灯的位置太远,不然会闪;
  3. 按键延时就设置为90就行,不快也不慢如果开发板反复无常,就是硬件有问题,要求更换!
  4. 减小值的位置添加清屏或者在写显示函数的时候格式化数字的位数。
  5. 计算要从简
  6. 关于时间的操作可以在TIM中断服务函数中进行的,就不要拿出来
  7. 要使用定时器中断服务函数,就必须用中断方式打开或关闭定时器!
  8. PWM输出的定时器需要打开对应定时器、打开PWM输出!
  9. PWM输入的定时器也需要打开,还有对应的两个通道!
  10. 我发现题目没有让我们交.ioc文件,所以我们应该把所有的Src和Inc中的文件都提交。

题目分析与计划:

设计规范 

       我自己设计的时候没有章法,从而设计的有点潦草,浪费了很多时间。设计的时候要冷静、全面、要遵守一个确定的顺序。

        现在我要确定一个章法,这是第一版,以后更新的版本请在附录中查看。

  1. 浏览一遍题目,有一个整体印象。
  2. 仔细读题目,综合考虑如何配置外设和实现功能。
    1. 确定需要的外设和功能。约定圆形为外设分析、三角型为功能分析,图形中的数字来区分不同的模块。
  3. 设计流程,主循环前做什么、主循环中做什么。
    1. 确定主循环前打开需要的外设
    2. 根据题目一步一步的设计流程,不要遗漏,字不要写的太大,而且要留有足够的空间用来修改。
      1. 流程包括一条主线任务,调用函数就是支线任务。
    3. 同时在另一张草稿纸上写下需要的变量,按照外设或题目中的信息命名,要有特征性和区分度。
  4. 这样设计下来最多1小时,按照设计配置外设和写程序最多2小时,检查和优化1小时,最多4个小时就完成了。
  5. 全局变量定义:

    经验规则:

    标志变量:uint8_t ,只取个位数或0,1

    与计算有关或者需要显示:uint32_t

    浮点型:float

  6. 检查和优化:

    1. 备份项目

    2. 仔细根据题目,检查功能是否符合题目描述,然后备份项目

    3. 将代码中有警告部分想办法修复,将变量的类型缩小,减小内存占用。

浏览题目

得到的信息:PWM输出和输入,LED3个、KEY4个、ADC、LCD3种界面。

详细阅读与分析

硬件要求:

①PWM输出:PA1

        (1)占空比可调,3.3中要求%的形式,周期设置为100的倍数,范围10%~85%

        (2)频率为8k和4k可切换(默认4k)

        (3)由上述可知:PCBTimer设置为80MHz,周期设置为200(4k)和100(8k),计算预分频系数,设置为99;初始的占空比设置为10%也就是20。

②PWM输入:PA7

③ADC输入:R37

        (1)读取的信息要做处理,转换为①PWM输出中的占空比

④LED、KEY的响应时间,要注意

⑤LCD界面要显示的变量要列出来

功能要求:

常用功能:4个按键的响应功能

三角形1:读取ADC计算占空比

三角形2:计算速度

三角形3:记录速度

需要的定时器的功能:

长按功能:TIM6

变频功能:TIM7

判断是否保留速度数据:TIM4

整体流程设计

自己写设计流程草稿的时候要按照自己的习惯简写,我这里是为了方便描述才写这么多的。

  • 启动PWM输入和输出;
  • 主循环中轮询检测按键、读取ADC、计算速度。
    • (支线)按键1的响应:按键B1按下一次就切换一种模式,设置标志位B1_key=1/2/3来区分模式;

  • 当B1_key=1时为DATA模式;
  • 显示频率模式、占空比、速度。
    • (支线)按键2的响应:当B1_key=1时且没有进行频率切换的时,开始频率切换;切换次数加一;
    • (支线)变频功能:频率切换在TIM7中进行,每次100ms让LD2状态翻转,50次就是运行5s。
    • (支线)按键4的响应:当B1_key=1时长按B4,锁定占空比的值;短按解锁。
      • 也就是停止将ADC读取的值转换为占空比,应该改变全局标志。
      • 长按功能:属于常用功能,见下文。

  • 当B1_key=2时为PARA模式;
  • 显示R和K,范围1到10,有减法操作,占2位。
    • (支线)按键2的响应:当B1_key=2时,切换PARA模式。
    • (支线)按键3的响应:当B1_key=2时,判断PARA模式,进行加操作,要限制在1~10;
    • (支线)按键4的响应:当B1_key=2时,判断PARA模式,进行加操作,要限制在1~10;

  • 当B1_key=3时为PARA模式;
  • 显示最快速度值、高频模式最快速度值
    • (支线)按键1的响应:B1_key=3时记录之前的R、K值,如果其中一个发生了改变,就更新值并开启判定是否记录速度功能;(第一次做的时候没有分析)
    • (支线)判定是否记录速度功能:设置判断定时器是否打开的标志,如果定时器没打开就打开定时器,如果定时器已经打开就说明未计时完成,重启定时器;定时器计时2s,超时就把判断是什么模式,判断最大值然后记录。

  • 其他支线
    • (支线)轮询检测按键:常用函数,在框架中
    • (支线)读取ADC:如果没锁定,就读取ADC的值,转换为电压,按照图像计算占空比,判断是低频还是高频,设置占空比
    • (支线)计算速度:判断是低频还是高频,带入值计算即可
全局变量定义:

经验规则:

标志变量:uint8_t ,只取个位数或0,1

与计算有关或者需要显示:uint32_t

浮点型:float

uint8_t B1_key=1;            //1-DATA模式;2-PARA模式;3-RECD模式
char LCD_str[20];            //LCD字符

//DATA模式
uint8_t PWMOut_Mode=1;       //1-低频;0-高频
uint32_t PWMOut_Pluse=10;    //占空比
float Data_V=0.0;            //速度,题目要求有一位小数

uint32_t PWMIn_f=0;          //PWM的频率要么为4k要么为8k,计算速度时用

//占空比计算
uint32_t volt=0;             //电压值
uint8_t Pluse_C=1;           //占空比锁定标志:1-未锁定;0-锁定

//PARA模式
uint8_t PARA_mode=1;         //PARA模式:1-改变R;0-改变K
uint32_t PARA_R=1;           //R取1到10
uint32_t PARA_K=1;           //K取1到10
uint32_t oldPARA_R=1;        //前一次的R
uint32_t oldPARA_K=1;        //前一次的K

//RECD模式
uint8_t V_change=1;          //速度改变标志,如果2s内没有改变速度说明可以记录速度   
float V_Hmax=0.0;            //高频模式下最快速度
float V_Lmax=0.0;            //低频模式下最快速度

//频率切换   
uint8_t changeFlag=1;        //切换标志位:1-切换未进行;0-切换进行中
uint32_t changeNum=0;        //频率切换次数
uint32_t ChangeTim=0;        //PWM频率变化次数,变化50次完成一次频率转换
uint32_t Period=199;         //周期值,PWM频率通过改变周期来实现

一、外设配置与代码框分析

LED:锁存器操作包装函数,直接在main.c实现

void LED_switch(uint16_t GPIO_Pin,GPIO_PinState PinState)
{
	HAL_GPIO_WritePin(LED_LE_GPIO_Port,LED_LE_Pin,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,LED1_Pin|LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin|LED6_Pin|LED7_Pin|LED8_Pin,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,GPIO_Pin,PinState);
	HAL_GPIO_WritePin(LED_LE_GPIO_Port,LED_LE_Pin,GPIO_PIN_RESET);
}

KEY:扫描函数、长按操作

按键扫描函数框架:所有的按键引发事件的操作都在这里实现

//按键1响应函数
void B1Fun()
{
}

//按键2响应函数
void B2Fun()
{
}

//按键3响应函数
void B3Fun()
{
}

//按键4响应函数
void B4Fun()
{
}


//按键扫描
void KeyScan()
{
	uint32_t keyDelay=50;
	if(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin)==GPIO_PIN_RESET)
	{
		HAL_Delay(keyDelay);
		if(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin)==GPIO_PIN_RESET)
		{
			B1Fun();
		}
	}
	
	if(HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin)==GPIO_PIN_RESET)
	{
		HAL_Delay(keyDelay);
		if(HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin)==GPIO_PIN_RESET)
		{
			B2Fun();
		}
	}
	
	if(HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin)==GPIO_PIN_RESET)
	{
		HAL_Delay(keyDelay);
		if(HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin)==GPIO_PIN_RESET)
		{
			B3Fun();
		}
	}
	
	if(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin)==GPIO_PIN_RESET)
	{
		HAL_Delay(keyDelay);
		if(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin)==GPIO_PIN_RESET)
		{
			B4Fun();
		}
	}
}

长按操作: 按下按键后打开定时器,定时器计时2s,如果定时器正在运行,就轮询检测按键是否松开,如果松开就是短按。如果定时器计时完成就关闭定时器,等待按键松开,就是长按。

//PWM_C初始化为1,在定时器超时后PWM_C=0判定为长按
if(PWM_C==1)//长按
{
    HAL_TIM_Base_Start_IT(&htim6);
    while(HAL_TIM_Base_GetState(&htim6)==HAL_TIM_STATE_BUSY)//定时器正在运行
	{
    	if(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin)==GPIO_PIN_SET)//按键提前松开,就是短按
   		{
			HAL_TIM_Base_Stop_IT(&htim6);
			return;
		}
	}
    //如果是长按就等待按键松开
	if(PWM_C==0)
	    while(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin)==GPIO_PIN_RESET);
}
else//短按
    PWM_C=1;

LCD:各种界面的设计,在主循环中实现

PWM输入:

  • PA7——TIM3_CH2
  • 预分频系数:Prescaler (PSC - 16 bits value),设置为0(即保持默认)
  • 定时器周期:Counter Period,设置到最大(即保持默认)
  • 频率计算公式:

        根据以上配置,PWM波的频率=APB1 Timer/CH1读取的值

        PWM波的频率 = 定时器频率/CH1读取的值

        定时器频率 = APB1 Timer/(预分频系数+1);

PWM输出:

  • 通过设置 Prescaler             来设置定时器频率 
  • 通过设置 Counter Period   来设置周期(计数值)
  • 通过设置 Pulse                   来设置占空比
  • 通过设置 CH Polarity         来设置极性
  • PWM波的频率设置计算公式:

预分频系数 = (APB1 Timer/PWM波的频率 )-1;

周期 = 定时器频率/PWM波的频率

预分频系数 = (APB1 Timer/定时器频率 )-1;

  • 根据题目确定配置,如果要配置%的占空比,周期就设置为100的倍数。然后根据题目和便于计算原则,设置预分频系数。
  • 在程序中修改预分频系数、周期、占空比的函数如下
    __HAL_TIM_SET_COUNTER()    //设置计数值,即周期
    __HAL_TIM_SET_PRESCALER()  //设置预分频系数
    __HAL_TIM_SET_COMPARE(设备句柄地址,通道号,脉宽值);    //设置占空比

ADC:

  • ADC2_IN15——PB15——R37(电压采集1)
  • ADC Mode:选择需要的通道,选择single_ended
  • Rank里的Sampling Time:采样时间,不设置也行,建议比赛时为提高准确性设置为640.5。
  • 转换结果电压计算公式:
  • 实际电压值(mV)=ADC读取值*VREF+(mV)/2^(转换精度)

    VREF+是ADC转换的正参考电压,开发板上理论是3300mV;转换精度可以在CubeMX中设置,默认为12;

二、软件配置

参考附录的内容,建立名为“ ”的项目。

按照分析配置外设:

时钟配置:

LED

KEY

PWM输出

PWM输入

ADC

打开ADC2的通道15即可,其余默认


TIM4、TIM6、TIM7配置:

 打开并设置中断优先级:

生成项目文件后,打开MDK;

导入LCD驱动程序文件,编译。

三、程序功能实现

代码规则如下:

【蓝桥杯嵌入式】五、省赛一条龙(一)全部功能总结汇总-CSDN博客

主循环代码

  /* USER CODE BEGIN WHILE */
	HAL_TIM_Base_Start(&htim2);
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
	
	HAL_TIM_Base_Start(&htim3);
	HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
	HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
  while (1)
  {
		KeyScan();
		Read_ADC();
		Set_V();
		if(B1_key==1)
		{

			LED_Switch(LED1_Pin|LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin|LED6_Pin|LED7_Pin|LED8_Pin,GPIO_PIN_SET);
			if(Pluse_C==0)
				LED_Switch(LED3_Pin,GPIO_PIN_RESET);
			LED_Switch(LED1_Pin,GPIO_PIN_RESET);
			
			LCD_DisplayStringLine(Line2,(u8*)"        DATA");
			
			if(PWMOut_Mode==1)
				LCD_DisplayStringLine(Line4,(u8*)"     M=L");
			else
				LCD_DisplayStringLine(Line4,(u8*)"     M=H");
			
			sprintf(LCD_str,"     P=%2d%c",PWMOut_Pluse,'%');
			LCD_DisplayStringLine(Line5,(u8*)LCD_str);
			
			sprintf(LCD_str,"     V=%.1f",Data_V);
			LCD_DisplayStringLine(Line6,(u8*)LCD_str);
		}
		else if(B1_key==2)
		{
			LED_Switch(LED1_Pin|LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin|LED6_Pin|LED7_Pin|LED8_Pin,GPIO_PIN_SET);
			
			LCD_DisplayStringLine(Line2,(u8*)"        PARA");

			sprintf(LCD_str,"     R=%2d",PARA_R);
			LCD_DisplayStringLine(Line4,(u8*)LCD_str);
			
			sprintf(LCD_str,"     K=%2d",PARA_K);
			LCD_DisplayStringLine(Line5,(u8*)LCD_str);
		}
		else if(B1_key==3)
		{
			LED_Switch(LED1_Pin|LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin|LED6_Pin|LED7_Pin|LED8_Pin,GPIO_PIN_SET);

			LCD_DisplayStringLine(Line2,"        RECD");

			sprintf(LCD_str,"     N=%d",changeNum);
			LCD_DisplayStringLine(Line4,(u8*)LCD_str);
			
			sprintf(LCD_str,"     MH=%.1f",V_Hmax);
			LCD_DisplayStringLine(Line5,(u8*)LCD_str);			
			
			sprintf(LCD_str,"     ML=%.1f",V_Lmax);
			LCD_DisplayStringLine(Line6,(u8*)LCD_str);		
		}
    /* USER CODE END WHILE */

各个按键功能代码

//按键1
void B1Fun()
{
	B1_key++;
	LCD_Clear(Black);
	if(B1_key==3&&(oldPARA_K!=PARA_K||oldPARA_R!=PARA_R))
	{
		rem_V();
	}
	else if(B1_key>3)
		B1_key=1;
}

//按键2
void B2Fun()
{
	if(B1_key==1&&changeFlag==1)
	{
		changeNum++;
		changeFlag=0;
		HAL_TIM_Base_Start_IT(&htim7);
	}
	else if(B1_key==2)
	{
		if(PARA_mode==1)
			PARA_mode=0;
		else 
			PARA_mode=1;
	}
}

//按键3
void B3Fun()
{
	if(B1_key==2)
	{
		if(PARA_mode==1)
		{
			PARA_R++;
			if(PARA_R>10)
				PARA_R=1;
		}

		else
		{
			PARA_K++;
			if(PARA_K>10)
				PARA_K=1;
		}
	}
}

//按键4
void B4Fun()
{
	if(B1_key==1)
	{
		if(Pluse_C==1)//³¤°´
		{
			
			HAL_TIM_Base_Start_IT(&htim6);
			while(HAL_TIM_Base_GetState(&htim6)==HAL_TIM_STATE_BUSY)
			{
				if(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin)==GPIO_PIN_SET)
				{
					HAL_TIM_Base_Stop_IT(&htim6);
					return;
				}
			}
			if(Pluse_C==0)
				while(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin)==GPIO_PIN_RESET);
		}
		else
		{
				Pluse_C=1;
		}
	}
	
	else if(B1_key==2)
	{
		if(PARA_mode==1)
		{
			PARA_R--;
			if(PARA_R<1)
				PARA_R=10;
		}
		else
		{
			PARA_K--;
			if(PARA_K<1)
				PARA_K=10;
		}
	}
}

功能函数代码:

//读取ADC电压值,改变占空比
void Read_ADC()
{
	HAL_ADC_Start(&hadc2);
	if(Pluse_C==1&&HAL_ADC_PollForConversion(&hadc2,200)==HAL_OK)
	{
		volt=(3300*HAL_ADC_GetValue(&hadc2))>>12;
		if(volt<=1000)
			PWMOut_Pluse=10;
		else if(volt>=3000)
			PWMOut_Pluse=85;
		else
			PWMOut_Pluse=(volt-1000)/27+10;
		
		if(PWMOut_Mode==1)
			__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,PWMOut_Pluse*2);
		else
			__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,PWMOut_Pluse);
	}
	
}

//计算速度
void Set_V()
{
	
	if(PWMOut_Mode==1)
		PWMIn_f=4000;
	else
		PWMIn_f=8000;
	
	Data_V=(PWMIn_f*6.28*PARA_R)/(100*PARA_K);
}

//记录速度
void rem_V()
{
	if(V_change==0)
	{
		HAL_TIM_Base_Stop_IT(&htim4);
		HAL_TIM_Base_Start_IT(&htim4);
	}
	else 
	{
		HAL_TIM_Base_Start_IT(&htim4);
		V_change=0;
	}
}

stm32g4xx_it.c中TIM4、TIM6、TIM7代码

/**
  * @brief This function handles TIM4 global interrupt.
  */
void TIM4_IRQHandler(void)
{
  /* USER CODE BEGIN TIM4_IRQn 0 */

  /* USER CODE END TIM4_IRQn 0 */
  HAL_TIM_IRQHandler(&htim4);
  /* USER CODE BEGIN TIM4_IRQn 1 */
	V_change=1;
	if(PWMOut_Mode==1)
	{
		if(Data_V>V_Lmax)
			V_Lmax=Data_V;
	}
	else
	{
		if(Data_V>V_Hmax)
			V_Hmax=Data_V;		
	}
	HAL_TIM_Base_Stop_IT(&htim4);
  /* USER CODE END TIM4_IRQn 1 */
}

/**
  * @brief This function handles TIM6 global interrupt, DAC1 and DAC3 channel underrun error interrupts.
  */
void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */

  /* USER CODE END TIM6_DAC_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */

		Pluse_C=0;
		LED_Switch(LED1_Pin|LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin|LED6_Pin|LED7_Pin|LED8_Pin,GPIO_PIN_SET);
		LED_Switch(LED1_Pin|LED3_Pin,GPIO_PIN_RESET);
		HAL_TIM_Base_Stop_IT(&htim6);

  /* USER CODE END TIM6_DAC_IRQn 1 */
}

/**
  * @brief This function handles TIM7 global interrupt.
  */
void TIM7_IRQHandler(void)
{
  /* USER CODE BEGIN TIM7_IRQn 0 */

  /* USER CODE END TIM7_IRQn 0 */
  HAL_TIM_IRQHandler(&htim7);
  /* USER CODE BEGIN TIM7_IRQn 1 */
	ChangeTim++;
	if(ChangeTim==50)
	{
		ChangeTim=0;
		changeFlag=1;
		if(PWMOut_Mode==1)
		{
			PWMOut_Mode=0;
			__HAL_TIM_SetAutoreload(&htim2,99);
		}
		else
		{
			PWMOut_Mode=1;
			__HAL_TIM_SetAutoreload(&htim2,199);
		}
		
		HAL_TIM_Base_Stop_IT(&htim7);
	}
	else 
	{
		if(PWMOut_Mode==1)
			Period=200-ChangeTim*2;
		else
			Period=100+ChangeTim*2;
		
		__HAL_TIM_SetAutoreload(&htim2,Period);
		__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,(uint32_t)(Period/PWMOut_Pluse));
		
		LED_Switch(LED1_Pin|LED2_Pin|LED3_Pin|LED4_Pin|LED5_Pin|LED6_Pin|LED7_Pin|LED8_Pin,GPIO_PIN_SET);
		LED_Switch(LED1_Pin,GPIO_PIN_RESET);
		HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
		HAL_GPIO_WritePin(GPIOD,LED_LE_Pin,GPIO_PIN_SET);
		HAL_GPIO_WritePin(GPIOD,LED_LE_Pin,GPIO_PIN_RESET);
	}
  /* USER CODE END TIM7_IRQn 1 */
}

四、保存结果

打包Core文件中所有文件,还有Gxxxxxxxx.hex文件。

  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值