前言

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

任务要求

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读取/写入函数

总结

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

最后,附上代码逻辑的思维导图,需要自取。
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐