十六届蓝桥杯嵌入式省赛快要开始了,最近在练习往届比赛模拟题以及真题,在这里分享一下我在完成第十五届蓝桥杯嵌入式省赛真题的感受以及遇到的一些问题,过程中也借鉴了一些其他人的一些思路。
看到这篇文章的可能大部分都已经看过题目内容,这里我就偷一下懒,不添加题目截图了。看到题目,首先我们将其分为几个主要的模块,我主要是分成了LCD屏幕显示、按键功能、LED指示灯、PA15和PB4的脉冲频率采集。编程思路主要是先完成模块基本功能,留恰当的变量作为函数功能接口,与其他模块对接,最后再将各个模块联系起来调试。
因为文章结构原因,变量定义以及函数封装做过修改,完整代码压缩包已上传,请查看
一、LCD屏幕显示
封装一个显示刷新函数,放在while(1)中不断循环,sprint函数中字符串注意题目要求的格式,数好空格,注意大小写,以及line是从line0开始数
//LCD刷新函数,将此函数放在主函数的while(1)中循环刷新显示即可
uint8_t page = 0; //页码,操作换页是只需给page赋值即可
uint8_t page0_mode = 0; //数据界面的显示模式(频率模式和周期模式)
void lcd_proc(void)
{
if(page == 0)//数据界面
{
LCD_DisplayStringLine(Line1,(u8 *)" DATA ");
if(page0_mode == 0)//数据界面的频率显示模式
{
//A通道
if(Freq_A <= 1000 && Freq_A >= 0)
{
sprintf((char *)buf,(char *)" A=%.0fHz ",Freq_A);
LCD_DisplayStringLine(Line3,(u8 *)buf);
}
else if(Freq_A > 1000)
{
sprintf((char *)buf,(char *)" A=%.2fKHz ",Freq_A/1000.00f);
LCD_DisplayStringLine(Line3,(u8 *)buf);
}
else if(Freq_A<0)
{
sprintf((char *)buf,(char *)" A=NULL ");
LCD_DisplayStringLine(Line3,(u8 *)buf);
}
//B通道
if(Freq_B <= 1000 && Freq_B >= 0)
{
sprintf((char *)buf,(char *)" B=%.0fHz ",Freq_B);
LCD_DisplayStringLine(Line4,(u8 *)buf);
}
else if(Freq_B > 1000)
{
sprintf((char *)buf,(char *)" B=%.2fKHz ",Freq_B/1000.00f);
LCD_DisplayStringLine(Line4,(u8 *)buf);
}
else if(Freq_B<0)
{
sprintf((char *)buf,(char *)" B=NULL ");
LCD_DisplayStringLine(Line4,(u8 *)buf);
}
}
else if(page0_mode == 1)//数据界面的周期显示模式
{
//A通道
if(cycle_A <= 1000&& cycle_A >= 0)
{
sprintf((char *)buf,(char *)" A=%.0fuS ",cycle_A);
LCD_DisplayStringLine(Line3,(u8 *)buf);
}
else if(cycle_A > 1000)
{
sprintf((char *)buf,(char *)" A=%.2fmS ",cycle_A/1000.00f);
LCD_DisplayStringLine(Line3,(u8 *)buf);
}
else if(cycle_A<0)
{
sprintf((char *)buf,(char *)" A=NULL ");
LCD_DisplayStringLine(Line3,(u8 *)buf);
}
//B通道
if(cycle_B <= 1000&& cycle_B >= 0)
{
sprintf((char *)buf,(char *)" B=%.0fuS ",cycle_B);
LCD_DisplayStringLine(Line4,(u8 *)buf);
}
else if(cycle_B > 1000)
{
sprintf((char *)buf,(char *)" B=%.2fmS ",cycle_B/1000.00f);
LCD_DisplayStringLine(Line4,(u8 *)buf);
}
else if(cycle_B<0)
{
sprintf((char *)buf,(char *)" B=NULL ");
LCD_DisplayStringLine(Line4,(u8 *)buf);
}
}
}
else if(page == 1)//参数界面
{
LCD_DisplayStringLine(Line1,(u8 *)" PARA ");
sprintf((char *)buf,(char *)" PD=%dHz ",PD);
LCD_DisplayStringLine(Line3,(u8 *)buf);
sprintf((char *)buf,(char *)" PH=%dHz ",PH);
LCD_DisplayStringLine(Line4,(u8 *)buf);
sprintf((char *)buf,(char *)" PX=%dHz ",PX);
LCD_DisplayStringLine(Line5,(u8 *)buf);
}
else if(page == 2)//统计界面
{
LCD_DisplayStringLine(Line1,(u8 *)" RECD ");
sprintf((char *)buf,(char *)" NDA=%d ",NDA);
LCD_DisplayStringLine(Line3,(u8 *)buf);
sprintf((char *)buf,(char *)" NDB=%d ",NDB);
LCD_DisplayStringLine(Line4,(u8 *)buf);
sprintf((char *)buf,(char *)" NHA=%d ",NHA);
LCD_DisplayStringLine(Line5,(u8 *)buf);
sprintf((char *)buf,(char *)" NHB=%d ",NHB);
LCD_DisplayStringLine(Line6,(u8 *)buf);
}
}
二、按键功能
采用状态机机制,可实现按键消抖,长按短按功能
struct keys
{
char judge_sta; //状态机
char key_sta; //存储按键电平值
char single_flag; //按键标志位,0为未按下,1为短按,2为长按
uint16_t time_cnt; //计数器,用来长按计时
};
struct keys key[4]={0,0,0,0}; //定义结构体变量,分别存储四个按键状态
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//TIM6配置了10ms定时中断,完成按键消抖以及长按功能
if(htim->Instance == TIM6)
{
//读取gpio电平值
key[0].key_sta = HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin);
key[1].key_sta = HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin);
key[2].key_sta = HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin);
key[3].key_sta = HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin);
//遍历四个按键状态,实现四个按键功能独立
for( uint8_t i=0 ; i<4 ; i++ )
{
switch (key[i].judge_sta)
{
case 0:
{
if(key[i].key_sta == 0) //如果按键i按下
{
key[i].judge_sta = 1; //状态机变为1
}
}
break;
case 1:
{
if(key[i].key_sta == 0) //如果10ms后按键i仍然按下
{
key[i].judge_sta = 2;//状态机变为2
}
}
break;
case 2:
{
key[i].time_cnt ++; //检测到按下后,10ms自增一次
if(key[i].key_sta == 1) //如果按键松开
{
key[i].judge_sta = 0; //状态机回到0
if(key[i].time_cnt <=100) //如果计数值小于等于100,即不超过1s
{
key[i].single_flag = 1; //判断按键i为短按
}
else if(key[i].time_cnt > 100) //如果计数值大于100,即超过1s
{
key[i].single_flag = 2; //判断按键i为长按
}
key[i].time_cnt = 0; //计数器清零
}
}
break;
}
}
}
}
uint16_t PD = 1000;
uint16_t PH = 5000;
int32_t PX = 0;
uint16_t NDA = 0;
uint16_t NDB = 0;
uint16_t NHA = 0;
uint16_t NHB = 0;
uint8_t param_sel = 0; //参数选择
uint8_t page0_mode = 0; //在屏幕刷新函数那定义过,此处为了完整性我在写一次,读者可以按需删除
//此函数在while中不断刷新扫描
void key_proc(void)
{
if(key[0].single_flag == 1) //B1短按
{
if(page == 1)
{
if(param_sel == 0)
{
PD += 100;
if(PD > 1000)
{
PD = 100;
}
}
else if(param_sel == 1)
{
PH += 100;
if(PH > 10000)
{
PH = 1000;
}
}
else if(param_sel == 2)
{
PX += 100;
if(PX > 1000)
{
PX = -1000;
}
}
}
key[0].single_flag = 0; //按键按下执行任务后清空按键标志位,其他三个按键也一样
}
if(key[1].single_flag == 1) //B2短按
{
if(page == 1)
{
if(param_sel == 0)
{
PD -= 100;
if(PD < 100)
{
PD = 1000;
}
}
else if(param_sel == 1)
{
PH -= 100;
if(PH < 1000)
{
PH = 10000;
}
}
else if(param_sel == 2)
{
PX -= 100;
if(PX < -1000)
{
PX = 1000;
}
}
}
key[1].single_flag = 0;
}
if(key[2].single_flag == 1) //B3短按
{
if(page == 1)
{
param_sel ++;
if(param_sel >= 3)
{
param_sel = 0;
}
}
else if(page == 0)
{
page0_mode ++;
if(page0_mode >= 2)
{
page0_mode = 0;
}
}
key[2].single_flag = 0;
}
if(key[2].single_flag == 2) //B3长按
{
if(page == 2)
{
NDA = 0;
NDB = 0;
NHA = 0;
NHB = 0;
}
key[2].single_flag = 0;
}
if(key[3].single_flag == 1) //B4短按
{
if(page == 0)
{
LCD_Clear(Black);
page = 1;
param_sel = 0; //每次从数据界面回到参数界面,默认为设置参数PD
}
else if(page == 1)
{
LCD_Clear(Black);
page =2;
}
else if(page == 2)
{
LCD_Clear(Black);
page =0;
page0_mode = 0; //每次从统计界面回到数据界面,默认为频率显示模式
}
key[3].single_flag = 0;
}
}
三、频率采集
//频率采集,在STM32cubeMX中配置好,检测到上升沿即会触发中断,进入回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
uint32_t tim_value = 0;
if(htim == &htim2)
{
tim_value = TIM2->CNT; //读取计数值
TIM2->CNT = 0; //遇到上升沿清空计数值
freq_A = (1000000/tim_value); //时基单元配置预分频为79(80-1),1000000Hz
if(freq_A>20000)freq_A = 20000; //按题目要求限幅
if(freq_A<400)freq_A = 400; //按题目要求限幅
}
else if(htim == &htim3)
{
tim_value = TIM3->CNT; //读取计数值
TIM3->CNT = 0; //遇到上升沿清空计数值
freq_B = (1000000/tim_value); //时基单元配置预分频为79(80-1),1000000Hz
if(freq_B>20000)freq_B = 20000; //按题目要求限幅
if(freq_B<400)freq_B = 400; //按题目要求限幅
}
}
四、超限、突变次数检测
//这里跟按键的那个回调函数一样的,里面的if条件不同
//因为这里我要分开写才这样拆开,正常写的时候应该写在一起,用if、else判断
extern int32_t PX ;
uint8_t judgeA = 0;
uint8_t judgeB = 0;
extern uint16_t PH ;
extern uint16_t PD ;
extern uint16_t NDA ;
extern uint16_t NDB ;
extern uint16_t NHA ;
extern uint16_t NHB ;
uint16_t freqA_Max ;
uint16_t freqA_Min ;
uint16_t freqB_Max ;
uint16_t freqB_Min ;
uint16_t time_windows = 0; //3s时间窗口
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//每100ms进入一次(题目要求1秒更新10次)
if(htim->Instance == TIM7)
{
//加上校准值,Freq_A/B才是最终要显示刷新的变量
Freq_A = freq_A + PX;
Freq_B = freq_B + PX;
//计算周期
cycle_A = 1000000/Freq_A;
cycle_B = 1000000/Freq_B;
//超限判断
if(judgeA == 0)
{
if(freq_A > PH)
{
NHA ++;
judgeA = 1;
}
}
if(freq_A < PH)
{
judgeA = 0;
}
if(judgeB == 0)
{
if(freq_B > PH)
{
NHB ++;
judgeB = 1;
}
}
if(freq_B < PH)
{
judgeB = 0;
}
//突变判断
if(time_windows < 30)
{
time_windows ++;
if(freqA_Max<Freq_A)
{
freqA_Max = Freq_A;
}
if(freqB_Max<Freq_B)
{
freqB_Max = Freq_B;
}
if(freqA_Min>Freq_A||freqA_Min == 0)
{
freqA_Min = Freq_A;
}
if(freqB_Min>Freq_B||freqB_Min == 0)
{
freqB_Min = Freq_B;
}
}
else if(time_windows>=30)
{
//判断最大值与最小值之差是否大于PD
if((freqA_Max - freqA_Min) > PD)
{
NDA++;
}
if((freqB_Max - freqB_Min) > PD)
{
NDB++;
}
freqA_Max = 0;
freqA_Min = 0;
freqB_Max = 0;
freqB_Min = 0;
time_windows = 0; //清空窗口值
}
}
}
五、LED指示灯
指示灯放在最后写最简单,此时大致功能都已经实现了
//LED刷新函数,放在while中循环
void led_proc(void)
{
//保证LD4-LD7一直熄灭
HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LD7_GPIO_Port,LD7_Pin,GPIO_PIN_SET);
//数据界面LD1亮,否则灭
if(page == 0)
{
HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);
}
//Freq_A大于PH,LD2亮,否则灭
if(Freq_A > PH)
{
HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,GPIO_PIN_SET);
}
//Freq_B大于PH,LD3亮,否则灭
if(Freq_B > PH)
{
HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_SET);
}
//突变次数大于3,LD8亮,否则灭
if(NDA >= 3 || NDB >= 3)
{
HAL_GPIO_WritePin(LD8_GPIO_Port,LD8_Pin,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(LD8_GPIO_Port,LD8_Pin,GPIO_PIN_SET);
}
//锁存器PD2
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
六、main函数
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 */
LCD_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_TIM6_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM7_Init();
/* USER CODE BEGIN 2 */
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
key_proc();
lcd_proc();
led_proc();
}
/* USER CODE END 3 */
}