目录
刚刚结束24年蓝桥杯,要做第一个发码的人!(写的不一定对,大家多多指正)
题目
效果
第十五届蓝桥杯嵌入式组省赛
分析
本题使用到的外设较少,主要考察程序逻辑的编写。
在CubeMX中只需要打开两个频率捕获通道(TIM2_CH1、TIM3_CH1)、两个定时中断(随便选,我用的是TIM4和TIM17)、LED(PC8~PC15、PD2)、按键(PB0~PB2、PA0)。开启后在主循环前初始化:
HAL_TIM_Base_Start_IT(&htim4); //开启10ms定时中断
HAL_TIM_Base_Start_IT(&htim17); //开启0.1s定时中断
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); //开启捕获中断
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); //开启捕获中断
LED_Init(); //关闭所有led
LED_Ctrl(led|=0x01); //点亮led1
key_init(); //按键初始化
LCD_Init();
LCD_Clear(Black);
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
程序的主体主要分为四块:频率测量、lcd显示、按键功能、中断处理,比较难的是频率突变(PD)、频率超限(PH)功能的实现。
首先我们先定义一些变量:
uint8_t display=0; //lcd显示界面标志位,0→数据界面,1→参数界面,2→统计界面
char buf[25]; //lcd显示用的每行字符串承装器
int cnt2 = 0 ,frq2 = 0; //单位为Hz,uS
int cnt3 = 0 ,frq3 = 0;
char f_or_t='f'; //切换频率周期显示
char DHX='D'; //切换参数选择
float frqA,frqB; //超过1kHz变为浮点数
float tA,tB; //超过1kuS变为mS
int PD=1000,PH=5000,PX=0; //参数
int NDA=0,NDB=0,NHA=0,NHB=0; //统计次数
频率测量
这部分就是常规的频率测量,在板子的基本例程中就可以找到。
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim2)
{
cnt2 = __HAL_TIM_GET_COUNTER(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
frq2 = 1000000/cnt2;
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
if(htim == &htim3)
{
cnt3 = __HAL_TIM_GET_COUNTER(&htim3);
__HAL_TIM_SetCounter(&htim3,0);
frq3 = 1000000/cnt3;
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
}
}
LCD显示
根据题目要求,一共三个界面(数据、参数、记录),根据display的值选择展示哪个界面,根据题目要求展示相应内容。大家可以一边对着代码一边对着题目要求对照,注意各个变量的作用,我就不赘述了。
void DI(void) //Display Interface
{
switch(display)
{
case 0: //数据界面
{
LCD_DisplayStringLine(Line1,(uint8_t *)" DATA ");
if(f_or_t=='f')
{
frq2+=PX;
frq3+=PX;
if(frq2<0) LCD_DisplayStringLine(Line3,(uint8_t *)" A=NULL ");
else if(frq2>1000)
{
frqA=(float)frq2/1000;
sprintf(buf," A=%.2fKHz ",frqA);
LCD_DisplayStringLine(Line3,(uint8_t *)buf);
}
else
{
sprintf(buf," A=%dHz ",frq2);
LCD_DisplayStringLine(Line3,(uint8_t *)buf);
}
if(frq3<0) LCD_DisplayStringLine(Line4,(uint8_t *)" B=NULL ");
else if(frq3>1000)
{
frqB=(float)frq3/1000;
sprintf(buf," B=%.2fKHz ",frqB);
LCD_DisplayStringLine(Line4,(uint8_t *)buf);
}
else
{
sprintf(buf," B=%dHz ",frq3);
LCD_DisplayStringLine(Line4,(uint8_t *)buf);
}
}
else if(f_or_t=='t')
{
if(frq2<0) LCD_DisplayStringLine(Line3,(uint8_t *)" A=NULL ");
else if(cnt2>1000)
{
tA=(float)cnt2/1000;
sprintf(buf," A=%.2fmS ",tA);
LCD_DisplayStringLine(Line3,(uint8_t *)buf);
}
else
{
sprintf(buf," A=%duS ",cnt2);
LCD_DisplayStringLine(Line3,(uint8_t *)buf);
}
if(frq3<0) LCD_DisplayStringLine(Line4,(uint8_t *)" B=NULL ");
else if(cnt3>1000)
{
tB=(float)cnt3/1000;
sprintf(buf," B=%.2fmS ",tB);
LCD_DisplayStringLine(Line4,(uint8_t *)buf);
}
else
{
sprintf(buf," B=%duS ",cnt3);
LCD_DisplayStringLine(Line4,(uint8_t *)buf);
}
}
break;
}
case 1: //参数界面
{
LCD_DisplayStringLine(Line1,(uint8_t *)" PARA ");
sprintf(buf," PD=%dHz ",PD);
LCD_DisplayStringLine(Line3,(uint8_t *)buf);
sprintf(buf," PH=%dHz ",PH);
LCD_DisplayStringLine(Line4,(uint8_t *)buf);
sprintf(buf," PX=%dHz ",PX);
LCD_DisplayStringLine(Line5,(uint8_t *)buf);
break;
}
case 2: //记录界面
{
LCD_DisplayStringLine(Line1,(uint8_t *)" RECD ");
sprintf(buf," NDA=%d ",NDA);
LCD_DisplayStringLine(Line3,(uint8_t *)buf);
sprintf(buf," NDB=%d ",NDB);
LCD_DisplayStringLine(Line4,(uint8_t *)buf);
sprintf(buf," NHA=%d ",NHA);
LCD_DisplayStringLine(Line5,(uint8_t *)buf);
sprintf(buf," NHB=%d ",NHB);
LCD_DisplayStringLine(Line6,(uint8_t *)buf);
break;
}
default:break;
}
if(NDA>=3||NDB>=3) LED_Ctrl(led|=0x80);
else LED_Ctrl(led&=~0x80);
}
按键
按键的判断我采用的是状态机法,基本操作在第十四届蓝桥杯嵌入式省赛中已经进行过讲解了,也可以去网上学习一下,我在这里主要讲主循环中的按键循环函数。
void key_while(void)
{
if(key[0].kind=='s')
{
if(display==1)
{
if(DHX=='D')
{
PD+=100;
if(PD>1000) PD=1000;
if(PD<100) PD=100;
}
else if(DHX=='H')
{
PH+=100;
if(PH>10000) PH=10000;
if(PH<1000) PH=1000;
}
else if(DHX=='X')
{
PX+=100;
if(PX>1000) PX=1000;
if(PX<-1000) PX=-1000;
}
}
key[0].kind='n';
}
if(key[1].kind=='s')
{
if(display==1)
{
if(DHX=='D')
{
PD-=100;
if(PD>1000) PD=1000;
if(PD<100) PD=100;
}
else if(DHX=='H')
{
PH-=100;
if(PH>10000) PH=10000;
if(PH<1000) PH=1000;
}
else if(DHX=='X')
{
PX-=100;
if(PX>1000) PX=1000;
if(PX<-1000) PX=-1000;
}
}
key[1].kind='n';
}
if(key[2].kind=='s')
{
if(display==0)
{
if(f_or_t=='f') f_or_t='t';
else if(f_or_t=='t') f_or_t='f';
}
else if(display==1)
{
if(DHX=='D') DHX='H';
else if(DHX=='H') DHX='X';
else if(DHX=='X') DHX='D';
}
key[2].kind='n';
}
if(key[2].kind=='l')
{
if(display==2)
NDA=0,NDB=0,NHA=0,NHB=0;
key[2].kind='n';
}
if(key[3].kind=='s')
{
if(display==0)
{
LCD_Clear(Black);
display=1;
LED_Ctrl(led&=~0x01);
DHX='D';
}
else if(display==1)
{
LCD_Clear(Black);
display=2;
}
else if(display==2)
{
LCD_Clear(Black);
display=0;
f_or_t='f';
LED_Ctrl(led|=0x01);
}
key[3].kind='n';
}
}
以上就是针对本题的按键判断函数,可以对照题目要求一一查看各个功能。函数中的变量在前面有说明。
频率超限
题目要求当通道 A 或 B 的频率大于 PH 时,对应的超限次数 NHA/B加1,如图:
为了实现此要求,我们需要设置一个长度为2的队列,记录此时刻和上一时刻的频率,如果此时刻的频率大于PH且上一时刻的频率小于PH,则说明超限一次,NHx++。如下:
int PHA[2],PHB[2];
void PH_beyond(void) //频率超限计数
{
PHA[1]=frq2;
PHB[1]=frq3;
if(PHA[1]>PH&&PHA[0]<PH) NHA++;
if(PHB[1]>PH&&PHB[0]<PH) NHB++;
if(PHA[1]>PH) LED_Ctrl(led|=0x02);
else LED_Ctrl(led&=~0x02);
if(PHB[1]>PH) LED_Ctrl(led|=0x04);
else LED_Ctrl(led&=~0x04);
PHA[0]=PHA[1];
PHB[0]=PHB[1];
}
频率突变
题目要求时间窗口内(3 秒),某个通道采集到的频率最大值和最小值的差值大于 PD 时,该通道频率突变次数 NDA/B 加 1,如图:
性能要求中明确指出频率数据更新频次为10 次/秒,所以我们在这里开启一个0.1s的定时中断,并设置一个长度为3s÷0.1s=30的队列,以存储临近3s内各个时刻的频率值。
每次进中断时,将队头元素出队,将新测得的此时刻的频率值入队,再找出队列中的最大值与最小值,若二者之差大于PD,则NDx++。
(比赛时时间紧张,为了图省事我直接用的数组,故时间复杂度要多一些,不过也无所谓)
uint8_t i=0;
int PDA[30]={0},PDB[30]={0};
int max_a,min_a,max_b,min_b;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM4)
{
key_interrupt();
}
if(htim->Instance==TIM17)
{
for(i=0;i<29;++i) //更新
{
PDA[i]=PDA[i+1];
PDB[i]=PDB[i+1];
}
PDA[29]=frq2;
PDB[29]=frq3;
if(PDA[0]!=0 && PDB[0]!=0)
{
max_a=PDA[29],min_a=PDA[29];
max_b=PDB[29],min_b=PDB[29];
for(i=28;i>0;--i) //排序
{
if(PDA[i]>max_a) max_a=PDA[i];
if(PDA[i]<min_a) min_a=PDA[i];
if(PDB[i]>max_b) max_b=PDB[i];
if(PDB[i]<min_b) min_b=PDB[i];
}
if(max_a-min_a>PD) ++NDA;
if(max_b-min_b>PD) ++NDB;
}
}
}
由于PDx数组初始值默认为0,所以为防止刚上电时PDx[29]很大,而后面的时刻还没有被更新,导致差值一定大于零引起误判,所以加一条判断语句使系统3s后再执行频率突变检测功能。
程序结构
在主循环中执行这三个函数:
while (1)
{
key_while();
DI();
PH_beyond();
}
中断的工作在“频率突变”讲解部分中已经体现。
源码
完整工程已经上传到CSDN,点击下方链接即可,免费下载,欢迎批评指正。