十一届蓝桥杯单片机组省赛题目及程序解析(使用B站西风模板)

 

目录

 前言

一、题目

二、程序功能实现

1.电压数据获取功能函数

 2.按键功能部分

3.数码管功能函数

4.LED显示

5.中断服务函数

6.主程序

三、完整程序

1.main.c

2.Init.c

 3.LED.c

 4.Seg.c

 5.Key.c

 6.iic.c

 总结


前言

        今天完成了十一届的省赛题目,这套题目相对来说比较简单,写起来也算是比较顺利。现将题目与程序一并分享出来,供之后回顾总结。同时,也希望看到这篇文章的朋友能够对其中的不足进行批评指正,欢迎大家在评论区交流讨论。

一、题目

十一届的题目如下:

        这次的题目,使用到了PCF8591和EEPROM两个外设,均使用IIC进行通信,在底层添加时,仅需要添加官方提供的iic.c文件即可。

二、程序功能实现

以下是程序部分:

1.电压数据获取功能函数

        在这个函数中,进行电压的采集,并且对于触发计数进行了判断。需要注意的是,AD_Read函数的返回值是unsigned char类型的数字量,范围是0~255,而程序中定义的电压参数是float型,范围是0~5。因此,在下面进行触发计数判定时,要给电压参数R_V_canshu * 51,并进行强制类型转换,将其转为0~255再与电压数据进行比较。

/*电压数据获取函数(同时获取计数次数)*/
void ADC_data_Get()
{
	if(Time_ADC_100ms) return;
	Time_ADC_100ms = 1;//数据获取减速,70ms进入
	
	R_V_data_old = R_V_data;//保存前值,用于判断是否计数
	R_V_data = AD_Read(0x43);//获取电压
	
	if(R_V_data_old >= (unsigned char)(R_V_canshu *51) && R_V_data <= (unsigned char)(R_V_canshu *51))
	{
		jishu_num++;
	}		//计数判定,这里参数0~5,电压0~255,故要给R_V_canshu乘51
}

       (这段有点啰嗦,感觉是废话

         在此次的题目中,对电压数据采集时间和电压数据刷新时间进行了不同的要求,其中采集时间要求小于0.1s,刷新时间要求小于0.3s。虽然按照之前的习惯,将数据采集放在数码管显示函数里面,然后将数码管显示函数的减速调小一点应该也可以,但是为了符合题目,也为了避免再次出现这种要求且恰好不能将采集与显示放到一个函数里面时能及时应对,这里为数据采集单独写了一个功能函数,该函数在主程序的while(1)循环内调用。

        对于该函数的减速时间的确定,题目要求采集速度小于0.1s,迎合题目要求,且考虑到其他函数运行也会消耗时间,这里留出一定余量,设定减速时间为70ms,即理想情况下每70ms运行一次这个函数,进行一次数据采集。

 2.按键功能部分

        按键这部分没什么好说的,就是正常的显示模式切换、数据的加减以及清空这些。唯一值得说的一点是,这次题目中提到了一个无效按键的概念,这在其他题目中确实没有见过。不过倒也好写,定义一个变量用来存储无效按键的次数,没有在要求界面下按下的时候变量值+1,只要有正常的按键功能被执行,计数值清空即可。

        再有一点容易忽视的是,如果之前使用过NE555的话,记得把P34引脚的跳线帽拔了,不然的话只要按下S16这一列,数据就疯狂的变(按下一次相当于按下了无数次)。拔掉之后就正常了

/*按键功能区*/
	switch(Key_down)
	{
		case 12:		//模式切换,该按键总是有效,故按下后无效次数记为0
			if(++Dis_mode == 3) Dis_mode = 0;
			Key_nouse_num = 0;
		break;
		case 13:		//计数次数清零,计数模式有效
			if(Dis_mode == 2)
			{
				jishu_num = 0;//计数值清零
				Key_nouse_num = 0;//无效次数清零
			}
			else
				Key_nouse_num++;//无效次数+1
		break;
		case 16:		//参数+0.5,参数界面有效
			if(Dis_mode == 1)
			{
				R_V_canshu = R_V_canshu < 5.0 ? R_V_canshu + 0.5 : 0.0;//参数+0.5,并设定范围最大到5,大于5归0
				Key_nouse_num = 0;
			}
			else
				Key_nouse_num++;
		break;
		case 17:
			if(Dis_mode == 1)
			{
				R_V_canshu = R_V_canshu > 0.0 ? R_V_canshu - 0.5 : 5.0;//参数+0.5,并设定范围最小到0,小于0置5
				Key_nouse_num = 0;
			}
			else
				Key_nouse_num++;
		break;
	}

3.数码管功能函数

        这里就是根据模式控制变量去选定当前模式,没什么特殊的地方。注意这里显示的时候需要将0~255转换到0~5并且进行数据类型的转换就好。

        再就是这里要吐槽一点题目,做了几套题目了,有些题目对于某些点的描述很含糊,甚至根本不给你说,这些就要凭借自己的理解去写了(好多题都是因为这些在4T进行测评的时候扣分了),就像这里的技术次数的显示,题目并没有说明固定用几位数码管显示或者高位为0时熄灭什么的,这里我使用的是高位熄灭的写法。

/*数据处理区域*/
	switch(Dis_mode)
	{
		case 0:		//电压显示界面
			Seg_dis[0] = 11;Seg_dian[5] = 1;
			Seg_dis[5] = ((unsigned int)((float)R_V_data / 51.0) %10);
			Seg_dis[6] = ((unsigned int)((float)R_V_data / 51.0 * 10) %10);
			Seg_dis[7] = ((unsigned int)((float)R_V_data / 51.0 * 100) %10);
		break;
		case 1:	//参数显示界面
			Seg_dis[0] = 12;Seg_dian[5] = 1;
			Seg_dis[5] = (unsigned int)R_V_canshu %10;
			Seg_dis[6] = (unsigned int)(R_V_canshu * 10) %10;
			Seg_dis[7] = (unsigned int)(R_V_canshu * 100) %10;
		break;
		case 2:	//计数次数显示界面
			R_V_canshu_save = (unsigned char)(R_V_canshu * 10);//从参数界面切换到该界面时,将参数传值给save变量
			Write_EEPROM(&R_V_canshu_save,0,1);//将save变量存入ROM(注:本来这一步打算写在S12下,但不知为何存不进去,便写在这里)
			Seg_dis[0] = 13;Seg_dian[5] = 0;
			Seg_dis[5] = jishu_num >= 100 ? jishu_num /100 %10 : 10;
			Seg_dis[6] = jishu_num >= 10 ? jishu_num /10 %10 : 10;//计数值高位为0时熄灭,否则正常显示
			Seg_dis[7] = jishu_num %10;
		break;
	}

4.LED显示

        LED的显示,LED1需要用的定时器的功能,5s的定时是放在定时器中断服务函数中的。LED2的计数值为奇数时点亮使用取余符号来进行判定。LED3的点亮就没什么说的了,对无效按键的次数进行判定。

/*其他显示(LED、蜂鸣器、继电器)*/
void Other_Dis_Take()
{
	LED_dis[0] = LED1_enable_flag;//电压小于参数5s点亮,否则熄灭(计时使用定时器)
	//计数次数为奇数时点亮(注:需要使用 % ,使用除号时,如1/2为0,无法判定)
    LED_dis[1] = ((jishu_num % 2) != 0);
	LED_dis[2] = (Key_nouse_num >= 3);//无效按键次数大于3点亮
}
	if(R_V_data < (R_V_canshu * 51))	//电压值小于参数开始计数,直至大于等于参数
	{
		if(++Time_5s > 5000)	//计时时长超过5s,LED1使能标志置位
		{
			Time_5s = 5001;
			LED1_enable_flag = 1;
		}
	}
	else		//电压值大时,清空计数值,并将使能位复位,便于下次触发计时
	{
		Time_5s = 0;
		LED1_enable_flag = 0;
	}

5.中断服务函数

        定时器中针对数据获取函数加了一个减速,时间这里设定的是70ms。 

/*定时器1中断服务函数*/
void Timer1_Take() interrupt 3
{
	if(++Key_show_down == 10) Key_show_down = 0;//按键减速10ms
	if(++Seg_show_down == 200) Seg_show_down = 0;//数码管减速100ms
	if(++Time_ADC_100ms == 70) Time_ADC_100ms = 0;//ADC减速70ms(注:题目要求小于100,留一定余量,取70)
	if(++Seg_pos == 8) Seg_pos = 0;//数码管位扫描
	Display(Seg_pos,Seg_dis[Seg_pos],Seg_dian[Seg_pos]);//数码管显示函数
	LED(Seg_pos,LED_dis[Seg_pos]);//LED显示函数
	
	if(R_V_data < (R_V_canshu * 51))	//电压值小于参数开始计数,直至大于等于参数
	{
		if(++Time_5s > 5000)	//计时时长超过5s,LED1使能标志置位
		{
			Time_5s = 5001;
			LED1_enable_flag = 1;
		}
	}
	else		//电压值大时,清空计数值,并将使能位复位,便于下次触发计时
	{
		Time_5s = 0;
		LED1_enable_flag = 0;
	}
}

6.主程序

        题目要求上电读取EEPROM的值,因此这里在while(1)之外进行了一次数据读取操作。这里要注意的是,使用EEPROM之前,最好是先写一个给里面存数据的程序,把数据先存进去,在写读取函数去验证程序是否写对着。不然的话因为ROM是掉电不丢失的,上次在里面存的具体是啥咱也不清楚(这里说的上次是n年之前你用这颗EEPROM的时候),先读出来的话很容易产生一些错误让我们对自己的程序逻辑产生怀疑(其实程序写的是对的)。就像这里正常的数据应该是0~50的数,但是如果这里之前存储了0xAA,这读出来在都不可能是对的,到时显示到数码管是错的,还会影响其他的一些现象跟着出错。因此需要先写入,在进行读取的验证。

/*主程序*/
void main()
{
	Init();//系统初始化(关闭蜂鸣器、LED)
	Timer1Init();//定时器初始化
	Read_EEPROM(&R_V_canshu_save,0,1);	//从ROM读取电压参数值
	R_V_canshu = R_V_canshu_save / 10.0;
	while(1)
	{
		ADC_data_Get();
		Key_Take();
		Seg_Take();
		Other_Dis_Take();
	}
}

三、完整程序

        下面附上完整的程序,需要的朋友可以自行粘贴验证,注意使用的时候需要自行添加.h文件,仅需要在其中进行函数声明即可。

1.main.c

/*头文件*/
#include <STC15F2K60S2.H>
#include <intrins.H>
#include <Init.H>
#include <LED.H>
#include <Key.H>
#include <Seg.H>
#include <I2C.H>

/*全局变量声明区域*/
unsigned char Key_show_down;//按键减速变量
unsigned char Key_val,Key_old,Key_up,Key_down;//按键扫描变量,键值、前值、上升沿、下降沿
unsigned int Seg_show_down;//数码管减速变量
unsigned char Seg_pos;//数码管扫描位变量
unsigned char Seg_dis[] = {10,10,10,10,10,10,10,10};//数码管显示缓存数组
unsigned char Seg_dian[] = {0,0,0,0,0,0,0,0};//小数点显示缓存数组
unsigned char LED_dis[] = {0,0,0,0,0,0,0,0};//LED显示缓存数组

/*程序逻辑变量*/
unsigned char Time_ADC_100ms;//电压采集刷新时间(小于100ms,这里使用80ms)
unsigned char Dis_mode;//模式控制变量,0-数据,1-参数,2-计数
unsigned char R_V_data,R_V_data_old;//电位器电压原始数据,0~255,上一次的电压数据
unsigned char jishu_num;//计数次数
float R_V_canshu;//电压参数
unsigned char R_V_canshu_save;//存储到EEPROM的参数值
unsigned int Time_5s;//电压小于参数5s定时
bit LED1_enable_flag;//LED1使能标志位
unsigned char Key_nouse_num;//按键无效次数

/*函数定义区域*/
/*电压数据获取函数(同时获取计数次数)*/
void ADC_data_Get()
{
	if(Time_ADC_100ms) return;
	Time_ADC_100ms = 1;//数据获取减速
	
	R_V_data_old = R_V_data;//保存前值,用于判断是否计数
	R_V_data = AD_Read(0x43);//获取电压
	
	if(R_V_data_old >= (unsigned char)(R_V_canshu *51) && R_V_data <= (unsigned char)(R_V_canshu *51))
	{
		jishu_num++;
	}		//计数判定,这里参数0~5,电压0~255,故要给R_V_canshu乘51
}

/*按键功能函数*/
void Key_Take()
{
	if(Key_show_down) return;
	Key_show_down = 1;//按键减速
	
	Key_val = Key_jz();//读取按键键值(矩阵)
	Key_down = Key_val &(Key_old ^ Key_val);//读取按键下降沿
	Key_up = ~Key_val &(Key_old ^ Key_val);//读取按键上升沿
	Key_old = Key_val;//辅助扫描
	
	/*按键功能区*/
	switch(Key_down)
	{
		case 12:		//模式切换,该按键总是有效,故按下后无效次数记为0
			if(++Dis_mode == 3) Dis_mode = 0;
			Key_nouse_num = 0;
		break;
		case 13:		//计数次数清零,计数模式有效
			if(Dis_mode == 2)
			{
				jishu_num = 0;//计数值清零
				Key_nouse_num = 0;//无效次数清零
			}
			else
				Key_nouse_num++;//无效次数+1
		break;
		case 16:		//参数+0.5,参数界面有效
			if(Dis_mode == 1)
			{
				R_V_canshu = R_V_canshu < 5.0 ? R_V_canshu + 0.5 : 0.0;//参数+0.5,并设定范围最大到5,大于5归0
				Key_nouse_num = 0;
			}
			else
				Key_nouse_num++;
		break;
		case 17:
			if(Dis_mode == 1)
			{
				R_V_canshu = R_V_canshu > 0.0 ? R_V_canshu - 0.5 : 5.0;//参数+0.5,并设定范围最小到0,小于0置5
				Key_nouse_num = 0;
			}
			else
				Key_nouse_num++;
		break;
	}
}

/*数据显示功能函数*/
void Seg_Take()
{
	if(Seg_show_down) return;
	Seg_show_down = 1;//数码管减速
	/*数据处理区域*/
	switch(Dis_mode)
	{
		case 0:		//电压显示界面
			Seg_dis[0] = 11;Seg_dian[5] = 1;
			Seg_dis[5] = ((unsigned int)((float)R_V_data / 51.0) %10);
			Seg_dis[6] = ((unsigned int)((float)R_V_data / 51.0 * 10) %10);
			Seg_dis[7] = ((unsigned int)((float)R_V_data / 51.0 * 100) %10);
		break;
		case 1:	//参数显示界面
			Seg_dis[0] = 12;Seg_dian[5] = 1;
			Seg_dis[5] = (unsigned int)R_V_canshu %10;
			Seg_dis[6] = (unsigned int)(R_V_canshu * 10) %10;
			Seg_dis[7] = (unsigned int)(R_V_canshu * 100) %10;
		break;
		case 2:	//计数次数显示界面
			R_V_canshu_save = (unsigned char)(R_V_canshu * 10);//从参数界面切换到该界面时,将参数传值给save变量
			Write_EEPROM(&R_V_canshu_save,0,1);//将save变量存入ROM(注:本来这一步打算写在S12下,但不知为何存不进去,便写在这里)
			Seg_dis[0] = 13;Seg_dian[5] = 0;
			Seg_dis[5] = jishu_num >= 100 ? jishu_num /100 %10 : 10;
			Seg_dis[6] = jishu_num >= 10 ? jishu_num /10 %10 : 10;//计数值高位为0时熄灭,否则正常显示
			Seg_dis[7] = jishu_num %10;
		break;
	}
}

/*其他显示(LED、蜂鸣器、继电器)*/
void Other_Dis_Take()
{
	LED_dis[0] = LED1_enable_flag;//电压小于参数5s点亮,否则熄灭(计时使用定时器)
	LED_dis[1] = ((jishu_num % 2) != 0);//计数次数为奇数时点亮(注:需要使用 % ,使用除号时,如1/2为0,无法判定)
	LED_dis[2] = (Key_nouse_num >= 3);//无效按键次数大于3点亮
}

/*定时器1初始化(系统定时器)*/
void Timer1Init(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_Take() interrupt 3
{
	if(++Key_show_down == 10) Key_show_down = 0;//按键减速10ms
	if(++Seg_show_down == 200) Seg_show_down = 0;//数码管减速100ms
	if(++Time_ADC_100ms == 70) Time_ADC_100ms = 0;//ADC减速70ms(注:题目要求小于100,留一定余量,取70)
	if(++Seg_pos == 8) Seg_pos = 0;//数码管位扫描
	Display(Seg_pos,Seg_dis[Seg_pos],Seg_dian[Seg_pos]);//数码管显示函数
	LED(Seg_pos,LED_dis[Seg_pos]);//LED显示函数
	
	if(R_V_data < (R_V_canshu * 51))	//电压值小于参数开始计数,直至大于等于参数
	{
		if(++Time_5s > 5000)	//计时时长超过5s,LED1使能标志置位
		{
			Time_5s = 5001;
			LED1_enable_flag = 1;
		}
	}
	else		//电压值大时,清空计数值,并将使能位复位,便于下次触发计时
	{
		Time_5s = 0;
		LED1_enable_flag = 0;
	}
}

/*主程序*/
void main()
{
	Init();//系统初始化(关闭蜂鸣器、LED)
	Timer1Init();//定时器初始化
	Read_EEPROM(&R_V_canshu_save,0,1);	//从ROM读取电压参数值
	R_V_canshu = R_V_canshu_save / 10.0;
	while(1)
	{
		ADC_data_Get();
		Key_Take();
		Seg_Take();
		Other_Dis_Take();
	}
}

2.Init.c

#include <STC15F2K60S2.H>

void Init()
{
	P0 = 0xff;//LED全灭
	P2 = P2 & 0x1f | 0x80;//打开LED通道
	P2 &= 0x1f;//关闭通道
	
	P0 = 0x00;//关闭蜂鸣器继电器(经过ULN2003芯片自带反相功能)
	P2 = P2 & 0x1f | 0xA0;//打开LED通道
	P2 &= 0x1f;//关闭通道
}

 3.LED.c

        这里的蜂鸣器控制函数是没有用到的,可以直接删掉。

#include <STC15F2K60S2.H>

/*
LED控制
参数:控制位,是否使能(点亮)
*/
void LED(unsigned char wei,unsigned char enable)
{
	static unsigned char temp = 0x00;
	static unsigned char temp_old = 0xff;
	
	if(enable)
		temp |= 0x01 << wei;//给点亮位传1
	else
		temp &= ~(0x01<<wei);//给点亮位传0
	
	if(temp != temp_old)	//避免译码器、锁存器频繁开关,仅在LED状态变化时打开通道
	{
		P0 = ~temp;//将点亮位传给P2(给0亮)
		P2 = P2 & 0x1f | 0x80;//打开LED通道
		P2 &= 0x1f;//关闭通道
		temp_old = temp;//新旧值交换
	}
}

/*
蜂鸣器控制
参数:模块选择变量(1-蜂鸣器,2-MOTOR,3-继电器)使能变量
注意:蜂鸣器-P06,MOTOR-P05,继电器-P04
*/
void Other(unsigned char wei,unsigned char enable)
{
	static unsigned char temp = 0x00;
	static unsigned char temp_old = 0xff;
	
	if(enable)
		temp |= 0x80 >> wei;
	else
		temp &= ~(0x80 >> wei);
	if(temp != temp_old)
	{
		P0 = temp;
		P2 = P2 & 0x1f | 0xA0;//打开通道
		P2 &= 0x1f;//关闭通道
		temp_old = temp;//新旧值交换
	}
}

 4.Seg.c

#include <STC15F2K60S2.H>

code unsigned char DX[] = {0xc0,0xf9,0xa4,0xb0,0x99,
0x92,0x82,0xf8,0x80,0x90,0xff,
0xC1,0x8C,0xC8};//11-U,12-P,13-n

void Display(unsigned char wei,unsigned char duan,unsigned char dian)
{
	P0 = 0xff;//段选消影
	P2 = P2 & 0x1f | 0xe0;//打开段选通道
	P2 &= 0x1f;//关闭通道
	
	P0 = 0x01 << wei;//不使用数组方式,直接使用位操作选择点亮位(类比LED)
	P2 = P2 & 0x1f | 0xc0;//打开位选通道
	P2 &= 0x1f;//关闭通道
	
	P0 = DX[duan];
	if(dian)
		P0 &= 0x7f;//有小数点,将其按位与0x7F,将最高位0传递到P0
	P2 = P2 & 0x1f | 0xe0;//打开段选通道
	P2 &= 0x1f;//关闭通道
}

 5.Key.c

        这里的独立按键函数也没使用到,甚至矩阵按键中没有用到的也可以删掉,节省部分单片机的存储空间。

#include <STC15F2K60S2.H>

unsigned char Key_jz()
{
	unsigned char key = 0;
	/*
	使用串口时,用P30,P31,有硬件冲突。
	为防止对串口的影响,关闭轮询定时器(定时器1),结束按键扫描后再打开定时器。
	*/
	ET1 = 0;
	
	P3=0xff;P42=1;P44=0;//一列导通,扫描行号(0为通)
	if(P30==0) key=7;
	if(P31==0) key=6;
	if(P32==0) key=5;
	if(P33==0) key=4;
	
	P3=0xff;P42=0;P44=1;
	if(P30==0) key=11;
	if(P31==0) key=10;
	if(P32==0) key=9;
	if(P33==0) key=8;
	
	P3=0xff;P42=1;P44=1;P35=0;
	if(P30==0) key=15;
	if(P31==0) key=14;
	if(P32==0) key=13;
	if(P33==0) key=12;
	
	P3=0xff;P42=1;P44=1;P34=0;
	if(P30==0) key=19;
	if(P31==0) key=18;
	if(P32==0) key=17;
	if(P33==0) key=16;
	
	P3=0xff;ET1 = 1;//重新开启轮询定时器
	return key;//返回键值
}

unsigned char Key_dl()
{
	unsigned char key = 0;
	if(P30 == 0) key = 7;
	if(P31 == 0) key = 6;
	if(P32 == 0) key = 5;
	if(P33 == 0) key = 4;
	return key;
}

 6.iic.c

 这个同样,没有用到的DAC函数之间删掉。同时,这里在eeprom写入的时候,最后添加了4个255的延时,之前看西风的讲解视频时候说是需要添加上,不然会出现什么问题记不太清了,但是在实际使用过程中,我把这四句去掉也能正常读写,也可能是目前还没遇到他说的问题,索引这里注释了“待验证”。再一个就是开头的这个宏定义,在十六届模拟Ⅱ的时候需要将这里改成5,否则4T测评会扣分,但是这次我没有改,测评也是正常的,就很疑惑。目前对这里到底要不要改,我的习惯是,使用光敏电阻的时候,数据会跳的很快,我就给他改成5,仅使用电位器时,数据比较稳定,我就不用改这里。各位看博客的朋友有什么新的见解也可以在评论区分享出来。

/*	#   I2C代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <STC15F2K60S2.H>
#include <intrins.H>

#define DELAY_TIME	10

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

//I2C等待
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--);      	
}

//I2C开始
void I2CStart(void)
{
    sda = 1;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 0;
	I2C_Delay(DELAY_TIME);
    scl = 0;    
}

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

//I2C发送数据
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;  
}

//I2C接收数据
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;    
}

//I2C等待应带
unsigned char I2CWaitAck(void)
{
	unsigned char ackbit;
	
    scl = 1;
	I2C_Delay(DELAY_TIME);
    ackbit = sda; 
    scl = 0;
	I2C_Delay(DELAY_TIME);
	
	return ackbit;
}

//I2C发送应答
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);
}

/*
读AD值
参数:通道(光敏0x41,变阻器0x43)
		(注意:同时读取两路信号时要用光敏电阻变量读取电位器值,电位器变量读取光敏电阻值)
返回值:AD值(0-255)

|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
|		PCF8591有一个模数转换值存储区,当主机未发送读取某一通道指令时,该区存储值默认128。				|
|有多路同时读取时,如光敏(AIN1)和电位器(AIN3),读光敏时调用读取函数将通道编码发送给芯片,	|
|该芯片返回的是存储区预存的值(128),同时将通道1的值读取出来,之后读取电位器时将1通道的值给	|
|电位器,同时将3通道值读出存储,以此循环																										|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
unsigned char AD_Read(unsigned char adder)
{
	unsigned char temp = 0;
	
	I2CStart();
	I2CSendByte(0x90);//发送写命令
	I2CWaitAck();//等待应答
	I2CSendByte(adder);//发送地址(通道,光敏0x41,变阻器0x43)
	I2CWaitAck();//等待应答
	
	I2CStart();
	I2CSendByte(0x91);//发送读命令
	I2CWaitAck();//等待应答
	temp = I2CReceiveByte();//接收数据
	I2CSendAck(1);//发送非应答信号
	I2CStop();//停止
	
	return temp;
}

/*
写入DA值(向PCF8591)
参数:数字量(0~255对应输出模拟量0~5V)
		(注意:使用时可用对应模拟量乘51,从J3的19号引脚读出)
*/
void DA_Write(unsigned char date)
{
	I2CStart();
	I2CSendByte(0x90);//发送写命令
	I2CWaitAck();//等待应答
	
	I2CSendByte(0x40);//使能DA转换(40~43均可,尽量与题目涉及模块保持一致,e.g.涉及光敏-41)
	I2CWaitAck();//等待应答
	I2CSendByte(date);//使能DA转换
	I2CWaitAck();//等待应答
	I2CStop();//停止
}

/*
按页写入EEPROM
参数:输入内容(指针),写入地址(8的整数倍),数据长度(按字节算,int型要拆解成char)
*/
void Write_EEPROM(unsigned char* EEPROM_Date,unsigned char adds,unsigned char Date_Byte_num)
{
	I2CStart();
	I2CSendByte(0xA0);//选择芯片并发送写命令
	I2CWaitAck();//等待应答
	
	I2CSendByte(adds);//发送写入的地址
	I2CWaitAck();//等待应答
	
	while(Date_Byte_num--)//(注意自减在后,保证数据长度完整)
	{
		I2CSendByte(*EEPROM_Date++);//数据逐个写入EEPROM芯片
		I2CWaitAck();
		I2C_Delay(200);
	}
	I2CStop();//停止
	I2C_Delay(255);
	I2C_Delay(255);
	I2C_Delay(255);
	I2C_Delay(255);//延时四次,作用待验证
}

/*
按页读出EEPROM数据
参数:数据保存地址(指针),待读取的地址,数据长度(按字节算,int型要拆解成char)
*/
void Read_EEPROM(unsigned char* EEPROM_Date,unsigned char adds,unsigned char Date_Byte_num)
{
	I2CStart();
	I2CSendByte(0xA0);//选择芯片并发送写命令
	I2CWaitAck();//等待应答
	
	I2CSendByte(adds);//发送读取的地址
	I2CWaitAck();//等待应答
	
	I2CStart();
	I2CSendByte(0xA1);//选择芯片并发送读命令
	I2CWaitAck();//等待应答
	
	while(Date_Byte_num--)//(注意自减在后,保证数据长度完整)
	{
		*EEPROM_Date++ = I2CReceiveByte();//接收数据
		if(Date_Byte_num)
			I2CSendAck(0);//发送应答信号
		else
			I2CSendAck(1);//发送非应答信号
	}
	I2CStop();//停止
}

 总结

        以上便是这次的题目与程序了,写完总体的感受来说,算是比较简单的一套题了,难度有点像今年的模拟Ⅰ和Ⅲ,写的也算是比较顺利了。这类题做完之后,依旧感觉十四届的省赛是最难的一届,这套题我至今没有满分,当然这也跟我前面提到的对题目的理解有关,但是除此之外也确实是很难。

        最后的最后,离今年的蓝桥杯还有十天,继续刷题吧,愿诸君共奋进!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值