第十一届蓝桥杯单片机国赛

什么?4T模拟赛和省赛做起来轻轻松松?不妨来挑战一下第十一届国赛,这一届的国赛居然没考超声波、串口通信!只要你正确地理解了题目的意思,规避出题人挖的坑,拿个国一轻轻松松。

附件:第十一届蓝桥杯单片机国赛


一、题目分析以及底层代码搭建

1.内存空间不足

看完一遍题目,感觉挺正常的,不知道哪里有坑,其实,在开头的硬件框图中,出题人已经告诉了你本题最大的坑就是存储空间不够。在备赛专栏前篇中有提到,如果你定义变量的时候没有加上存储区修饰符时,该变量的存储区修饰符默认是xdate型的,xdata 变量存储在单片机的外部RAM中,访问速度最慢,并且默认值通常不为0。

所以如果你没有指定存储区修饰符,会导致以下结果:

  • 对于需要频繁使用的变量(比如定时器、Led、Seg相关变量),访问速度一旦慢就会导致时序不精确、程序性能下降,一个程序使用很多需要频繁访问的变量时会导致程序运行到某一部分时程序崩溃,你如果做过跟串口通信有关的题目就对此深有体会了吧,串口数据缓存区会时不时的恶心你一下。
  • 编译时提示错误,如code size exceeds available memory,烧录时失败,提示存储空间不足。

解决方法:

  • 减少xdate的使用,多使用pdateidate
  • 对于程序中不会更改的常量前缀加上code
  • 减少float型变量的使用

2.隐藏条件

其他要求的第四点指出:参数在参数设置时不生效,所以进行参数设置的时候,要再定义一个新的变量存放参数原来的值进行更改数值。

3.初始化底层代码

  • Init.h
c 复制代码
#ifndef __Init_H__
#define __Init_H__

void SystemInit();

#endif
  • Init.c
c 复制代码
#include <STC15F2K60S2.H>

void SystemInit()
{
	P0 = 0xff;
	P2 = P2 & 0x1f | 0x80;
	P2 &= 0x1f;
	
	P0 = 0x00;
	P2 = P2 & 0x1f | 0xa0;
	P2 &= 0x1f;
}

4.Led底层代码

  • Led.h
c 复制代码
#ifndef __Led_H__
#define __Led_H__

void LedDisp(unsigned char *ucLed);

#endif
  • Led.c
c 复制代码
#include <STC15F2K60S2.H>

void LedDisp(unsigned char *ucLed)
{
	unsigned char i, temp = 0x00;
	static unsigned char temp_old = 0xff;
	
	for(i = 0; i < 8; i++)
		temp |= (ucLed[i] << i);

	if(temp_old != temp)
	{
		P0 = ~temp;
		P2 = P2 & 0x1f | 0x80;
		P2 &= 0x1f;
		temp_old = temp;
	}
}

5.Key底层代码

  • Key.h
c 复制代码
#ifndef __Key_H__
#define __Key_H__

unsigned char KeyDisp();

#endif
  • Key.c
c 复制代码
#include <STC15F2K60S2.H>

unsigned char KeyDisp()
{
	unsigned char temp = 0;
	P44 = 0;
	P42 = 1;
	P35 = 1;
	P34 = 1;
	if(P32 == 0) temp = 5;
	if(P33 == 0) temp = 4;
	P44 = 1;
	P42 = 0;
	P35 = 1;
	P34 = 1;
	if(P32 == 0) temp = 9;
	if(P33 == 0) temp = 8;

	return temp;
}

6.Seg底层代码

  • Seg.h
c 复制代码
#ifndef __Seg_H__
#define __Seg_H__

void SegDisp(unsigned char wela, unsigned char dula, unsigned char point);

#endif
  • Seg.c
c 复制代码
#include <STC15F2K60S2.H>

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
0xff, //空
0xbf, //-
0x8e, //F
0x89, //H
0x9a  //S
};

void SegDisp(unsigned char wela, unsigned char dula, unsigned char point)
{
	P0 = 0xff;
	P2 = P2 & 0x1f | 0xe0;
	P2 &= 0x1f;
	
	P0 = (0x01 << wela);
	P2 = P2 & 0x1f | 0xc0;
	P2 &= 0x1f;
	
	P0 = Seg_Table[dula];
	if(point)
		P0 &= 0x7f;
	P2 = P2 & 0x1f | 0xe0;
	P2 &= 0x1f;
}

7.ds1302底层代码

  • ds1302.h
c 复制代码
#ifndef __ds1302_H__
#define __ds1302_H__

void SetRtc(unsigned char *Rtc);
void GetRtc(unsigned char *Rtc);

#endif
  • ds1302.c
c 复制代码
#include <STC15F2K60S2.H>
#include <intrins.h>

sbit SCK = P1^7;
sbit RST = P1^3;
sbit SDA = P2^3;

void Write_Ds1302(unsigned  char temp) 
{
	unsigned char i;
	for (i=0;i<8;i++)     	
	{ 
		SCK = 0;
		SDA = temp&0x01;
		temp>>=1; 
		SCK=1;
	}
}   

void Write_Ds1302_Byte( unsigned char address,unsigned char dat )     
{
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1; 	_nop_();  
 	Write_Ds1302(address);	
 	Write_Ds1302(dat);		
 	RST=0; 
}

unsigned char Read_Ds1302_Byte ( unsigned char address )
{
 	unsigned char i,temp=0x00;
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1;	_nop_();
 	Write_Ds1302(address);
 	for (i=0;i<8;i++) 	
 	{		
		SCK=0;
		temp>>=1;	
 		if(SDA)
 		temp|=0x80;	
 		SCK=1;
	} 
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
	SCK=1;	_nop_();
	SDA=0;	_nop_();
	SDA=1;	_nop_();
	return (temp);			
}

code unsigned char DS1302_Arr[4] = {0x84,0x82,0x80,0x8E};

void SetRtc(unsigned char *Rtc)
{
	unsigned char i;
	Write_Ds1302_Byte(DS1302_Arr[3],0x00);
	
	for(i = 0; i < 3; i++)
		Write_Ds1302_Byte(DS1302_Arr[i],Rtc[i]);
	
	Write_Ds1302_Byte(DS1302_Arr[3],0x80);
}

void GetRtc(unsigned char *Rtc)
{
	unsigned char i;
	
	for(i = 0; i < 3; i++)
		Rtc[i] = Read_Ds1302_Byte(DS1302_Arr[i]+1);
}

8.ds18b20底层代码

  • ds18b20.h
c 复制代码
#ifndef __ds18b20_H__
#define __ds18b20_H__

float TemRead();

#endif
  • ds18b20.c
c 复制代码
#include <STC15F2K60S2.H>

sbit DQ = P1^4;
//
void Delay_OneWire(unsigned int t)  
{
	unsigned char i;
	while(t--){
		for(i=0;i<12;i++);
	}
}

//
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
	Delay_OneWire(5);
}

//
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;
		}	    
		Delay_OneWire(5);
	}
	return dat;
}

//
bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;
  	Delay_OneWire(12);
  	DQ = 0;
  	Delay_OneWire(80);
  	DQ = 1;
  	Delay_OneWire(10); 
    initflag = DQ;     
  	Delay_OneWire(5);
  
  	return initflag;
}

float TemRead()
{
	idata unsigned char TL, TH;
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0x44);
	
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0xbe);
	
	TL = Read_DS18B20();
	TH = Read_DS18B20();
	
	return ((TH << 8) | TL) / 16.0;
}

9.iic底层代码

  • iic.h
c 复制代码
#ifndef __iic_H__
#define __iic_H__

unsigned char AverageFilter();

#endif
  • iic.c
c 复制代码
#include <STC15F2K60S2.H>
#include <intrins.h>

#define DELAY_TIME	5

sbit scl = P2^0;
sbit sda = P2^1;

static void I2C_Delay(unsigned char n)
{
    do
    {
        _nop_();_nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();_nop_();		
    }
    while(n--);      	
}

void I2CStart(void)
{
    sda = 1;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 0;
	I2C_Delay(DELAY_TIME);
    scl = 0;    
}

//
void I2CStop(void)
{
    sda = 0;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 1;
	I2C_Delay(DELAY_TIME);
}

//
void I2CSendByte(unsigned char byt)
{
    unsigned char i;
	
    for(i=0; i<8; i++){
        scl = 0;
		I2C_Delay(DELAY_TIME);
        if(byt & 0x80){
            sda = 1;
        }
        else{
            sda = 0;
        }
		I2C_Delay(DELAY_TIME);
        scl = 1;
        byt <<= 1;
		I2C_Delay(DELAY_TIME);
    }
	
    scl = 0;  
}

//
unsigned char I2CReceiveByte(void)
{
	unsigned char da;
	unsigned char i;
	for(i=0;i<8;i++){   
		scl = 1;
		I2C_Delay(DELAY_TIME);
		da <<= 1;
		if(sda) 
			da |= 0x01;
		scl = 0;
		I2C_Delay(DELAY_TIME);
	}
	return da;    
}

//
unsigned char I2CWaitAck(void)
{
	unsigned char ackbit;
	
    scl = 1;
	I2C_Delay(DELAY_TIME);
    ackbit = sda; 
    scl = 0;
	I2C_Delay(DELAY_TIME);
	
	return ackbit;
}

//
void I2CSendAck(unsigned char ackbit)
{
    scl = 0;
    sda = ackbit; 
	I2C_Delay(DELAY_TIME);
    scl = 1;
	I2C_Delay(DELAY_TIME);
    scl = 0; 
	sda = 1;
	I2C_Delay(DELAY_TIME);
}

unsigned char Ad()
{
	unsigned char temp;
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	I2CSendByte(0x01);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0x91);
	I2CWaitAck();
	temp = I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
	
	return temp;
}

unsigned char AverageFilter() 
{
	unsigned char i;
  unsigned int sum = 0;
	for (i = 0; i < 10; i++) 
	{
		sum += Ad();// 多次采样
  }
  return (unsigned char)(sum / 10);  
}

10.main.c

使用下面这个模板做省赛题是可以的,做国赛题就有点难受了,但是指定了存储区修饰符还是可以做的,如果你学过计算机操作系统或者嵌入式操作系统,可以试着用调度器来更改以下代码。

c 复制代码
#include <STC15F2K60S2.H>
#include "Init.h"
#include "LED.h"
#include "Key.h"
#include "Seg.h"
#include "ds1302.h"
#include "ds18b20.h"
#include "iic.h"

/* 变量声明区 */
unsigned char Key_Slow; //按键减速变量 10ms 
unsigned char Key_Val, Key_Down, Key_Up, Key_Old; //按键检测四件套
unsigned int Seg_Slow; //数码管减速变量 500ms
unsigned char Seg_Buf[] = {10,10,10,10,10,10,10,10,10,10};//数码管缓存数组
unsigned char Seg_Pos;//数码管缓存数组专用索引
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点使能数组
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//LED显示数据存放数组
unsigned int Time_1s, f;

/* 按键处理函数 */
void Key_Proc()
{
	if(Key_Slow) return;
	Key_Slow = 1; //按键减速
	
	Key_Val = Key();
	Key_Down = Key_Val & ~Key_Old;	 
	Key_Up = ~Key_Val & Key_Old;
	Key_Old = Key_Val;
	
}

/* 信息处理函数 */
void Seg_Proc()
{
	if(Seg_Slow) return;
	Seg_Slow = 1; //数码管减速
	
}

/* 其他显示函数 */
void Led_Proc()
{

}

/* 定时器0只用于计数 */
void Timer0_Init(void)		//1毫秒@12.000MHz
{
	TMOD &= 0xF0;			//设置定时器模式
    TMOD |= 0x05;
	TL0 = 0;				//设置定时初始值
	TH0 = 0;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
}

/* 定时器1用于计时 */
void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x18;				//设置定时初始值
	TH1 = 0xFC;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
    ET1 = 1;
	EA = 1;
}

/* 定时器1中断服务函数 */
void Timer1_Server() interrupt 3
{
	if(++Key_Slow == 10) Key_Slow = 0; //按键延迟
	if(++Seg_Slow == 100) Seg_Slow = 0; //数码管延迟
	if(++Seg_Pos == 8) Seg_Pos = 0;	   //数码管显示
  	Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
	LED_Disp(Seg_Pos,ucLed[Seg_Pos]);
	/* NE555 */
    if(++Time_1s == 1000)
    {
      Time_1s = 0;
      f = (TH0 << 8) | TL0;
      TH0 = TL0 = 0;
    }
}

void main()
{
	Init();
	Timer0_Init();
	Timer1_Init();
	while(1)
	{
		Key_Proc(); 
		Seg_Proc();
		Led_Proc();
	}
}

二、数码管模块

1.代码框架构建


可以定义bit MainMode来控制主页面(0-数据页面 1-参数页面),在数据页面中使用unsigned char SegMode来控制显示页面(0-时间显示 1-温度显示 2-亮暗状态),在参数页面中使用unsigned char SetMode来控制显示页面(0-时间参数 1-温度参数 2-指示灯参数)。

可以得到以下伪代码:

c 复制代码
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long int u32;

/* 界面参数 */  
idata bit MainMode;			  //主界面 0-数据界面 1-参数界面
idata u8 SegMode; 		  	//数据界面分界面 0-时间 1-温度 2-亮暗状态
idata u8 SetMode;         //参数界面分界面 0-时间参数 1-温度参数 2-指示灯参数

void SegProc()
{
	if(!MainMode)//数据界面
	{
		switch(SegMode)
		{
			case 0://时间显示页面
				//...
			break;
				
			case 1://温度显示页面
				//...
			break;
			
			case 2://亮暗状态显示页面
				//...
			break;
		}
	}
	else//参数界面
	{
		switch(SetMode)
		{
			case 0://时间参数
				//...
			break;
				
			case 1://温度参数
				//...
			break;
			
			case 2://指示灯参数
				//...
			break;
		}
	}
}

void KeyProc()
{
	KeyVal = KeyDisp();
	KeyDown = KeyVal & ~KeyOld;
	KeyDown = ~KeyVal & KeyOld;
	KeyOld = KeyVal;
	
	switch(KeyDown)
	{
		case 4:
			MainMode = !MainMode;
		break;
		
		case 5:
			if(!MainMode)//数据界面
			{
				SegMode++;
				if(SegMode == 3)
					SegMode = 0;
			}
			else//参数界面
			{
				SetMode++;
				if(SetMode == 3)
					SetMode = 0;
			}
		break;
	}
}

2.数据页面

时间显示页面和温度显示页面都很容易实现,这边不使用float型变量去接收实时温度读取函数的返回值,而是使用unsigned int类型变量去接收实时温度读取函数的返回值放大10倍的值。

也可以在数码管函数内直接处理温度读取和时钟读取,但是可能数码管函数返回时有任务没做完,最好是分成多个处理函数分别处理对应的任务。

c 复制代码
/* 温度 */
idata u16 Tem_10x;        //温度读取放大10倍
/* 时间 */
pdata u8 Rtc[3] = {0x16,0x59,0x50};

void DS18B20Proc()
{
	Tem_10x = TemRead() * 10;
}

void DS1302Proc()
{
	GetRtc(Rtc);
}

void SegProc()
{
	unsigned char i;
	if(!MainMode)//数据界面
	{
		switch(SegMode)
		{
			case 0:
				SegPoint[2] = 0;
				SegBuf[2] = SegBuf[5] = 11;
				for(i = 0; i < 3; i++)
				{
					SegBuf[3*i] = Rtc[i] / 16;
					SegBuf[3*i+1] = Rtc[i] % 16;
				}
			break;
				
			case 1:
				SegBuf[0] = 12;
				SegBuf[1] = SegBuf[2] = SegBuf[3] = SegBuf[4] = 10;
				SegBuf[5] = Tem_10x / 100;
				SegBuf[6] = Tem_10x / 10 % 10;
				SegBuf[7] = Tem_10x % 10;
				SegPoint[6] = 1;
			break;
		}
	}
}			

void main()
{
	SystemInit();
	Timer0_Init();
	SetRtc(Rtc);//上电时设置当前时间
	while(1)
	{
		//...
	}
}

判断电压低于多少时为遮光状态,这个数值是需要你根据板子上的情况进行判断的,先假设一个数值为遮光临界值,然后用夹逼法慢慢求出最接近遮光状态的数值,一般来说,电压低于1V为遮光状态。

同样的,为了避免使用float型变量,下面也是使用unsigned int型变量接受光敏电阻的电压放大100倍后的电压值。

c 复制代码
/* AD */
idata u16 RD1_100x;       //光敏电阻电压放大100倍
idata bit DarkFlag;       //环境为暗检测标志位 0-亮 1-暗

void ADProc()
{
	RD1_100x = AverageFilter() / 51.0 * 100;
	DarkFlag = (RD1_100x < 100) ? 1 : 0;
}

void SegProc()
{
	unsigned char i;
	if(!MainMode)//数据界面
	{
		switch(SegMode)
		{
			case 0:
				//...
			break;
				
			case 1:
				//...
			break;
			
			case 2:
				SegBuf[0] = 13;
				SegBuf[1] = 10;
				SegBuf[2] = RD1_100x / 100;
				SegBuf[3] = RD1_100x / 10 % 10;
				SegBuf[4] = RD1_100x % 10;
				SegBuf[5] = 10;
				SegBuf[6] = 10;
				SegBuf[7] = DarkFlag;
				SegPoint[2] = 1;
				SegPoint[6] = 0;
			break;
		}
	}
}

3.参数页面


参数界面的实现是很容易的,这边注意的是参数在退出时才生效,所以每个参数都要再定义一个新的参数接收原始值。

c 复制代码
/* 温度 */
idata u16 Tem_10x;        //温度读取放大10倍
idata u8 TemSet = 25;     //温度参数,默认值25,范围00~99
idata u8 TemSetDis = 25;  //温度参数在修改过程中的值
/* 时间 */
pdata u8 Rtc[3] = {0x16,0x59,0x50};
idata u8 HourSet = 17;    //小时参数,默认值17,范围00~23	
idata u8 HourSetDis = 17; //小时参数在修改过程中的值
/* LED */
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
c 复制代码
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long int u32;

/* 界面参数 */  
idata bit MainMode;			  //主界面 0-数据界面 1-参数界面
idata u8 SegMode; 		  	//数据界面分界面 0-时间 1-温度 2-亮暗状态
idata u8 SetMode;         //参数界面分界面 0-时间参数 1-温度参数 2-指示灯参数
/* AD */
idata u16 RD1_100x;       //光敏电阻电压放大100倍
idata bit DarkFlag;       //环境为暗检测标志位 0-亮 1-暗
/* 温度 */
idata u16 Tem_10x;        //温度读取放大10倍
idata u8 TemSet = 25;     //温度参数,默认值25,范围00~99
idata u8 TemSetDis = 25;  //温度参数在修改过程中的值
/* 时间 */
pdata u8 Rtc[3] = {0x16,0x59,0x50};
idata u8 HourSet = 17;    //小时参数,默认值17,范围00~23	
idata u8 HourSetDis = 17; //小时参数在修改过程中的值
/* LED */
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
/* 数码管 */
idata u8 SegPos;
pdata u8 SegBuf[8] = {10,10,10,10,10,10,10,10};
pdata u8 SegPoint[8] = {0,0,0,0,0,0,0,0};

void DS18B20Proc()
{
	Tem_10x = TemRead() * 10;
}

void DS1302Proc()
{
	GetRtc(Rtc);
}

void ADProc()
{
	RD1_100x = AverageFilter() / 51.0 * 100;
	DarkFlag = (RD1_100x < 100) ? 1 : 0;
}

void SegProc()
{
	unsigned char i;
	if(!MainMode)//数据界面
	{
		//...
	}
	else//参数界面
	{
		switch(SetMode)
		{
			case 0:
				SegPoint[2] = SegPoint[6] = 0;
				SegBuf[0] = 13;
				SegBuf[1] = 4;
				SegBuf[2] = 10;
				SegBuf[3] = 10;
				SegBuf[4] = 10;
				SegBuf[5] = 10;
				SegBuf[6] = (HourSetDis / 10) ? HourSetDis / 10 : 10;
				SegBuf[7] = HourSetDis % 10;
			break;
			
			case 1:
				SegBuf[0] = 13;
				SegBuf[1] = 5;
				SegBuf[6] = (TemSetDis / 10) ? TemSetDis / 10 : 10;
				SegBuf[7] = TemSetDis % 10;
			break;
			
			case 2:
				SegBuf[0] = 13;
				SegBuf[1] = 6;
				SegBuf[6] = 10;
				SegBuf[7] = LedSetDis;
			break;
		}
	}
}

4.数码管完整代码

c 复制代码
void SegProc()
{
	unsigned char i;
	if(!MainMode)//数据界面
	{
		switch(SegMode)
		{
			case 0:
				SegPoint[2] = 0;
				SegBuf[2] = SegBuf[5] = 11;
				for(i = 0; i < 3; i++)
				{
					SegBuf[3*i] = Rtc[i] / 16;
					SegBuf[3*i+1] = Rtc[i] % 16;
				}
			break;
				
			case 1:
				SegBuf[0] = 12;
				SegBuf[1] = SegBuf[2] = SegBuf[3] = SegBuf[4] = 10;
				SegBuf[5] = Tem_10x / 100;
				SegBuf[6] = Tem_10x / 10 % 10;
				SegBuf[7] = Tem_10x % 10;
				SegPoint[6] = 1;
			break;
			
			case 2:
				SegBuf[0] = 13;
				SegBuf[1] = 10;
				SegBuf[2] = RD1_100x / 100;
				SegBuf[3] = RD1_100x / 10 % 10;
				SegBuf[4] = RD1_100x % 10;
				SegBuf[5] = 10;
				SegBuf[6] = 10;
				SegBuf[7] = DarkFlag;
				SegPoint[2] = 1;
				SegPoint[6] = 0;
			break;
		}
	}
	else//参数界面
	{
		switch(SetMode)
		{
			case 0:
				SegPoint[2] = SegPoint[6] = 0;
				SegBuf[0] = 13;
				SegBuf[1] = 4;
				SegBuf[2] = 10;
				SegBuf[3] = 10;
				SegBuf[4] = 10;
				SegBuf[5] = 10;
				SegBuf[6] = (HourSetDis / 10) ? HourSetDis / 10 : 10;
				SegBuf[7] = HourSetDis % 10;
			break;
			
			case 1:
				SegBuf[0] = 13;
				SegBuf[1] = 5;
				SegBuf[6] = (TemSetDis / 10) ? TemSetDis / 10 : 10;
				SegBuf[7] = TemSetDis % 10;
			break;
			
			case 2:
				SegBuf[0] = 13;
				SegBuf[1] = 6;
				SegBuf[6] = 10;
				SegBuf[7] = LedSetDis;
			break;
		}
	}
}

三、按键模块

S4和S5已经在数码管模块中说过了,这边需要注意的是切换主页面进入的是对应主页面的第一个页面。

S8和S9的实现也是很简单的,只需要注意每一个参数的初始值和边界值就可以了。

c 复制代码
/* LED */
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
/* 温度 */
idata u16 Tem_10x;        //温度读取放大10倍
idata u8 TemSet = 25;     //温度参数,默认值25,范围00~99
idata u8 TemSetDis = 25;  //温度参数在修改过程中的值
/* 时间 */
pdata u8 Rtc[3] = {0x16,0x59,0x50};
idata u8 HourSet = 17;    //小时参数,默认值17,范围00~23	
idata u8 HourSetDis = 17; //小时参数在修改过程中的值
/* 按键 */
idata u8 KeyVal,KeyDown,KeyUp,KeyOld;

void KeyProc()
{
	KeyVal = KeyDisp();
	KeyDown = KeyVal & ~KeyOld;
	KeyDown = ~KeyVal & KeyOld;
	KeyOld = KeyVal;
	
	switch(KeyDown)
	{
		case 4:
			if(!MainMode)
			{
				TemSetDis = TemSet;
				HourSetDis = HourSet;
				LedSetDis = LedSet;
				MainMode = 1;
				SetMode = 0;
			}
			else
			{
				TemSet = TemSetDis;
				HourSet = HourSetDis;
				LedSet = LedSetDis;
				MainMode = 0;
				SegMode = 0;
			}
		break;
		
		case 5:
			if(!MainMode)//数据界面
			{
				SegMode++;
				if(SegMode == 3)
					SegMode = 0;
			}
			else//参数界面
			{
				SetMode++;
				if(SetMode == 3)
					SetMode = 0;
			}
		break;
			
		case 8://减按键
			if(MainMode)
			{
				if(SetMode == 0)
				{
					if(--HourSetDis == 255)
						HourSetDis = 0;
				}
				else if(SetMode == 1)
				{
					if(--TemSetDis == 255)
						TemSetDis = 0;
				}
				else
				{
					if(--LedSetDis == 3)
						LedSetDis = 4;
				}
			}
		break;
			
		case 9://加按键
			if(MainMode)
			{
				if(SetMode == 0)
				{
					if(++HourSetDis == 24)
						HourSetDis = 23;
				}
				else if(SetMode == 1)
				{
					if(++TemSetDis == 100)
						TemSetDis = 99;
				}
				else
				{
					if(++LedSetDis == 9)
						LedSetDis = 8;
				}
			}
		break;
	}
}

四、Led模块

c 复制代码
/* LED */
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
idata bit L1Light;        //指示灯L1点亮标志位 0灭 1亮
idata bit L2Light;        //指示灯L2点亮标志位 0灭 1亮
idata bit L3Light;        //指示灯L3点亮标志位 0灭 1亮
idata u16 Time_3s;        //暗环境计时变量
idata u16 Time_3000ms;    //亮环境计时变量
  • L1使能判断:若判断当前时间处于小时参数整点至下一个 8 时之间,指示灯 L1 点亮,反之熄灭。
    下一个8时,简单理解就是24+8=32时,所以只需要判断当前时间是否在小时参数和32区间即可,注意的是,当前时间是以十六进制存储的,所以比较时要转换成十进制再比较。
c 复制代码
void DS1302Proc()
{
	unsigned char Hour;
	GetRtc(Rtc);
	Hour = (Rtc[0] / 16) * 10 + Rtc[0] % 16;
	L1Light = (HourSet <= Hour && Hour < 32);
}
void LedProc()
{
	ucLed[0] = L1Light;
}
  • L2使能判断:若判断当采集到的温度数据小于温度参数,指示灯 L2 点亮,反之熄灭。
c 复制代码
void DS18B20Proc()
{
	Tem_10x = TemRead() * 10;
	L2Light = (Tem_10x < TemSet * 10);
}

void LedProc()
{
	ucLed[1] = L2Light;
}
  • L3使能判断:若判断环境处于"暗"状态,且持续时间超过 3 秒,指示灯 L3 点亮;环境处于"亮"状态,且持续时间超过 3 秒,指示灯 L3 熄灭
    L3使能是有很多种方法的,当处于黑暗环境下时,对应的计时变量开始计时,超过3秒时让L3点亮,处于明亮环境下也一样。
    什么时候清除计时变量呢?可以在黑暗环境下清除明亮环境的计时变量,在明亮环境清除黑暗环境下的计时变量。
c 复制代码
/* LED */
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
idata bit L1Light;        //指示灯L1点亮标志位 0灭 1亮
idata bit L2Light;        //指示灯L2点亮标志位 0灭 1亮
idata bit L3Light;        //指示灯L3点亮标志位 0灭 1亮
idata u16 Time_3s;        //暗环境计时变量
idata u16 Time_3000ms;    //亮环境计时变量
c 复制代码
void ADProc()
{
	RD1_100x = AverageFilter() / 51.0 * 100;
	DarkFlag = (RD1_100x < 100) ? 1 : 0;
	DarkFlag ? (Time_3000ms = 0) : (Time_3s = 0);
}

void LedProc()
{
	ucLed[2] = L3Light;
}

void Timer0_Isr(void) interrupt 1
{
	if(++SegPos == 8)
		SegPos = 0;
	
	SegDisp(SegPos,SegBuf[SegPos],SegPoint[SegPos]);
	
	if(DarkFlag)//黑暗环境
	{
		if(++Time_3s >= 3001)
		{
			Time_3s = 3001;
			L3Light = 1;
		}
	}
	else//光明环境
	{
		if(++Time_3000ms >= 3001)
		{
			Time_3000ms = 3001;
			L3Light = 0;
		}
	}
}
  • L4~L8使能判断:若判断环境处于"暗"状态,通过 LED 指示灯参数指定的 LED 指示灯点亮,反之熄灭, L4-L8 中未被指定的 LED 指示灯应处于熄灭状态。
    由于点亮/熄灭指示灯是可以自定义的,所以需要加上互斥点亮(如果你只写ucLed[LedSet-1]=DarkFlag,不能实现这种情况:在黑暗环境下,第四个灯亮,然后进入参数页面改成第五个灯亮,这样子第四个、第五个灯都会亮。)
c 复制代码
/* LED */
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
idata bit L1Light;        //指示灯L1点亮标志位 0灭 1亮
idata bit L2Light;        //指示灯L2点亮标志位 0灭 1亮
idata bit L3Light;        //指示灯L3点亮标志位 0灭 1亮
idata u16 Time_3s;        //暗环境计时变量
idata u16 Time_3000ms;    //亮环境计时变量
c 复制代码
void LedProc()
{
	if(DarkFlag)
	{
		for(i = 3; i < 8; i++)
			ucLed[i] = (i == LedSet-1);
	}
	else
		for(i = 3; i < 8; i++)
			ucLed[i] = 0;
}

五、代码整合

c 复制代码
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Seg.h"
#include "Key.h"
#include "Led.h"
#include "iic.h"
#include "ds1302.h"
#include "ds18b20.h"

typedef unsigned char u8;
typedef unsigned int u16;

/* 界面参数 */  
idata bit MainMode;		  //主界面 0-数据界面 1-参数界面
idata u8 SegMode; 		  //数据界面分界面 0-时间 1-温度 2-亮暗状态
idata u8 SetMode;         //参数界面分界面 0-时间参数 1-温度参数 2-指示灯参数
/* AD */
idata u16 RD1_100x;       //光敏电阻电压放大100倍
idata bit DarkFlag;       //环境为暗检测标志位 0-亮 1-暗
/* 温度 */
idata u16 Tem_10x;        //温度读取放大10倍
idata u8 TemSet = 25;     //温度参数,默认值25,范围00~99
idata u8 TemSetDis = 25;  //温度参数在修改过程中的值
/* 时间 */
pdata u8 Rtc[3] = {0x16,0x59,0x50};
idata u8 HourSet = 17;    //小时参数,默认值17,范围00~23	
idata u8 HourSetDis = 17; //小时参数在修改过程中的值
/* 按键 */
idata u8 KeyVal,KeyDown,KeyUp,KeyOld;
/* LED */
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u8 LedSet = 4;      //指示灯参数,默认值4,范围4~8
idata u8 LedSetDis = 4;   //指示灯参数修改过程中的值
idata bit L1Light;        //指示灯L1点亮标志位 0灭 1亮
idata bit L2Light;        //指示灯L2点亮标志位 0灭 1亮
idata bit L3Light;        //指示灯L3点亮标志位 0灭 1亮
idata u16 Time_3s;        //暗环境计时变量
idata u16 Time_3000ms;    //亮环境计时变量
/* 数码管 */
idata u8 SegPos;
pdata u8 SegBuf[8] = {10,10,10,10,10,10,10,10};
pdata u8 SegPoint[8] = {0,0,0,0,0,0,0,0};

void KeyProc()
{
	KeyVal = KeyDisp();
	KeyDown = KeyVal & ~KeyOld;
	KeyDown = ~KeyVal & KeyOld;
	KeyOld = KeyVal;
	
	switch(KeyDown)
	{
		case 4:
			if(!MainMode)
			{
				TemSetDis = TemSet;
				HourSetDis = HourSet;
				LedSetDis = LedSet;
				MainMode = 1;
				SetMode = 0;
			}
			else
			{
				TemSet = TemSetDis;
				HourSet = HourSetDis;
				LedSet = LedSetDis;
				MainMode = 0;
				SegMode = 0;
			}
		break;
		
		case 5:
			if(!MainMode)//数据界面
			{
				SegMode++;
				if(SegMode == 3)
					SegMode = 0;
			}
			else//参数界面
			{
				SetMode++;
				if(SetMode == 3)
					SetMode = 0;
			}
		break;
			
		case 8://减按键
			if(MainMode)
			{
				if(SetMode == 0)
				{
					if(--HourSetDis == 255)
						HourSetDis = 0;
				}
				else if(SetMode == 1)
				{
					if(--TemSetDis == 255)
						TemSetDis = 0;
				}
				else
				{
					if(--LedSetDis == 3)
						LedSetDis = 4;
				}
			}
		break;
			
		case 9://加按键
			if(MainMode)
			{
				if(SetMode == 0)
				{
					if(++HourSetDis == 24)
						HourSetDis = 23;
				}
				else if(SetMode == 1)
				{
					if(++TemSetDis == 100)
						TemSetDis = 99;
				}
				else
				{
					if(++LedSetDis == 9)
						LedSetDis = 8;
				}
			}
		break;
	}
}

void SegProc()
{
	unsigned char i;
	if(!MainMode)//数据界面
	{
		switch(SegMode)
		{
			case 0:
				SegPoint[2] = 0;
				SegBuf[2] = SegBuf[5] = 11;
				for(i = 0; i < 3; i++)
				{
					SegBuf[3*i] = Rtc[i] / 16;
					SegBuf[3*i+1] = Rtc[i] % 16;
				}
			break;
				
			case 1:
				SegBuf[0] = 12;
				SegBuf[1] = SegBuf[2] = SegBuf[3] = SegBuf[4] = 10;
				SegBuf[5] = Tem_10x / 100;
				SegBuf[6] = Tem_10x / 10 % 10;
				SegBuf[7] = Tem_10x % 10;
				SegPoint[6] = 1;
			break;
			
			case 2:
				SegBuf[0] = 13;
				SegBuf[1] = 10;
				SegBuf[2] = RD1_100x / 100;
				SegBuf[3] = RD1_100x / 10 % 10;
				SegBuf[4] = RD1_100x % 10;
				SegBuf[5] = 10;
				SegBuf[6] = 10;
				SegBuf[7] = DarkFlag;
				SegPoint[2] = 1;
				SegPoint[6] = 0;
			break;
		}
	}
	else//参数界面
	{
		switch(SetMode)
		{
			case 0:
				SegPoint[2] = SegPoint[6] = 0;
				SegBuf[0] = 13;
				SegBuf[1] = 4;
				SegBuf[2] = 10;
				SegBuf[3] = 10;
				SegBuf[4] = 10;
				SegBuf[5] = 10;
				SegBuf[6] = (HourSetDis / 10) ? HourSetDis / 10 : 10;
				SegBuf[7] = HourSetDis % 10;
			break;
			
			case 1:
				SegBuf[0] = 13;
				SegBuf[1] = 5;
				SegBuf[6] = (TemSetDis / 10) ? TemSetDis / 10 : 10;
				SegBuf[7] = TemSetDis % 10;
			break;
			
			case 2:
				SegBuf[0] = 13;
				SegBuf[1] = 6;
				SegBuf[6] = 10;
				SegBuf[7] = LedSetDis;
			break;
		}
	}
}

void LedProc()
{
	unsigned char i;
	ucLed[0] = L1Light;
	ucLed[1] = L2Light;
	ucLed[2] = L3Light;
	if(DarkFlag)
	{
		for(i = 3; i < 8; i++)
			ucLed[i] = (i == LedSet-1);
	}
	else
		for(i = 3; i < 8; i++)
			ucLed[i] = 0;
	LedDisp(ucLed);
}

void DS18B20Proc()
{
	Tem_10x = TemRead() * 10;
	L2Light = (Tem_10x < TemSet * 10);
}

void DS1302Proc()
{
	unsigned char Hour;
	GetRtc(Rtc);
	Hour = (Rtc[0] / 16) * 10 + Rtc[0] % 16;
	L1Light = (HourSet <= Hour && Hour < 32);
}

void ADProc()
{
	RD1_100x = AverageFilter() / 51.0 * 100;
	DarkFlag = (RD1_100x < 100) ? 1 : 0;
	DarkFlag ? (Time_3000ms = 0) : (Time_3s = 0);
}

void Timer0_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
	EA = 1;
}

void Timer0_Isr(void) interrupt 1
{
	if(++SegPos == 8)
		SegPos = 0;
	
	SegDisp(SegPos,SegBuf[SegPos],SegPoint[SegPos]);
	
	if(DarkFlag)
	{
		if(++Time_3s >= 3001)
		{
			Time_3s = 3001;
			L3Light = 1;
		}
	}
	else
	{
		if(++Time_3000ms >= 3001)
		{
			Time_3000ms = 3001;
			L3Light = 0;
		}
	}
}

void main()
{
	SystemInit();
	Timer0_Init();
	SetRtc(Rtc);
	while(1)
	{
		//
	}
}

如果程序有错误或者不理解的地方,请及时联系我。

相关推荐
无际单片机编程2 小时前
单片机OTA升级中Bootloader怎么判断APP有没有问题?
java·stm32·单片机·嵌入式硬件·嵌入式
代码总长两年半2 小时前
STM32---FreeRTOS消息队列
stm32·单片机·嵌入式硬件
触角010100013 小时前
STM32 I2C驱动开发全解析:从理论到实战 | 零基础入门STM32第五十步
驱动开发·stm32·单片机·嵌入式硬件
赴遥4 小时前
ESP32S3N16R8驱动ST7701S屏幕(vscode+PlatfoemIO)
vscode·单片机·esp32·st7701s
沐欣工作室_lvyiyi5 小时前
基于单片机的防火防盗报警系统设计(论文+源码)
人工智能·stm32·单片机·嵌入式硬件·物联网·目标跟踪
廿二松柏木5 小时前
三级嵌入式学习ing 考点25、26
单片机·嵌入式硬件·学习
技术干货贩卖机6 小时前
0基础 | 看懂原理图Datasheet 系列1
笔记·stm32·单片机·嵌入式硬件·学习
待什么青丝9 小时前
【TMS570LC4357】之工程创建
c语言·单片机
summer__77779 小时前
3.3.2 Proteus第一个仿真图
单片机·proteus