当前位置:   article > 正文

蓝桥杯第十四届电子类单片机组决赛程序设计_第十四届蓝桥杯单片机国赛

第十四届蓝桥杯单片机国赛

目录

前言

单片机资源数据包_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闪烁
  • 继电器:当距离参数-5<=测距结果<=距离参数+5,并且采集到的温度<=温度参数,继电器闭合,否则断开。

2)注意事项

  • 上电初始状态
  • 性能要求
  • 数据参数调整范围
  • 在菜单界面中,大部分都要求比如四位数码管显示一个数据,如果这个数据不够四位,则高位的数码管熄灭
  • 在S8的功能6,6s内记录距离的过程中,所有按键失效(包括同时按下S8和S9)
  • S8和S9长按超过2s则复位,其触发时机是“长按了2s”而非“长按2s之后松开按键”

二、显示功能实现

1.关于高位为0时数码管熄灭功能的实现

这个的意思就是,如果使用四位数码管显示一个数码,但是待显示的数据不足四位,比如只有三位,这个数是340,则只用三个数码管显示数据,四个数码管显示的结果应该是“熄灭”“3”“4”“0”,而非“0”“3”“4”“0”。之前写数码管时,都是直接让第一个数码管显示数据的千位,第二个显示百位,第三个显示十位,第四个显示个位。比如像下边这样

Nixie_num[0]=value/1000%10;
Nixie_num[1]=value/100%10;
Nixie_num[2]=value/10%10;
Nixie_num[3]=value/1%10;

然后再在定时器里在数码管对应的位置显示Nixie_num数组内的数据(如果是按照我之前写的代码的话)。

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
};

unsigned char location=0;

void Timer0_Isr(void) interrupt 1
{
    P0=0x01<<location;NIXIE_CHECK();
    P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
    
    if(++location>=8)
        location=0;

}

但是如果改为是0的话,直接这样处理,高位就不是熄灭,而是显示0了。显然不符合要求。其实到这里,大家至少应该能想到最最笨的处理方法了——判断数据的位数,在依次显示需要显示的位数,或者熄灭不需要显示的位。也就是这样:

unsigned char Wei_shu=0;
if(value/1000>0)Wei_shu=4;
else if(value/100>0)Wei_shu=3;
else if(value/10>0)Wei_shu=2;
else if(value/1>0)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<<=8;
    T|=low;
    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<<=8;
    temp|=low;
    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&=~(0x01<<x);P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;

现在,只穿一个参数点亮一个LED灯已经不能实现题目要求了:

我们干脆直接手动修改Led_Num的值,然后给P0赋值,最后开关一次锁存器。

1.LED灯显示距离功能的实现

LED指示灯的第一个功能,就是在测距界面(mod==10)下,显示距离值,这里我们加一个数数,定时器数100ms,每100ms处理一次LED灯(因为超声波更新的也不会那么快,而且后边也有100ms闪烁的功能),切记不要一直重复地给P0赋值,开关锁存器,LED灯容易误闪烁。

下面是代码演示

if(mod==10&&is_100ms==1)//距离显示界面下,led灯显示距离(注意取反)
{
    is_100ms=0;//这个是100ms是额外的处理,减慢led处理的速度,
    Led_Num=~remote;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
}

2.其他LED灯功能

其他LED灯功能就中规中矩了,都是之前提到了,不过既然这里已经舍弃使用LED_ON(x)的宏函数了,就干脆都直接修改Led_Num的值,来控制LED灯

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;
    }
}

六、代码实现

先说一句,过年回家的着急,万用表没带,关于DAC输出的我都没办法测试,包括前几篇提到的,不过应该也没什么大问题。本次写的代码中涉及到有许多简单重复的if判断,写的时候都改成三目运算符了。

main.c

  1. #include <stc15.h>
  2. #include <intrins.h>
  3. #include "onewire.h"
  4. #include "iic.h"
  5. code unsigned char Seg_Table[] =
  6. {
  7. 0xc0, //0
  8. 0xf9, //1
  9. 0xa4, //2
  10. 0xb0, //3
  11. 0x99, //4
  12. 0x92, //5
  13. 0x82, //6
  14. 0xf8, //7
  15. 0x80, //8
  16. 0x90, //9
  17. //0. 1. 2. 3. 4. 5. 6. 7. 8. 9.
  18. 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
  19. 0xFF,//熄灭
  20. 0xBF,//- 21
  21. 0x8C,//P 22
  22. 0x8E,//F 23
  23. };
  24. unsigned char Led_Num=0xFF;
  25. /*在这次国赛题目中,关于LED的宏用着不太方便*/
  26. #define LED_ON(x) Led_Num&=~(0x01<<x);P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
  27. #define LED_OFF(x) Led_Num|=0x01<<x; P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
  28. #define LED_OFF_ALL() Led_Num=0xFF; P0=0xFF;P2|=0x80;P2&=0x9F;P2&=0x1F;
  29. #define NIXIE_CHECK() P2|=0xC0;P2&=0xDF;P2&=0x1F;
  30. #define NIXIE_ON() P2|=0xE0;P2&=0xFF;P2&=0x1F;
  31. unsigned char ULN=0x00;
  32. #define RELAY_ON() ULN|=0x10; P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
  33. #define RELAY_OFF() ULN&=0xEF; P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
  34. #define BUZZER_ON() ULN|=0x40; P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
  35. #define BUZZER_OFF() ULN&=0xBF; P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
  36. sbit TX=P1^0;//定义超声波的TX
  37. sbit RX=P1^1;//定义超声波的RX
  38. void Timer0_Init(void); //1毫秒@12.000MHz
  39. void Timer1_Init(void); //@12.000MHz
  40. void Delay100ms(void); //@12.000MHz
  41. void get_key(void);//按键读取与处理
  42. void read_ul(void);//读取超声波测距(其他子函数均在read_ul前定义了,就不在这里声明了)
  43. void show_menu(void);//显示菜单
  44. void run(void);//主运行函数
  45. void led_run(void);//led运行函数
  46. void relay_run(void);//继电器运行函数
  47. unsigned char location=0;//当前扫描到的数码管的位置,中间变量
  48. unsigned char Nixie_num[]={20,20,20,20,20,20,20,20};//数码管待显示的数据
  49. unsigned char key_value=0;//读取到的键值,中间变量
  50. unsigned int temp=0;//温度,这里为了方便处理温度的小数部分,已经将温度扩大了10倍
  51. unsigned int remote=0;//距离
  52. unsigned char mod=10;//菜单模式,取值10,20,21,30,31,32,分分别对应三个菜单的各个子菜单
  53. unsigned int speed=340;//超声波传播速度
  54. unsigned char remote_canshu=40;//距离参数
  55. unsigned char wendu_canshu=30;//温度参数
  56. signed int remote_jiaozhun=0;//距离校准(有符号)
  57. unsigned char dac_xiaxian=10;//为便于除了。扩大了10倍,取值1到20,对应0.1v到20v
  58. unsigned char jilu_remote=0;//记录距离(用于DAC输出)
  59. bit is_read_ul=1;//读取超声波的标志位
  60. bit remote_danwei=0;//0:cm,1:0
  61. bit is_jilu=0;//是否处在记录距离的标志位(就是6s那个状态)
  62. bit is_2s_changan=0;//记录是否完成长按2s按键的标志位
  63. bit restart=0;//重置标志位
  64. bit is_100ms=0;
  65. void main()
  66. {
  67. BUZZER_OFF();//关闭蜂鸣器
  68. RELAY_OFF();//关闭继电器
  69. read_temp();//初始化温度传感器
  70. Timer0_Init();//定时器0初始化
  71. Timer1_Init();//定时器1初始化(超声波)
  72. EA=1;
  73. Delay100ms();//如果想上电第一次就能读取到正确的温度,可以多加六七个Delay100ms()
  74. //mod=0;
  75. while(1)
  76. {
  77. get_key();
  78. run();
  79. //Delay100ms();
  80. }
  81. }
  82. void run(void)
  83. {
  84. temp=read_temp();//读取温度
  85. led_run();//控制led灯
  86. relay_run();//控制继电器
  87. if(is_read_ul==1)//每500ms读取一次超声波(见定时器0)
  88. {
  89. read_ul();
  90. is_read_ul=0;
  91. }
  92. if(remote_danwei!=0&&mod!=10)//确保每一次从其它菜单进入菜单10时,距离单位都是10:cm,也即remote_danwei=0
  93. remote_danwei=0;
  94. show_menu();
  95. }
  96. unsigned int count_500ms=0;
  97. unsigned int count_6s=0;
  98. unsigned int count_2s=0;
  99. unsigned int count_100ms=0;
  100. void Timer0_Isr(void) interrupt 1
  101. {
  102. P0=0x01<<location;NIXIE_CHECK();
  103. P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
  104. if(++location>=8)
  105. location=0;
  106. //is_read_ul为0时,500ms后被置为1,用于每500ms读取一次超声波
  107. if(is_read_ul==0)
  108. {
  109. if(++count_500ms>500)
  110. {
  111. is_read_ul=1;
  112. count_500ms=0;
  113. }
  114. }
  115. //is_jilu为1时,6s后被置为0(用于s8的第6个功能)
  116. if(is_jilu==1)
  117. {
  118. if(++count_6s==6000)
  119. {
  120. is_jilu=0;
  121. count_6s=0;
  122. }
  123. }
  124. //is_2s_changan为0时,2s后被置为1,根据将is_2s_changan置为0之后
  125. //检查is_2s_changan是否被置为1可以检测,从将is_2s_changan置为0到现在
  126. //是否过了2s(用于检查长按2s)
  127. if(is_2s_changan==0)
  128. {
  129. if(++count_2s>2000)
  130. {
  131. is_2s_changan=1;
  132. count_2s=0;
  133. }
  134. }
  135. if(is_100ms==0)//100ms,用于led闪烁
  136. {
  137. if(++count_100ms>100)
  138. {
  139. count_100ms=0;
  140. is_100ms=1;
  141. }
  142. }
  143. }
  144. void Timer0_Init(void) //1毫秒@12.000MHz
  145. {
  146. AUXR |= 0x80; //定时器时钟1T模式
  147. TMOD &= 0xF0; //设置定时器模式
  148. TL0 = 0x20; //设置定时初始值
  149. TH0 = 0xD1; //设置定时初始值
  150. TF0 = 0; //清除TF0标志
  151. TR0 = 1; //定时器0开始计时
  152. ET0 = 1; //使能定时器0中断
  153. }
  154. void Timer1_Init(void) //@12.000MHz
  155. {
  156. AUXR |= 0x40; //定时器时钟1T模式
  157. TMOD &= 0x0F; //设置定时器模式
  158. TL1 = 0x00; //设置定时初始值
  159. TH1 = 0x00; //设置定时初始值
  160. TF1 = 0; //清除TF1标志
  161. //TR1 = 1; //定时器1开始计时
  162. //ET1 = 1; //使能定时器1中断
  163. }
  164. void Delay100ms(void) //@12.000MHz
  165. {
  166. unsigned char data i, j, k;
  167. _nop_();
  168. _nop_();
  169. i = 5;
  170. j = 144;
  171. k = 71;
  172. do
  173. {
  174. do
  175. {
  176. while (--k);
  177. } while (--j);
  178. } while (--i);
  179. }
  180. void Delay5ms(void) //@12.000MHz
  181. {
  182. unsigned char data i, j;
  183. i = 59;
  184. j = 90;
  185. do
  186. {
  187. while (--j);
  188. } while (--i);
  189. }
  190. void get_key()
  191. {
  192. unsigned char key_P3=P3;
  193. unsigned char key_P4=P4;
  194. float V=0;//中间变量,记录需要输出的电压值
  195. //当处在6s距离记录的状态下
  196. if(is_jilu==1)//6秒的距离记录具有最高的优先级,在记录过程中,所有按键功能失效
  197. {
  198. jilu_remote=remote;//实时记录距离信息
  199. //restart=0;
  200. key_value=0;
  201. return;//直接返回,不再往下进行
  202. }
  203. P44=0;
  204. if(P32==0){Delay5ms();while(P32==0){run();}Delay5ms();key_value=5;}
  205. else if(P33==0){Delay5ms();while(P33==0){run();}Delay5ms();key_value=4;}
  206. P42=0;
  207. if(P32==0)
  208. {
  209. Delay5ms();
  210. while(P32==0)//按下s9
  211. {
  212. run();
  213. /*以下为同时长按s8和s9*/
  214. is_2s_changan=0;//在按下s9,但没按下s8之前,已经将2s数数置为0,确保按下s8时is_2s_changan=0;
  215. while(P33==0)//按下s8(此时处于同时按下S9和S8的状态太)
  216. {
  217. run();
  218. if(is_2s_changan==1)//如果这个状态持续了2s,一直等到is_2s_changan=1了,说明长按了2s
  219. {
  220. restart=1;//启动重置功能(见下方的if(restart==1))
  221. break;//并跳出等待(题目的意思貌似是,按够2s直接重置,不管松没松按键,所以要break。
  222. //如果要等松开按键才重置的话,可以把这个和下边那个break及其if判断注释掉
  223. }
  224. if(!(P32==0))
  225. break;
  226. }
  227. if(restart==1)
  228. break;
  229. }
  230. Delay5ms();
  231. key_value=9;
  232. }
  233. else if(P33==0)
  234. {
  235. Delay5ms();
  236. while(P33==0)
  237. {
  238. run();
  239. /*以下为同时长按s8和s9*/
  240. /*同上*/
  241. is_2s_changan=0;
  242. while(P32==0)
  243. {
  244. run();
  245. if(is_2s_changan==1)
  246. {
  247. restart=1;
  248. break;
  249. }
  250. if(!(P33==0))
  251. break;
  252. }
  253. if(restart==1)
  254. break;
  255. }
  256. Delay5ms();
  257. key_value=8;
  258. }
  259. //重置数据
  260. if(restart==1)
  261. {
  262. restart=0;
  263. mod=10;//重置菜单
  264. remote_canshu=40;//重置距离参数
  265. wendu_canshu=30;//重置温度参数
  266. remote_jiaozhun=0;//重置距离校准
  267. speed=340;//重置速度
  268. dac_xiaxian=10;//重置DAC下限
  269. remote_danwei=0;//重置距离单位为:cm
  270. key_value=0;//key_value=0,使得刚才长按2s的效果不会被当成短按处理
  271. }
  272. //s4模式切换
  273. if(key_value==4)
  274. {
  275. if(mod==10)//在测距界面
  276. {
  277. mod=20;//跳转到参数界面
  278. }
  279. else if(mod==20||mod==21)//在参数界面
  280. {
  281. mod=30;//跳转到工厂界面
  282. }
  283. else if(mod==30||mod==31||mod==32)//在工厂界面
  284. {
  285. mod=10;//跳转到测距界面
  286. }
  287. }
  288. //s5在同一个界面下不同子菜单之间跳转
  289. else if(key_value==5)
  290. {
  291. if(mod==10)//在测距界面,调整距离单位
  292. remote_danwei=~remote_danwei;
  293. else if(mod==20)//在参数界面的距离参数界面,跳转到温度参数界面,下类似
  294. mod=21;
  295. else if(mod==21)
  296. mod=20;
  297. else if(mod==30)
  298. mod=31;
  299. else if(mod==31)
  300. mod=32;
  301. else if(mod==32)
  302. mod=30;
  303. }
  304. //s8加
  305. else if(key_value==8)
  306. {
  307. if(mod==20)
  308. remote_canshu=remote_canshu<90?remote_canshu+10:90;//距离参数+10=(取值范围10到90)
  309. else if(mod==21)
  310. wendu_canshu=wendu_canshu<80?wendu_canshu+1:80;//温度参数+10(0到80)
  311. else if(mod==30)
  312. remote_jiaozhun=remote_jiaozhun<90?remote_jiaozhun+5:90;//距离校准+5(取值-90到90)
  313. else if(mod==31)
  314. speed=speed<9990?speed+10:9990;//超声波速度+10(取值10到9990)
  315. else if(mod==32)
  316. dac_xiaxian=dac_xiaxian<200?dac_xiaxian+1:20;//DAC下限加0.1(取值0.1到2v)
  317. //注意前边已经提到为便于处理DAC扩大了10倍,,所以这里加的是1
  318. else if(mod==10)
  319. is_jilu=1;//在测距模式下,按下s8触发一次6s的记录
  320. }
  321. //s9减,与上边的加类似,三目运算符的作用均为控制取值范围
  322. else if(key_value==9)
  323. {
  324. if(mod==20)
  325. remote_canshu=remote_canshu>10?remote_canshu-10:10;
  326. else if(mod==21)
  327. wendu_canshu=wendu_canshu>0?wendu_canshu-1:0;
  328. else if(mod==30)
  329. remote_jiaozhun=remote_jiaozhun>-90?remote_jiaozhun-5:-90;
  330. else if(mod==31)
  331. speed=speed>10?speed-10:10;
  332. else if(mod==32)
  333. dac_xiaxian=dac_xiaxian>10?dac_xiaxian-1:10;
  334. else if(mod==20&&!(jilu_remote==0))
  335. {
  336. V=(5-dac_xiaxian)/80*jilu_remote+dac_xiaxian-10*(5-dac_xiaxian)/80;//计算需要输出的电压
  337. V=V>dac_xiaxian?V:dac_xiaxian;//输出电压限幅
  338. V=V<5?V:5;
  339. wirte_pcf((unsigned char)(V/5*255));
  340. }
  341. }
  342. }
  343. void Delay14us(void) //@12.000MHz
  344. {
  345. unsigned char data i;
  346. _nop_();
  347. _nop_();
  348. i = 45;
  349. while (--i);
  350. }
  351. void send_wave(void)
  352. {
  353. unsigned char i=0;
  354. for(i=0;i<8;i++)//8个40kHz的超声波
  355. {
  356. TX=1;Delay14us();
  357. TX=0;Delay14us();
  358. }
  359. }
  360. void read_ul(void)
  361. {
  362. unsigned int ul_time;//记录超声波来回的时间(注意没有单位),中间变量
  363. send_wave();//发送超声波
  364. TR1=1;//开始计时
  365. while((RX==1)&&(TF1==0));//等待接受返回的数据
  366. TR1=0;//接收到返回的数据,停止计时
  367. if(TF1==1)//如果是因为定时器溢出,说明没检测到底有效数据
  368. {
  369. ul_time=0;
  370. TF1=0;
  371. }
  372. else//检查到有效数据
  373. {
  374. /*读取超声波来回的时间*/
  375. ul_time=TH1;
  376. ul_time<<=8;
  377. ul_time|=TL1;
  378. }
  379. /*距离=时间*速度/2=定时器时间/单片机主频*速度(m/s)*100/2+距离校准 单位:cm*/
  380. remote=ul_time*0.00000452115*speed+remote_jiaozhun>0?ul_time*0.0000041667*speed+remote_jiaozhun:0;
  381. ul_time=0;
  382. TH1=0;
  383. TL1=0;
  384. }
  385. void show_menu(void)
  386. {
  387. unsigned char jiaozhun=0;
  388. if(mod==10)//测距界面
  389. {
  390. Nixie_num[0]=temp/100>0 ? temp/100%10:20;//显示温度十位或者熄灭(十位为0)
  391. Nixie_num[1]=temp/10>0 ? temp/10%10+10:20+10;//显示个位加小数点,十位各位为0时显示0.
  392. Nixie_num[2]=temp/1%10;
  393. Nixie_num[3]=21;
  394. if(remote_danwei==0)//显示单位为cm时,直接显示。最高位之前的各位熄灭
  395. {
  396. Nixie_num[4]=remote/1000>0 ? remote/1000%10:20;
  397. Nixie_num[5]=remote/100>0 ? remote/100%10:20;
  398. Nixie_num[6]=remote/10>0 ? remote/10%10:20;
  399. //Nixie_num[7]=remote/1>0 ? remote/1%10:0;//数据连一位数都没有,则显示0而非全部熄灭
  400. Nixie_num[7]=remote/1%10;//数据连一位数都没有,则显示0而非全部熄灭
  401. }
  402. else if(remote_danwei==1)//如果单位是m
  403. {
  404. Nixie_num[4]=remote/1000>0 ? remote/1000%10:20;
  405. Nixie_num[5]=remote/100>0 ? remote/100%10+10:10;//精确到小数点后两位,只需要在倒数第二位前加个小数点即可
  406. Nixie_num[6]=remote/10>0 ? remote/10%10:0;
  407. Nixie_num[7]=remote/1>0 ? remote/1%10:0;
  408. }
  409. }
  410. else if(mod==20)//参数界面1距离参数
  411. {
  412. Nixie_num[0]=22;//P
  413. Nixie_num[1]=1;
  414. Nixie_num[2]=20;
  415. Nixie_num[3]=20;
  416. Nixie_num[4]=20;
  417. Nixie_num[5]=20;
  418. Nixie_num[6]=remote_canshu/10>0 ? remote_canshu/10%10:0;//显示距离参数
  419. Nixie_num[7]=remote_canshu/1>0 ? remote_canshu/1%10:0;
  420. }
  421. else if(mod==21)//参数界面2温度参数
  422. {
  423. Nixie_num[0]=22;//P
  424. Nixie_num[1]=2;
  425. Nixie_num[2]=20;
  426. Nixie_num[3]=20;
  427. Nixie_num[4]=20;
  428. Nixie_num[5]=20;
  429. Nixie_num[6]=wendu_canshu/10>0 ? wendu_canshu/10%10:0;//显示温度参数
  430. Nixie_num[7]=wendu_canshu/1>0 ? wendu_canshu/1%10:0;
  431. }
  432. else if(mod==30)//工厂1校准值
  433. {
  434. Nixie_num[0]=23;//F
  435. Nixie_num[1]=1;
  436. Nixie_num[2]=20;
  437. Nixie_num[3]=20;
  438. Nixie_num[4]=20;
  439. //正负号转化,remote_jiaozhun是实际值,带正负。jiaozhun是中间变量,不带符号,值与remote_jiaozhun的绝对值相同
  440. //用abs()也行
  441. jiaozhun=remote_jiaozhun>0?remote_jiaozhun:-remote_jiaozhun;
  442. //PS,距离参数取值10到90,故数码管5熄灭(正数)或显示-(负数)后两位显示数据
  443. if(remote_jiaozhun>=0)//正
  444. {
  445. Nixie_num[5]=20;//熄灭
  446. Nixie_num[6]=jiaozhun/10>0 ? jiaozhun/10%10:20;
  447. Nixie_num[7]=jiaozhun/1>0 ? jiaozhun/1%10:0;
  448. }
  449. else//负
  450. {
  451. if(jiaozhun/10>0)
  452. {
  453. Nixie_num[5]=21;//显示-
  454. Nixie_num[6]=jiaozhun/10%10;//距离参数
  455. Nixie_num[7]=jiaozhun/1%10;
  456. }
  457. else
  458. {
  459. Nixie_num[5]=20;
  460. Nixie_num[6]=21;//显示-
  461. Nixie_num[7]=jiaozhun/1%10;//距离参数
  462. }
  463. }
  464. }
  465. else if(mod==31)//工厂2速度
  466. {
  467. Nixie_num[0]=23;//F
  468. Nixie_num[1]=2;
  469. Nixie_num[2]=20;
  470. Nixie_num[3]=20;
  471. Nixie_num[4]=speed/1000>0 ? speed/1000%10:20;//显示速度
  472. Nixie_num[5]=speed/100>0 ? speed/100%10:20;
  473. Nixie_num[6]=speed/10>0 ? speed/10%10:20;
  474. Nixie_num[7]=speed/1>0 ? speed/1%10:20;
  475. }
  476. else if(mod==32)//工厂3DAC输出下限
  477. {
  478. Nixie_num[0]=23;//F
  479. Nixie_num[1]=3;
  480. Nixie_num[2]=20;
  481. Nixie_num[3]=20;
  482. Nixie_num[4]=20;
  483. Nixie_num[5]=20;
  484. Nixie_num[6]=dac_xiaxian/10%10+10;//DAC下限
  485. Nixie_num[7]=dac_xiaxian/1%10;
  486. }
  487. }
  488. void led_run(void)
  489. {
  490. if(mod==10&&is_100ms==1)//距离显示界面下,led灯显示距离(注意取反)
  491. {
  492. is_100ms=0;//这个是100ms是额外的处理,减慢led处理的速度,
  493. Led_Num=~remote;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
  494. }
  495. else if(mod==20||mod==21)//在参数界面下,L8点亮,同时其它灯熄灭
  496. {
  497. if(Led_Num!=~(0x80))//如果在参数界面下,未处在“L8点亮,同时其它灯熄灭”的状态,则使“L8点亮,同时其它灯熄灭”
  498. {
  499. Led_Num=~0x80;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
  500. }
  501. }
  502. else if((mod==30||mod==31||mod==32)&&is_100ms==1)//在工厂模式下L1以100ms闪烁
  503. {
  504. is_100ms=0;//is_100ms为0时,100ms后会被置为1,每次进入这个else if,则翻转一次L1
  505. if(Led_Num==~(0x01))//如果点亮了,则熄灭
  506. {
  507. Led_Num=~0x00;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
  508. }
  509. else if(Led_Num!=~(0x01))//如果熄灭了,则点亮
  510. {
  511. Led_Num=~0x01;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
  512. }
  513. }
  514. }
  515. bit relay_is_on=0;//继电器状态,1打开,0关闭
  516. void relay_run()
  517. {
  518. //如果距离参数-5<当前距离<距离参数+5,并且当前温度小于温度参数(前两个条件都成立的话,就需要打开继电器)
  519. //并且继电器没有打开,则打开继电器
  520. if(remote_canshu-5<=remote&&remote<=remote_canshu+5&&temp/10<=wendu_canshu&&relay_is_on==0)
  521. {
  522. RELAY_ON();
  523. relay_is_on=1;
  524. }
  525. //如果不满足上述前两个条件(此时需要关闭继电器)
  526. //并且继电器没有关闭,则关闭继电器
  527. else if(!(remote_canshu-5<=remote&&remote<=remote_canshu+5&&temp/10<=wendu_canshu)&&relay_is_on==1)
  528. {
  529. RELAY_OFF();
  530. relay_is_on=0;
  531. }
  532. }

onewire.c

  1. /* # 单总线代码片段说明
  2. 1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
  3. 2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
  4. 中对单片机时钟频率的要求,进行代码调试和修改。
  5. */
  6. #include <stc15.h>
  7. #include <intrins.h>
  8. #include "onewire.h"
  9. sbit DQ=P1^4;
  10. //
  11. void Delay_OneWire(unsigned int t)
  12. {
  13. unsigned char i;
  14. while(t--){
  15. for(i=0;i<12;i++);
  16. }
  17. }
  18. //
  19. void Write_DS18B20(unsigned char dat)
  20. {
  21. unsigned char i;
  22. for(i=0;i<8;i++)
  23. {
  24. DQ = 0;
  25. DQ = dat&0x01;
  26. Delay_OneWire(5);
  27. DQ = 1;
  28. dat >>= 1;
  29. }
  30. Delay_OneWire(5);
  31. }
  32. //
  33. unsigned char Read_DS18B20(void)
  34. {
  35. unsigned char i;
  36. unsigned char dat;
  37. for(i=0;i<8;i++)
  38. {
  39. DQ = 0;
  40. dat >>= 1;
  41. DQ = 1;
  42. if(DQ)
  43. {
  44. dat |= 0x80;
  45. }
  46. Delay_OneWire(5);
  47. }
  48. return dat;
  49. }
  50. //
  51. bit init_ds18b20(void)
  52. {
  53. bit initflag = 0;
  54. DQ = 1;
  55. Delay_OneWire(12);
  56. DQ = 0;
  57. Delay_OneWire(80);
  58. DQ = 1;
  59. Delay_OneWire(10);
  60. initflag = DQ;
  61. Delay_OneWire(5);
  62. return initflag;
  63. }
  64. unsigned int read_temp(void)
  65. {
  66. unsigned int temp=0;
  67. unsigned char low=0;
  68. unsigned char high=0;
  69. unsigned char xiaoshu=0;
  70. init_ds18b20();
  71. Write_DS18B20(0xCC);
  72. Write_DS18B20(0x44);
  73. Delay_OneWire(200);
  74. init_ds18b20();
  75. Write_DS18B20(0xCC);
  76. Write_DS18B20(0xBE);
  77. low=Read_DS18B20();
  78. high=Read_DS18B20();
  79. temp=high;
  80. temp&=0x0F;
  81. temp<<=8;
  82. temp|=low;
  83. temp>>=4;
  84. /*获取小数部分*/
  85. xiaoshu=low;
  86. xiaoshu&=0x0F;
  87. temp=temp*10+xiaoshu;//温度扩大了十倍,把小数点后一位也加上了
  88. return temp;
  89. }

onewire.h

  1. #ifndef _ONEWIRE_H_
  2. #define _ONEWIRE_H_
  3. unsigned int read_temp(void);
  4. #endif

iic.c

  1. /* # I2C代码片段说明
  2. 1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
  3. 2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
  4. 中对单片机时钟频率的要求,进行代码调试和修改。
  5. */
  6. #define DELAY_TIME 5
  7. #include <stc15.h>
  8. #include <intrins.h>
  9. #include "iic.h"
  10. sbit sda=P2^1;
  11. sbit scl=P2^0;
  12. //
  13. static void I2C_Delay(unsigned char n)
  14. {
  15. do
  16. {
  17. _nop_();_nop_();_nop_();_nop_();_nop_();
  18. _nop_();_nop_();_nop_();_nop_();_nop_();
  19. _nop_();_nop_();_nop_();_nop_();_nop_();
  20. }
  21. while(n--);
  22. }
  23. //
  24. void I2CStart(void)
  25. {
  26. sda = 1;
  27. scl = 1;
  28. I2C_Delay(DELAY_TIME);
  29. sda = 0;
  30. I2C_Delay(DELAY_TIME);
  31. scl = 0;
  32. }
  33. //
  34. void I2CStop(void)
  35. {
  36. sda = 0;
  37. scl = 1;
  38. I2C_Delay(DELAY_TIME);
  39. sda = 1;
  40. I2C_Delay(DELAY_TIME);
  41. }
  42. //
  43. void I2CSendByte(unsigned char byt)
  44. {
  45. unsigned char i;
  46. for(i=0; i<8; i++){
  47. scl = 0;
  48. I2C_Delay(DELAY_TIME);
  49. if(byt & 0x80){
  50. sda = 1;
  51. }
  52. else{
  53. sda = 0;
  54. }
  55. I2C_Delay(DELAY_TIME);
  56. scl = 1;
  57. byt <<= 1;
  58. I2C_Delay(DELAY_TIME);
  59. }
  60. scl = 0;
  61. }
  62. //
  63. unsigned char I2CReceiveByte(void)
  64. {
  65. unsigned char da;
  66. unsigned char i;
  67. for(i=0;i<8;i++){
  68. scl = 1;
  69. I2C_Delay(DELAY_TIME);
  70. da <<= 1;
  71. if(sda)
  72. da |= 0x01;
  73. scl = 0;
  74. I2C_Delay(DELAY_TIME);
  75. }
  76. return da;
  77. }
  78. //
  79. unsigned char I2CWaitAck(void)
  80. {
  81. unsigned char ackbit;
  82. scl = 1;
  83. I2C_Delay(DELAY_TIME);
  84. ackbit = sda;
  85. scl = 0;
  86. I2C_Delay(DELAY_TIME);
  87. return ackbit;
  88. }
  89. //
  90. void I2CSendAck(unsigned char ackbit)
  91. {
  92. scl = 0;
  93. sda = ackbit;
  94. I2C_Delay(DELAY_TIME);
  95. scl = 1;
  96. I2C_Delay(DELAY_TIME);
  97. scl = 0;
  98. sda = 1;
  99. I2C_Delay(DELAY_TIME);
  100. }
  101. void wirte_pcf(unsigned char dat)
  102. {
  103. I2CStart();
  104. I2CSendByte(0x90);
  105. I2CWaitAck();
  106. I2CSendByte(0x40);
  107. I2CWaitAck();
  108. I2CSendByte(dat);
  109. I2CWaitAck();
  110. I2CStop();
  111. }

iic.h

  1. #ifndef _IIC_H_
  2. #define _IIC_H_
  3. void wirte_pcf(unsigned char dat);
  4. #endif

临近比赛了,这里也提前祝参加比赛的同学能够拿个好奖,也对得起这么久的努力了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/282997
推荐阅读
相关标签
  

闽ICP备14008679号