蓝桥杯单片机学习16——十一届省赛题详解

十一届省赛题

  • 前言
  • 任务要求
  • 1.基本要求
  • 2.竞赛板配置要求
  • 3.硬件框图
  • 4.功能描述
  • 实现思路
  • 任务难点
  • 代码实现
  • 1.变量定义部分
  • 2.main函数
  • 3.初始化函数
  • 4.数码管显示任务函数
  • 5.按键矩阵扫描函数
  • 6.逻辑处理函数
  • 7.数据处理相关函数
  • 8.定时器函数
  • 9.AD转换函数
  • 10.EEPROM读取/写入函数
  • 总结
  • 前言

    上期我们介绍了十二届省赛题,这其我们来学习一下十一届省赛题

    任务要求

    1.基本要求

    2.竞赛板配置要求

    3.硬件框图

    4.功能描述







    以上就是十一届省赛题的全部要求,下面我来讲一下我的实现思路。

    实现思路

    看完题目后,我个人的思路还是挺明确的:

    1. 首先先写一遍LS138相关外设控制,讲蜂鸣器,继电器关闭,通过两个数组控制LED/数码管的显示内容,写一个矩阵按键扫描函数,实现基本的功能函数。
    2. 完成前期的准备工作,初始化定时器,再去编写对应的页面函数,以及定义相关的变量、标志位、计数位。
    3. 编写ADC和AT24C02的使用函数,完成所有函数的编写。测试各模块是否能正常工作
    4. 根据任务要求,编写相关的逻辑,优化代码。

    任务难点

    本次省赛题的难点,我认为有如下两点:

  • 第一个难点应该是如何实现计数器加一的条件,题目中要求的是需要VIN3处于一个下降的趋势,且于Vp相等时加一
  • 我的实现思路是定义一个VIN3和VIN3_Pre,通过比较VIN3和VIN4_Pre的大小就可以比较出VIN3的变化趋势,然后再将VIN3和Vp进行比较就可以实现对计数值加一。

  • 第二个难点就是实现对无效按键次数累加,当次数大于三次时,L3亮起,当按键按下且有效时,L3熄灭
  • 我的实现思路是这样的:我有一个按键扫描函数,当有按键按下时,无效按键次数加一,然后再对应的按键处理逻辑部分讲无效按键次数清零,如果无效按键次数大于3,将L3点亮,反之则熄灭。

    不过我发现这样的方法是不行的,具体原因还没找出来,等我找到了再说吧,我们先看下面的代码。

    代码实现

    我这次的代码旗本框图上和十二届省赛题是一致的,所以这次我不会将全部的代码贴上,只会展示我认为需要的代码。

    1.变量定义部分

    #include <STC15F2K60S2.H>
    #include "Key.h"
    #include "LS138.h"
    #include "iic.h"
    #include "interrupt.h"
    unsigned char SEG[8] = {33,33,33,33,33,33,33,33};       //显示数码管数组,33为不亮
    unsigned char LED[8] = {0,0,0,0,0,0,0,0};               //LED显示数组
        
    
     int VIN3 = 324;    //采集电压               增加                 不变              减小
     int VIN3_Pre =0;   //VIN3变化趋势  VIN3_Pre <  VIN3  VIN3_Pre =  VIN3   VIN3_Pre > VIN3  
     int Vp = 300;      //参考电压
    unsigned int  Count = 0;     //计数值
    
    unsigned int Vin_State  =0;     //为1时VIN3变化趋势为下降
    unsigned int Page_Num = 1;      //当前界面的值
    unsigned int Key_Num = 0;       //按键的值
     int Mistake_Num = 0;           //无效按键的次数
    
    unsigned char Vin_Flag = 0;     //VIN3 < Vp标志位
    unsigned char Show_Flag = 0;    //数码管更新标志。每10ms更新一次
    

    2.main函数

    main()
    {
        Init();     //初始化
        while(1)
        {
            Show_Task();    //各种功能函数
            Key_Task();
            Logic_Task();
            Data_Task();
        }
    }
    

    是的,就是这这么简介,哈哈哈

    3.初始化函数

    //初始化函数
    void Init(void)
    {
        unsigned char dat = 0;  
        dat = AT24C02_Read_Byte(0x00);  
        if(dat != 0xFF)     //判断不为第一次上电,则读取EEPROM内的Vp值,反之则不读取
        {
           Vp = dat *10; 
        }
        LS_Init();  //LS138部分外设初始化
        Timer1_Init();  //定时器1初始化,16位自动重装载,1ms产生一次中断
    }
    

    初始化函数负责调用各个外设,中断的初始化,以及判断AT24C02是否读取等任务

    4.数码管显示任务函数

    //数据显示页面
    void SEG_Page1(void)
    {
        SEG[0] = 30;
        SEG[1] = 33;
        SEG[2] = 33;
        SEG[3] = 33;
        SEG[4] = 33;
        SEG[5] = VIN3/100%10 + 10;
        SEG[6] = VIN3/10%10;
        SEG[7] = VIN3%10;
    }
    //参数设置页面
    void SEG_Page2(void)
    {
        SEG[0] = 29;
        SEG[1] = 33;
        SEG[2] = 33;
        SEG[3] = 33;
        SEG[4] = 33;
        SEG[5] = Vp/100%10 + 10;
        SEG[6] = Vp/10%10;
        SEG[7] = Vp%10;
    }
    //计数页面
    void SEG_Page3(void)
    {
        SEG[0] = 28;
        SEG[1] = 33;
        SEG[2] = 33;
        SEG[3] = 33;
        SEG[4] = 33;
        SEG[5] = 33;
        SEG[6] = Count/10%10;
        SEG[7] = Count%10;
    }
    //数码管/LED显示相关内容函数
    void Show_Task(void)
    {
        switch(Page_Num)    //根据当前界面的值确定需要显示的页面
        {
            case 1: SEG_Page1();        //显示数据页面 
                break;
            case 2: SEG_Page2();        //显示参数页面
                break;
            case 3: SEG_Page3();        //显示1计数页面
                break;
        }
        if(Show_Flag)                   //每10ms更新一次数码管/LED显示
        {
            SEG_Control(SEG);           //显示数码管
            LED_Control(LED);           //显示LED
            Show_Flag= 0;               //取消显示标志位
        }
    }
    

    包括对应的界面函数以及通过Pag_Num变量决定需要显示的界面,每10ms刷新一次数码管显示。

    5.按键矩阵扫描函数

    //按键扫描函数
    void Key_Task(void)
    {
        unsigned int temp = 0;
        temp =  KeyScan();
        if(temp != 0)       //若有按键按下则读取按键的值
        {
            Key_Num = temp;
            Mistake_Num++;  //有按键按下则无效按键数量加一
        }
    }
    

    当有按键按下时,才会获取对应的按键值

    6.逻辑处理函数

    //处理逻辑相关函数
    void Logic_Task(void)
    {
        if(Key_Num == 12)   //按键十二按下,切换显示界面
        {
            Mistake_Num = 0;        //此次按键是有效按键,将其清零
            Page_Num++;
            AT24C02_Write_Byte(0x00,Vp*0.1);  //将Vp存入EEPROM中  
            if(Page_Num >= 4)
            {
                 
                Page_Num = 1;
            }
        }
                
        if(Key_Num == 13 && Page_Num == 3)  //按键十三按下
        {
            Mistake_Num = 0;             //此次按键是有效按键,将其清零
            Count = 0;              //Count清零
        }
        
        if(Key_Num == 16 && Page_Num == 2)  //按键十六按下
        {
            Mistake_Num = 0;     //此次按键是有效按键,将其清零
            Vp += 50;       //Vp加0.5V
            if(Vp > 500)
            {
                Vp = 0;     //大于5V,则等于0V
            }
        }
        if(Key_Num == 17 && Page_Num == 2)      //按键十七按下
        {
            Mistake_Num = 0;             //此次按键是有效按键,将其清零
            Vp -= 50;       //Vp减0.5V
            if(Vp < 0)
            {
                Vp = 500;   //小于0V,则等于5V
            }
        }
        
        if(Mistake_Num >=3)     //当个无效按键超过三次,L3亮起
        {
            LED[2] = 1;
        }
        else                    //反之则熄灭
        {   
            LED[2] = 0; 
        }
        
        if(VIN3 < Vp)           //VIN3 < Vp,将对应标志位置一
        {
            Vin_Flag = 1;
        }
        else
        {
            Vin_Flag = 0;       //反之则置0
            LED[0] = 0;         //熄灭L1
        }
        
        if(Count %2 == 1)       //Count为奇数时,L2点亮
        {
            LED[1] = 1;
        }
        else
        {
            LED[1] = 0;         //为偶数时熄灭
        }
        
    }
    

    包括对不同按键按下的逻辑处理代码,以及判条件控制LED改变的相关代码

    7.数据处理相关函数

    //初始数据相关的函数
    void Data_Task(void)
    {
        VIN3_Pre = VIN3;                //将当前的VIN3记为之前的值
        VIN3 = PCF8591_AD_Conversion(3);        //读取当前的VIN3电压
        if(VIN3_Pre > VIN3  )           
        {
           Vin_State = 1;       //VIN3变化趋势为下降
        }
        
    }
    

    完成对电压采集,Count加一等内容。

    8.定时器函数

    //定时器1初始化函数,1ms进来一次
    void Timer1_Init(void)
    {
        TMOD |= 0x00;    //设置定时器1工作模式0,16位自动重装载
    //    TMOD |= 0x10;    //设置定时器1工作模式1
    //    TMOD |= 0x20;    //设置定时器1工作模式2,8位自动重装载
        TL1 = 0x18;     //设置定时器溢出时间
        TH1 = 0xFC;
        ET1 = 1;        //开启定时器0中断允许
        EA=1;           //开启总中断允许
        TR1 = 1;        //开启定时器0
        PT1 = 0;        //设置优先级为低优先级,为1时设置为高优先级
    }
    
    //定时器1服务函数
    void External_Hander2() interrupt 3
    {
        static unsigned int Timer1_Count  = 0;      //定时器计数变量
        static unsigned char Vin_Count  =0;         //VIN3 < Vp 对应计数变量
        Timer1_Count++; 
        if(Vin_State == 1)  //判断Count是否需要++
        {
            if(Vp == VIN3)
            {
                Count++;
            }
            Vin_State =0;
        }
        if(Timer1_Count %10 == 0)   //10ms更新一次数码管
        {
            Show_Flag = 1;
        }
        
        if(Timer1_Count >= 1000)    //1s触发一次
        {
            if(Vin_Flag)
            {
                Vin_Count++;
                if(Vin_Count >=5)
                {
                    LED[0] = 1;     //VIN3 < Vp 5s后点亮LED1
                    Vin_Count = 0;
                }
            }
    
            Timer1_Count = 0;
        }
    }
    

    定时器1初始化,10ms更新一次数码管/LED显示,判断Count++条件,以及进行计时工作。

    9.AD转换函数

    //PCF8591AD转换函数,channel为模拟输入的通道
     int  PCF8591_AD_Conversion(unsigned char channel )
    {
         int dat = 0;
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Write_Addr);   //写入从机PCF8591的写地址,
        if(IIC_WaitAck())           //等待从机应答,为1表示不应答,为0表示应答
        {   
            IIC_Stop();         //如果不应答,则结束通信
            return 0;
        }
        IIC_SendByte(0x00 | channel);   //写入控制指令,单端输入,AD转换,选择通道channel
        IIC_WaitAck();      //这一步不能少,否则读出来一直是255
        IIC_Stop();         //结束本次通信,改变收发方向
        
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Read_Addr);    //写入从机PCF8591的读地址,
        if(IIC_WaitAck())           //等待应答
        {
            IIC_Stop();          //如果不应答,则结束通信
            return 0;
        }
        dat = IIC_RecByte();    //读取AD转换出的数字量 
        IIC_SendAck(1);         //不应答
    
        IIC_Stop();             //结束通信
        
        //这里读到了数据,可以进行数据处理。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
        dat = dat*1.96 ;
        
        return dat;
    }
    

    负责AD采集工作

    10.EEPROM读取/写入函数

    //AT24C02写字节函数,Addr为数据写入的地址,dat为需要写入的数据
    void AT24C02_Write_Byte(unsigned char Addr,unsigned char dat)
    {
            IIC_Start();                        //开始信号
            IIC_SendByte(AT24C02_WriteByte);    //开始寻址,选择为写数据
            IIC_WaitAck();                      //等待应答
            IIC_SendByte(Addr);                 //写入数据存放的地址
            IIC_WaitAck();                      //等待应答
            IIC_SendByte(dat);                  //发送数据
            IIC_WaitAck();                      //等待应答
            IIC_Stop();                         //结束通信
    
    }
    //AT24C02读字节函数,返回值为读出的数据,Addr为数据读出的地址
    unsigned char AT24C02_Read_Byte(unsigned char Addr)
    {
        unsigned char dat = 0;          //定义变量,存放读出的数据
        IIC_Start();                    //开始通信
        IIC_SendByte(AT24C02_WriteByte);//开始寻址,选择为写数据
        IIC_WaitAck();                  //等待应答
        IIC_SendByte(Addr);             //写入数据读出的地址
        IIC_WaitAck();                  //等待应答
        IIC_Start();                    //开始下一次通信,因为IIC通信每改变一次数据传输方向,必需重新开始通信
        IIC_SendByte(AT24C02_ReadByte); //开始寻址,选择为读数据
        IIC_WaitAck();                  //等待应答
        dat = IIC_RecByte();            //读出数据
        IIC_SendAck(1);                 //发送不应答,(发1表示不应答)
        IIC_Stop();                     //结束通信
        return dat;                     //返回读出的数据
    }
    

    负责EEPROM读取/写入函数

    总结

    看到这里,这一期省赛题目的分享也应该时接近尾声了,后续的话,应该还会继续分析两期左右的省赛题,比较也快到比赛的日子了。还有就是,我可能也会更新一两期的客观题,比较三十分也是很香的。

    最后,附上代码逻辑的思维导图,需要自取。

    作者:不想写代码的我

    物联沃分享整理
    物联沃-IOTWORD物联网 » 蓝桥杯单片机学习16——十一届省赛题详解

    发表回复