蓝桥杯嵌入式第15届真题-个人理解+解析
个人吐槽
#因为最近蓝桥杯快要开始了,我舍不得米白费了,所以就认真刷刷模拟题,但是我感觉真题会更好,所以就看了一下上届的真题。不过它是真的长,我看着就头晕,但是还是把几个模块认真分析了一下就还是很容易写的。
#分析的话,频率超限和频率突变,这位博主就写的很好,所以我就直接使用他的来了,很清晰,所以这里更多的是以我的视角一起来分析一下想法。题目的话,就不截取了。第十五届蓝桥杯嵌入式组省赛题目分析及代码_蓝桥杯嵌入式15届赛题-CSDN博客
个人心得部分
对于题目来说,每一个题目的要求都是有用的。 写题目的时候一定要注意。 比如说性能要求那里,频率的测量范围,真的会影响到后面的测评结果。所以一定要大致的扫描完题目后,细致地针对题目要求来进行代码编写。
STM32CUBEMX 配置部分
所用到的外设有
- LED1-7 默认配置 LD1-LD7
- 四个按键 B1,B2,B3,B4 --- 这个后续会在我的代码里出现的,注意
- 这里我用到了差不多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 */
}
以上就是我的全部内容了,若有疑问请评论区提出。
我写的比较潦草,最近要去刷一下题了,所以大体写的很快。