温馨提示:这篇文章已超过409天没有更新,请注意相关的内容是否还可用!
蓝桥杯第十四届电子类单片机组决赛的程序设计环节是一场高水平的编程竞赛。参赛选手需要在规定时间内完成复杂的编程任务,展示他们的算法设计和实现能力。这场比赛是电子类单片机领域的盛会,吸引了众多优秀的选手参加。通过这场比赛,参赛选手可以锻炼自己的编程技能,为未来的科技发展和创新做出贡献。
目录
前言
单片机资源数据包_2023(点击下载)
一、第十四届比赛题目
1.比赛题目
2.题目解读
1)任务要求
2)注意事项
二、显示功能实现
1.关于高位为0时数码管熄灭功能的实现
2.关于显示小数位的处理
3.关于“校准值”的正负数据的处理
三、温度传感器小数部分的处理
四、两个按键长按2s功能的实现
五、LED灯功能的实现
1.LED灯显示距离功能的实现
2.其他LED灯功能
六、代码实现
main.c
onewire.h
iic.c
iic.h
前言
关于决赛的题,这也是我头一次自己去做,真心感觉好难啊,而且有许多“套路”都不能用了,这里来剖析一下我写的第十四届决赛代码,也是对前边提到的许多代码,关于“套路”不能用时,该如何去处理。
此外,决赛的题目官网上没有,也没链接可放了,我直接截图把题目放出来,决赛和省赛的资源数据包好像是一样的,第十四届比赛也就是在2023年,今年是第十五届比赛。
单片机资源数据包_2023(点击下载)
一、第十四届比赛题目
1.比赛题目
2.题目解读
1)任务要求
- 数码管显示菜单,分别为测距界面,参数界面和工厂模式界面,其中参数界面有两个子菜单,分别为距离参数和温度参数,工厂模式界面有三个子菜单,分别距离校准、超声波传播速度和DAC输出下限设置
- 测距界面下,前三位显示温度,保留小数点后一位,第四位显示“-”,后四位显示距离(距离的单位可以切换)
- 距离参数界面,数码管前两位显示“P1”,最后两位显示距离参数(单位:CM)
- 温度参数界面,数码管前两位显示“P2”,最后两位显示温度参数
- 距离校准界面,数码管前两位显示“F1”,最后三位显示校准值,校准值有正负号
- 超声波速度设置界面,数码管前两位显示“F2”,最后四位显示超声波速度
- DAC输出下限界面,数码管前两位显示“F3”,后两位显示DAC下限,精确到小数点后一位
- 按键S4定义为菜单切换,可以在测距界面、参数设置界面、工厂模式之间切换(在各个菜单的子菜单下也可切换,默认切换到下一个界面的第一个子菜单)
- 按键S5定义为子菜单切换,在测距界面下,按下S5,在切换超声波数据的单位,在cm和m之间切换。在参数设置界面,或者工厂界面下,按下S5可以在对应的子菜单内切换
- 按键S8和S9没啥介绍的,除了两个特殊功能之外,其他都是简单的加加减减。直接上图
- DAC输出,根据“记录的距离”以及距离的范围和DAC下限输出对应的电压
- 测距功能与一般的超声波一致,距离=超声波速度*来回的时间/2+超声波距离校准值,其中超声波速度和距离校准值都是可以手动设置的
- LED灯:在距离界面下,LED显示当前距离;在参数界面下,L8点亮;在工厂模式下,L1以0.1s闪烁
- 继电器:当距离参数-50)Wei_shu=1;
if(Wei_shu==4)//四位数据,四个数码管都显示数据
{
Nixie_num[0]=value/1000%10;
Nixie_num[1]=value/100%10;
Nixie_num[2]=value/10%10;
Nixie_num[3]=value/1%10;
}
else if(Wei_shu==3)//三位数据,第一个数码管熄灭,后三个显示数据
{
Nixie_num[0]=10;//假设Nixie_num=10时对应该位熄灭,下同
Nixie_num[1]=value/100%10;
Nixie_num[2]=value/10%10;
Nixie_num[3]=value/1%10;
}
elseif(Wei_shu==2)//两位数据,前两个数码管熄灭,后两个显示数据
{
Nixie_num[0]=10;
Nixie_num[1]=10;
Nixie_num[2]=value/10%10;
Nixie_num[3]=value/1%10;
}
else if(Wei_shu==1)//一位数据,前三个数码管熄灭,最后一个显示数据
{
Nixie_num[0]=10;
Nixie_num[1]=10;
Nixie_num[2]=10;
Nixie_num[3]=value/1%10;
}
这种方法当然可行,但是太麻烦了(反正我刚接触单片机编程时,遇到这个问题就是这样想的,也不知道和大家想到一样不一样)。现在在反过来看问题,我们完全可以边判断数据的位数,边显示数据。如果value/1000>0,说明这个数据是一个四位(或者以上)数据,则该位显示Value/1000%10(千位),否则熄灭,其他数码管同理,个位的数码管如果也都没有数据的话,则直接显示0即可,不然整个数据位就全部熄灭了。这里用到了三目运算符,是编程的基础,就不过多介绍了
Nixie_num[4]=value/1000>0 ? value/1000%10:20;
Nixie_num[5]=value/100>0 ? value/100%10:20;
Nixie_num[6]=value/10>0 ? value/10%10:20;
//Nixie_num[7]=value/1>0 ? value/1%10:0;//数据连一位数都没有,则显示0而非全部熄灭
Nixie_num[7]=value/1%10;//数据连一位数都没有,则显示0而非全部熄灭
至此,我们就实现了“数据不足四位,高位熄灭”的功能,对应题目的话,大概在这些地方提到过
2.关于显示小数位的处理
之前我们显示数码管,都是通过断码表Seg_Table来完成Nixie_num数组内的数据到数码管显示的数据之间的映射的。比如基本的Nixie_num[0]=0就代表第0位显示0(如果不修改Seg_Table数组内的值的话)。我们也知道,0和0.的段码绝对是不一样的,虽然只相差一点,我们不妨把Seg_Table数组内,0到9为段码对应数组0到9,10到19为段码对应0.到9.(注意有小数点欧)。具体0.到9.的段码如何计算,这里就不在介绍了,完善后的Seg_Table为
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. 1. 2. 3. 4. 5. 6. 7. 8. 9.
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
}
这样,我们写Nixie_num[0]=0,表示数码管第0位显示0,Nixie_num[0]=0+10就表示数码管第0位显示0.了。这也算一个小窍门吧,以后真的考这个赚钱时,肯定还是优先考虑把函数封装好。
需要显示小数点的地方题目上还是比较多的,比如
3.关于“校准值”的正负数据的处理
没错,这个正负数据仅针对题目要求显示的校准值
这个校准值不但要显示正负,还要完成第二章第一节提到的高位为0时熄灭的功能,正数的处理跟前边提到的一样,这里主要介绍负数的处理。第二章第一节实现了在判断数据长度的同时显示数据,而这里还需要根据数据的长度,判断正负号显示的位置,我是没更好的办法了,只能使用第二章第一节提到的那个“笨方法”了。不过还好,因为校准值取值是-90到90,每次增减也是5,所以校准值只有可能是两位数或者一位数,判断起来也好判断
if(remote_jiaozhun>=0)//正
{
Nixie_num[5]=20;//熄灭
Nixie_num[6]=jiaozhun/10>0 ? jiaozhun/10%10:20;
Nixie_num[7]=jiaozhun/1>0 ? jiaozhun/1%10:0;
}
else//负
{
if(jiaozhun/10>0)
{
Nixie_num[5]=21;//显示-
Nixie_num[6]=jiaozhun/10%10;//距离参数
Nixie_num[7]=jiaozhun/1%10;
}
else
{
Nixie_num[5]=20;
Nixie_num[6]=21;//显示-
Nixie_num[7]=jiaozhun/1%10;//距离参数
}
}
三、温度传感器小数部分的处理
之前咱们写的温度传感器读取是这样的
unsigned int read_18b20()
{
unsigned int T=0;//定义温度
unsigned char low=0;//用于接受温度的低八位
unsigned char high=0;//用于接受温度的高八位
init_ds18b20();//初始化DS18B20
Write_DS18B20(0xCC);//跳过ROM检测
Write_DS18B20(0x44);//发送开始温度转换的命令
Delay_OneWire(200);//温度转化需要时间,这里直接延时一下。。注意应避免连续读取DS18B20
init_ds18b20();//重新初始化DS18B20
Write_DS18B20(0xCC);//跳过ROM检查
Write_DS18B20(0xBE);//发送读取温度数据的指令
low=Read_DS18B20();//接收低八位
high=Read_DS18B20();//接收高八位
T=high;
T&=0x0F;//第八位的高四位置0,也就是不考虑符号位
T=4;//舍去低八位的低四位,也就是不考虑小数位
return T;
}
这里是实打实的直接舍去了符号位和小数位,因为符号位和小数位一般用不上,但是偏偏在国赛出现了温度传感器需要读取到小数点后一位,其实也简单。
我们知道从温度传感器读取到的温度数据是16位的温度数据,其中高八位的高四位是符号位,低八位的低四位是小数位,我们之前都是只取中间八位,也就是高八位的低四位和低八位的高四位,也就是只有温度的整数部分,现在我们只需要加上小数部分即可。
但是直接加小数的话,温度值可就得变成float型的数据了,这显然不是我们想要看到的,我们不妨把温度数据扩大十倍,也就是整数部分*10加小数部分,这样我们就还可以使用unsigned int来记录温度数据了。修改之后的代码
unsigned int read_temp(void)
{
unsigned int temp=0;
unsigned char low=0;
unsigned char high=0;
unsigned char xiaoshu=0;
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0x44);
Delay_OneWire(200);
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE);
low=Read_DS18B20();
high=Read_DS18B20();
temp=high;
temp&=0x0F;
temp=4;
/*获取小数部分*/
xiaoshu=low;
xiaoshu&=0x0F;
temp=temp*10+xiaoshu;//温度扩大了十倍,把小数点后一位也加上了
return temp;
}
四、两个按键长按2s功能的实现
在第十四届省赛已经实现了按键的长按,这里就不再赘述,我们这里要解决的是这个:
也就是同时按下两个按键,并长按两秒。
其实,用理性的角度来解释的话,是不存在“同时按下两个按键”的过程的,只可能是“按下一个按键后,按下第二个按键”,因此,我们只需要在按下S8或S9时,判断S9或S8是否被按下,两种情况分别对应按下S8后按下S9和按下S9后按下S8,当检查到两个按键都被按下之后,我们再开始数数,把它当按下一个按键的长按处理。
需要注意的,应避免按下一个S8之后按下S9,此时松开S8,保持S9按下,这种情况不能算作S8和S9同时按下。我们的短按都是在松开按键之后才生效的,而题目要求按下S8和S9达到2s就触发复位,也就是说不需要再松开S9或S9(好事,不然又得多一堆判断了),因此,如果S8和S9瞎按的话,就比如这一段话最开始提到的情况,那确实会出现一些不太好的情况,这涉及到底层逻辑的问题,而且题目也没要求,所以就暂时不管了。
至于按下按键2s,我们还是使用定时器数数,定义一个标志位is_2s_changan,如果is_2s_changan为0时,2s后会被置为1,通过判断将is_2s_changan置0到松开按键之前is_2s_changan是否被置为1,就可以判断是否长按够2s了。
对于两个按键的处理类似,这里只介绍其中一个:
if(P32==0)
{
Delay5ms();
while(P32==0)//按下s9
{
run();
/*以下为同时长按s8和s9*/
is_2s_changan=0;//在按下s9,但没按下s8之前,已经将2s数数置为0,确保按下s8时is_2s_changan=0;
while(P33==0)//按下s8(此时处于同时按下S9和S8的状态太)
{
run();
if(is_2s_changan==1)//如果这个状态持续了2s,一直等到is_2s_changan=1了,说明长按了2s
{
restart=1;//启动重置功能(见下方的if(restart==1))
break;//并跳出等待(题目的意思貌似是,按够2s直接重置,不管松没松按键,所以要break。
//如果要等松开按键才重置的话,可以把这个和下边那个break及其if判断注释掉
}
if(!(P32==0))
break;
}
if(restart==1)
break;
}
Delay5ms();
key_value=9;
}
五、LED灯功能的实现
之前写的代码都是LED_ON(X),通过一个宏函数,快速点亮一个LED灯,但是现在,至少对于这个国赛题是一点也不行了,我们只能单独写。要说也简单,之前的宏函数都是根据传入的参数x来改变Led_Num的值,进而改变Led灯的状态,如下(代码有点长,其实是一行,可能显示不下就被换行了):
#define LED_ON(x) Led_Num&=~(0x010 ? remote/1%10:0;//数据连一位数都没有,则显示0而非全部熄灭 Nixie_num[7]=remote/1%10;//数据连一位数都没有,则显示0而非全部熄灭 } else if(remote_danwei==1)//如果单位是m { Nixie_num[4]=remote/1000>0 ? remote/1000%10:20; Nixie_num[5]=remote/100>0 ? remote/100%10+10:10;//精确到小数点后两位,只需要在倒数第二位前加个小数点即可 Nixie_num[6]=remote/10>0 ? remote/10%10:0; Nixie_num[7]=remote/1>0 ? remote/1%10:0; } } else if(mod==20)//参数界面1距离参数 { Nixie_num[0]=22;//P Nixie_num[1]=1; Nixie_num[2]=20; Nixie_num[3]=20; Nixie_num[4]=20; Nixie_num[5]=20; Nixie_num[6]=remote_canshu/10>0 ? remote_canshu/10%10:0;//显示距离参数 Nixie_num[7]=remote_canshu/1>0 ? remote_canshu/1%10:0; } else if(mod==21)//参数界面2温度参数 { Nixie_num[0]=22;//P Nixie_num[1]=2; Nixie_num[2]=20; Nixie_num[3]=20; Nixie_num[4]=20; Nixie_num[5]=20; Nixie_num[6]=wendu_canshu/10>0 ? wendu_canshu/10%10:0;//显示温度参数 Nixie_num[7]=wendu_canshu/1>0 ? wendu_canshu/1%10:0; } else if(mod==30)//工厂1校准值 { Nixie_num[0]=23;//F Nixie_num[1]=1; Nixie_num[2]=20; Nixie_num[3]=20; Nixie_num[4]=20; //正负号转化,remote_jiaozhun是实际值,带正负。jiaozhun是中间变量,不带符号,值与remote_jiaozhun的绝对值相同 //用abs()也行 jiaozhun=remote_jiaozhun>0?remote_jiaozhun:-remote_jiaozhun; //PS,距离参数取值10到90,故数码管5熄灭(正数)或显示-(负数)后两位显示数据 if(remote_jiaozhun>=0)//正 { Nixie_num[5]=20;//熄灭 Nixie_num[6]=jiaozhun/10>0 ? jiaozhun/10%10:20; Nixie_num[7]=jiaozhun/1>0 ? jiaozhun/1%10:0; } else//负 { if(jiaozhun/10>0) { Nixie_num[5]=21;//显示- Nixie_num[6]=jiaozhun/10%10;//距离参数 Nixie_num[7]=jiaozhun/1%10; } else { Nixie_num[5]=20; Nixie_num[6]=21;//显示- Nixie_num[7]=jiaozhun/1%10;//距离参数 } } } else if(mod==31)//工厂2速度 { Nixie_num[0]=23;//F Nixie_num[1]=2; Nixie_num[2]=20; Nixie_num[3]=20; Nixie_num[4]=speed/1000>0 ? speed/1000%10:20;//显示速度 Nixie_num[5]=speed/100>0 ? speed/100%10:20; Nixie_num[6]=speed/10>0 ? speed/10%10:20; Nixie_num[7]=speed/1>0 ? speed/1%10:20; } else if(mod==32)//工厂3DAC输出下限 { Nixie_num[0]=23;//F Nixie_num[1]=3; Nixie_num[2]=20; Nixie_num[3]=20; Nixie_num[4]=20; Nixie_num[5]=20; Nixie_num[6]=dac_xiaxian/10%10+10;//DAC下限 Nixie_num[7]=dac_xiaxian/1%10; } } void led_run(void) { if(mod==10&&is_100ms==1)//距离显示界面下,led灯显示距离(注意取反) { is_100ms=0;//这个是100ms是额外的处理,减慢led处理的速度, Led_Num=~remote;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F; } else if(mod==20||mod==21)//在参数界面下,L8点亮,同时其它灯熄灭 { if(Led_Num!=~(0x80))//如果在参数界面下,未处在“L8点亮,同时其它灯熄灭”的状态,则使“L8点亮,同时其它灯熄灭” { Led_Num=~0x80;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F; } } else if((mod==30||mod==31||mod==32)&&is_100ms==1)//在工厂模式下L1以100ms闪烁 { is_100ms=0;//is_100ms为0时,100ms后会被置为1,每次进入这个else if,则翻转一次L1 if(Led_Num==~(0x01))//如果点亮了,则熄灭 { Led_Num=~0x00;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F; } else if(Led_Num!=~(0x01))//如果熄灭了,则点亮 { Led_Num=~0x01;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F; } } } bit relay_is_on=0;//继电器状态,1打开,0关闭 void relay_run() { //如果距离参数-5
还没有评论,来说两句吧...