蓝桥杯单片机第十三届国赛真题解析(西风版本模板)

     还是一样,先叠个夹,驻波本人大一,专业知识不是很强,第一次写博客,若有错误欢迎指出,也希望各位多多包涵!!!

   十三届国赛的题目试题总体不是很难,唯一的难点就是MOTOR输出,还有考的设备比较多,考察平时对底层的积累。

一.数码管

     首先我们还是先看数码管

        数码管整体不是很难,我在这就直接上代码了。

/*变量区域*/
idata uc Seg_Disp_Mode = 0;//0 频率界面 1湿度界面 2测距界面 3参数界面
idata bit Freq_Show_Mode = 0;//频率显示模式 0HZ 1KHZ
idata bit Distance_Show_Mode = 0;//测距显示模式 0CM 1M
idata uc Param_Show_Mode = 0;//参数显示模式 0频率参数 1湿度参数 2距离参数
idata bit Seg_Enable = 0;

/* 数据变量 */
idata ui Freq_Time = 0;//频率计时变量 1s
idata ui Frequency = 0;//频率
idata uc Hum = 0;//湿度
idata uc Distance = 0;//距离
idata uc Param[3] = {90,40,6};//0频率参数*10 10-120 1湿度参数 10-60 2距离参数*10 1-12

/*代码区域*/
void Seg_Proc()
{
	if(Seg_Enable == 0)
	 return;
	switch (Seg_Disp_Mode)
	{
	case 0:
		Seg_Buf[0] = 11;//F
		Seg_Buf[1] = 10;
		if(Freq_Show_Mode == 0)//如果在HZ界面
		{
			Seg_Buf[2] = (Frequency>=100000)?Frequency/100000%10:10;
			Seg_Buf[3] = (Frequency>=10000)?Frequency/10000%10:10;
			Seg_Buf[4] = (Frequency>=1000)?Frequency/1000%10:10;
			Seg_Buf[5] = (Frequency>=100)?Frequency/100%10:10;
			Seg_Buf[6] = (Frequency>=10)?Frequency/10%10:10;
			Seg_Buf[7] = Frequency%10;
		}
		else
		{
			Seg_Buf[2] = (Frequency>=10000000)?Frequency/10000000%10:10;
			Seg_Buf[3] = (Frequency>=1000000)?Frequency/1000000%10:10;
			Seg_Buf[4] = (Frequency>=100000)?Frequency/100000%10:10;
			Seg_Buf[5] = (Frequency>=10000)?Frequency/10000%10:10;
			Seg_Buf[6] = Frequency/1000%10+',';
			Seg_Buf[7] = Frequency/100%10;
		}
		break;
	  case 1:
		Seg_Buf[0] = 12;//H
		Seg_Buf[1] = 10;
		Seg_Buf[2] = 10;
		Seg_Buf[3] = 10;
		Seg_Buf[4] = 10;
		Seg_Buf[5] = 10;
		Seg_Buf[6] = (Hum>=10)?Hum/10:10;
		Seg_Buf[7] = Hum%10;
		break;
	  case 2:
		Seg_Buf[0] = 13;//A
		Seg_Buf[1] = 10;
		Seg_Buf[2] = 10;
		Seg_Buf[3] = 10;
		Seg_Buf[4] = 10;
		if(Distance_Show_Mode == 0)//如果在CM界面
		{
			Seg_Buf[5] = (Distance>=100)?Distance/100%10:10;
			Seg_Buf[6] = (Distance>=10)?Distance/10%10:10;
			Seg_Buf[7] = Distance%10;
		}
		else //如果在M界面
		{
			Seg_Buf[5] = Distance/100%10+',';
			Seg_Buf[6] = Distance/10%10;
			Seg_Buf[7] = Distance%10;
		}
        break;
	  case 3:
		Seg_Buf[0] = 14;//P
		Seg_Buf[1] = Param_Show_Mode+1;
		Seg_Buf[2] = 10;
		Seg_Buf[3] = 10;
		Seg_Buf[4] = 10;
		switch (Param_Show_Mode)
		{
		case 0:
		    Seg_Buf[5] = (Param[0]>=100)?Param[0]/100%10:10;
            Seg_Buf[6] = Param[0]/10%10+',';
            Seg_Buf[7] = Param[0]%10;
			break;
		case 1:
		    Seg_Buf[5] = 10;
		    Seg_Buf[6] = (Param[1]>=10)?Param[1]/10%10:10;
            Seg_Buf[7] = Param[1]%10;
			break;
		case 2:
		    Seg_Buf[6] = 10;
		    Seg_Buf[6] = Param[2]/10%10+',';
            Seg_Buf[7] = Param[2]%10;
			break;
		}
		break;
	}
}

二.键盘   

     然后来看键盘 

    在这里我们要注意:题目中所提及的是独立按键而不是矩阵键盘,所以要修改一下底层到独立键盘,若使用矩阵键盘则4T连坐到个位数分数。所以比赛时务必注意。

   

    这里还是没啥难点,要注意的是参数临界情况是循环的情况,即最大值->最小值,最小值->最大值。以及长按后继电器计数清零要更新EEPROM的存储值。

    没啥难点,还是直接上代码。

void Key_Proc()
{
  //读取按键值
  Key_Val = Key_Read();
  //判断按键是否按下
	Key_Down = Key_Val & (Key_Val ^ Key_Old);
  //判断按键是否释放
	Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
  //更新按键旧值
	Key_Old = Key_Val;
	switch (Key_Down)
	{	
		case 4:
		Seg_Disp_Mode = (++Seg_Disp_Mode)% 4;
		if(Seg_Disp_Mode == 3)
		 Param_Show_Mode = 0;
		break;
		case 5:
		if(Seg_Disp_Mode == 3)//参数界面
		{
			Param_Show_Mode = (++Param_Show_Mode)% 3;
		}
		break;
		case 6:
		if(Seg_Disp_Mode == 2)//测距界面
		{
			Distance_Show_Mode = !Distance_Show_Mode;
		}
		else if(Seg_Disp_Mode == 3)//参数界面
		{
			switch (Param_Show_Mode)
			{
			case 0:
				Param[0] += 5;
				if(Param[0] > 120)
					Param[0] = 10;
				break;
			case 1:
				Param[1] += 10;
				if(Param[1] > 60)
					Param[1] = 10;
				break;
			case 2:
				if(++Param[2] > 12)
					Param[2] = 1;
				break;
			}
		}
		break;
		case 7:
		if(Seg_Disp_Mode == 0)//频率界面
		{
			Freq_Show_Mode = !Freq_Show_Mode;
		}
		else if(Seg_Disp_Mode == 3)//参数界面
		{
			switch (Param_Show_Mode)
			{
			case 0:
				Param[0] -= 5;
				if(Param[0] < 10)
					Param[0] = 120;
				break;
			case 1:
				Param[1] -= 10;
				if(Param[1] < 10)	
					Param[1] = 60;
				break;
			case 2:
				if(--Param[2] < 1)
					Param[2] = 12;
				break;
			}
		}
		break;
	}
	if(Seg_Disp_Mode == 1)//湿度界面
	{
	    if(Key_Down == 7)
		  Long_Down_Judge = 1;
		if(Key_Up == 7)
		{
			Long_Down_Judge = 0;
			if(Long_Down_Time > 1000)
			Relay_Count = 0;
			EEPROM_Write(&Relay_Count,0,1);
			Long_Down_Time = 0;
		}
	}
}

 三.外设+Led

         本题中考的外设比较多考了Freq频率测量,EEPROM,AD_DA电压输出,超声波,还有一个考的极少的Motor电机输出。我们发现在这里需要用中断的外设较多,Freq、超声波、Motor脉冲、还有调度器等主函数本来要用的一个定时器。所以我们定时器基本要全部用上。

         本题中我对定时器的规划是这样的:Freq频率测量用定时器0,调度器和主函数的中断需求用定时器1,Motor 用定时器2,超声波用pca定时器。

         我们由简到难,先来看Freq频率测量

    1.Freq频率测量

              本题中freq频率测量没有啥子难点,考察的比较常规。还是先直接上代码。

/* 定时器0初始化函数 */
void Timer0Init(void)		//100微秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x05;
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x00;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}

/*测量函数*/
void Read_Freq()
{ 
	Seg_Enable = 1;//数码管使能
	Frequency = TL0|(TH0<<8);
	TH0 = 0;
	TL0 = 0;
}
/*调度器*/
/*配置调度器*/
idata task_c Scheduler_Task[] = 
{
    {Led_Proc,1,0},//Led运行函数 1ms执行一次
	{Key_Proc,10,0},//按键运行函数 1ms执行一次
	{Seg_Proc,100,0},//数码管运行函数 100ms执行一次
	{AD_DA,160,0},//数模电读取函数 160ms执行一次
	{Read_Distance,500,0},//距离读取函数 500ms执行一次
	{Read_Freq,1000,0},//频率读取函数 1000ms执行一次
};

            在上面的代码中我对于频率测量做了2个小小的改变

         (1)我们数码管的第一次测量需要1s的时间,但是在这个时间当中我们的数码管很大概率已经开始显示。这样就会出现Freq开始显示0的情况。为了避免这种情况我给数码管定义了一个使能变量。当完成第一次频率测量时数码管才开始使能,数码管才开始显示。(具体代码见上面数码管模块)这样子就避免了频率开始显示0的情况。

        (2)我讲频率测量放进了调度器当中,而不是中断。这样子可以避免中断的代码过多,引起一些Bug,同时不可避免的频率测量的精度也变低了。(但是通过4t没有问题)

        2.AD_DA输出 

         AD_DA在本题中分为2个模块:(1)通过Ad读取电压来换算成湿度数据

                                                            (2)通过湿度数据来输出电压

         两个模块都没啥难度,主要考察的就是直线图的换算,建议就是把每一个数据都强制类型转换成float。这样子可以避免可能的整数型造成误差。直接上代码。

/*AD_DA*/
void AD_DA()
{
  float temp = 0;
	//湿度读取
  Hum = (uc)(Ad_Read(0x43)/51.0*20.0);

  if(Hum<Param[1])
    temp = 1;
  else if(Hum>=Param[1] && Hum<80)
    temp = 4.0/(80 - Param[1])*(Hum - Param[1]) + 1;
  else
   temp  = 5;
  Da_Write(temp*51.0);
}

    3.超声波

    超声波出的也比较常规,唯一的难点在数码管显示的2个模式(cm,m)下(怎么写具体看我十四届国赛的博客https://blog.csdn.net/2402_87342010/article/details/148035388?spm=1001.2014.3001.5501) 。

我们还是直接上代码

//U_Wave.c
#include <U_Wave.h>
#include <intrins.h>

sbit RX = P1^1;
sbit TX = P1^0;	

void Delay12us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	i = 3;
	while (--i);
}


void U_Wave_Init()
{
	unsigned char i;
  EA = 0;//关闭中断
	for(i=0;i<8;i++)
	{
	  TX = 1;
		Delay12us();
		TX = 0;
		Delay12us();
	}
  EA = 1;//开启中断
}

unsigned char U_Wave_Data()
{
	unsigned long int U_Wave_Time;
	//初始化PCL
  CMOD  = 0x00;
	CH = CL = 0;
	U_Wave_Init();//发送超声波
	CR = 1;
	while((RX == 1)&&(CF == 0));//若接受到超声波 RX = 0 若计时器溢出CF = 1
	CR = 0;//停止计时
	if(CF == 0)
	{
	  U_Wave_Time = (CH<<8)|CL;
		return U_Wave_Time*0.017;
	}
	else
	{
	  CF = 0;//清空标志位
		return 0;
	}
}


//main.c
/*距离读取函数*/
void Read_Distance()
{
  unsigned char temp;
  temp = U_Wave_Data();
  if(temp != 0)
  //Distance = Mean_Filter(temp);
  Distance = temp;
}

      注意一下这里距离就别滤波了,4t好像是瞬间变大的 如果用均值滤波的化就会出现bug使得测量不准。

     4.Motor脉冲输出

        到本题中最陌生的版块了,Motor脉冲输出。在讲本道题的Motor脉冲输出之前我们先大致介绍一些什么是脉冲输出。

       (1).脉冲输出的介绍

           PWM( 脉冲宽度调制) 是一种通过调整数字输出信号的高低电平时间比例来控制功率传递的技术。 其核心概念是占空比, 即在一个完整周期内高电平持续时间与总周期时间的百分比。 例如, 对于一个频率为100Hz( 周期为10ms) 的PWM信号, 若占空比为20%, 则高电平持续2ms, 低电平持续8ms; 若占空比为50%, 高低电平各持续5ms; 若占空比为80%, 则高电平持续8ms, 低电平持续2ms。

           PWM在单片机中主要用于数码管、Led的调光与Motor(或P34)的脉冲输出。

      (2)Motor底层代码

        首先我们还是来看底层原理图。

     我们发现Motor和继电器蜂鸣器用在一个 锁存器当中,对应的是P05的位置,所以在这里我们让电极和继电器用一个变量存储信息(与Led分开)。我们明显发现在这个锁存器当中有两个非门,所以我们直接将中间变量的值赋给P0就够了而不用对变量取反。若要使P05 = 1,对应的是temp |= 0x20。

    下面是代码

idata unsigned char Temp_1_Val = 0x00,Temp_1_Old = 0xff;//һ��Ҫ����ֵ

void Relay(bit Enable)
{
  unsigned char Temp;
	if(Enable)
		Temp_1_Val |= 0x10;
	else 
		Temp_1_Val &= ~(0x10);
	if(Temp_1_Val!=Temp_1_Old)
	{
	  P0 = Temp_1_Val;//��ȡ��
		Temp = P2 & 0x1f;
		Temp|= 0xa0;
		P2 = Temp;//����
		Temp = P2&0x1f;
		P2 = Temp;//����
		Temp_1_Old = Temp_1_Val;//���¾�ֵ
	}
}

void MOTOR(bit enable)
{
  if (enable)
    Temp_1_Val |= 0x20;
  else
    Temp_1_Val &= ~(0x20);
  if (Temp_1_Val != Temp_1_Old)
  {
    P0 = Temp_1_Val;
    P2 = P2 & 0x1f | 0xa0;
    P2 &= 0x1f;
    Temp_1_Old= Temp_1_Val;
  }
}

(3).本题中如何实现

        现在我们回到本题。

       本题中需要我们干两个事情 1.输出一个1KHz的脉冲

                                                    ii.输出一定占空比的频率的脉冲

       i.输出1KHz的脉冲 

         如果我们要输出一个1KHz的脉冲的话,整个脉冲的周期为1ms,但是我们需要满足占空比的要求,占空比分别为20%,与80%。一般来说我们需要将周期分为100份然后通过占空比的大小来分,如占空比为12则高电平占12分。但是本题中占空比都为整十的数,那么高电平必然占整个周期的10*x份(x为整数)。那么我们化简一下,只将一个周期分成10份是不是也可以完成题目的要求,即若占空比为x*10%那么高电平占x份。那么如此的话我们只需开一个100us的中断即可。那么我们设置一个100us的定时器2。

      我们在ISP当中选择定时器15系列 12T模式 100us 复制C代码。但是我们知道ISP当中的定时器代码是没有开中断的。接下来我们再开中断。

     首先先是总中断 EA = 1;

     然后是定时器2的中断,ET2 = 1?写出这个代码的一看就是没有仔细看芯片手册的 

     在定时器2当中我们使用IE2来配置定时器2的中断 

        我们发现定时器2的中断允许位在 IE2的第3位,且IE2为不可位寻址。那么如果我需要将ET2赋1的话,即使得IE2 |= 0X04;

       到这我们100us的定时器2初始化就配置完了,下面是代码

void Timer2_Init(void) // 100微秒@12.000MHz
{
	AUXR &= 0xFB; // 定时器时钟12T模式
	T2L = 0x9C;	  // 设置定时初始值
	T2H = 0xFF;	  // 设置定时初始值
	AUXR |= 0x10; // 定时器2开始计时
	IE2 |= 0x04;  // 使能定时器2中断
	EA = 1; // 开启总中断
}
      ii.输出一定占空比的频率的脉冲

      首先我们来回顾上面的一些内容,我们说我们把一个周期分成了10份,然后若占空比为X*10%的话高电平就占X份。那么我们是不是可以定义两个变量,一个记录占空比数据的1/10即Pwm_Duty,一个在0-9循环即Pwm_Period。如果 Pwm_Period<Pwm_Duty,那么就输出高电平,否则就输出低电平。我们举一个例子,我们假设占空比为20%,那么Pwm_Duty的值为2。那么当Pwm_Period<2,即Pwm_Period = 0或1时输出高电平,那么高电平是不是占整个周期的2份。当Pwm_Period>=2,即Pwm_Period = 2,3,4,5,6,7,8时输出低电平,那么低电平是不是占整个周期的8份。那么我们是不是产生了占空比为20的脉冲了。

     然后我们回到题目。

    题目中说频率数据大于频率参数输出80%的占空比,否则输出20%的占空比,那么我们就在频率记录函数的下面进行一个判断,判断频率数据与频率参数的关系来给占空比赋值。再在定时器2的中断服务当中,通过判定  Pwm_Period与Pwm_Duty的关系来判定输出的时高电平还是低电平。

具体代码如下:

void Read_Freq()
{ 
	Seg_Enable = 1;//数码管使能
	Frequency = TL0|(TH0<<8);
	TH0 = 0;
	TL0 = 0;
	if(Frequency>(ui)Param[0]*100)
	 Pwm_Duty = 2;
	 else
	 Pwm_Duty = 8;
}

void Timer2_Server(void) interrupt 12
{
		if(Pwm_Period < Pwm_Duty)
		  MOTOR(0);
		else
		  MOTOR(1);
		Pwm_Period = (++Pwm_Period)%10;
}

    5.Led 与E2PROM 

  ​    因为E2PROM与继电器相关,都在Led_Proc函数当中,使用我就一起来说明了

    Led部分的话没有说明难度,考的比较基础,我在这就不再赘述了。这里我们主要看EERPOM

    EEPROM的功能是记录继电器的开关次数。那么就有一个问题出来了,如果我们一旦距离结果大于距离参数的情况下,继电器闭合次数就加1,如下面代码所示

if((Distance>Param[2]*10))
{
		Relay_Enable = 1;
        Relay_Count++;
        EEPROM_Write(&Relay_Count,0,1);
}
    else
	   Relay_Enable = 0;

    那么在4t当中你就会收获这个错误 

    为什么会出现这个情况呢?

     我们来想一下,什么时候我们开关次数加1,是不是继电器从关->开或者从关->开的时候才加1,如果是从开-> 开或者关->关的时候的时候是不是不需要加1。但是上面的代码当中是不是有可能在两次当中距离是一直大于参数的,那么这个时候是不是就不需要加1。那么为了修正这个问题,我们只需定义一个现在变量和过去变量,然后判断这两个变量是否相等就行了。

   具体代码如下:

 if((Distance>Param[2]*10))
		Relay_Enable_Val = 1;
    else
	   Relay_Enable_Val = 0;
	if(Relay_Enable_Old != Relay_Enable_Val)
	{
		Relay_Count++;
		EEPROM_Write(&Relay_Count,0,1);
	}

 到这我们Led和EEPOM部分就完成了,EEPROM还有一个长按清0我们在按键当中的代码有所提及,Led的完整代码如下。

void Led_Proc()
{
	/* Led部分 */
	ucLed[0] = (Seg_Disp_Mode == 0) || (Flash_Judge[0] == 1);
	ucLed[1] = (Seg_Disp_Mode == 1) || (Flash_Judge[1] == 1);
	ucLed[2] = (Seg_Disp_Mode == 2) || (Flash_Judge[2] == 1);
	ucLed[3] = (Frequency>Param[0]*100);
	ucLed[4] = (Hum>Param[1]);
	ucLed[5] = (Distance>Param[2]*10);
    
	Relay_Enable_Old = Relay_Enable_Val;
    if((Distance>Param[2]*10))
		Relay_Enable_Val = 1;
    else
	   Relay_Enable_Val = 0;
	if(Relay_Enable_Old != Relay_Enable_Val)
	{
		Relay_Count++;
		EEPROM_Write(&Relay_Count,0,1);
	}
	Led_Disp(ucLed);
	Relay(Relay_Enable_Val);
}

  到这这道题的代码就完成了。下面是完整的代码。

四.完整代码 

//Led.c
#include <Led.h>

idata unsigned char Temp_0_Val,Temp_0_Old;

void Led_Disp(unsigned char*ucLed)
{
	unsigned char Temp;
  Temp_0_Val = 0x00;
	Temp_0_Val = ucLed[0]|(ucLed[1]<<1)|(ucLed[2]<<2)|(ucLed[3]<<3)
	             |(ucLed[4]<<4)|(ucLed[5]<<5)|(ucLed[6]<<6)|(ucLed[7]<<7);
	if(Temp_0_Val!=Temp_0_Old)
	{
	  P0 = ~Temp_0_Val;
		Temp = P2 & 0x1f;
		Temp|= 0x80;
		P2 = Temp;//����
		Temp = P2&0x1f;
		P2 = Temp;//����
		Temp_0_Old = Temp_0_Val;//���¾�ֵ
	}
}

void Led_Off()
{
	unsigned char Temp;
    P0 = 0xff;
		Temp = P2 & 0x1f;
		Temp|= 0x80;
		P2 = Temp;//����
		Temp = P2&0x1f;
		P2 = Temp;//����
		Temp_0_Old = 0x00;//���¾�ֵ
}
idata unsigned char Temp_1_Val = 0x00,Temp_1_Old = 0xff;//һ��Ҫ����ֵ

void Relay(bit Enable)
{
  unsigned char Temp;
	if(Enable)
		Temp_1_Val |= 0x10;
	else 
		Temp_1_Val &= ~(0x10);
	if(Temp_1_Val!=Temp_1_Old)
	{
	  P0 = Temp_1_Val;//��ȡ��
		Temp = P2 & 0x1f;
		Temp|= 0xa0;
		P2 = Temp;//����
		Temp = P2&0x1f;
		P2 = Temp;//����
		Temp_1_Old = Temp_1_Val;//���¾�ֵ
	}
}
//main.c
/*头文件声明区*/
//器件底层
#include <Led.H>
#include <Init.H>
#include <Seg.H>
#include <Key.H>
#include <U_Wave.H>
#include <iic.H>
#include <ds1302.H>
#include <onewire.H>
#include <Uart.H>
#include <Filtering.h>

//库函数底层
#include <STC15F2K60S2.H>
#include <intrins.h>
#include <math.h>
#include <String.H>

/*变量声明区*/
typedef unsigned char uc;
typedef unsigned int ui;
idata uc ucLed[8] = {0,0,0,0,0,0,0,0};//Led数据存储数组
idata uc Key_Val,Key_Old,Key_Down,Key_Up;//按键处理哈四年专用变量
idata uc Seg_Pos,Seg_Buf[8] = {10,10,10,10,10,10,10,10};//数码管动态扫描专用变量
idata unsigned long int uwTick;//系统计时变量

/* 数码管显示变量 */
idata uc Seg_Disp_Mode = 0;//0 频率界面 1湿度界面 2测距界面 3参数界面
idata bit Freq_Show_Mode = 0;//频率显示模式 0HZ 1KHZ
idata bit Distance_Show_Mode = 0;//测距显示模式 0CM 1M
idata uc Param_Show_Mode = 0;//参数显示模式 0频率参数 1湿度参数 2距离参数
idata bit Seg_Enable = 0;

/* 数据变量 */
idata ui Freq_Time = 0;//频率计时变量 1s
idata ui Frequency = 0;//频率
idata uc Hum = 0;//湿度
idata uc Distance = 0;//距离
idata uc Param[3] = {90,40,6};//0频率参数*10 10-120 1湿度参数 10-60 2距离参数*10 1-12
idata uc Led_SlowDown[3];//数码管减速变量 100ms
idata uc Flash_Judge[3] = {0,0,0};//数码管闪烁判断变量
idata uc Relay_Count = 0;//继电器计数变量
idata bit Long_Down_Judge = 0;//长按判断变量
idata ui Long_Down_Time = 0;//长按计时变量
idata uc Pwm_Period;//PWM周期 1ms
idata uc Pwm_Duty = 2;//PWM占空比
idata bit Pwm_Enable = 0;//PWM使能
idata bit Relay_Enable_Val = 0 ,Relay_Enable_Old = 1;//继电器使能

/*按键处理函数*/
void Key_Proc()
{
  //读取按键值
  Key_Val = Key_Read();
  //判断按键是否按下
	Key_Down = Key_Val & (Key_Val ^ Key_Old);
  //判断按键是否释放
	Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
  //更新按键旧值
	Key_Old = Key_Val;
	switch (Key_Down)
	{	
		case 4:
		Seg_Disp_Mode = (++Seg_Disp_Mode)% 4;
		if(Seg_Disp_Mode == 3)
		 Param_Show_Mode = 0;
		break;
		case 5:
		if(Seg_Disp_Mode == 3)//参数界面
		{
			Param_Show_Mode = (++Param_Show_Mode)% 3;
		}
		break;
		case 6:
		if(Seg_Disp_Mode == 2)//测距界面
		{
			Distance_Show_Mode = !Distance_Show_Mode;
		}
		else if(Seg_Disp_Mode == 3)//参数界面
		{
			switch (Param_Show_Mode)
			{
			case 0:
				Param[0] += 5;
				if(Param[0] > 120)
					Param[0] = 10;
				break;
			case 1:
				Param[1] += 10;
				if(Param[1] > 60)
					Param[1] = 10;
				break;
			case 2:
				if(++Param[2] > 12)
					Param[2] = 1;
				break;
			}
		}
		break;
		case 7:
		if(Seg_Disp_Mode == 0)//频率界面
		{
			Freq_Show_Mode = !Freq_Show_Mode;
		}
		else if(Seg_Disp_Mode == 3)//参数界面
		{
			switch (Param_Show_Mode)
			{
			case 0:
				Param[0] -= 5;
				if(Param[0] < 10)
					Param[0] = 120;
				break;
			case 1:
				Param[1] -= 10;
				if(Param[1] < 10)	
					Param[1] = 60;
				break;
			case 2:
				if(--Param[2] < 1)
					Param[2] = 12;
				break;
			}
		}
		break;
	}
	if(Seg_Disp_Mode == 1)//湿度界面
	{
	    if(Key_Down == 7)
		  Long_Down_Judge = 1;
		if(Key_Up == 7)
		{
			Long_Down_Judge = 0;
			if(Long_Down_Time > 1000)
			Relay_Count = 0;
			EEPROM_Write(&Relay_Count,0,1);
			Long_Down_Time = 0;
		}
	}
}

/*信息处理函数*/
void Seg_Proc()
{
	if(Seg_Enable == 0)
	 return;
	switch (Seg_Disp_Mode)
	{
	case 0:
		Seg_Buf[0] = 11;//F
		Seg_Buf[1] = 10;
		if(Freq_Show_Mode == 0)//如果在HZ界面
		{
			Seg_Buf[2] = (Frequency>=100000)?Frequency/100000%10:10;
			Seg_Buf[3] = (Frequency>=10000)?Frequency/10000%10:10;
			Seg_Buf[4] = (Frequency>=1000)?Frequency/1000%10:10;
			Seg_Buf[5] = (Frequency>=100)?Frequency/100%10:10;
			Seg_Buf[6] = (Frequency>=10)?Frequency/10%10:10;
			Seg_Buf[7] = Frequency%10;
		}
		else
		{
			Seg_Buf[2] = (Frequency>=10000000)?Frequency/10000000%10:10;
			Seg_Buf[3] = (Frequency>=1000000)?Frequency/1000000%10:10;
			Seg_Buf[4] = (Frequency>=100000)?Frequency/100000%10:10;
			Seg_Buf[5] = (Frequency>=10000)?Frequency/10000%10:10;
			Seg_Buf[6] = Frequency/1000%10+',';
			Seg_Buf[7] = Frequency/100%10;
		}
		break;
	  case 1:
		Seg_Buf[0] = 12;//H
		Seg_Buf[1] = 10;
		Seg_Buf[2] = 10;
		Seg_Buf[3] = 10;
		Seg_Buf[4] = 10;
		Seg_Buf[5] = 10;
		Seg_Buf[6] = (Hum>=10)?Hum/10:10;
		Seg_Buf[7] = Hum%10;
		break;
	  case 2:
		Seg_Buf[0] = 13;//A
		Seg_Buf[1] = 10;
		Seg_Buf[2] = 10;
		Seg_Buf[3] = 10;
		Seg_Buf[4] = 10;
		if(Distance_Show_Mode == 0)//如果在CM界面
		{
			Seg_Buf[5] = (Distance>=100)?Distance/100%10:10;
			Seg_Buf[6] = (Distance>=10)?Distance/10%10:10;
			Seg_Buf[7] = Distance%10;
		}
		else
		{
			Seg_Buf[5] = Distance/100%10+',';
			Seg_Buf[6] = Distance/10%10;
			Seg_Buf[7] = Distance%10;
		}
        break;
	  case 3:
		Seg_Buf[0] = 14;//P
		Seg_Buf[1] = Param_Show_Mode+1;
		Seg_Buf[2] = 10;
		Seg_Buf[3] = 10;
		Seg_Buf[4] = 10;
		switch (Param_Show_Mode)
		{
		case 0:
		    Seg_Buf[5] = (Param[0]>=100)?Param[0]/100%10:10;
            Seg_Buf[6] = Param[0]/10%10+',';
            Seg_Buf[7] = Param[0]%10;
			break;
		case 1:
		    Seg_Buf[5] = 10;
		    Seg_Buf[6] = (Param[1]>=10)?Param[1]/10%10:10;
            Seg_Buf[7] = Param[1]%10;
			break;
		case 2:
		    Seg_Buf[6] = 10;
		    Seg_Buf[6] = Param[2]/10%10+',';
            Seg_Buf[7] = Param[2]%10;
			break;
		}
		break;
	}
}

/*Led处理函数*/
void Led_Proc()
{
	/* Led部分 */
	ucLed[0] = (Seg_Disp_Mode == 0) || (Flash_Judge[0] == 1);
	ucLed[1] = (Seg_Disp_Mode == 1) || (Flash_Judge[1] == 1);
	ucLed[2] = (Seg_Disp_Mode == 2) || (Flash_Judge[2] == 1);
	ucLed[3] = (Frequency>Param[0]*100);
	ucLed[4] = (Hum>Param[1]);
	ucLed[5] = (Distance>Param[2]*10);
    
	Relay_Enable_Old = Relay_Enable_Val;
    if((Distance>Param[2]*10))
		Relay_Enable_Val = 1;
    else
	   Relay_Enable_Val = 0;
	if(Relay_Enable_Old != Relay_Enable_Val)
	{
		Relay_Count++;
		EEPROM_Write(&Relay_Count,0,1);
	}
	Led_Disp(ucLed);
	Relay(Relay_Enable_Val);
}

/*AD_DA*/
void AD_DA()
{
  float temp = 0;
	//湿度读取
  Hum = (uc)(Ad_Read(0x43)/51.0*20.0);

  if(Hum<Param[1])
    temp = 1;
  else if(Hum>=Param[1] && Hum<80)
    temp = 4.0/(80 - Param[1])*(Hum - Param[1]) + 1;
  else
   temp  = 5;
  Da_Write(temp*51.0);
}

/*距离读取函数*/
void Read_Distance()
{
  unsigned char temp;
  temp = U_Wave_Data();
  if(temp != 0)
  Distance = Mean_Filter(temp);
}

/*频率读取函数*/
void Read_Freq()
{ 
	Seg_Enable = 1;//数码管使能
	Frequency = TL0|(TH0<<8);
	TH0 = 0;
	TL0 = 0;
	if(Frequency>(ui)Param[0]*100)
	 Pwm_Duty = 2;
	 else
	 Pwm_Duty = 8;
}

/* 定时器0初始化函数 */
void Timer0Init(void)		//100微秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x05;
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x00;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}


/*计时器1初始化函数*/
void Timer1Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;		//定时器时钟12T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x18;		//设置定时初值
	TH1 = 0xFC;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	EA = 1;
	ET1 = 1;
}
void Timer2_Init(void) // 100微秒@12.000MHz
{
	AUXR &= 0xFB; // 定时器时钟12T模式
	T2L = 0x9C;	  // 设置定时初始值
	T2H = 0xFF;	  // 设置定时初始值
	AUXR |= 0x10; // 定时器2开始计时
	IE2 |= 0x04;  // 使能定时器2中断
	EA = 1; // 开启总中断
}

void Timer1_Server() interrupt 3
{
    uwTick++;

	//L1闪烁
	if(Seg_Disp_Mode == 3 && Param_Show_Mode == 0)
	{
	    if(++Led_SlowDown[0] == 100)
		{
		    Led_SlowDown[0] = 0;
			Flash_Judge[0] = Flash_Judge[0]?0:1;
		}
	}
	else
	{
		Led_SlowDown[0] = 0;
		Flash_Judge[0] = 0;
	}

	//L2闪烁
	if(Seg_Disp_Mode == 3 && Param_Show_Mode == 1)
	{
	    if(++Led_SlowDown[1] == 100)
		{
		   Led_SlowDown[1] = 0;
			Flash_Judge[1] = Flash_Judge[1]?0:1;
		}
	}
	else
	{
		Led_SlowDown[1] = 0;
		Flash_Judge[1] = 0;
	}

	//L3闪烁
	if(Seg_Disp_Mode == 3 && Param_Show_Mode == 2)
	{
	    if(++Led_SlowDown[2] == 100)
		{
		   Led_SlowDown[2] = 0;
			Flash_Judge[2] = Flash_Judge[2]?0:1;
		}
	}
	else
	{
		Led_SlowDown[2] = 0;
		Flash_Judge[2] = 0;
	}

	/* 长按判断 */
	if(Long_Down_Judge == 1)
	{
		if(++Long_Down_Time > 1000)
		   Long_Down_Time = 1001;
	}
	/*数码管动态扫描*/
	Seg_Pos = (++Seg_Pos)%8;
	if(Seg_Buf[Seg_Pos]>=',')//一定有=号
	  Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos]-',',1);
	else
		Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],0);
}

void Timer2_Server(void) interrupt 12
{
		if(Pwm_Period < Pwm_Duty)
		  MOTOR(0);
		else
		  MOTOR(1);
		Pwm_Period = (++Pwm_Period)%10;
}

/*定义调度器结构体*/
typedef struct
{
  void (*task_func)(void);
	unsigned long int rate_ms;//运行周期
	unsigned long int last_run;//上一次运行的时间
}task_c;

/*配置调度器*/
idata task_c Scheduler_Task[] = 
{
    {Led_Proc,1,0},//Led运行函数 1ms执行一次
	{Key_Proc,10,0},//按键运行函数 1ms执行一次
	{Seg_Proc,100,0},//数码管运行函数 100ms执行一次
	{AD_DA,160,0},//数模电读取函数 160ms执行一次
	{Read_Distance,500,0},//距离读取函数 500ms执行一次
	{Read_Freq,1000,0},//频率读取函数 1000ms执行一次
};

idata uc Task_Num;//调度器任务数量

/*调度器初始化*/
void Scheduler_Init()
{
  Task_Num = sizeof(Scheduler_Task)/sizeof(task_c);
}

/*调取器运行函数*/
void Scheduler_Run()
{
  unsigned char i;
	for(i=0;i<Task_Num;i++)
	{
		unsigned long int Now_Time = uwTick;
	  if(Now_Time > (Scheduler_Task[i].last_run+Scheduler_Task[i].rate_ms))
		{
		  Scheduler_Task[i].last_run = Now_Time;//顺序不能反
			Scheduler_Task[i].task_func();
		}
	}
}


/*main*/
void main()
{
    System_Init();
	Timer0Init();
	Scheduler_Init();//放在定时器初始化前
	Timer2_Init();
	Timer1Init();
	while(1)
	{
    Scheduler_Run();
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值