蓝桥杯单片机第十三届国赛真题解析-测评满分代码
蓝桥杯单片机第十三届国赛真题解析-测评满分代码
蓝桥杯单片机比赛是许多电子工程和计算机科学学生的重要竞技舞台,而第十三届国赛的真题更是凝聚了出题者的智慧与参赛者的汗水。本文将深入解析该届比赛中的几个技术难点,包括NE555频率测量、IIC时序问题、按键长按检测、PWM脉冲输出以及EEPROM存储等,通过具体的代码示例和详细解释,帮助读者掌握这些关键技术点。
一. NE555频率测量
1. P34引脚问题
NE555在第十四届和第十五届省赛中均有考到,因为测频率需要用到P34,而对于使用全部的矩阵键盘来说也是需要对P34进行操作的,这个时候就产生了矛盾,如果在按键底层对P34进行了操作就会使频率测量出现较大的误差(正常频率3.4w左右),频率可能会降至2w多。所以我们在写按键底层时不要对P34进行操作(题目在出现ne555时键盘也不会用到最后一列)。
2. 定时器
频率测量需使用两个定时器。定时器0用来计数,定时器1用来计时,所以超声波我们来使用PCA(可编程计数阵列)。如果题目还需要使用串口,那么串口就用定时器2。
3. 高位熄灭
高位熄灭在最近几次省国赛中均有考到,那么该如何实现呢?我采用的是最无脑的if else语句来判断:
if(dat/100==0) //一定要从低位开始逐步往高位判断
{
Nixie_SetBuf(3,19); //熄灭位19
Nixie_SetBuf(4,19);
Nixie_SetBuf(5,19);
Nixie_SetBuf(6,19);
Nixie_SetBuf(7,dat/10);
Nixie_SetBuf(8,dat%10);
}
else if(dat/1000==0)
{
Nixie_SetBuf(3,19);
Nixie_SetBuf(4,19);
Nixie_SetBuf(5,19);
Nixie_SetBuf(6,dat/100);
Nixie_SetBuf(7,dat/10%10);
Nixie_SetBuf(8,dat%10);
}
else if(dat/10000==0)
{
Nixie_SetBuf(3,19);
Nixie_SetBuf(4,19);
Nixie_SetBuf(5,dat/1000);
Nixie_SetBuf(6,dat/100%10);
Nixie_SetBuf(7,dat/10%10);
Nixie_SetBuf(8,dat%10);
}
else
{
Nixie_SetBuf(3,19);
Nixie_SetBuf(4,dat/10000);
Nixie_SetBuf(5,dat/1000%10);
Nixie_SetBuf(6,dat/100%10);
Nixie_SetBuf(7,dat/10%10);
Nixie_SetBuf(8,dat%10);
}
二. IIC时序被打断问题(导致数据跳闪 归零)
当中断里遇到较为复杂的逻辑时或硬件原因P2口复用,导致时序会有被打断的可能(不仅限于IIC)从而导致读取数据发生问题(跳变 归零)。
方法一:
仔细审题,观察该数据读取是否需要全程放入while中读取,还是只用在某个特定的界面,如果在特定界面读取,只需要设置一个标志位,一段时间读取一次即可。
方法二:
在AD底层开关中断,这样能有效解决时序被打断问题:
unsigned char AD_Read(unsigned char addr)
{
unsigned char temp;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
EA=0; // 关闭全局中断
I2CWaitAck();
temp=I2CReceiveByte();
I2CSendAck(1);
EA=1; // 开启全局中断
I2CStop();
return temp;
}
但不停的开关中断会对定时器产生一定影响(数码管刷新可能也会有问题),特别是需要测定频率时,对定时器要求较高,导致频率测量出现误差(目前还未找到解决办法)。所以在需要进行频率测量时,谨慎随意开关中断。
方法三:
P2口连续操作锁存器太快导致数据跳变,可以在底层(锁存器模块)分开来操作:
#include <STC15F2K60S2.H>
void InitHC138(unsigned char n)
{
switch(n)
{
case 4: P2&=0x1f;P2|=0x80;break;
case 5: P2&=0x1f;P2|=0xa0;break;
case 6: P2&=0x1f;P2|=0xc0;break;
case 7: P2&=0x1f;P2|=0xe0;break;
}
P2&=0x1f;
}
三. 按键长按
关于按键长按的方法很简单,通过检测按键的上升沿和下降沿之间的时间间隔,设置相应的标志位就好了:
if(turn==1) // 在while中
{
if(Key_Down==7) time1s=1;
else if(Key_Up==7) time1s=2;
}
if(time1s==1) // 定时器中判断 当检测到s7按键按下时开始计时
{
count11++;
}
if(time1s==2) // s7按键抬起
{
if(count11>=10000) // 如果此时时间大于1s
{
n=0; // 继电器存储次数清零
}
count11=0; // 无论时间是否满足大于1s 只要抬起s7按键 计时就得清零 否则会影响下一次的判断
time1s=0;
}
四. 脉冲输出(PWM)
通过电机驱动引脚PWM输出脉冲信号是本套试题的难点之一。
1. 与继电器冲突
通过观察原理图,N_MOTOR引脚对应的为D5,也就是P05,不难发现D5到N_MOTOR中间经过一个74HC573锁存器,对应的是Y5C。这个时候就有问题了,MOTOR与继电器之间发生了冲突,那么如何解决这个问题呢?因为我们采用的是封装好的继电器模块:
void Relay(unsigned char flag) // 继电器模块
{
static unsigned char temp_old = 0xff;
if(flag)
temp |= 0x10;
else
temp &= ~0x10;
if(temp != temp_old)
{
P0 = temp;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
temp_old = temp;
}
}
所以可以照葫芦画瓢写一个MOTOR模块:
void Motor(unsigned char flag)
{
static unsigned char temp_old = 0xff;
if(flag)
temp |= 0x20;
else
temp &= ~0x20;
if(temp != temp_old)
{
P0 = temp;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
temp_old = temp;
}
}
注意:这两个模块中的temp就不能再使用局部变量了,所以要定义一个全局变量:
unsigned char temp = 0x00;
2. 代码编写
题目要求1KHZ的脉冲信号,周期也就是1ms。如果我们配置的定时器是100us的,那么就是以10为总体来分配占空比。如果配置的是10us的定时器,就是以100为总体分配占空比。我使用的是100us的定时器。(建议尝试用10us的定时器,这样分配总额为100份,结果肯定比10份的更精确)
if(dat>(fset*100)) // 在定时器内 脉冲输出
{
count6++;
if(count6<9)
{
Motor(1);
}
else if(count6<=10&&count6>=9)
{
Motor(0);
}
else if(count6==11) count6=0;
}
else if(dat<=(fset*100))
{
count12++;
if(count12<=2)
{
Motor(1);
}
else if(count12<=10&&count12>2)
{
Motor(0);
}
else if(count12==11) count12=0;
}
注:因为主程序的所有中断都是放在一个定时器内的,所以测出来的频率有较小的误差,结果会不到1KHZ。我们可以尝试再开一个定时器专门用来输出脉冲信号,这个方法本人还未尝试,有试过成功了的小伙伴可以分享在评论区,一起学习!
五. EEPROM存储继电器开关次数
按照题目要求,记录继电器的开关次数,这里指的是开算一次,关也算一次,也就是说我们需要记录下继电器从最开始的关闭状态到下一次的开启状态,和从开启状态到下一次的关闭状态,那么我们该如何实现呢?
思路:设一个标志来表示继电器的两种不同状态,然后在特定的一段时间内去检测两种状态是否发生了改变:
if(length>lengthset*10)
{
Relay(1); num=1; // 继电器开启 让num为1
}
else
{
Relay(0); num=0; // 继电器关闭 num为0 最开始初始状态是为0的
}
if(count10==10) // 定时器内1ms判断一次
{
// flag3 初值为0
if(flag3!=num) // 一段时间后如果num发生改变 则表明继电器状态改变 存储加一
{
n++;
flag3=num; // 再让flag等于此时继电器状态的值
}
count10=0;
}
最后是如何存储到EEPROM中去: