蓝桥杯单片机学习第十五课——第十二届省赛题解析

第十二届省赛题

  • 前言
  • 上一期遗留的问题
  • 解决的方法
  • 这样写的好处
  • 任务要求
  • 1.基本要求
  • 2.竞赛板配置要求
  • 3.硬件框图
  • 4.功能描述
  • 实现思路
  • 代码实现
  • 1.main.C
  • 2.LS138.h
  • 3.LS138.C
  • 4.按键扫描函数
  • 5.温度读取函数
  • 6.DA转换函数
  • 总结
  • 前言

    书接上文,上期我们基本完成了十三届省赛题,但还是存在一些问题,本期我将对上期存在的一些问题,提出一些解决方案,并加以实践验证可行性,废话少说,让我们往下看。

    上一期遗留的问题

    上期我们提到,数码管和LED在使用的时候会存在外设之间相互干扰的问题,在我们不断的探索之下,我发现其实不仅仅是LED和数码管,继电器和蜂鸣器之间也会存在相互干扰的问题…………
    导致我们在控制其中一个外设时,其他的外设也会被干扰,出现一些奇怪的问题,对于这种干扰,我们老师教了我一种很神奇的方法,下面让我来介绍一些这种方法神奇在哪。

    解决的方法

  • 通过创建数组来实现对数码管/LED的控制
  • 什么意思呢?我们知道,我们蓝桥杯单片机是通过74HC573锁存器实现P0口对大量外设的控制的,这样的方法毋庸置疑是节省了大量的IO口,但也由于这样的设定,导致P0口的电平一直处于跳变之中,上一秒控制LED的亮起与熄灭,下一秒就去控制数码管的显示了,导致每一个LED(外设)的状态没有被记录下来,而下一次再来控制LED的时候,此时的P0口电平早已不是之前控制LED的电平状态了,也就是因为这个原因,导致由74HC573锁存器控制的各个外设之间相互干扰,互相打架。

    因此我们可以通过创建一个数组,来控制LED/数码管,甚至所有通过锁存器控制的外设。具体实现代码如下:

    1. LED

    数组LED[8] 记录8个LED的状态,为1亮起,为0熄灭,

    unsigned char LED[8] = {0,0,0,0,0,0,0,0};       //LED显示数组
    //LED写入函数,写入内容为LED数组的内容,1表示开启LED,反之则为关闭LED
    void LED_Control(unsigned char* p )
    {
        unsigned char temp = 0;
        unsigned char i = 0;
        for(i=0;i<8;i++)
        {
            if(*(p+i) != 0)
            {
                temp |= 0x01<< i ;
            }
        }
        LS_Set(4);
        P0 = ~temp;
        LS_Set(0);
    }
    

    2. 数码管

    通过对SEG[8] 写入数据,将SEG_Index[SEG[pos]] 写入P0口,实现对数码管的完美控制

    unsigned char SEG[8] = {20,20,20,20,20,20,20,20};   //数码管显示数组
    //数码管段码
                                        //0   1                                       9    
    unsigned char code SEG_Index[30] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
                                        /*0.  1.                                      9. */
                                        0x40, 0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x10,0x20,
                                        /*消隐   。   -     P    A    C*/
                                        0xFF,0x7F,0xBF,0x8C,0x88,0xC6};
    //数码管显示函数,显示内容为SEG_Index【SEG】数组的内容,
    void SEG_Control(unsigned char* p)
    {
        unsigned char i = 0;
        for(i=0;i<8;i++)
        {
            LS_Set(0);
            LS_Set(6);
            P0 = 0x01<<i;
            LS_Set(0);
            LS_Set(7);
            P0 = SEG_Index[*(p+i)];
            LS_Set(0);
            P0 = 0xFF;		
            DelayXms(1);
            
        }
    }
    
    												其他的外设也可以用类似的控制方法实现,后面遇到了我再去实现
    

    这样写的好处

    好处是什么呢?那可太多了。

    下次我们需要修改数码管/LED的状态时,只需要改变数组中的内容,然后通过定时器每个一段时间(我用的是10ms)刷新一次LED/数码管的状态,就可以实现对LED/数码管的完美控制了。而且还减少了CPU的占用。

    你就说好不好吧😎😎

    解决了这个问题之后,我们继续回到今天的正题,十二届省赛题实操。

    任务要求

    1.基本要求

    2.竞赛板配置要求

    3.硬件框图

    4.功能描述







    实现思路

    关于实现思路,我做出一个优化,具体结构是这样的:


    我将单片机代码进行了一个区分,分别为数码管显示函数、按键扫描函数、逻辑处理函数、数据处理函数,将不同功能的代码放置到对应的函数,然后每隔一段时间对数码管和按键扫描函数调用,这样看起来更加简介,在便于调试的同时,节省了单片机的资源。

    代码实现

    1.main.C

    #include <STC15F2K60S2.H>
    #include "LS138.h"
    #include "Key.h"
    #include "onewire.h"
    #include "iic.h"
        
    unsigned char LED[8] = {0,0,0,0,0,0,0,0};       //LED显示数组
    unsigned char SEG[8] = {20,20,20,20,20,20,20,20};   //数码管显示数组
    unsigned int Tempreature = 2425;                //温度
    unsigned int Tempreature_Set = 2500;            //温度参数
    unsigned int V_DAC= 325;                        //DAC输出电压
    unsigned int Key_Num = 0;                       //键码值
    
    unsigned int Page = 1;                          //显示的页面
    unsigned int Mode = 1;                          //模式
    unsigned char SEG_Flag = 0;                     //数码管显示标志
    unsigned char Tempreature_Flag = 0;             //温度读取标志位
    
    //定时器1初始化,十六位自动重装载,1ms触发一次
    void Timer1_Init(void)
    {
        TMOD = 0x00;
        ET1 = 1;
        EA = 1;
        TH1 = 0xFC;
        TL1 = 0x18;
        TR1 = 1;
    }
    //初始化函数
    void Init()
    {
        LS_Init();      //关闭蜂鸣器和继电器
        Timer1_Init();  //定时器1初始化
        LED_Control(LED);   //关闭所有LED
        SEG_Control(SEG);   //关闭所有数码管
       Tempreature =  DS_Read_Tempreatur();     //读取温度
    }
    
     //温度界面显示函数
    void Wendu_Page(unsigned  int Tempreature) 
    {
        SEG[0] = 25;
        SEG[1] = 20;
        SEG[2] = 20;
        SEG[3] = 20;
        SEG[4] = Tempreature/1000%10;
        SEG[5] = Tempreature/100%10  +10;
        SEG[6] = Tempreature/10%10;
        SEG[7] = Tempreature%10;
    }
    //参数设置显示界面
    void Canshu_Page(unsigned int Tempreature_Set )
    {
        SEG[0] = 23;
        SEG[1] = 20;
        SEG[2] = 20;
        SEG[3] = 20;
        SEG[4] = 20;
        SEG[5] = 20;
        SEG[6] = Tempreature_Set/1000%10;
        SEG[7] = Tempreature_Set/100%10;
    }
    
    //DAC输出电压显示界面
    void DAC_Page(unsigned int V_DAC)
    {
        SEG[0] = 24;
        SEG[1] = 20;
        SEG[2] = 20;
        SEG[3] = 20;
        SEG[4] = 20;
        SEG[5] = V_DAC/100%10 + 10 ;
        SEG[6] = V_DAC/10%10;
        SEG[7] = V_DAC%10;
    }
    
    //数码管任务,通过Page来决定显示的界面,
    void SEG_Task(void)
    {   
        switch(Page)
        {                                           //控制对应的LED亮起
            case 1: Wendu_Page(Tempreature);        LED[1] = 1;LED[2] = 0; LED[3] = 0;
                break;
            case 2: Canshu_Page(Tempreature_Set);   LED[1] = 0;LED[2] = 1; LED[3] = 0;
                break;
            case 3: DAC_Page(V_DAC);                LED[1] = 0;LED[2] = 0; LED[3] = 1;
                break;
            
        }
    
        if(SEG_Flag)
        {
            SEG_Control(SEG);       //10ms更新一次数码管和LED显示状态
            LED_Control(LED);
            SEG_Flag = 0;
        }
    }
    
    //按键扫描函数,有按键按下则返回键码值,否则键码值为0
    void Key_Task(void)
    {
        unsigned int temp = 0;
        temp = KeyScan();
        if(temp != 0)
        {
            Key_Num = temp;
        }
        
    }
    
    //处理逻辑相关的任务
    void Logic_Task(void)
    {
        unsigned  int temp = 0;
        temp = Tempreature_Set;
        if(Key_Num == 4)        //按键4按下,改变显示界面
        {
            Page++;
            if(Page >= 4)
            {
                Page = 1;
            }
        }
        if(Key_Num == 5)        //按键5按下,改变模式
        {
            Mode++;
            if(Mode >= 3)
            {
                Mode = 1;
            }
        }
        if(Page == 2 && Key_Num == 8)   //参数界面下,按键8按下,参数减一
        {
            temp -= 100;
        }
        if(Page == 2 && Key_Num == 9)   //参数界面下,按键8按下,参数加一
        {
            temp += 100;
        }
        Tempreature_Set = temp;
    }
    
    //处理数据相关的任务函数
    void Data_Task(void)
    {
        if(Tempreature_Flag)    //每100ms测量一次温度
        {
            Tempreature =  DS_Read_Tempreatur();
            Tempreature_Flag = 0;
        }
        if(Mode == 1)       //模式1,DAC输出电压与温度相关
        {
            LED[0] = 1;
            if(Tempreature < Tempreature_Set )
            {
                IIC_DAC(0x00);  //温度小于温度参数,输出0V电压
            }
            else
            {
                IIC_DAC(0xFF);  //温度大于温度参数,暑促5V电压
            }
        }
        if(Mode == 2)       //模式2,DAC输出电压如图7所给关系
        {
            LED[0] = 0;     
            if(Tempreature <= 2000)     //小于20度,输出1V
            {
                IIC_DAC(0x33);
            }
            if(Tempreature >= 4000)     //大于40度,输出4V
            {
                IIC_DAC(0xCC);
            }
            else                        //呈线性关系
            {
                 IIC_DAC((1 + (Tempreature /100 - 20)*3/20) *51) ;
            }
        }
    }
    
    void main()
    {
        Init();     //初始化函数调用
        while(1)
        {
            SEG_Task();         //数码管,LED显示函数
            Key_Task();         //按键扫描函数
            Logic_Task();       //逻辑相关处理
            Data_Task();        //数据相关处理
        }
    }
    
    //定时器中断服务函数
    void Timer1_Handler() interrupt 3
    {
        static unsigned int Timer1_Count = 0;
        Timer1_Count++;
        
    if(Timer1_Count % 10 == 0)  //每10ms刷新一次数码管和LED显示状态
    {
        SEG_Flag = 1;
    }
        if(Timer1_Count % 100 == 0)     //每100ms读取一次温度
        {
            Tempreature_Flag = 1;
        }
        
        
    }
    

    2.LS138.h

    #ifndef __LS138_H_
    #define __LS138_H_
    
    #include <STC15F2K60S2.H>
    
    //延时函数,延时Xms  
    void DelayXms(unsigned int x);
    
    //初始化函数
    void LS_Init(void);
    
    //LS138位选函数,输入0为清除位选
    void LS_Set(unsigned int dat);
    
    //LED写入函数,写入内容为LED数组的内容,1表示开启LED,反之则为关闭LED
    void LED_Control(unsigned char* p );
    
    //数码管显示函数,显示内容为SEG_Index【SEG】数组的内容,
    void SEG_Control(unsigned char* p);
    
    #endif 
    

    3.LS138.C

    #include "LS138.h"
    
    //数码管段码
                                        //0   1                                       9    
    unsigned char code SEG_Index[30] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
                                        /*0.  1.                                      9. */
                                        0x40, 0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x10,0x20,
                                        /*消隐   。   -     P    A    C*/
                                        0xFF,0x7F,0xBF,0x8C,0x88,0xC6};
            
    //延时函数,延时Xms                                    
    void DelayXms(unsigned int x)		//@12.000MHz
    {
    	unsigned char i, j,k;
        
    	for(k=0;k<x;k++)
        {
                i = 12;
            j = 169;
            do
            {
                while (--j);
            } while (--i);
        }
    }
    //初始化函数
    void LS_Init(void)
    {
        LS_Set(5);
        P0 = 0x00;  //关闭蜂鸣器和继电器
        LS_Set(0);
    }
    //LS138位选函数,输入0为清除位选
    void LS_Set(unsigned int dat)
    {
        switch(dat)
        {
            case 0: P2 = P2 & 0x1F;
                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;
    
        }
    }
    //LED写入函数,写入内容为LED数组的内容,1表示开启LED,反之则为关闭LED
    void LED_Control(unsigned char* p )
    {
        unsigned char temp = 0;
        unsigned char i = 0;
        for(i=0;i<8;i++)
        {
            if(*(p+i) != 0)
            {
                temp |= 0x01<< i ;
            }
        }
        LS_Set(4);
        P0 = ~temp;
        LS_Set(0);
    }
    
    //数码管显示函数,显示内容为SEG_Index【SEG】数组的内容,
    void SEG_Control(unsigned char* p)
    {
        unsigned char i = 0;
        for(i=0;i<8;i++)
        {
            LS_Set(0);
            LS_Set(6);
            P0 = 0x01<<i;
            LS_Set(0);
            LS_Set(7);
            P0 = SEG_Index[*(p+i)];
            LS_Set(0);
            P0 = 0xFF;
            DelayXms(1);
            
        }
    }
    

    4.按键扫描函数

    #include "Key.h"
    
    //按键扫描函数,返回值为键码值,键码值为0表示按键按下为送手或无按键按下
    unsigned int KeyScan(void)
    {
        	static unsigned char    cnt = 0,	//continue value
    	                last_trg = 0;	        //last trigger value
    	unsigned char   trg = 0,	        //trigger value
                cur = 0,                //current value
                value = 3,             //必须初始化为3
                key_x = 0,
                key_y = 0;
    
        P3 = 0x0f;
        P4 = 0x00;
        if(!P30)        key_x = 3;          //获取X轴坐标
        else if(!P31)   key_x = 2;
        else if(!P32)   key_x = 1;
        else if(!P33)   key_x = 0;
    
        P3 = 0xf0;
        P4 = 0xff;
        if(!P34)        key_y = 4;          //获取Y轴坐标
        else if(!P35)   key_y = 3;
        else if(!P42)   key_y = 2;
        else if(!P44)   key_y = 1;
    
        cur = key_y^0;
        trg = cur^cnt & cur;
        cnt = cur;
        if((last_trg ^ trg & last_trg) && cur)
        {
            //计算矩阵按键的键值
            value = key_x + key_y * 4;
        }
        last_trg = trg;
    
        return value;    //返回独立按键的键值
        
    }
    

    5.温度读取函数

    需要注意的是:这里的单总线延时函数,需要加上一个:for(i=0;i<12;i++);
    为什么要这样呢?这是因为官方提供的延时函数时12T工作模式下的,而我们使用的单片机工作在1T模式下,因此需要作此改动。

    //单总线延时函数
    void Delay_OneWire(unsigned int t)  //STC89C52RC
    {
    		unsigned char i;
    	while(t--){
    		for(i=0;i<12;i++);
    	}
    }
    //DS18B20读取温度函数
    unsigned int  DS_Read_Tempreatur(void)
    {
        unsigned int tempreature = 0;
        unsigned char HSB,LSB;
        
        init_ds18b20();
        Write_DS18B20(0xCC);
        Write_DS18B20(0x44);
        
        init_ds18b20();
        Write_DS18B20(0xCC);
        Write_DS18B20(0xBE);
        LSB = Read_DS18B20();
        HSB = Read_DS18B20();
        
        tempreature = (HSB << 8 )| LSB ;
        tempreature = tempreature /16 *100;
        tempreature = tempreature + (LSB & 0x0F) * 6.25 ;
        return tempreature;     //返回值放大了100倍,可实现保留两位小数
    }
    

    6.DA转换函数

    //DA转换函数,转换范围为0~5V
    void IIC_DAC(unsigned char dat)
    {
        IIC_Start();
        IIC_SendByte(0x90);
        IIC_WaitAck();
        IIC_SendByte(0x40);
        IIC_WaitAck();
        IIC_SendByte(dat);
        IIC_WaitAck();
        IIC_Stop();
    }
    

    以上就是赛题用到的所有代码,不包含官方提供的驱动代码,如果那里有问题的话,欢迎评论区留言探讨。👀👀👀

    总结

    十二届的上省赛题总体上来说难度会比十三届的低,也许是我学会了新的方法的原因,也许时我变强了,哈哈哈哈。不过我觉得我上面提供的思路确实不错,对于还没有自己代码风格的小伙伴可以尝试一下,强烈推荐。🫡🫡🫡

    作者:不想写代码的我

    物联沃分享整理
    物联沃-IOTWORD物联网 » 蓝桥杯单片机学习第十五课——第十二届省赛题解析

    发表评论