当前位置: 首页 > news >正文

蓝桥杯嵌入式第15届真题-个人理解+解析

个人吐槽

#因为最近蓝桥杯快要开始了,我舍不得米白费了,所以就认真刷刷模拟题,但是我感觉真题会更好,所以就看了一下上届的真题。不过它是真的长,我看着就头晕,但是还是把几个模块认真分析了一下就还是很容易写的。

#分析的话,频率超限和频率突变,这位博主就写的很好,所以我就直接使用他的来了,很清晰,所以这里更多的是以我的视角一起来分析一下想法。题目的话,就不截取了。第十五届蓝桥杯嵌入式组省赛题目分析及代码_蓝桥杯嵌入式15届赛题-CSDN博客

个人心得部分

对于题目来说,每一个题目的要求都是有用的。 写题目的时候一定要注意。 比如说性能要求那里,频率的测量范围,真的会影响到后面的测评结果。所以一定要大致的扫描完题目后,细致地针对题目要求来进行代码编写。 

STM32CUBEMX 配置部分

所用到的外设有

  1. LED1-7   默认配置 LD1-LD7  
  2. 四个按键 B1,B2,B3,B4 --- 这个后续会在我的代码里出现的,注意
  3. 这里我用到了差不多4个Timer 四个定时器  

Timer2,Timer16 都是通道1,配置为输入捕获,且时钟源为内部时钟。 psc为80 - 1其他保持默认。

Timer6 我是用于按键状态机   定时器中断处理 按键状态机 它的频率为100hz 。 psc,arr你怎么给取决于自己。只要保证定时器为100hz即可。其他默认

Timer4 我是用于处理频率数据 10hz,psc,arr由你决定。只要保证为10hz即可。其他保持默认。

模块分割(先从简单的部分开始)

1.变量部分

/*
主要的变量是以下部分,之前我写过按键状态机,
那个变量的话,我单独拿出来讲。

由于是第一次写这个省赛题目, 我会标出这个代码所在文件

main.c
*/
typedef enum
{
	LCD_SHOW_DATA_PAGE_FREQUENCE,     /* 数据界面(频率) */
	LCD_SHOW_DATA_PAGE_TIMES    ,      /* 数据界面(周期) */
	LCD_SHOW_PARA_PAGE          ,     /* 参数界面*/
	LCD_SHOW_COUNT_PAGE         ,     /* 统计界面*/
}LCD_Page;


typedef enum
{
	PARA_NONE = 0,
	PARA_PD,
	PARA_PH,
	PARA_PX,
}Para; //参数

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
char       text[20]                       ;
int        ccl_value_1 = 0, ccl_value_2 = 0;   // 获取输入捕获值

/* 对于频率部分 fA / fB = FA / FB + PX  */
int        fA = 0,fB = 0;                   // 频率值
int PD = 1000,PH = 5000,PX = 0;               
float      fre_A = 0,fre_B = 0;         
float      A_times = 0,B_times = 0;



int   NDA = 0,NDB = 0;   /*  A/B通道频率突变次数 */
int   NHA = 0,NHB = 0;   /*  A/B通道频率超限次数 */
int   PHA[2],PHB[2];
uint16_t A_us = 0,B_us = 0;   /* A,B的时间 (us)*/
float    A_ms = 0, B_ms = 0;  /* A,B的时间 (ms)*/
LCD_Page   mypage = LCD_SHOW_DATA_PAGE_FREQUENCE;   /* 初始化为 数据界面(频率)*/  
LCD_Page   Key3page = LCD_SHOW_DATA_PAGE_FREQUENCE;
Para       mypara = PARA_NONE;  //用于参数选择

/* --- 频率突变部分 --- */
int PDA[30] = {0},PDB[30] = {0};
int Max_A,Max_B,Min_A,Min_B;
uint16_t timerA = 0,timerB = 0; // 3s的时间窗口标志位


/* --- 最终频率--- */
int FreA = 0,FreB = 0;

 上面是main.c 函数中运用到的变量值

/*
    fun.h
*/

#ifndef _fun_h
#define _fun_h
#include "headfile.h"

#define   KEY_NUM          4
#define   DEBOUNCE_CNT     2
#define   LONG_PRESS_THRESH  900  // 长按判断阈值(ms)

#define LED_CloseAll()				do{GPIOC->ODR = 0xFFFFU;}while(0)

/*   枚举按键状态类型  */
typedef enum 
{
	KEY_STATE_IDLE,
	KEY_STATE_DEBOUNCE,
	KEY_STATE_PRESSED,
//	KEY_STATE_RELEASE_DEBOUNCE	
}KeyState;


typedef enum
{ 
	KEY_EVT_NONE,       //没有按下? 
	KEY_EVT_PRESS,      //按下事件
	KEY_EVT_RELEASE,    //释放事件
	KEY_EVT_LONG_PRESS  // 长按事件
}KeyEventType;

typedef struct
{
	KeyEventType type;
	uint32_t duration;   //事件持续时间(ms)
	
}KeyEvent;

// 增强按键结构体
typedef struct {
    GPIO_TypeDef* GPIOx;
    uint16_t GPIO_Pin;
    KeyState state;
    uint32_t pressStart;    // 按下开始时间
    uint32_t pressDuration; // 当前按压持续时间
    uint8_t eventFlag;      // 事件标志
    KeyEvent event;         // 事件详情
} KeyHandle;

void Keys_StateMachine_Handler(void);

/*   获取当前按压持续时间   */
uint32_t Get_KeyPressDuration(uint8_t KeyID);

// 事件轮询函数
KeyEvent Poll_KeyEvent(uint8_t keyID);

/* 获取按键事件 */
uint8_t Get_Key(uint8_t KeyID);

void LED_Ctrl(uint8_t num,uint8_t mode);
void LED_CLOSEALL(void);
#endif

这里是我自己定义的fun.h文件 里面包括我自己定义的按键状态机 + LED灯控制。

/*
    headfile.h
*/

#ifndef _headfile_h
#define _headfile_h

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "stm32g4xx.h"                  // Device header

#include "string.h"
#include "stdint.h"
#include "main.h"
#include "gpio.h"
#include "TIM.h"

#include "fun.h"
#include "lcd.h"

#endif

这个是headfile.h文件 里面集成了大部分所要用到的头文件,就无需我们总是在不同的头文件中重复引入相同的头文件。

1.5 频率捕获部分

/*
main.c

输入捕获部分
*/

// 变量部分
int        ccl_value_1 = 0, ccl_value_2 = 0;   // 获取输入捕获值

/* 对于频率部分 fA / fB = FA / FB + PX  */
int        fA = 0,fB = 0;                   // 频率值
int PD = 1000,PH = 5000,PX = 0;               
float      fre_A = 0,fre_B = 0;       
//


//代码部分


/*    频率获取部分
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	/*      r40 输出捕获   通道 A */
   if(htim->Instance == TIM2)
   {

	   fA = 1000000 / (TIM2->CCR1+1);
	   TIM2->CNT = 0;

// 这里我们根据 性能要求部分推出
	   if(fA > 20000) fA = 20000;
	   else if(fA < 400) fA = 400;
	   FreA = fA + PX;
	   ccl_value_1 = 1000000 / FreA;
	   /*这里我根据测评出的结果 推出来,它其实周期的计算是根据最终的频率来计算的
	   而不是我最先测出的fA来进行计算。*/
   }
   
   /*      R39 输出捕获  通道 B  */
   if(htim->Instance == TIM16)
   {

    	fB = 1000000 / (TIM16->CCR1+1);
	   TIM16->CNT = 0;
	   if(fB > 20000) fB = 20000;
	   else if(fB < 400) fB = 400;
	   FreB = fB + PX;
	   ccl_value_2 = 1000000 / FreB;
   }
}

为什么我们在输入捕获那里 还写一个if..else。 因为我们可以具体去看性能要求部分。

400 hz --- 20000hz 

// 这里我们根据 性能要求部分推出
       if(fA > 20000) fA = 20000;
       else if(fA < 400) fA = 400;

2.按键部分

按键部分的话,我就不细说了,它在我写的博客里,就是最近的博客。直接上代码了。 头文件是fun.h 就是上面那个代码。

/*
 此为 fun.c 里面包括了
1.按键状态机 + 状态轮询函数(我进行封装了)
2.LED控制
*/
#include "fun.h"

// 初始化 按键
static KeyHandle Keys[KEY_NUM] = 
{
	{B1_GPIO_Port,B1_Pin,KEY_STATE_IDLE,0,0,0,{KEY_EVT_PRESS, 0}},
	{B2_GPIO_Port,B2_Pin,KEY_STATE_IDLE,0,0,0,{KEY_EVT_PRESS, 0}},
	{B3_GPIO_Port,B3_Pin,KEY_STATE_IDLE,0,0,0,{KEY_EVT_PRESS, 0}},
	{B4_GPIO_Port,B4_Pin,KEY_STATE_IDLE,0,0,0,{KEY_EVT_PRESS, 0}},
};


void Keys_StateMachine_Handler(void)
{
	static uint8_t debounceCnt[KEY_NUM] = {0};
	uint32_t now  = HAL_GetTick();   // 获取tick时间
	uint8_t i;
	
	for(i = 0;i<KEY_NUM;i++)
	{
		uint8_t pinState = HAL_GPIO_ReadPin(Keys[i].GPIOx,Keys[i].GPIO_Pin);   //读取按键状态
		
		switch(Keys[i].state)
		{
			case KEY_STATE_IDLE:
				//如果按下
				if(pinState == GPIO_PIN_RESET)
				{
					Keys[i].state = KEY_STATE_DEBOUNCE; //第一次按下的时侯
					debounceCnt[i] = 0;
					Keys[i].pressStart = now;   //记录下初始按下时间
				}
				break;
			
			case KEY_STATE_DEBOUNCE:
				if (pinState == GPIO_PIN_RESET) { // 持续检测按下状态
					if (++debounceCnt[i] >= DEBOUNCE_CNT) {
						Keys[i].state = KEY_STATE_PRESSED;
						Keys[i].event.type = KEY_EVT_PRESS;
						Keys[i].event.duration = 0;
						Keys[i].eventFlag = 1;
						debounceCnt[i] = 0;
					}
				} else { // 中途释放则回到IDLE
					Keys[i].state = KEY_STATE_IDLE;
					debounceCnt[i] = 0;
				}
				break;				
							
				
			case KEY_STATE_PRESSED:
				/*  持续更新按压时间 */
				Keys[i].pressDuration = now - Keys[i].pressStart;  // now 不断在更新
				
				if(Keys[i].pressDuration >= LONG_PRESS_THRESH)
				{
					Keys[i].event.type = KEY_EVT_LONG_PRESS;
					Keys[i].event.duration = Keys[i].pressDuration; //获取按下时间
					Keys[i].eventFlag = 1;
					Keys[i].pressStart = now;  // 重置计时避免重复触发
				}
				
				if(pinState == GPIO_PIN_SET)
				{
					Keys[i].state = KEY_STATE_IDLE;
					Keys[i].event.type = KEY_EVT_RELEASE;
					Keys[i].event.duration = now - Keys[i].pressDuration;
					Keys[i].eventFlag = 1;
				}
				break;


		}	
	}
}


/*   获取当前按压持续时间   */
uint32_t Get_KeyPressDuration(uint8_t KeyID)
{
	if(KeyID >= KEY_NUM) return 0;  // 超出范围
	return Keys[KeyID].pressDuration;  // 返回持续时间
}

// 事件轮询函数
KeyEvent Poll_KeyEvent(uint8_t keyID)
{
    KeyEvent evt = {KEY_EVT_NONE, 0};
    if(keyID >= KEY_NUM) return evt;
    
    if(Keys[keyID].eventFlag){
        evt = Keys[keyID].event;
        Keys[keyID].eventFlag = 0;
        // 重置持续时间(释放事件后)
        if(evt.type == KEY_EVT_RELEASE){
            Keys[keyID].pressDuration = 0;
        }
    }
    return evt;
}



/* 获取按键事件    B1 --- KeyID -  0 */
uint8_t Get_Key(uint8_t KeyID)
{
	if(KeyID >= KEY_NUM ) return 0;
	if(Keys[KeyID].eventFlag)
	{
		Keys[KeyID].eventFlag = 0;
		return 1;
	}
	else
	{
		return 0;
	}
}


/*led亮灭控制*/
void LED_Ctrl(uint8_t num,uint8_t mode)
{
	 HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8 << (num - 1),mode ? GPIO_PIN_RESET:GPIO_PIN_SET);
	 HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
	
}


void LED_CLOSEALL(void)
{
	 HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
	 HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
	
}

接下来就是直接放到定时器里面,每10ms触发一次检测,通过定时器来消除抖动。

/*
main.c 部分
*/
/*      TIM定时器处理部分     */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	/*    处理按键   100hz */
  if(htim->Instance == TIM6)
  {
	  Keys_StateMachine_Handler();   //此处每10ms进入一次按键状态机
  }
}

3.按键轮询

/*
  main.c
前言,在前面,我们已经完成了按键状态机,所以现在可以正式
开启,按键检测(按键轮询 都可以) 
*/

// 所用到的变量 (这里是我进行再一次重复拿出来,加深你的印象)
typedef enum
{
	LCD_SHOW_DATA_PAGE_FREQUENCE,     /* 数据界面(频率) */
	LCD_SHOW_DATA_PAGE_TIMES    ,      /* 数据界面(周期) */
	LCD_SHOW_PARA_PAGE          ,     /* 参数界面*/
	LCD_SHOW_COUNT_PAGE         ,     /* 统计界面*/
}LCD_Page;


typedef enum
{
	PARA_NONE = 0,
	PARA_PD,
	PARA_PH,
	PARA_PX,
}Para; //参数 针对于参数界面,我用的是switch + enum

int        ccl_value_1 = 0, ccl_value_2 = 0;   // 获取输入捕获值
int        fA = 0,fB = 0;                   // 频率值
int PD = 1000,PH = 5000,PX = 0;               
float      fre_A = 0,fre_B = 0;            //转换为float值
float      A_times = 0,B_times = 0;       // 周期值转为float值


LCD_Page   mypage = LCD_SHOW_DATA_PAGE_FREQUENCE;   /* 初始化为 数据界面(频率)*/  
LCD_Page   Key3page = LCD_SHOW_DATA_PAGE_FREQUENCE;
Para       mypara = PARA_NONE;  //用于参数选择

int FreA = 0,FreB = 0; //最终频率
//


// 代码部分




/* 按键轮询处理及逻辑优化 */
void Key_While()
{
    /*------ 页面控制变量 ------*/
    static uint8_t DataSubPage = 0;          // 数据子页面 0-频率 1-次数
    static uint8_t ChoosePara = 0;           // 参数选择 0-PD 1-PH 2-PX
    static uint8_t ChoosePage = 0;           // 页面选择 0-数据 1-参数 2-统计

    /*------ 按键事件处理 ------*/
    KeyEvent KEYS[4];
    for(uint8_t i = 0; i < 4; i++) {
        KEYS[i] = Poll_KeyEvent(i);
    }

    /*------ 按键1: 参数加 ------*/
    if(KEYS[0].type == KEY_EVT_PRESS) {
		LCD_Clear(Black);
		if(mypage == LCD_SHOW_PARA_PAGE) // 加入了页面限制
		{
        switch(mypara) {
            case PARA_PD: PD = (PD >= 1000) ? 1000 : (PD + 100); break;
            case PARA_PH: PH = (PH >= 10000) ? 10000 : (PH + 100); break;
            case PARA_PX: PX = (PX >= 1000) ? 1000 : (PX + 100); break;
        }
	}
    }
    
    /*------ 按键2: 参数减 ------*/
    else if(KEYS[1].type == KEY_EVT_PRESS) {
		LCD_Clear(Black);
		if(mypage == LCD_SHOW_PARA_PAGE) //将按键只在参数界面才可以作用
		{
        switch(mypara) {
            case PARA_PD: PD = (PD <= 100) ? 100 : (PD - 100); break;
            case PARA_PH: PH = (PH <= 1000) ? 1000 : (PH - 100); break;
            case PARA_PX: PX = (PX <= -1000) ? -1000 : (PX - 100); break;
        }
	}
    }
    
    /*------ 按键3: 参数切换/子页面切换 ------*/
    else if(KEYS[2].type == KEY_EVT_PRESS) {
		LCD_Clear(Black);
        switch(Key3page) {
            /* 参数页切换 */
            case LCD_SHOW_PARA_PAGE:

					ChoosePara = (ChoosePara + 1) % 3;  // 优化为循环切换
					switch(ChoosePara) {
						case 0: mypara = PARA_PD; break;
						case 1: mypara = PARA_PH; break;
						case 2: mypara = PARA_PX; break;
						break;
					}
					break;
            /* 数据页子页面切换 */
            case LCD_SHOW_DATA_PAGE_FREQUENCE:
            case LCD_SHOW_DATA_PAGE_TIMES:
                DataSubPage = (DataSubPage + 1) % 2;  // 使用常量更佳
                LCD_Clear(Black);
                Key3page = (DataSubPage == 0) ? LCD_SHOW_DATA_PAGE_FREQUENCE : LCD_SHOW_DATA_PAGE_TIMES;
                mypage = Key3page;  // 保持页面同步
                break;

        }
    }
	    /*------ 按键3: 参数切换/子页面切换/统计页长按清零 ------*/
    else if(KEYS[2].type == KEY_EVT_LONG_PRESS) {  // 新增长按处理
        if(Key3page == LCD_SHOW_COUNT_PAGE) {
			NDA = NDB = NHA = NHB = 0;
            LCD_Clear(Black);
            // 可以添加统计页刷新函数
        }
    }
    
    /*------ 按键4: 主页面切换 ------*/
    else if(KEYS[3].type == KEY_EVT_PRESS) {
        ChoosePage = (ChoosePage + 1) % 3;  // 循环切换
        
        LCD_Clear(Black);
        switch(ChoosePage) {
            case 0:  // 数据页
                DataSubPage = 0;
                Key3page = LCD_SHOW_DATA_PAGE_FREQUENCE;
                mypage = LCD_SHOW_DATA_PAGE_FREQUENCE;
                break;
                
            case 1:  // 参数页
                ChoosePara = 0;  // 重置参数选择
                Key3page = LCD_SHOW_PARA_PAGE;
                mypage = LCD_SHOW_PARA_PAGE;
                mypara = PARA_PD;  // 默认选中PD
                break;
                
            case 2:  // 统计页
                Key3page = LCD_SHOW_COUNT_PAGE;
                mypage = LCD_SHOW_COUNT_PAGE;
                break;
        }
    }
}



对于按键3部分的处理,其实我处理的很粗超,其实用if...else if...反而更快,更方便。 对于那个数据子页面的转换,我是以一种互斥量的想法编写的,我拿了这个值,那你就不能拿。对于这个转换也是 所以 key3page 是用来控制现在是哪个页面 ---> 对于按键来说。  mypage是用来决定lcd显示的。  其他的就比较清晰了。

4.LCD显示

/*
 main.c
这里是LCD显示部分
*/

// 需要的变量  这个只是用于提示各位接下来会出现的变量,这些之前已经定义过了
/* USER CODE BEGIN PM */
typedef enum
{
	LCD_SHOW_DATA_PAGE_FREQUENCE,     /* 数据界面(频率) */
	LCD_SHOW_DATA_PAGE_TIMES    ,      /* 数据界面(周期) */
	LCD_SHOW_PARA_PAGE          ,     /* 参数界面*/
	LCD_SHOW_COUNT_PAGE         ,     /* 统计界面*/
}LCD_Page;


typedef enum
{
	PARA_NONE = 0,
	PARA_PD,
	PARA_PH,
	PARA_PX,
}Para; //参数


int        ccl_value_1 = 0, ccl_value_2 = 0;   // 获取输入捕获值

/* 对于频率部分 fA / fB = FA / FB + PX  */
int        fA = 0,fB = 0;                   // 频率值
int PD = 1000,PH = 5000,PX = 0;               
float      fre_A = 0,fre_B = 0;         
float      A_times = 0,B_times = 0;



int   NDA = 0,NDB = 0;   /*  A/B通道频率突变次数 */
int   NHA = 0,NHB = 0;   /*  A/B通道频率超限次数 */
int   PHA[2],PHB[2];
uint16_t A_us = 0,B_us = 0;   /* A,B的时间 (us)*/
float    A_ms = 0, B_ms = 0;  /* A,B的时间 (ms)*/
LCD_Page   mypage = LCD_SHOW_DATA_PAGE_FREQUENCE;   /* 初始化为 数据界面(频率)*/  
LCD_Page   Key3page = LCD_SHOW_DATA_PAGE_FREQUENCE;
Para       mypara = PARA_NONE;  //用于参数选择
//


//代码部分
void LCD_SHOWPAGE(void)
{

	switch(mypage)
	{
		case LCD_SHOW_DATA_PAGE_FREQUENCE:
		    LCD_DisplayStringLine(Line1,(uint8_t * )"        DATA   ");
			
			if(FreA<0){LCD_DisplayStringLine(Line3,(uint8_t *)"     A=NULL  ");}
			else if(FreA > 1000)
			{
				fre_A = (float)FreA / 1000.0f;
				sprintf(text,"     A=%.2fKHz   ",fre_A);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);
			}else
			{
				sprintf(text,"     A=%dHz      ",FreA);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);		
			}
			
		
			if(FreB < 0){LCD_DisplayStringLine(Line4,(uint8_t *)"     B=NULL");}
			else if(FreB > 1000)
			{
				fre_B = (float)FreB / 1000.0f;
				sprintf(text,"     B=%.2fKHz   ",fre_B);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);				
			}else
			{
				sprintf(text,"     B=%dHz     ",FreB);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);				
			}
			break;
		
		case LCD_SHOW_DATA_PAGE_TIMES:

		    LCD_DisplayStringLine(Line1,(uint8_t * )"        DATA   ");		
			
		/*		对于通道A	这里我进行了修改 通过判断频率是否为负,进而判断是否为NULL	*/
			if(FreA < 0){LCD_DisplayStringLine(Line3,(uint8_t *)"     A=NULL");}
			else if(ccl_value_1 > 1000)
			{
				A_times = (float)ccl_value_1 / 1000.0f;
				sprintf(text,"     A=%.2fmS   ",A_times);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);
			}else
			{
				sprintf(text,"     A=%duS  ",ccl_value_1);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);
			}
			
			// 这里我进行了修改 通过判断频率是否为负,进而判断是否为NULL
			if(FreB < 0){LCD_DisplayStringLine(Line4,(uint8_t *)"     B=NULL");}
			else if(ccl_value_2 > 1000)
			{
				B_times = (float)ccl_value_2 / 1000.0f;
				sprintf(text,"     B=%.2fmS  ",B_times);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);
			}else
			{
				sprintf(text,"     B=%duS  ",ccl_value_2);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);
			}
			break;
			
	
		case LCD_SHOW_PARA_PAGE:
			LED_Ctrl(1,0);
			LCD_DisplayStringLine(Line1,(uint8_t *)"       PARA    ");
			sprintf(text,"     PD=%dHz   ",PD);
			LCD_DisplayStringLine(Line3,(uint8_t *)text);
			sprintf(text,"     PH=%dHz   ",PH);
			LCD_DisplayStringLine(Line4,(uint8_t *)text);
			sprintf(text,"     PX=%dHz   ",PX);
			LCD_DisplayStringLine(Line5,(uint8_t *)text);		
			break;
		
		case LCD_SHOW_COUNT_PAGE:
			LED_Ctrl(1,0);
			LCD_DisplayStringLine(Line1,(uint8_t *)"        RECD   ");
			sprintf(text,"     NDA=%d    ",NDA);
			LCD_DisplayStringLine(Line3,(uint8_t *)text);
			sprintf(text,"     NDB=%d    ",NDB);
			LCD_DisplayStringLine(Line4,(uint8_t *)text);
			sprintf(text,"     NHA=%d    ",NHA);
			LCD_DisplayStringLine(Line5,(uint8_t *)text);
			sprintf(text,"     NHB=%d    ",NHB);
			LCD_DisplayStringLine(Line6,(uint8_t *)text);
			break;
		
		default:
			break;
	}
	if(NDA >= 3 || NDB >= 3) {LED_Ctrl(8,1);}
	else{LED_Ctrl(8,0);}
	if(mypage == LCD_SHOW_DATA_PAGE_FREQUENCE || mypage == LCD_SHOW_DATA_PAGE_TIMES)
	{
		LED_Ctrl(1,1);
	}
	else
	{
		LED_Ctrl(1,0);
	}
}

5.频率处理部分

5.1频率超限部分 

/*
  main.c

处理频率超限
*/
//变量部分
int   NDA = 0,NDB = 0;   /*  A/B通道频率突变次数 */
int   NHA = 0,NHB = 0;   /*  A/B通道频率超限次数 */
int   PHA[2],PHB[2]; // 为什么是数组,PHA[0] --- 上一次的值  PHA[1] --- 这一次的值



//代码部分
void PH_Beyond(void)
{
	/*获取最新的值*/
	PHA[1] = FreA;
	PHB[1] = FreB;
	
	if(PHA[1] > PH && PHA[0] < PH){NHA++;}
	if(PHB[1] > PH && PHB[0] < PH){NHB++;}
	
	if(PHA[1] > PH) {LED_Ctrl(2,1);}
	else{LED_Ctrl(2,0);}
	
	if(PHB[1] > PH){LED_Ctrl(3,1);}
	else{LED_Ctrl(3,0);}
	
	PHA[0] = PHA[1];
	PHB[0] = PHB[1];
}

5.2频率突变部分

/*
   main.c  
  频率突变部分  用到TIM4定时器 频率为10hz
*/

//变量部分


int PDA[30] = {0},PDB[30] = {0};
int Max_A,Max_B,Min_A,Min_B;

//
/*      TIM定时器处理部分     */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	/*    处理按键   100hz */
  if(htim->Instance == TIM6)
  {
	  Keys_StateMachine_Handler();   
  }
  
  /*     处理获取的频率数据  10hz  */
  if(htim->Instance == TIM4)
  {

	if(timerA == 0)
	{
		Max_A = FreA;
		Min_A = FreA;
	}else
	{
		if(Max_A < FreA){Max_A = FreA;}
		if(Min_A > FreA){Min_A = FreA;}
	}
	timerA++;
	
	if(timerA >= 30)
	{
		timerA = 0;
		if((Max_A - Min_A) > PD){NDA++;}
	}
	
	if(timerB == 0)
	{
		Max_B = FreB;
		Min_B = FreB;
	}else
	{
		if(Max_B < FreB){Max_B = FreB;}
		if(Min_B > FreB){Min_B = FreB;}
	}
	timerB++;
	
	if(timerB >= 30)
	{
		timerB = 0;
		if((Max_B - Min_B) > PD){NDB++;}
	}
	
	
  }
}

这里的原理就类似于时间窗口,一个大小为3s的时间窗口,不断检测进入窗口的值,一边进入,一边释放,类似于队列的效果。这样就可以实时的测出最大值,最小值。注意最小值的初始状态一定要和最大值相同。不然就会一直保持那个值(无法达到检测最小值的效果了)

总体逻辑部分

/*
 main.c
*/

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
typedef enum
{
	LCD_SHOW_DATA_PAGE_FREQUENCE,     /* 数据界面(频率) */
	LCD_SHOW_DATA_PAGE_TIMES    ,      /* 数据界面(周期) */
	LCD_SHOW_PARA_PAGE          ,     /* 参数界面*/
	LCD_SHOW_COUNT_PAGE         ,     /* 统计界面*/
}LCD_Page;


typedef enum
{
	PARA_NONE = 0,
	PARA_PD,
	PARA_PH,
	PARA_PX,
}Para; //参数

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
char       text[20]                       ;
int        ccl_value_1 = 0, ccl_value_2 = 0;   // 获取输入捕获值

/* 对于频率部分 fA / fB = FA / FB + PX  */
int        fA = 0,fB = 0;                   // 频率值
int PD = 1000,PH = 5000,PX = 0;               
float      fre_A = 0,fre_B = 0;         
float      A_times = 0,B_times = 0;



int   NDA = 0,NDB = 0;   /*  A/B通道频率突变次数 */
int   NHA = 0,NHB = 0;   /*  A/B通道频率超限次数 */
int   PHA[2],PHB[2];
uint16_t A_us = 0,B_us = 0;   /* A,B的时间 (us)*/
float    A_ms = 0, B_ms = 0;  /* A,B的时间 (ms)*/
LCD_Page   mypage = LCD_SHOW_DATA_PAGE_FREQUENCE;   /* 初始化为 数据界面(频率)*/  
LCD_Page   Key3page = LCD_SHOW_DATA_PAGE_FREQUENCE;
Para       mypara = PARA_NONE;  //用于参数选择

int PDA[30] = {0},PDB[30] = {0};
//uint8_t i;
int Max_A,Max_B,Min_A,Min_B;

int FreA = 0,FreB = 0;
uint16_t timerA = 0,timerB = 0;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/*    频率获取部分
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	/*      r40 输出捕获   通道 A */
   if(htim->Instance == TIM2)
   {

	   fA = 1000000 / (TIM2->CCR1+1);
	   TIM2->CNT = 0;
	   if(fA > 20000) fA = 20000;
	   else if(fA < 400) fA = 400;
	   FreA = fA + PX;
	   ccl_value_1 = 1000000 / FreA;
	   /*这里我根据测评出的结果 推出来,它其实周期的计算是根据最终的频率来计算的
	   而不是我最先测出的fA来进行计算。*/
   }
   
   /*      R39 输出捕获  通道 B  */
   if(htim->Instance == TIM16)
   {

    	fB = 1000000 / (TIM16->CCR1+1);
	   TIM16->CNT = 0;
	   if(fB > 20000) fB = 20000;
	   else if(fB < 400) fB = 400;
	   FreB = fB + PX;
	   ccl_value_2 = 1000000 / FreB;
   }
}

/*      TIM定时器处理部分     */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	/*    处理按键   100hz */
  if(htim->Instance == TIM6)
  {
	  Keys_StateMachine_Handler();   
  }
  
  /*     处理获取的频率数据  10hz  */
  if(htim->Instance == TIM4)
  {

	if(timerA == 0)
	{
		Max_A = FreA;
		Min_A = FreA;
	}else
	{
		if(Max_A < FreA){Max_A = FreA;}
		if(Min_A > FreA){Min_A = FreA;}
	}
	timerA++;
	
	if(timerA >= 30)
	{
		timerA = 0;
		if((Max_A - Min_A) > PD){NDA++;}
	}
	
	if(timerB == 0)
	{
		Max_B = FreB;
		Min_B = FreB;
	}else
	{
		if(Max_B < FreB){Max_B = FreB;}
		if(Min_B > FreB){Min_B = FreB;}
	}
	timerB++;
	
	if(timerB >= 30)
	{
		timerB = 0;
		if((Max_B - Min_B) > PD){NDB++;}
	}
	
	
  }
}





/* 按键轮询处理及逻辑优化 */
void Key_While()
{
    /*------ 页面控制变量 ------*/
    static uint8_t DataSubPage = 0;          // 数据子页面 0-频率 1-次数
    static uint8_t ChoosePara = 0;           // 参数选择 0-PD 1-PH 2-PX
    static uint8_t ChoosePage = 0;           // 页面选择 0-数据 1-参数 2-统计

    /*------ 按键事件处理 ------*/
    KeyEvent KEYS[4];
    for(uint8_t i = 0; i < 4; i++) {
        KEYS[i] = Poll_KeyEvent(i);
    }

    /*------ 按键1: 参数加 ------*/
    if(KEYS[0].type == KEY_EVT_PRESS) {
		LCD_Clear(Black);
		if(mypage == LCD_SHOW_PARA_PAGE) // 加入了页面限制
		{
        switch(mypara) {
            case PARA_PD: PD = (PD >= 1000) ? 1000 : (PD + 100); break;
            case PARA_PH: PH = (PH >= 10000) ? 10000 : (PH + 100); break;
            case PARA_PX: PX = (PX >= 1000) ? 1000 : (PX + 100); break;
        }
	}
    }
    
    /*------ 按键2: 参数减 ------*/
    else if(KEYS[1].type == KEY_EVT_PRESS) {
		LCD_Clear(Black);
		if(mypage == LCD_SHOW_PARA_PAGE)
		{
        switch(mypara) {
            case PARA_PD: PD = (PD <= 100) ? 100 : (PD - 100); break;
            case PARA_PH: PH = (PH <= 1000) ? 1000 : (PH - 100); break;
            case PARA_PX: PX = (PX <= -1000) ? -1000 : (PX - 100); break;
        }
	}
    }
    
    /*------ 按键3: 参数切换/子页面切换 ------*/
    else if(KEYS[2].type == KEY_EVT_PRESS) {
		LCD_Clear(Black);
        switch(Key3page) {
            /* 参数页切换 */
            case LCD_SHOW_PARA_PAGE:

					ChoosePara = (ChoosePara + 1) % 3;  // 优化为循环切换
					switch(ChoosePara) {
						case 0: mypara = PARA_PD; break;
						case 1: mypara = PARA_PH; break;
						case 2: mypara = PARA_PX; break;
						break;
					}
					break;
            /* 数据页子页面切换 */
            case LCD_SHOW_DATA_PAGE_FREQUENCE:
            case LCD_SHOW_DATA_PAGE_TIMES:
                DataSubPage = (DataSubPage + 1) % 2;  // 使用常量更佳
                LCD_Clear(Black);
                Key3page = (DataSubPage == 0) ? LCD_SHOW_DATA_PAGE_FREQUENCE : LCD_SHOW_DATA_PAGE_TIMES;
                mypage = Key3page;  // 保持页面同步
                break;

        }
    }
	    /*------ 按键3: 参数切换/子页面切换/统计页长按清零 ------*/
    else if(KEYS[2].type == KEY_EVT_LONG_PRESS) {  // 新增长按处理
        if(Key3page == LCD_SHOW_COUNT_PAGE) {
			NDA = NDB = NHA = NHB = 0;
            LCD_Clear(Black);
            // 可以添加统计页刷新函数
        }
    }
    
    /*------ 按键4: 主页面切换 ------*/
    else if(KEYS[3].type == KEY_EVT_PRESS) {
        ChoosePage = (ChoosePage + 1) % 3;  // 循环切换
        
        LCD_Clear(Black);
        switch(ChoosePage) {
            case 0:  // 数据页
                DataSubPage = 0;
                Key3page = LCD_SHOW_DATA_PAGE_FREQUENCE;
                mypage = LCD_SHOW_DATA_PAGE_FREQUENCE;
                break;
                
            case 1:  // 参数页
                ChoosePara = 0;  // 重置参数选择
                Key3page = LCD_SHOW_PARA_PAGE;
                mypage = LCD_SHOW_PARA_PAGE;
                mypara = PARA_PD;  // 默认选中PD
                break;
                
            case 2:  // 统计页
                Key3page = LCD_SHOW_COUNT_PAGE;
                mypage = LCD_SHOW_COUNT_PAGE;
                break;
        }
    }
}

/* 
Log: 显示部分 数据的频率和周期显示会有冲突

*/
void LCD_SHOWPAGE(void)
{

	switch(mypage)
	{
		case LCD_SHOW_DATA_PAGE_FREQUENCE:
		    LCD_DisplayStringLine(Line1,(uint8_t * )"        DATA   ");
			
			if(FreA<0){LCD_DisplayStringLine(Line3,(uint8_t *)"     A=NULL  ");}
			else if(FreA > 1000)
			{
				fre_A = (float)FreA / 1000.0f;
				sprintf(text,"     A=%.2fKHz   ",fre_A);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);
			}else
			{
				sprintf(text,"     A=%dHz      ",FreA);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);		
			}
			
		
			if(FreB < 0){LCD_DisplayStringLine(Line4,(uint8_t *)"     B=NULL");}
			else if(FreB > 1000)
			{
				fre_B = (float)FreB / 1000.0f;
				sprintf(text,"     B=%.2fKHz   ",fre_B);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);				
			}else
			{
				sprintf(text,"     B=%dHz     ",FreB);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);				
			}
			break;
		
		case LCD_SHOW_DATA_PAGE_TIMES:

		    LCD_DisplayStringLine(Line1,(uint8_t * )"        DATA   ");		
			
		/*		对于通道A	这里我进行了修改 通过判断频率是否为负,进而判断是否为NULL	*/
			if(FreA < 0){LCD_DisplayStringLine(Line3,(uint8_t *)"     A=NULL");}
			else if(ccl_value_1 > 1000)
			{
				A_times = (float)ccl_value_1 / 1000.0f;
				sprintf(text,"     A=%.2fmS   ",A_times);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);
			}else
			{
				sprintf(text,"     A=%duS  ",ccl_value_1);
				LCD_DisplayStringLine(Line3,(uint8_t *)text);
			}
			
			// 这里我进行了修改 通过判断频率是否为负,进而判断是否为NULL
			if(FreB < 0){LCD_DisplayStringLine(Line4,(uint8_t *)"     B=NULL");}
			else if(ccl_value_2 > 1000)
			{
				B_times = (float)ccl_value_2 / 1000.0f;
				sprintf(text,"     B=%.2fmS  ",B_times);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);
			}else
			{
				sprintf(text,"     B=%duS  ",ccl_value_2);
				LCD_DisplayStringLine(Line4,(uint8_t *)text);
			}
			break;
			
	
		case LCD_SHOW_PARA_PAGE:
			LED_Ctrl(1,0);
			LCD_DisplayStringLine(Line1,(uint8_t *)"       PARA    ");
			sprintf(text,"     PD=%dHz   ",PD);
			LCD_DisplayStringLine(Line3,(uint8_t *)text);
			sprintf(text,"     PH=%dHz   ",PH);
			LCD_DisplayStringLine(Line4,(uint8_t *)text);
			sprintf(text,"     PX=%dHz   ",PX);
			LCD_DisplayStringLine(Line5,(uint8_t *)text);		
			break;
		
		case LCD_SHOW_COUNT_PAGE:
			LED_Ctrl(1,0);
			LCD_DisplayStringLine(Line1,(uint8_t *)"        RECD   ");
			sprintf(text,"     NDA=%d    ",NDA);
			LCD_DisplayStringLine(Line3,(uint8_t *)text);
			sprintf(text,"     NDB=%d    ",NDB);
			LCD_DisplayStringLine(Line4,(uint8_t *)text);
			sprintf(text,"     NHA=%d    ",NHA);
			LCD_DisplayStringLine(Line5,(uint8_t *)text);
			sprintf(text,"     NHB=%d    ",NHB);
			LCD_DisplayStringLine(Line6,(uint8_t *)text);
			break;
		
		default:
			break;
	}
	if(NDA >= 3 || NDB >= 3) {LED_Ctrl(8,1);}
	else{LED_Ctrl(8,0);}
	if(mypage == LCD_SHOW_DATA_PAGE_FREQUENCE || mypage == LCD_SHOW_DATA_PAGE_TIMES)
	{
		LED_Ctrl(1,1);
	}
	else
	{
		LED_Ctrl(1,0);
	}
}



void PH_Beyond(void)
{
	/*获取最新的值*/
	PHA[1] = FreA;
	PHB[1] = FreB;
	
	if(PHA[1] > PH && PHA[0] < PH){NHA++;}
	if(PHB[1] > PH && PHB[0] < PH){NHB++;}
	
	if(PHA[1] > PH) {LED_Ctrl(2,1);}
	else{LED_Ctrl(2,0);}
	
	if(PHB[1] > PH){LED_Ctrl(3,1);}
	else{LED_Ctrl(3,0);}
	
	PHA[0] = PHA[1];
	PHB[0] = PHB[1];
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM4_Init();
  MX_TIM6_Init();
  MX_TIM3_Init();
  MX_TIM8_Init();
  MX_TIM16_Init();
  /* USER CODE BEGIN 2 */

  
  LCD_Init();
  LCD_Clear(Black);
  LCD_SetBackColor(Black);
  LCD_SetTextColor(White);
  HAL_TIM_Base_Start_IT(&htim6);
  HAL_TIM_Base_Start_IT(&htim4);
  HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
  HAL_TIM_IC_Start_IT(&htim16,TIM_CHANNEL_1);
  LED_CLOSEALL();   /* 关闭所有的LED	*/
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  Key_While();
	  LCD_SHOWPAGE();
	  PH_Beyond();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

以上就是我的全部内容了,若有疑问请评论区提出。 

我写的比较潦草,最近要去刷一下题了,所以大体写的很快。 

http://www.dtcms.com/a/112132.html

相关文章:

  • 【系统】换硬盘不换系统,使用WIN PE Ghost镜像给电脑无损扩容换硬盘
  • Python3.13安装教程-2025最新版超级详细图文安装教程(附所需安装包环境)
  • PhotoShop学习04
  • 详解大模型四类漏洞
  • Vue2+Vue3 45-90集学习笔记
  • P12013 [Ynoi April Fool‘s Round 2025] 牢夸 Solution
  • CMAKE中使用外部动态库
  • C++中,应尽可能将引用形参声明为const
  • Smart Link 技术全面解析
  • 使用人工智能大模型腾讯元宝和ttsmp3工具,免费使用文字进行配音
  • Python入门(6):Python序列结构-元组
  • FastAPI-Cache2: 高效Python缓存库
  • Linux系统调用编程
  • 嵌入式开发中栈溢出的处理方法
  • MySQL学习笔记(一)——MySQL下载安装配置
  • 一文全面了解GEO中的知识图谱
  • leetcode数组-长度最小的子数组
  • 【Git】“warning: LF will be replaced by CRLF”的解决办法
  • 【MySQL】DML:添加-修改-删除数据 (数据操作语言) 学习笔记
  • 投影向量的计算公式推导
  • RTOS任务句柄的作用
  • Unity的插件TouchScripts插件的新手入门指南和常用的API使用方法
  • Tradingview 策略分享 - SSL 混合和 CE 交易策略
  • Android Fresco 框架动态图支持模块源码深度剖析(七)
  • 【FAQ】HarmonyOS SDK 闭源开放能力 —Account Kit(3)
  • LabVIEW中VISA Write 与 GPIB Write的差异
  • 【Linux】冯·诺依曼体系结构
  • kotlin中主构造函数是什么
  • Java EE期末总结(第一章)
  • 【图像处理基石】什么是neural style transfer?