看到很多人在问第十六届蓝桥杯单片机难不难,以及实现多少功能可以获得省一。
- 先介绍下我的作答情况吧,选择题只对一题,程序题的求连续两次距离差值没有考虑负数的情况,其他功能都实现了,成绩是福建省省一第一页,感谢4T老爷手下留情。
- 至于第十六届蓝桥杯单片机组的难度呢?我认为比十五届难(主要难在阅读能力与排坑能力),比十四届简单(毕竟十四届是真神)。
- 以下代码仅供参考,不能保证完全正确。
附件:第十六届蓝桥杯单片机组省赛第一次
一、模板搭建
本题的页面流转要实现四个页面,并且参数设置时参数是不生效的,所以在进入参数设置时要再定义一个新的变量去代替原来的参数进行修改,退出参数设置时再保存修改后的值。
至此,可以先搭建以下模板:
- main.c
#include <STC15F2K60S2.H>
#include "ds18b20.h"
#include "wave.h"
#include "init.h"
#include "led.h"
#include "key.h"
#include "seg.h"
#include "iic.h"
typedef unsigned char u8;
typedef unsigned int u16;
/*按键*/
idata u8 keyVal,keyDown,keyUp,keyOld;
/*数码管*/
idata u8 segPos;
pdata u8 segBuf[8] = {10,10,10,10,10,10,10,10};
idata u8 segMode; //0-环境 1-运动 2-参数 3-统计
/*指示灯*/
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
/*温度*/
idata u8 tem; //环境温度
/*AD*/
idata u8 light; //光敏电阻放大10倍
/*超声波*/
idata u8 distance; //距离数据(1s采集1次)
/*参数*/
idata u8 keySlow;
idata u8 segSlow;
idata u8 iicSlow;
idata u8 temSlow;
idata u8 waveSlow;
idata u8 setDis[2] = {30,30}; //温度参数/距离参数控制值
idata u8 setCtr[2] = {30,30}; //温度参数/距离参数改变值
idata bit setCtrMode; //温度参数/距离参数改变值索引
void keyProc()
{
if(keySlow) return;
keySlow = 1;
keyVal = keyDisp();
keyDown = keyVal & ~keyOld;
keyUp = ~keyVal & keyOld;
keyOld = keyVal;
switch(keyDown)
{
case 4:
if(++segMode == 4)
segMode = 0;
//退出参数设置时保存修改值
if(segMode == 3)
{
setCtrMode = 0;
setDis[0] = setCtr[0];
setDis[1] = setCtr[1];
}
break;
case 5://参数设置子页面切换
if(segMode == 2)
setCtrMode = !setCtrMode;
break;
case 8://参数加 30~80
break;
case 9://参数加 30~80
break;
}
}
void segProc()
{
if(segSlow) return;
segSlow = 1;
switch(segMode)
{
case 0:
//...
break;
case 1:
//...
break;
case 2:
//...
break;
case 3:
//...
break;
}
}
void ledProc()
{
}
void iicProc()
{
if(iicSlow) return;
iicSlow = 1;
light = lightRead() / 51.0 * 10;
}
void waveProc()
{
if(waveSlow) return;
waveSlow = 1;
distancee = waveGet();
}
void temProc()
{
if(temSlow) return;
temSlow = 1;
tem = temRead();
}
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
EA = 1;
}
void Timer0_Isr(void) interrupt 1
{
if(++keySlow == 10) keySlow = 0;
if(++segSlow == 90) keySlow = 0;
if(++waveSlow == 160) keySlow = 0;
if(++iicSlow == 160) keySlow = 0;
if(++temSlow == 160) keySlow = 0;
if(++segPos == 8) segPos = 0;
segDisp(segPos, segBuf[segPos]);
ledDisp(ucLed);
}
void main()
{
systemInit();//系统初始化
waveInit();
temRead();
Timer0_Init();
while(1)
{
ledProc();
keyProc();
segProc();
iicProc();
temProc();
waveProc();
}
}
二、环境状态页面
环境状态页面需要处理光强等级,光强等级是通过光敏电阻电压值转换的。
只需在iicProc()
中简单处理即可:
这边为了节省空间,我使用了unsigned char
变量来接收光敏电阻电压值,在接收时将其放大10倍,后续转换时都要放大十倍再进行转换。
void iicProc()
{
if(iicSlow) return;
iicSlow = 1;
light = lightRead() / 51.0 * 10;//放大10倍
/*光照强度转化*/
if(light < 5) //光照等级4:V<0.5->v*10<5
lightLevel = 4;
else if(light < 20) //光照等级3:V<2->v*10<20
lightLevel = 3;
else if(light < 30) //光照等级2:V<3->v*10<30
lightLevel = 2;
else //光照等级1:V>=3
lightLevel = 1;
}
void segProc()
{
if(segSlow) return;
segSlow = 1;
switch(segMode)
{
case 0:
segBuf[0] = 11; //C
segBuf[1] = tem / 10;
segBuf[2] = tem % 10;
segBuf[4] = 10;
segBuf[5] = 10;
segBuf[6] = 12; //n
segBuf[7] = lightLevel;
break;
}
}
三、运动检测页面
运动检测页面应该是本题最难的一个页面了,当然这个页面的分数肯定是最多的,因为超声波影响了LED和继电器,一个细节没实现好会导致连坐丢分。
运动状态这张图可以帮助理解题意,上电2s内不读取距离数据,直到第2s开始读取第一次距离数据,这个时候运动状态是肯定发生变化了的,所以要锁住运动状态(锁住期间,就算测量距离从12cm变到6cm,运动状态仍然保持第2s的运动状态,直到第5s时才更新运动状态),并且距离数据的测量是有时间间隔的,每1s才测量一次距离数据。
同时还要注意,两次测量数据的差值是要考虑小的数据减大的数据的,这边可以用signed char
型变量定义数据差值,也可以用abs
,我认为是在相减时先判断哪个数据比较大,然后用大的减小的这种方法比较简单。
要实现的功能很多,碰到这种复杂功能实现,最稳妥的方法就是先实现小功能,再去改进。
1.上电两秒不采集距离数据
上电时,计数变量开始计时两秒,计时期间不进入数据读取(直接return退出),直到两秒是将标志位赋值为1,开始进行数据采集。
idata bit distanceLock; //刚上电2s时不采集距离数据
idata u16 time2s; //计时2s
void waveProc()
{
if(waveSlow) return;
waveSlow = 1;
//上电未满2s直接退出距离处理函数
if(!distanceLock)
return;
}
void Timer0_Isr(void) interrupt 1
{
//上电计时2s
if(!distanceLock)
{
if(++time2s == 2000)
distanceLock = 1;
}
}
2.数据采集以一秒为间隔
idata bit distanceFlag; //距离数据1s采集1次标志位
idata u16 time1s; //计时变量,1s采集1次数据
idata bit close; //接近判定标志位
void waveProc()
{
//采集数据
if(!distanceFlag)//保证1s采集1次距离
{
distance = waveGet();
close = distance < setDis[1]; //距离值低于距离参数触发接近判定
distanceFlag = 1;
}
}
void Timer0_Isr(void) interrupt 1
{
//1s采集1次数据
if(distanceFlag)
{
if(++time1s == 1000)
{
time1s = 0;
distanceFlag = 0;
}
}
}
3.求相邻两次数据的差值
idata u8 distance; //距离数据(1s采集1次)
idata u8 distanceLast; //上一次的距离数据
idata u8 diff; //两次距离的差值
void waveProc()
{
//...
//采集数据
if(!distanceFlag)//保证1s采集1次距离
{
distance = waveGet();
diff = (distance>distanceLast) ? distance-distanceLast : distanceLast-distance;
distanceLast = distance;
}
}
4.运动状态转换
idata u8 distanceMode = 1;//运动状态(根据diff来判断)
void waveProc()
{
//采集数据
if(!distanceFlag)//保证1s采集1次距离
{
distance = waveGet();
close = distance < setDis[1]; //距离值低于距离参数触发接近判定
distanceFlag = 1;
diff = (distance>distanceLast) ? distance-distanceLast : distanceLast-distance;
distanceLast = distance;
//差值转换运动状态
if(diff < 5) //静止(L1)
distanceMode = 1;
else if(diff < 10)//徘徊(L2)
distanceMode = 2;
else //运动(L3)
distanceMode = 3;
}
}
5.运动状态发生变化时锁定运动状态
idata u8 distanceLastMode;//上一次的运动状态
idata bit distanceModeLock;//运动状态锁定
idata u16 time3s;//锁定状态时计时3s
void waveProc()
{
if(waveSlow) return;
waveSlow = 1;
//上电未满2s直接退出距离处理函数
if(!distanceLock)
return;
//采集数据
if(!distanceFlag)//保证1s采集1次距离
{
distance = waveGet();
close = distance < setDis[1]; //距离值低于距离参数触发接近判定
distanceFlag = 1;
//状态锁定时不判断运动状态变化
if(!distanceModeLock)
{
diff = (distance>distanceLast) ? distance-distanceLast : distanceLast-distance;
distanceLast = distance;
//差值转换运动状态
if(diff < 5) //静止(L1)
distanceMode = 1;
else if(diff < 10)//徘徊(L2)
distanceMode = 2;
else //运动(L3)
distanceMode = 3;
//两次运动状态不同时锁定运动状态
if(distanceLastMode != distanceMode)
distanceModeLock = 1;
distanceLastMode = distanceMode;//更新运动状态
}
}
}
void Timer0_Isr(void) interrupt 1
{
//状态锁定计时3s
if(distanceModeLock)
{
if(++time3s == 3000)
{
time3s = 0;
distanceModeLock = 0;
}
}
}
void segProc()
{
if(segSlow) return;
segSlow = 1;
switch(segMode)
{
case 0:
//...
break;
case 1:
segBuf[0] = 13; //L
segBuf[1] = distanceMode;
segBuf[2] = 10;
segBuf[5] = distance / 100 ? distance / 100 : 0;
segBuf[6] = distance / 10 % 10;
segBuf[7] = distance % 10;
break;
}
}
四、参数设置、统计数据页面
当温度高于温度参数,测距数据小于距离参数时,继电器吸合,否则断开,统计数据页面记录的是继电器吸合的次数。
1.keyProc()
void keyProc()
{
if(keySlow) return;
keySlow = 1;
keyVal = keyDisp();
keyDown = keyVal & ~keyOld;
keyUp = ~keyVal & keyOld;
keyOld = keyVal;
switch(keyDown)
{
case 4:
if(++segMode == 4)
segMode = 0;
if(segMode == 3)
{
setCtrMode = 0;
setDis[0] = setCtr[0];
setDis[1] = setCtr[1];
}
break;
case 5:
if(segMode == 2)
setCtrMode = !setCtrMode;
break;
case 8://参数加 30~80
if(segMode == 2)
{
if(!setCtrMode)//温度+1
{
if(++setCtr[0] == 81)
setCtr[0] = 80;
}
else//距离+5
{
setCtr[1] += 5;
if(setCtr[1] == 85)
setCtr[1] = 80;
}
}
break;
case 9://参数加 30~80
if(segMode == 2)
{
if(!setCtrMode)//温度-1
{
if(--setCtr[0] == 19)
setCtr[0] = 20;
}
else//距离-5
{
setCtr[1] -= 5;
if(setCtr[1] == 15)
setCtr[1] = 20;
}
}
break;
}
}
2.segProc()
void segProc()
{
if(segSlow) return;
segSlow = 1;
switch(segMode)
{
case 2:
segBuf[0] = 14; //P
segBuf[1] = !setCtrMode ? 11 : 13; //C/L
segBuf[2] = 10;
segBuf[5] = 10;
segBuf[6] = setCtr[setCtrMode] / 10;
segBuf[7] = setCtr[setCtrMode] % 10;
break;
case 3:
segBuf[0] = 12; //n
segBuf[1] = 11; //C
segBuf[4] = relayCount / 1000 ? relayCount / 1000 : 10;
segBuf[5] = (segBuf[4]==10&&relayCount/100%10==0) ? 10 : relayCount/100%10;
segBuf[6] = (segBuf[5]==10&&relayCount/10%10==0) ? 10 : relayCount/10%10;
segBuf[7] = relayCount % 10;
break;
}
}
五、LED、继电器
1.继电器
idata u16 relayCount; //继电器吸合次数
idata bit relayHasCount;//继电器吸合一次只计数一次
void ledProc()
{
//上电未满2s直接退出led处理函数
if(!distanceLock)
return;
//继电器
temHigFlag = (tem > setDis[0]);
if(temHigFlag && close && !relayHasCount)
{
relayDisp(1);
relayCount++;
relayHasCount = 1;//继电器吸合已经计数了
}
if(!temHigFlag || !close)
{
relayDisp(0);
relayHasCount = 0;//重置计数标志位
}
}
2.LED
/*指示灯*/
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 time100ms; //计时100msL8闪烁
idata bit ledFlash; //L8闪烁标志位
void ledProc()
{
//L1~L4
if(close)
{
switch(lightLevel)
{
case 1:
ucLed[0] = 1;
ucLed[1] = ucLed[2] = ucLed[3] = 0;
break;
case 2:
ucLed[0] = ucLed[1] = 1;
ucLed[2] = ucLed[3] = 0;
break;
case 3:
ucLed[0] = ucLed[1] = ucLed[2] = 1;
ucLed[3] = 0;
break;
case 4:
ucLed[0] = ucLed[1] = ucLed[2] = ucLed[3] = 1;
break;
}
}
else
ucLed[0] = ucLed[1] = ucLed[2] = ucLed[3] = 0;
//L8
if(distanceMode == 1)
ucLed[7] = 0;
else if(distanceMode == 2)
ucLed[7] = 1;
else
ucLed[7] = ledFlash;
}
void Timer0_Isr(void) interrupt 1
{
//L8闪烁
if(distanceMode == 3)
{
if(++time100ms == 100)
{
time100ms = 0;
ledFlash = !ledFlash;
}
}
else
{
time100ms = 0;
ledFlash = 0;
}
}
六、双按键功能处理
1.底层修改
按键8和按键9在同一列,判断按键8是否按下判断的是P33引脚的是否为0,判断按键9是否按下判断的是P32引脚是否为0,所以两个按键同时按下就是P33引脚和P32引脚同时为0,根据这个理论可以修改按键底层。
- key.c
#include "key.h"
unsigned char keyDisp()
{
unsigned char temp = 0;
P44 = 0;
P42 = 1;
P35 = 1;
P34 = 1;
if(P33 == 0) temp = 4;
if(P32 == 0) temp = 5;
P44 = 1;
P42 = 0;
P35 = 1;
P34 = 1;
if(P33 == 0) temp = 8;
if(P32 == 0) temp = 9;
if(!P32 && !P33) temp = 89;
return temp;
}
2.main.c调用
idata bit keyPressFlag;//双按键同时按下标志位
idata u16 time2000ms; //双按键长按2s计时
void keyProc()
{
if(keySlow) return;
keySlow = 1;
keyVal = keyDisp();
keyDown = keyVal & ~keyOld;
keyUp = ~keyVal & keyOld;
keyOld = keyVal;
//双按键处理
if(keyDown == 89 && segMode == 3)
keyPressFlag = 1;
if(time2000ms == 2001) //长按超过2s
{
relayCount = 0;
keyPressFlag = 0;
time2000ms = 0;
}
if(keyUp == 89)
{
keyPressFlag = 0;
time2000ms = 0;
}
}
void Timer0_Isr(void) interrupt 1
{
//双按键长按2s
if(keyPressFlag)
{
if(++time2000ms >= 2000)
time2000ms = 2001;
}
}