前言
复习国赛之余突然想做一个涵盖所有考点,模块的整合,便于大家复习, 以下是本人总结的一些蓝桥杯往届国赛题中的考点,难点,包含基础分模块代码以及个人的浅浅理解,如果有任何更好的方案或者看的不太懂的地方欢迎大家在评论区指正,提问,或者需要完整蓝桥杯题目,分模块工程或者省赛资料也可以私信我。
目录
目录
工程资源
在国赛复习中打过的工程文件包含近5年的国赛主观题,分享给大家,有些工程代码缺失一些例如led之类的一些简单操作,但是整体代码基本都完成了,可以给大家参考一下思路。
https://www.alipan.com/s/mvbmkH8DKvW 提取码: vj73
基础考点
主观题主要功能都由基础考点,模块组成,想要拿奖熟练掌握基础考点是必须的,平时也要熟练掌握模块之间的协调问题,因为是基础部分就不做过多注释讲解,需要了解的可以搜索各个模块的详细原理,这里只展示各个模块效率较高,不容易与其他模块发生冲突的写法。
1.按键(必考)
1,独立按键(不常用)
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;
void KeyScanf()
{
if(S7==0)
{
countkey=0;while(countkey<=50);//定时器延时去抖
if(S7==0)
{
while(S7==0);//抬手
}
}
if(S6==0)
{
countkey=0;while(countkey<=50);
if(S6==0)
{
while(S6==0);
}
}
if(S5==0)
{
countkey=0;while(countkey<=50);
if(S5==0)
{
while(S5==0);
}
}
if(S4==0)
{
countkey=0;while(countkey<=50);
if(S4==0)
{
while(S4==0);
}
}
}
2,矩阵键盘(常用)
sbit R1 = P3^0;
sbit R2 = P3^1;
sbit R3 = P3^2;
sbit R4 = P3^3;
sbit C1 = P3^4;
sbit C2 = P3^5;
sbit C3 = P4^2;
sbit C4 = P4^4;
void KeyScanf()
{
R3=0;
R1=R2=R4=1;
C1=C2=C3=C4=1;
if(C4==0)//S5
{
countkey=0;while(countkey<=10);
if(C4==0)
{
while(C4==0);
}
}
if(C3==0)//S9
{
countkey=0;while(countkey<=10);
if(C3==0)
{
while(C3==0);
}
}
R4=0;
R1=R2=R3=1;
C1=C2=C3=C4=1;
if(C4==0)//S4
{
countkey=0;while(countkey<=10);
if(C4==0)
{
while(C4==0);
}
}
if(C3==0)//S8
{
countkey=0;while(countkey<=10);
if(C3==0)
{
while(C3==0);
}
}
}
2.数码管(必考)
1.定时器写法(国赛推荐)
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
//以下是0到9带小数点部分
0xc0+0x80, //0
0xf9+0x80, //1
0xa4+0x80, //2
0xb0+0x80, //3
0x99+0x80, //4
0x92+0x80, //5
0x82+0x80, //6
0xf8+0x80, //7
0x80+0x80, //8
0x90+0x80, //9
0x88, //A
0x83, //b
0xc6, //C
0xa1, //d
0x86, //E
0x8e,//F
0xff,// 26
0xbf,//- 27
0xc7,//l
0x89,//h
0xfe,
0xf7,
0x8c
};
uchar Seg_code[]={26,26,26,26,26,26,26,26};
void SelectHC573(uchar pos,uchar dat)
{
P0=dat;
switch(pos)
{
case 0:P2 = (P2&0x1f)|0x00;break;
case 4:P2 = (P2&0x1f)|0x80;break;
case 5:P2 = (P2&0x1f)|0xa0;break;
case 6:P2 = (P2&0x1f)|0xc0;break;
case 7:P2 = (P2&0x1f)|0xe0;break;
}
P2 = (P2&0x1f)|0x00;
}
void ServiceT0() interrupt 1//定时器刷新片选和段选,定时器初始化省略
{
TH0=(65535-1000)/256;
TL0=(65535-1000)%256;
SelectHC573(6,0x01<<j);
SelectHC573(7,Seg_Table[Seg_code[j]]);//片选
j++;if(j>7)j=0;//段选
}
void ShowTime()//数码管显示示范
{
Seg_code[0]=Time[2]/16;
Seg_code[1]=Time[2]%16;
Seg_code[2]=Seg_code[5]=27;
Seg_code[3]=Time[1]/16;
Seg_code[4]=Time[1]%16;
Seg_code[6]=Time[0]/16;
Seg_code[7]=Time[0]%16;
}
2.主函数刷新写法(省赛推荐)
void SelectHC573(uchar n)
{
switch(n)
{
case 0:P2 = (P2&0x1f)|0x00;break;
case 4:P2 = (P2&0x1f)|0x80;break;
case 5:P2 = (P2&0x1f)|0xa0;break;
case 6:P2 = (P2&0x1f)|0xc0;break;
case 7:P2 = (P2&0x1f)|0xe0;break;
}
}
void ShowSMG(uchar pos,uchar dat)
{
SelectHC573(6);
P0 = 0x01<<pos;
SelectHC573(7);
P0 = dat;
Delayus(300);
P0 = 0xff;
}
3.LED(必考)
比赛中对LED的写法的思路
uchar LED=0xff;
void CtrlLED()//这里是比赛试题中对LED控制的范例
{
if(CountS4==0)//通过判断各种条件
{
switch(CountS5)
{
case 0:LED = (LED&0xf8)|0x01;break;//改变LED变量的值
case 1:LED = (LED&0xf8)|0x02;break;
case 2:LED = (LED&0xf8)|0x04;break;
}
}
if(moshiflag==1)LED = LED&0xf7;else LED = LED|0x08;
if((disjilu[0]<=juliCS+5&&disjilu[0]>=juliCS-5)&&
(disjilu[1]<=juliCS+5&&disjilu[1]>=juliCS-5)&&
(disjilu[2]<=juliCS+5&&disjilu[2]>=juliCS-5))
{
LED = LED&0xef;
}
else LED = LED|0x10;
if(AD>=100)LED = LED&0xdf;else LED = LED|0x20;
SelectHC573(4,LED);//最后输出给HC573通道4
}
4.继电器,蜂鸣器(省赛高频)
原理和LED相同,当然也可以放在一个函数内去实现,思路是一样的
void Ctrljidian()
{
if(distance>disCS)
{
U5=U5|0x10;
}else U5=U5&0xef;
SeleHC573(5,U5);
if(pinlv>pinlvCS)zhankong=4;else zhankong=1;
}
5.AD转换,DA输出(IIC)(省赛高频)
基础,必须会,并且也要掌握原理
uchar AD_transport(uchar n)
{
uchar dat;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(n);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
dat = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return dat;
}
void DA_transprot(uchar n)
{
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x43);
I2CWaitAck();
I2CSendByte(n);
I2CWaitAck();
I2CStop();
}
6.eeprom(省赛高频)
基础,想拿奖就必须会,防止第一次烧写原数据块内容对我们变量内容的影响 ,可以参考我的写法(有注释讲解),注意读写动作要加间隔时间。
void WriteEeprom()
{
pinlvMSB = pinlvHX>>8&0xff;
pinlvLSB = pinlvHX&0xff;
Eeprom_Write(0x01,pinlvMSB);
Delayus(1000);
Eeprom_Write(0x02,pinlvLSB);
Delayus(1000);
tempMSB = tempHX>>8&0xff;
tempLSB = tempHX&0xff;
Eeprom_Write(0x03,tempMSB);
Delayus(1000);
Eeprom_Write(0x04,tempLSB);
Delayus(1000);
Eeprom_Write(0x05,dianyaHX);
}
void ReadEeprom()
{
if(Eeprom_Read(0x06)!=0x55)
{
Delayus(1000);
Eeprom_Write(0x06,0x55);
}
else
{
pinlvMSB = Eeprom_Read(0x01);
Delayus(1000);
pinlvLSB = Eeprom_Read(0x02);
Delayus(1000);
tempMSB = Eeprom_Read(0x03);
Delayus(1000);
tempLSB = Eeprom_Read(0x04);
Delayus(1000);
dianyaHX = Eeprom_Read(0x05);
Delayus(1000);
tempHX = (tempMSB<<8)|tempLSB;
pinlvHX = (pinlvMSB<<8)|pinlvLSB;
}
}
void ReadEeprom()//最好不要连续读或者连续写,读写动作之间最好加入间隔不然容易出错
{
if(Eeprom_Read(0x06)!=0x55)//对一个不用的存储块读数据来判断是不是第一次上电
{
Delayus(1000);
Eeprom_Write(0x06,0x55);//如果是第一次就写一个我们特定的数然后不做动作,防止变量被原来村粗的数据影响
}
else
{
pinlvMSB = Eeprom_Read(0x01);//不过不是就把上次的旧数据读出来
Delayus(1000);
pinlvLSB = Eeprom_Read(0x02);
Delayus(1000);
tempMSB = Eeprom_Read(0x03);
Delayus(1000);
tempLSB = Eeprom_Read(0x04);
Delayus(1000);
dianyaHX = Eeprom_Read(0x05);
Delayus(1000);
tempHX = (tempMSB<<8)|tempLSB;
pinlvHX = (pinlvMSB<<8)|pinlvLSB;
}
}
7.ds18b20(省赛高频)
基础不多解释,注意温度对时序有要求,尽量不要往里面加别的东西
void ReadTemp()
{
uchar MSB,LSB;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
LSB = Read_DS18B20();
MSB = Read_DS18B20();
temp = ((MSB<<8)|LSB)*6.25;
}
8.ds1302(省赛高频)
基础不多解释
uchar Ds1302_Write[7] = {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};
uchar Ds1302_Read[7] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
void Ds1302_config()
{
uchar i;
Write_Ds1302_Byte(0x8e,0x00);
for(i=0;i<3;i++)
{
Write_Ds1302_Byte(Ds1302_Write[i],Timer[i]);
}
Write_Ds1302_Byte(0x8e,0x80);
}
void Read_time()
{
uchar i;
for(i=0;i<3;i++)
{
Timer[i] = Read_Ds1302_Byte (Ds1302_Read[i]);
}
}
9.ne555频率输入(省赛国赛高频)
注意事项,使用不要频繁开关定时器,会对频率结果有很大影响
void Timer_init()
{
TMOD=0x16;
TH0=0xff;
TL0=0xff;
TH1=(65535-50000)/256;
TL1=(65535-50000)%256;
TR1=1;
TR0=1;
ET1=1;
ET0=1;
EA=1;
}
void Timer0_serves() interrupt 1
{
f++;
}
void Timer1_serves() interrupt 3
{
TL1=(65535-48000)%256;
TH1=(65535-48000)/256;
t_50ms++;
if(t_50ms>=20)
{
f2=f;
f=0;
t_50ms=0;
}
10.超声波PCA写法(国赛高频)
超声波只推荐PCA写法,节省定时器,也不容易冲突(需要头文件<STC15F2K60S2.H>)reg52没有这个外设的地址,芯片型号也要是STC15的
unsigned int Get_Csb()
{
unsigned int dis;
CMOD&=0x00; //定义工作模式
CH=0; //计数高八位
CL=0; //计数低八位
Send_Wave(); //发送40Khz波
CR=1; //开始计时
while((RX==1)&&(CF==0));//等待接收端接收到返回信号或者溢出
CR=0; //关闭计时
if(CF==0) //如果未超出测量范围,进行数据处理
{
dis=CH;
dis=(dis<<8)|CL;
dis=((dis/10)*17)/100+3;
}
else //如果超出测量范围,溢出标志位会硬件置一,我们需要在此软件清零
{
CF=0; //溢出标志位清零
dis=999;//也可以使用标志位
}
return dis;
}
void Delay12us() //@12.000MHz
{
unsigned char i;
_nop_();
_nop_();
i = 33;
while (--i);
}
void Send_Wave() //产生一个40Khz方波
{
unsigned char i;
for(i=0;i<8;i++)
{
TX=1;
Delay12us();
TX=0;
Delay12us();
}
}
11.串口(国赛难点)
串口的难点不在于怎么定义使用串口,而在于接收一个特定的字符串并判断类型(注意是字符串),还有在于一个可变变量的发送,具体如何实现我在下面进阶考点会说。
void Uart1_Init(void) //4800bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器时钟12T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TL1 = 0xCC; //设置定时初始值
TH1 = 0xFF; //设置定时初始值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
ES=1;
}
void Uart1_Isr(void) interrupt 4
{
if(RI==1)
{
RI=0;
buf[num]=SBUF;
num++;if(countuart==666)countuart=0;
}
}
void SendBytr(uchar dat)//单字符发送
{
SBUF = dat;
while(TI==0);
TI=0;
}
void SendString(uchar *p)//字符串发送
{
while(*p!='\0')
SendBytr(*p++);
}
进阶考点
这些一般是比赛中的考点,难点,想要完全写完主观题学会下面这些固定应对套路是必须的。
1.对一些参数涉及到正负数的调节
这里其实也没有特别好的解决办法,博主是用了一个笨方法,把变量拆分成两个变量,一个正数变量负责对数据进行加,一个负数变量负责对数据减,当调整参数时当正数变量减到0,就定义一个flag,当flag==1时就对我们要调参的数据减去负数变量,缺点:判断加减时比较麻烦。如果有更好的方法麻烦请告诉我,这对我很重要!
//此代码在按键函数中
if((CountS4==1)&&(CountS5==1))//S8
{
if(tiaozheng!=0)
{
tiaozheng-=100;//正变量
}
else
{
fushu=1;
if(tiaozheng2!=900)tiaozheng2+=100;//边界值
}
if((tiaozheng==0)&&(tiaozheng2==0))
{
fiag0 = 1;//负数标志位
}
else
{
fiag0 = 0;
}
}
if((CountS4==1)&&(CountS5==1))//s9同理
{
if(fushu==1)
{
if(tiaozheng2!=0)tiaozheng2-=100;
if(tiaozheng2==0)
{
fushu=0;
}
}
else
{
if(tiaozheng!=900)tiaozheng+=100;
}
if((tiaozheng==0)&&(tiaozheng2==0))
{
fiag0 = 1;
}
else
{
fiag0 = 0;
}
}
2.数码管闪烁
可以理解为超长周期的对数码管的PWM,用定时器来界定数码管亮或者灭的时间,用一段较短的代码演示一下思路(正常数码管显示在上面,不赘述)
void ServiceT0()interrupt 1
{
TH0 = (65535-1000)/256;
TL0 = (65535-1000)%256;
//数码管显示部分省略
countSMG++;if(countSMG==1000)countSMG=0;
}
void Showdate()
{
if(countSMG>=500)
{
Seg_code[0]=1;//显示1
}
else Seg_code[0]=26;//不显示
}
3.长短按
定义一个标志位变量来判断按键是否按下,如果按下则在定时器里对计时按键开始自增,当松手后,标志位归零,通过判断计时变量是否自增到阈值来判断长按还是短按;
if(anxiaFlag==1)changan++;//定时器中断内部写的
//——————————————————————————————————————————分割
//按键函数写的
R4=0;
R1 = R2 = R3 = 1;
C1 = C2 = C3 = C4 = 1;
if(C2==0)//s12
{
countkey=0;while(countkey<=10);
if(C2==0)
{
while(C2==0)anxiaFlag=1;//按下为1,计时自增
anxiaFlag=0;
if(changan<1000)//判断 小于1000为短按
{
changan=0;
CountS12++;
if(CountS13==0)
{
if(CountS12==3)CountS12=0;
}
else if(CountS12==2)CountS12=0;
}
else//长按
{
changan=0;
cishu=0;
}
}
4.检测两个按键同时按下
思路:在一个按键按下的while里判断另一个案件是否按下--->若按下--->定时器开始计时--->到达阈值--->执行操作
if(C3==0)//S9
{
countkey=0;while(countkey<=10);
if(C3==0)
{
while(C3==0)//按下状态内
{
R4=0;
R1 = R2 = R3 = 1;
C1 = C2 = C3 =C4= 1;
if(C3==0)//判断另一个按键按下了吗
{
countchangan=0;
while(C3==0)if(countchangan>=2000)ISP_CONTR = 0x20;//判断计时变量是否达到阈值
}
R3=0;
R1 = R2 = R4 = 1;
C1 = C2 = C3 =C4= 1;//回到原来那一行
}
5.串口检测字符串输入,并判断类型
对串口命令判断时,我们需要将收到的数据存在一个数组中,并且需要一定的缓冲时间等待命令发完,所以在串口收到第一个命令的第一个字节时,定时器计时30ms后在进行命令判断
我在这个代码中省下了一个变量,本来用来表示串口状态的,对于这个代码countuart=666时串口闲置,等于0就会开始自增增到30就读数组buf
void Uart1_Isr(void) interrupt 4
{
if(RI==1)
{
RI=0;
buf[num]=SBUF;
num++;if(countuart==666)countuart=0;//在这里省下了一个变量,本来用来表示串口状态的,对于这个代码countuart=666时串口闲置,等于0就会开始自增增到30就读数组buf
}
}
void CtrlUart()
{
if(countuart==30)//当串口开始接收后的30毫秒
{
countuart=666;//串口闲置标志
if(num>0)
{
memset(Sendbuf,0,sizeof(Sendbuf));//清零输出的数组
if(buf[0]=='S'&&buf[1]=='T'&&buf[2]=='\r'&&buf[3]=='\n'&&num>=4)//判断命令类型
{
sprintf(Sendbuf,"$%d,%.2f\r\n",(int)dis,temp_ture);//理解为给输出数组赋值,用占位符和强制类型转换来表示变量,需要用头文件#include "stdio.h"#include "string.h"
SendString(Sendbuf);//发送数组
}
else if(buf[0]=='P'&&buf[1]=='A'&&buf[2]=='R'&&buf[3]=='A'&&buf[4]=='\r'&&buf[5]=='\n'&&num>=6)
{
sprintf(Sendbuf,"#%d,%d\r\n",(int)disCS,(int)tempCS_ture);
SendString(Sendbuf);//同理
}
else
{
SendString("ERROR\r\n");//如果不是上面两种命令就打印错误
}
memset(buf,0,sizeof(buf));//清空串口接收数组
num=0;
}
}
}
6.串口输出可变变量
完整的在上面,单独截取出来方便查看
理解为给输出数组赋值,用占位符和强制类型转换来表示变量,需要用头文件
#include "stdio.h"
#include "string.h"
sprintf(Sendbuf,"$%d,%.2f\r\n",(int)dis,temp_ture);//理解为给输出数组赋值,用占位符和强制类型转换来表示变量,需要用头文件#include "stdio.h"#include "string.h"
SendString(Sendbuf);//发送数组
7.DA根据别的变量线性输出
这个最简单,也最经常考,多写几次就会了,数学问题没什么特别的思路
void CtrlDA()
{
uchar dianya;
if(distance<=10)dianya=10;//DA下限
if(distance>=80)dianya=50;//DA上限
if(distance>10&&distance<80)dianya=((distance-10)*0.571)+10;//中间线性区,算出倍率,减去下限×倍率再加下限
DA_transport(dianya*5.1);//输出
}
8.界面转换,子界面
必须会,界面转换思路就是根据变量的改变用switch嵌套来实现,用一串短代码例举
void Display()
{
switch(CountS4)//主界面
{
case 0://主界面1
switch(CountS5)
{
case 0:ShowTime();break;//子界面1
case 1:ShowDis();break;//子界面2
case 2:
switch(CountS8)
{
case 0:ShowDismax();break;//子界面的子界面1
case 1:ShowDispinjun();break;//子界面的子界面2
case 2:ShowDismin();break;//子界面的子界面3
}
break;
}
break;
case 1://主界面2
switch(CountS5)
{
case 0:ShowtimeCS();break;//子界面1
case 1:ShowjuliCS();break;//子界面2
}
break;
}
}
9.把一个16位整型拆成两个字符型并存进eeprom
由于eeprom数据块只能存储最多10位的数据,当我们需要存整型数据时就需要拆分成高八位和低八位 分别写入,下面是步骤
pinlvMSB = pinlvHX>>8&0xff;//高八位拆分
pinlvLSB = pinlvHX&0xff;//低八位拆分
Eeprom_Write(0x01,pinlvMSB);//分别写入不同的数据块
Delayus(1000);
Eeprom_Write(0x02,pinlvLSB);
Delayus(1000);
10.把两个字符型组合成整型
有的时候我们需要把数据的高低八位组合成一个完整的数据,步骤如下:
tempHX = (tempMSB<<8)|tempLSB;
pinlvHX = (pinlvMSB<<8)|pinlvLSB;
11.最大值,最小值,平均值
对一个变化的数据需要的处理,求出最大最小平均值处理
if(distance>dismax)dismax=distance;//最大值
if(distance<dismin)dismin=distance;//最小值
dispinjun =( distance * 10 + dispinjun)/cishu; //平均值
后语
目前就想到这些,有更好的想法或者考点欢迎在评论区和作者讨论,我评论私信有看到就会马上回复,欢迎大家来交流思路