蓝桥杯单片机学习15——第十二届省赛题

文章介绍了在蓝桥杯省赛题中遇到的数码管和LED相互干扰问题,以及如何通过创建数组来控制这些外设,减少干扰并优化代码结构。作者提供了详细的代码实现,包括数码管和LED的控制函数,以及温度读取和DA转换等功能,强调了这种方法的好处,如降低CPU占用和简化程序设计。
摘要由CSDN通过智能技术生成

前言

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

上一期遗留的问题

上期我们提到,数码管和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();
}

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

总结

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想写代码的我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值