蓝桥杯嵌入式17届省赛真题与解析
题目与概要







本文为蓝桥杯十七届嵌入式省赛真题解析,因为4T上评测估计要明年才开启,因此本文涉及的代码与得分项可能会有误差与错误,代码仅作参考用,如有其他错误请谅解、
CUBEMX配置如下

一般会开启一个定时器用于整体计时或者中断任务处理

外设模板代码与逻辑处理
定义的结构体 我的定义变量的方法是不太好的,例如文章中的十七届真题,逻辑复杂且多需要定义的变量就非常多,会导致写到后面忘记前面的变量,所以这种定义结构体的方法慎用。
#ifndef _FUN_H_
#define _FUN_H_
#include "config.h"
void LCD_Proc(void);
void Key_Scan(void);
void Sys_Init(void);
void IC_Proc(void);
void Interrupt(void);
void ADC_Proc(void);
void PWM_Proc(void);
void LED_Proc(uint16_t LED,uint16_t Mode);
void LED_Test(void);
typedef struct{
uint16_t page;
char text[30];
uint16_t LCD_ClearFlag;
uint16_t Select_Tar;
uint16_t Select_Para;
//PAGE1
uint16_t Mode;
float Pre_Realtime;
float Tra_Ins;
float Tra_Sum;
float Pa1_Duty;
float V_Offset;
//PAGE2
uint16_t TarD_Duty;
float TarP_Pre;
//PAGE3
float FH_Para;
float FL_Para;
float PL_Para;
uint16_t DH_Para;
//SYS
uint32_t Runtime_IC;
uint32_t Runtime_Auto;
uint32_t Runtime_Man;
uint32_t Runtime_KEY1;
uint32_t Runtime_KEY2;
uint32_t Runtime_LED2;
uint32_t Runtime_LED3;
//KEY
uint16_t Key_State[4];
uint16_t Key_Last_State[4];
uint16_t KEY_Flag;
//ADC
uint32_t DMA_ADC_Buff[30];
float ADC_Value;
//IC
uint16_t TIM16_FRE;
float Pa6_Duty;
uint16_t Pa6_Fre;
//PWM
uint16_t Auto_Flag;
uint16_t Man_Flag;
//LED
uint16_t LED_Flag1;
uint16_t LED_Flag2;
}Pro;
extern Pro pro;
#endif
LCD
void LCD_Proc(void){
if(pro.LCD_ClearFlag){
pro.LCD_ClearFlag=0;
LCD_Clear(Black);
}
switch(pro.page){
case 0: //12345678901234567890
sprintf(pro.text," MAIN ");
LCD_DisplayStringLine(Line1,(uint8_t*)pro.text);
sprintf(pro.text," M=%s ",pro.Mode?"AUTO":"MAN");
LCD_DisplayStringLine(Line3,(uint8_t*)pro.text);
sprintf(pro.text," P=%.1fBAR ",pro.Pre_Realtime);
LCD_DisplayStringLine(Line4,(uint8_t*)pro.text);
sprintf(pro.text," F=%.1fL/M ",pro.Tra_Ins);
LCD_DisplayStringLine(Line5,(uint8_t*)pro.text);
sprintf(pro.text," Q=%.0fL ",pro.Tra_Sum);
LCD_DisplayStringLine(Line6,(uint8_t*)pro.text);
sprintf(pro.text," D=%.0f%% ",pro.Pa1_Duty);
LCD_DisplayStringLine(Line7,(uint8_t*)pro.text);
sprintf(pro.text," V=%.1fV ",pro.V_Offset);
LCD_DisplayStringLine(Line8,(uint8_t*)pro.text);
break;
case 1: //12345678901234567890
sprintf(pro.text," OUTP ");
LCD_DisplayStringLine(Line1,(uint8_t*)pro.text);
sprintf(pro.text," TarD=%d%% ",pro.TarD_Duty);
LCD_DisplayStringLine(Line3,(uint8_t*)pro.text);
sprintf(pro.text," TarP=%.1fBAR ",pro.TarP_Pre/10);
LCD_DisplayStringLine(Line4,(uint8_t*)pro.text);
break;
case 2: //12345678901234567890
sprintf(pro.text," PARA ");
LCD_DisplayStringLine(Line1,(uint8_t*)pro.text);
sprintf(pro.text," FH=%.1fL/M ",pro.FH_Para);
LCD_DisplayStringLine(Line3,(uint8_t*)pro.text);
sprintf(pro.text," FL=%.1fL/M ",pro.FL_Para);
LCD_DisplayStringLine(Line4,(uint8_t*)pro.text);
sprintf(pro.text," PL=%.1fBAR ",pro.PL_Para/10);
LCD_DisplayStringLine(Line5,(uint8_t*)pro.text);
sprintf(pro.text," DH=%d%% ",pro.DH_Para);
LCD_DisplayStringLine(Line6,(uint8_t*)pro.text);
break;
}
}
LCD代码模板有很多这里不过多赘述
按键
void Key_Scan(void){
pro.Key_State[0]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
pro.Key_State[1]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
pro.Key_State[2]=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
pro.Key_State[3]=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
if(pro.Key_State[0]==0 && pro.Key_Last_State[0]==1){
pro.page++;
if(pro.page>2){
pro.page=0;
}
pro.Select_Tar=0;
pro.Select_Para=0;
pro.LCD_ClearFlag=1;
}
if(pro.Key_State[1]==0 && pro.Key_Last_State[1]==1){
if(pro.page==0){
pro.Mode=!pro.Mode;
}
else if(pro.page==1){
pro.Select_Tar=!pro.Select_Tar;
}
else if(pro.page==2){
pro.Select_Para++;
if(pro.Select_Para>3){
pro.Select_Para=0;
}
}
}
if(pro.Key_State[2]==0 && pro.Key_Last_State[2]==1){
if(pro.page==0){
pro.KEY_Flag=1;
}
else if(pro.page==1){
if(pro.Select_Tar==0){
pro.TarD_Duty+=5;
if(pro.TarD_Duty>95){
pro.TarD_Duty=95;
}
}
else{
pro.TarP_Pre+=5;
if(pro.TarP_Pre>95){
pro.TarP_Pre=95;
}
}
}
else if(pro.page==2){
switch(pro.Select_Para){
case 0:
pro.FH_Para+=1;
if(pro.FH_Para>40){
pro.FH_Para=40;
}
break;
case 1:
pro.FL_Para+=1;
if(pro.FL_Para>40){
pro.FL_Para=40;
}
break;
case 2:
pro.PL_Para+=5;
if(pro.PL_Para>95){
pro.PL_Para=95;
}
break;
case 3:
pro.DH_Para+=10;
if(pro.DH_Para>95){
pro.DH_Para=95;
}
break;
}
}
}
else if(pro.Key_State[2]==1 && pro.Key_Last_State[2]==0){
if(pro.page==0 && pro.Runtime_KEY1>=2000){
pro.V_Offset=pro.ADC_Value;
}
pro.KEY_Flag=0;
pro.Runtime_KEY1=0;
}
if(pro.Key_State[3]==0 && pro.Key_Last_State[3]==1){
if(pro.page==0){
pro.KEY_Flag=2;
}
else if(pro.page==1){
if(pro.Select_Tar==0){
pro.TarD_Duty-=5;
if(pro.TarD_Duty<5){
pro.TarD_Duty=5;
}
}
else{
pro.TarP_Pre-=5;
if(pro.TarP_Pre<5){
pro.TarP_Pre=5;
}
}
}
else if(pro.page==2){
switch(pro.Select_Para){
case 0:
pro.FH_Para-=1;
if(pro.FH_Para<4){
pro.FH_Para=4;
}
break;
case 1:
pro.FL_Para-=1;
if(pro.FL_Para<4){
pro.FL_Para=4;
}
break;
case 2:
pro.PL_Para-=5;
if(pro.PL_Para<10){
pro.PL_Para=10;
}
break;
case 3:
pro.DH_Para-=10;
if(pro.DH_Para<5){
pro.DH_Para=5;
}
break;
}
}
}
else if(pro.Key_State[3]==1 && pro.Key_Last_State[3]==0){
if(pro.page==0 && pro.Runtime_KEY2>=2000){
pro.Tra_Sum=0;
}
pro.KEY_Flag=0;
pro.Runtime_KEY2=0;
}
for(int i=0;i<4;i++){
pro.Key_Last_State[i]=pro.Key_State[i];
}
}
我更喜欢这个软件消抖的代码模板,用法都是差不多的。
就是用到了pro.Key_State[1]==0 && pro.Key_Last_State[1]==1 通过短暂延迟进行按键判断后改变当前状态。
具体的逻辑处理

这个功能其实挺简单其实就是动态变化斜率,在坐标轴上取两点分别是(3.3,10) (V_offset,0)
实时计算 y=kx+b 斜率与纵坐标的值,既
ADC
void ADC_Proc(void){
for(int i=0;i<30;i++){
pro.ADC_Value+=pro.DMA_ADC_Buff[i];
}
pro.ADC_Value=pro.ADC_Value/30*3.3f/4096;
if(pro.ADC_Value<pro.V_Offset){
pro.Pre_Realtime=0;
} //y=kx+b; k=10/(3.3-pro.V_Offset)
else{// b=-10*pro.V_Offset/(3.3-pro.V_Offset)
pro.Pre_Realtime=10*(pro.ADC_Value-pro.V_Offset)/(3.3f-pro.V_Offset);
}
}
一般我使用串口或者ADC都会开启DMA因为可以减小误差


还有一个功能是将当前电压值赋值给 V_offset 按键按下两秒后赋值一般我会开启一个10ms的定时器TIM1作为处理有时间变化的任务中断


//以下为KEY部分代码
if(pro.Key_State[2]==0 && pro.Key_Last_State[2]==1){
if(pro.page==0){
pro.KEY_Flag=1;
}
}
else if(pro.Key_State[2]==1 && pro.Key_Last_State[2]==0){
if(pro.page==0 && pro.Runtime_KEY1>=2000){
pro.V_Offset=pro.ADC_Value;
}
pro.KEY_Flag=0;
pro.Runtime_KEY1=0;
}
void Interrupt(void){
//篇幅限制其余中断判断代码删去
if(pro.LED_Flag1==1){
pro.Runtime_LED2+=10;
}
if(pro.LED_Flag2==1){
pro.Runtime_LED3+=10;
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
Interrupt();
}
通过每10ms扫描一次中断任务即可实现按键按下2s后进行赋值

这个功能其实有需要细心的点,流量其实是以L/M单位,既每分钟的流量,在考场上我以为只需要除60既化为每秒,然后扔进中断每100ms判断累加一次,但是以100m为采样周期实际上已经给了流量的单位为ms,对于流量的处理应该是除600 既将 Min->Ms 这是我考场上比较明显的错误,
void IC_Proc(void){
if(pro.TIM16_FRE<800){
pro.Tra_Ins=0;
}
else if(pro.TIM16_FRE>=800 && pro.TIM16_FRE<=8000){
pro.Tra_Ins=(float)pro.TIM16_FRE/200;
}
else{
pro.Tra_Ins=40;
}
}
void Interrupt(void){
pro.Runtime_IC+=10;
if(pro.Runtime_IC>=100){
pro.Tra_Sum+=pro.Tra_Ins/600;//L/M -> L/ms
pro.Runtime_IC=0;
}
}

这个功能其实考的是1s的中断配置与对于的逻辑判断,我的方法是多设立变量不怕多,就怕错
void PWM_Proc(void){
if(pro.Mode && pro.page!=1){
pro.Man_Flag=0;
LED_Proc(4,1);
if(pro.Pre_Realtime<(pro.TarP_Pre-5)/10){
pro.Auto_Flag=1;
}
else if(pro.Pre_Realtime>(pro.TarP_Pre+5)/10){
pro.Auto_Flag=2;
}
else{
pro.Auto_Flag=0;
LED_Proc(4,0);
}
}
else if(!pro.Mode && pro.page!=1){
pro.Auto_Flag=0;
LED_Proc(4,1);
if(pro.Pa1_Duty>pro.TarD_Duty){
pro.Man_Flag=1;
}
else if(pro.Pa1_Duty<pro.TarD_Duty){
pro.Man_Flag=2;
}
else{
pro.Man_Flag=0;
LED_Proc(4,0);
}
}
TIM2->CCR2=pro.Pa1_Duty;
}
void Interrupt(void){
if(pro.Auto_Flag==1){
pro.Runtime_Auto+=10;
if(pro.Runtime_Auto>1000){
pro.Runtime_Auto=0;
pro.Pa1_Duty--;
if(pro.Pa1_Duty<5){
pro.Pa1_Duty=5;
}
}
}
else if(pro.Auto_Flag==2){
pro.Runtime_Auto+=10;
if(pro.Runtime_Auto>1000){
pro.Runtime_Auto=0;
pro.Pa1_Duty++;
if(pro.Pa1_Duty>95){
pro.Pa1_Duty=95;
}
}
}
else{
pro.Runtime_Auto=0;
}
if(pro.Man_Flag==1){
pro.Runtime_Man+=10;
if(pro.Runtime_Man>=1000){
pro.Runtime_Man=0;
pro.Pa1_Duty--;
}
if(pro.Pa1_Duty<=pro.TarD_Duty){
pro.Man_Flag=0;
}
}
else if(pro.Man_Flag==2){
pro.Runtime_Man+=10;
if(pro.Runtime_Man>=1000){
pro.Runtime_Man=0;
pro.Pa1_Duty++;
}
if(pro.Pa1_Duty>=pro.TarD_Duty){
pro.Man_Flag=0;
}
}
else{
pro.Runtime_Man=0;
}
}
LED功能其实是比较简单的,没有复杂的闪烁与逻辑
void LED_Proc(uint16_t LED,uint16_t Mode){
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
if(Mode){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8<<(LED-1),GPIO_PIN_RESET);
}
else{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8<<(LED-1),GPIO_PIN_SET);
}
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
void LED_Test(void){
if(pro.TIM16_FRE>8000){
LED_Proc(1,1);
}
else{
LED_Proc(1,0);
}
if(pro.Tra_Ins<pro.FL_Para && pro.Pa1_Duty>pro.DH_Para){
pro.LED_Flag1=1;
if(pro.Runtime_LED2>2000){
LED_Proc(2,1);
}
}
else{
pro.LED_Flag1=0;
pro.Runtime_LED2=0;
LED_Proc(2,0);
}
if(pro.Tra_Ins>pro.FH_Para && pro.Pre_Realtime<pro.PL_Para/10){
pro.LED_Flag2=1;
if(pro.Runtime_LED3>2000){
LED_Proc(3,1);
}
}
else{
pro.LED_Flag2=0;
pro.Runtime_LED3=0;
LED_Proc(3,0);
}
LED_Proc(5,0);
LED_Proc(6,0);
LED_Proc(7,0);
LED_Proc(8,0);
}
void Interrupt(void){
if(pro.LED_Flag1==1){
pro.Runtime_LED2+=10;
}
if(pro.LED_Flag2==1){
pro.Runtime_LED3+=10;
}
}
至此大体的逻辑差不多都完成了
有一些我比较喜欢的小细节,在比赛中实时测量一下现在运行的占空比与频率

开启PA6的TIM3通道1和2的捕获功能,其中通道1为直接捕获上升沿,通道2为间接捕获下降沿,
对于的数据为TIM3的CCR1和CCR2
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
if(htim->Instance==TIM3){
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1){
uint16_t CCR1=TIM3->CCR1;
uint16_t CCR2=TIM3->CCR2;
TIM3->CNT=0;
pro.Pa6_Duty=(100*CCR2)/CCR1;
pro.Pa6_Fre=1e6/CCR1;
}
}
}
这样只需通过杜邦线连接PA1和PA6在LCD上显示pro.Pa6_Duty与pro.Pa6_Fre即可知道当前输出的占空比与频率,更进一步可以在LCD上显示各个Runtime的值,避免配置错误引发的定时混乱
最后以下为main.c的部分代码
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_DMA_Init();
MX_ADC2_Init();
MX_TIM1_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM16_Init();
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
LCD_Init();
LCD_Clear(Black);
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);
HAL_ADC_Start_DMA(&hadc2,pro.DMA_ADC_Buff,30);
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
TIM2->CCR2=5;
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&htim16,TIM_CHANNEL_1);
Sys_Init();
HAL_Delay(50);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
LCD_Proc();
Key_Scan();
IC_Proc();
ADC_Proc();
PWM_Proc();
LED_Test();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
小结
这份相对于十六届和十五届还是比较简单的,就是单纯的量大,没有复杂的逻辑处理与时间变化,没有像十六届的步进值,比较考验数据处理,也没有十五届的频率判断逻辑与题目误导,只是单纯量大,对变量的管理与数据变化需要比较细心
如需全部的代码,请私信或下载文章资源
更多推荐

所有评论(0)