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


    目录

    前言

    单片机资源数据包_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<     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<

    现在,只穿一个参数点亮一个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
    2. #include
    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<
    27. #define LED_OFF(x) Led_Num|=0x01<
    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<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
    7. #include
    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
    8. #include
    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

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

  • 相关阅读:
    《七月集训》第二十八日——动态规划
    macbook Safari 如何打开F12 Console 控制台 开发者工具 Developer Tools
    算法通关村第三关-青铜挑战数组专题
    Ansible的脚本 --- playbook 剧本
    压力测试-JMeter安装、入门、结果分析
    win11部署自己的privateGpt(2024-0304)
    win10语言切换调整为像win7一样,设置纯英文键盘切换,使用ctrol+shift切换键盘
    超详细Redis使用手册
    设计模式 -- 中介者模式(17)
    Java面试之封装、继承和多态(简洁易懂版)
  • 原文地址:https://blog.csdn.net/qq_73427280/article/details/136175084