蓝桥杯单片机第16届4T模拟赛三思路讲解
声明:以下代码仅供参考
蓝桥杯单片机第16届4T模拟赛三于3月2日结束,做完题目忘记把题目截下来了,成绩出了再补上,本篇文章只讲思路(思路大体上是没问题的,有些地方可能存在不足,见谅)。
本次模拟赛涉及的知识点都是很常见的,没有像13届、14届的几次模拟赛一样考到串口通信。涉及的模块:基础三件套(Led&Relay,按键、数码管)+ 进阶单件套(pcf8591的AD模块),考的模块虽然很少,数码管显示页面也很好实现,但是也是存在一个难点的,就是后面Led模块的如何正确的让继电器吸合,这个是本题最大的难点。
一、数码管部分
1.页面1
页面1要显示的格式是:
最左边的数码管显示字母C
,最后两位显示湿度的十位和个位(不足两位十位熄灭)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
C | 3 | 0 |
湿度是由pcf8591的AD模块读取通道3的电压值转换来的,他们的关系是(从图转换成函数)
h
u
m
i
d
i
t
y
=
{
10
,
U
≤
1
0.266
∗
U
−
16.666
,
1
≤
x
≤
4
90
,
U
≥
4
humidity = \begin{cases} 10,U\le1\\ 0.266*U-16.666,\,\,1\le x \le 4\\ 90,U\ge 4\\ \end{cases}
humidity=⎩
⎨
⎧10,U≤10.266∗U−16.666,1≤x≤490,U≥4
所以在AD的处理函数就可以先将通道3的电压读出来后直接进行湿度转换。下面给出的是用rb2_100x接收放大100倍后的电压值,你也可以直接使用float型变量接收未放大的电压值。
void ADProc()
{
rb2_100x = ADRead() * 100 / 51;
if(rb2_100x <= 100)
humidity = 10;
else if(rb2_100x >= 400)
humidity = 90;
else
humidity = 0.266 * rb2_100x - 16.666;
}
然后把humidity放到数码管显示时,最好是将中间三位显示成电压值(这样可以直观的看到湿度转换是不是正确的),比如这样:
switch(SegMode)
{
case 0:
SegBuf[0] = 11;
SegBuf[1] = 10;
SegBuf[2] = rb2_100x / 100;
SegBuf[3] = rb2_100x / 10 % 10;
SegBuf[4] = rb2_100x % 10;
SegBuf[5] = 10;
SegBuf[6] = humidity / 10;
SegBuf[7] = humidity % 10;
break;
}
实物显示:
电压为4时,对应湿度90
电压为1时,对应湿度10
2.页面2
页面2要显示的格式是:
最左边的数码管显示字母E
,最后两位显示湿度参数的十位和个位(不足两位十位熄灭)
湿度参数默认值为50
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
E | 5 | 0 |
这个就特别简单了,页面1的代码直接复制下来就可以了,唯一要改的地方就是页面2的显示要对湿度参数的十位进行判断,为0时熄灭。
case 1:
SegBuf[0] = 12;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = (humidity_set / 10) ? (humidity_set / 10) : 10;
SegBuf[7] = humidity_set % 10;
break;
3. 页面3
页面3要显示的格式是:
最左边的数码管显示字母H
,最后两位显示时间间隔的十位和个位(不足两位十位熄灭)
时间间隔默认值为3。
时间间隔是继电器点亮的间隔,跟数码管没关系,直接显示即可。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
E | 3 |
二、按键部分
按键只用到了按键S4、S5、S8、S9。
按键S4是切换页面,实现也很简单。
按键5是继电器启动标志位,在任何界面按一次标志位取反。
按键8、按键9:参数控制按键
在页面2按下,湿度参数每次加/减5,页面3按下,时间间隔每次加/减1,湿度参数取值范围:30 ~ 90,时间间隔取值范围:1 ~ 10。
当湿度参数达到最大值时,下次按下S8,回到最小值开始加,时间间隔也是如此。
比如:
case 8:
if(SegMode == 1)
{
humidity_set += 5;
if(humidity_set == 95)
humidity_set = 30;
}
else if(SegMode == 2)
{
if(++relay_light_time == 11)
relay_light_time = 1;
}
break;
三、继电器部分
这边继电器吸合的判断是继电器工作标志位为1而且湿度参数大于湿度的时候,继电器吸合,经过时间间隔设置的时间后,继电器闭合。
题目出的不是很严谨,因为第一次湿度参数大于湿度的时候,继电器进行吸合-断开操作后,它还可以重复吸合吗?当湿度参数或者湿度或者时间间隔数据变了,它还可以吸合吗?
根据正常人的思维,这边继电器应该是可以重复吸合的(当湿度数据或者湿度参数变了后),也就是说,湿度为30,湿度数据为35,继电器工作的时候,继电器吸合,3秒后断开,此时不能重复吸合,然后将湿度调成33,继电器吸合,3秒后断开。保持湿度不变,湿度数据变成40,此时继电器吸合,3秒后断开。
按照上面分析的继电器吸合逻辑,难点如下:
- 让继电器吸合很简单,让继电器闭合也很简单,要怎么让继电器吸合后经过时间间隔设定的时间后断开呢?
- 怎么避免继电器重复吸合的情况下又不影响湿度/湿度参数改变后让继电器吸合呢?
碰到这种逻辑性很强的题目,采取的做法是从大到小,也就是先写继电器不工作时断开、继电器工作时吸合,然后观察板子的现象是否正确(防止继电器底层代码书写错误)
void LedProc()
{
/*Relay*/
if(!relay_work)
relay_flag = 0;
else
relay_flag = 1;
Relay(relay_flag);
}
然后开始写继电器工作时,如果湿度参数大于湿度时,继电器吸合,否则断开。
void LedProc()
{
/*Relay*/
if(!relay_work)
relay_flag = 0;
else
{
if(humidity < humidity_set)
{
relay_flag = 1;
}
else
relay_flag = 0;
}
Relay(relay_flag);
}
然后就可以开始写继电器吸合后经过时间间隔设定的时间后断开了。
定义全局变量unsigned int time_relay_light
为时间计时变量,这边对time_relay_light
处理防止数据越界,然后当time_relay_light
大于等于时间间隔设定的时间时,继电器闭合。(relay_light_time
为时间间隔,由于定时器为1秒产生1次中断,所以将time_relay_light
和relay_light_time * 1000
进行比较)
void LedProc()
{
/*Relay*/
if(!relay_work)
relay_flag = 0;
else
{
if(humidity < humidity_set)
{
relay_flag = 1;
if(time_relay_light >= (relay_light_time * 1000))
relay_flag = 0;
}
else
relay_flag = 0;
}
Relay(relay_flag);
}
void Timer0_Isr(void) interrupt 1
{
if(relay_work && relay_flag)
{
if(++time_relay_light >= 10000)
time_relay_light = 10000;
}
}
然后就要开始写防止重复吸合了(湿度、湿度参数、继电器工作模式没有改变的情况下重复吸合)
idata bit relay_has_light;
void LedProc()
{
/*Relay*/
if(!relay_work)
relay_flag = 0;
else
{
//防止重复吸合
if(!relay_has_light)
{
relay_flag = 1;//继电器吸合
relay_has_light = 1;//继电器重复点亮判断生效
}
if(humidity < humidity_set)
{
relay_flag = 1;
if(time_relay_light >= (relay_light_time * 1000))
relay_flag = 0;
}
else
relay_flag = 0;
}
Relay(relay_flag);
}
防止重复吸合写完,就要写在哪里让relay_has_light
标志位清零了,这个时候,我们可以模仿一下Led的底层代码中定义的temp
和 temp_old
,也就是说,第一次湿度<湿度参数吸合继电器的时候,用humidity_old
和humidity_set_old
保存当前的humidity
和humidity_set
,所以判断relay_has_light
标志位清零只需要在下次的湿度<湿度参数的时候判断一下(humidity_old != humidity)
或者(humidity_set_old != humidity_set)
就可以了。
else
{
if(humidity < humidity_set)
{
if((humidity_old != humidity) || (humidity_set_old != humidity_set))
{
time_relay_light = 0;//重置计时器
relay_has_light = 0;//继电器重复点亮判断失效
humidity_old = humidity;
humidity_set_old = humidity_set;
}
if(!relay_has_light)
{
relay_flag = 1;//继电器吸合
relay_has_light = 1;//继电器重复点亮判断生效
}
if(time_relay_light >= (relay_light_time * 1000))
{
relay_flag = 0;//达到时间后继电器断开
}
}
else
{
relay_flag = 0;
time_relay_light = 0;
}
}
然后就要考虑最后一种情况了,当湿度参数和湿度都没有改变,只改变继电器标志位的时候,继电器也是要吸合的。
if(!relay_work)
{
relay_has_light = 0;
relay_flag = 0;
humidity_old = humidity_set_old = 0;//重置保存值
}
else
{
//...
}
这样写出的程序显示时是没有明显问题的(如果继电器吸合是按照上面我的理解来说的话),如果你有别的看法或者发现了错误,请在评论区告诉我。