蓝桥杯比赛培训笔记(基于STCCT107D训练板)

“蓝桥杯”比赛培训笔记

更新说明

第一次大更新:删除了一部分冗余的代码,将代码部分改得更为规范了些。
——2020年2月23日
第二次更新:在C语言相关中增添了“数组”及“指针”的部分知识点。
——2020年3月18日
第三次大更新:在C语言相关中增添了“sprintf函数”的部分知识点;增添了运用sprintf函数完成数码管显示及左移操作的代码,增添了利用定时器中断完成数码管动态扫描的代码,增添了目录并对一些文章阅读的细节进行了优化,增添了“代码块阅读说明”;对代码块内一些有小bug的代码进行了修复。
——2020年3月30日
第四次大更新:在C语言相关中增添了“DS1302实时时钟”、“DS18B20温度测量”、“I2C协议通信”和“长时操作和短时操作”的相关知识点;增添了DS1302实时时钟和DS18B20温度测量的相关代码;对所有代码的注释进行了优化,更方便阅读;优化了字体大小。
——2020年4月22日
第五次小更新:更正了C语言相关中“DS1302部分”和“sprintf”函数部分的错误和不当的描述。
DS1302部分:
错误语句:DS1302的数据是以压缩BCD码的格式输出的(初始化数据的写入格式则是非压缩BCD码)
正确语句:DS1302的数据是以压缩BCD码的格式输出的(初始化数据的写入格式也是压缩BCD码)
sprintf函数部分:
错误语句:该函数为标准输入输出函数,包含于“stdio.h”头文件中,其作用是将输入数字格式化为字符形式输出至字符串中。
正确语句:该函数为标准输入输出函数,包含于“stdio.h”头文件中,不严谨地说,其作用是将输入数字格式化为字符形式输出至字符串中。
——2020年4月29日
第六次小更新:在C语言相关中增添了“PCF8591”芯片的相关知识点;简化了所有代码中键盘消抖的部分;将所有代码中“int main(void)…return 0;”形式改为了“void main(void)…”形式,消除了“unreachable code”的警告。
——2020年6月18日
第七次小更新:在C语言相关中修改了“sprintf”函数相关说明部分,增添了新的使用技巧,增添了一些解释和示例代码,更方便读者理解;修改了“循环左/右移”部分,增添了“intrins.h”头文件中循环左/右移函数的声明及使用说明,对一些细节部分进行了优化。
——2020年6月19日
第八次大更新:在C语言相关中增添了“AT24C02”芯片的相关知识点。
寄语:蓝桥杯省赛已结束,我就是在AT24C02上栽跟头的,没怎么练习这个模块,希望大家引以为戒,一定要把所有模块好好掌握,每个模块至少编程3遍才行!加油!
——2020年7月5日

目录

  • “蓝桥杯”比赛培训笔记
    • 更新说明
    • 单片机的工作模式
    • C语言相关
      • 数组
      • 指针
      • sprintf函数
      • 间断长时操作和短时操作
      • 循环左/右移
      • 数码管动态扫描原理
      • 数码管闪烁
      • 按键抖动
      • 矩阵键盘
      • DS1302实时时钟
      • DS18B20温度测量
      • I2C协议通信
      • PCF8591
      • AT24C02
    • 实验代码
      • 代码部分阅读说明
      • LED灯
        • LED灯实验一 ↓
        • LED灯实验二 ↓
        • LED灯实验三 ↓
        • LED灯实验四 ↓
        • LED灯实验五 ↓
      • 数码管
        • 动态扫描显示0~7 ↓
        • 指定数码管闪烁 ↓
      • 按键综合
        • 独立键盘(常用方法) ↓
        • 独立键盘(简化方法) ↓
        • 用按键切换不同的数码管闪烁 ↓
        • 矩阵键盘(复杂方法) ↓
        • 矩阵键盘(简化方法) ↓
      • 定时器
        • 利用定时器中断计时 ↓
        • 利用sprintf函数完成数码管左移及显示 ↓
      • Ds1302实时时钟
        • 实时时钟的显示 ↓
          • main.c主文件
          • Ds1302.h头文件
          • Ds1302.c驱动文件
      • Ds18b20温度传感器
        • 实时温度的测量 ↓
          • main.c主文件
          • onewire.h头文件
          • onewire.c驱动文件

单片机的工作模式

  1. AB先传送地址,DB再传送数据。
  2. 地址确定后,需要根据芯片M74HC573M1R的原理锁存DB信号,即让38译码器选中Y0输出口(注意YnC=(Yn’+WR’)’)。
    原理如下:
    While the LE input is held at a high level, the Q outputs will follow the data input precisely. When LE is taken low, the Q outputs will be latched precisely at the logic level of D input data.
    即LE为高电平不锁存,低电平锁存。

注意:
当短接帽接至“IO”口时,使用的“IO模式”需要手动锁存地址。当短接帽接至“MM”口时,使用的“MM模式”可借助“XBYTE”命令(一定要大写)自动锁存地址。

C语言相关

数组

数组计算元素的个数是从“1”开始计算的,但是,选择元素时数组则是从“0”开始计算。
例如:Example [ 3 ] = { a , b , c } ;
Example数组共有3个元素,其中“a”为第0个元素,“b”为第1个元素,“c”为第2个元素。
利用初始化器初始化:利用初始化器进行数组初始化:

int a[8]={[6]=12};//把数组a中的第6个元素初始化为12,其余都为0。

注意:①如果初始化器后面有更多的值,那么就那些值就会被初始化在其之后。②后面的初始化会覆盖前面的初始化。

int a[8]={1,2,[4]=7,7,7,[0]=3};//其结果是a[8]={3,2,0,0,7,7,7,0};

指针

“*p”表示地址为p存储单元的内容;“p”表示地址;“&a”表示取a的地址;a为变量。

int *p;//初始化指针,但是该指针无指向地址!如果直接使用会有问题。
int *p=&a;//初始化指针,该形式只在定义时正确,表示以p指针所指向地址的值为变量a的值。
p=&a;//表示指针p所指向的地址就是a的地址。即地址间的赋值。
*p=a;//表示指针p所指向地址的内容就是内容a。

在C中,指针一定要初始化,指针加1指的是增加一个存储单元。对数组而言,这意味着把加1后的地址是下一个元素的地址,而不是下一个字节的地址,这是为什么必须声明指针所指向对象类型的原因之一。

sprintf函数

该函数为标准输入输出函数,包含于“stdio.h”函数中,不严谨的说,其作用是将输入的量(可为数字,可为字符)转为字符形式输出至字符串中,其输出格式和语法等都与“printf”函数一样,故可利用此函数实现许多功能。

但在使用时,若输入的量为数字型,则需注意C51编译器和C语言编译器的区别,因C51编译器十分“节省”,故需要加一个“unsigned int”进行强制转换,详情可看之后代码部分的注释。
大概格式如下:

sprintf(字符串地址,“占位符(可加上其他字符)”,(unsigned int)需要转换数字);

示例代码如下:

unsigned char zfsz[9];//第九个字符为“\0”,用来存放由sprintf函数自动加上的字符串结束标志。
unsigned int num=5555;
sprintf(zfsz,”QWQ %d”,(unsigned int)num);

此时字符数组里的内容应如下:

zfsz={‘Q’,‘W’,‘Q’,‘ ’,5,5,5,5};

若输入的量不是数字,而是字符,则不需要加“unsigned int”强制转换,但需要注意占位符“%d”要改为“%c”。
大概格式如下:

sprintf(字符串地址,“%c(可加上其他字符)”,字符量);

示例代码如下:

unsigned char zfsz[9]; //第九个字符为“\0”,用来存放由sprintf函数自动加上的字符串结束标志。
unsigned char wenzi=’a’;
sprintf(zfsz,^_^%c”,wenzi);

此时字符数组里的内容应如下:

zfsz={^,‘_’,^,‘ ’,.,.,.,‘a’};

间断长时操作和短时操作

长时操作:如让灯亮1秒,灭1秒。元件动作的同时也在计时。工作模式类似于数码管动态扫描。格式:动作(时段1)→停止(时段2)→动作(时段1)→停止(时段2)…
例如:

//代码实现的功能是根据jg(间隔)的值让LED灯亮jg秒、灭jg秒。
//jg通过按键控制赋予其不同的值。
unsigned int cjsj;//采集时间参数。
unsigned char cjsjfz;//采集时间辅助参数。
void T0_Ser(void) interrupt 1
{
		cjsj++;//开始计时。
		if(jg==60)//因为60s的计算超出了65536的范围,因此另外处理。
		{
			if((cjsj<60000)&&(cjsjfz==0))
				XBYTE[0x8000]=0;//LED灯亮。
			else if((cjsj<60000)&&(cjsjfz==1))
				XBYTE[0x8000]=0xff;//LED灯灭。
			if(cjsj>=60000)
				{
					cjsj=0;//初始化cjsj。
					cjsjfz++;//同时将cjsjfz加一,即让下次计时与上次计时区分开来。
				}
				if(cjsjfz==2)
					cjsjfz=0;//初始化cjsjfz参数。
		}
		else if((jg==1)||(jg==5)||(jg==30))//如果jg不为60s,则用常用的方法处理即可。
		{
			if(cjsj<1000*jg)//前1秒亮灯。
				XBYTE[0x8000]=0;
			else if((cjsj>=1000*jg)&&(cjsj<2000*jg))//后一秒灭灯。
				XBYTE[0x8000]=0xff;
			else if(cjsj>=2000*jg)
				cjsj=0;//初始化cjsj参数。
		}
}

短时操作:如,每隔1s对温度进行一次采样。注意,采样这个动作的时间是极短的,可以看作为瞬时的。先计时,元件再动作,且元件动作的时间极短,可看作元件动作时不计时。格式:计时→动作(时刻1)→计时→动作(时刻1)…
例如:

//代码实现的功能为每隔jg(间隔)秒,对环境温度进行一次采样并存储采样数据。
//jg通过按键控制赋予其不同的值。
void T0_Ser(void) interrupt 1 //T0中断服务函数。
{
	if(zt==1)//进入采集温度状态,开始采集温度。
	{
		cjsj++;//当进入采集温度数据界面时,开始计时。
		if((cjsj>=1000*jg)&&(wdgs<10))//当cjsj(采集时间)达到jg设置的时间时,执行以下操作。
		{
      		wdhc[wdgs]=wd;//将温度数据传递给wdhc(温度缓存)数组。
			cjsj=0;//cjsj清零,进行下一轮计时。
			wdgs++;//wdhc数组地址序号wdgs(温度个数)加1,数组等待下一次赋值。
		}
		if(wdgs>=10)//当wdgs(温度数据个数)等于10个时,执行以下操作。
		{
			zt=2;//工作状态切换至状态2。
			wdgs=0;//同时将wdgs初始化,方便进行下一轮测温。
		}
	}
}

总结,在对某个器件编写间断动作的代码时,一定要分清元件是进行长时动作还是短时动作,否则会出现辛辛苦苦编出来的代码并不符合要求的情况。且在编写代码时,一定要注意变量的类型大小,不可溢出。

循环左/右移

循环左/右移功能,在keil 5自带的intrins.h头文件中有相关的函数,但在keil 5中无法找到其对相关函数的定义,着实可惜。
打开头文件,可看到如下声明:

extern unsigned char _cror_    (unsigned char, unsigned char);//8位循环右移
extern unsigned int  _iror_    (unsigned int,  unsigned char);//16位循环右移
extern unsigned long _lror_    (unsigned long, unsigned char);//32位循环右移
extern unsigned char _crol_    (unsigned char, unsigned char);//8位循环左移
extern unsigned int  _irol_    (unsigned int,  unsigned char);//16位循环左移
extern unsigned long _lrol_    (unsigned long, unsigned char);//32位循环左移

函数使用方式为(以_cror_为例):

_cror_(被移动的8位变量,需移动的位数);

而以下是我参照网上代码写的左/右移函数,在此给各位读者做参考,以方便理解intrins.h头文件里左/右移函数。

/*循环左移*/
unsigned rol(unsigned val, int size)
{
  unsigned res = val << size;
  res |= val >> (32 - size);
  return res;
}
/*循环右移*/
unsigned ror(unsigned val, int size)
{
  unsigned res = val >> size;
  res |= val << (32 - size);
  return res;
}
//其中的“32”为int的位数,若需修改数据类型,以此类推。

数码管动态扫描原理

片选信号以一个极高的频率在不同数码管之间不断切换,通过利用人眼的暂留效应,让数码管“全部显示”。
工作的一般模式为:全灭→管1亮→全灭→管2亮… (发光二极管有余辉效应,因此要消隐全灭)
如此反复。消隐全灭不需要延迟时间。
注意:
在代码中,段选语句不需要延迟时间(若需加,一定要小于片选语句的延迟时间),但是,其一定嵌在“全灭”片选语句和“切换”片选语句之间。
例如:

	for(i=0;i<8;i++)//循环显示全部数码管。
	{
		XBYTE[0xE000]=0xff;//段选信号初始化。
		XBYTE[0xC000]=0;//片选信号初始化,片选信号的“全灭”语句。
		XBYTE[0xE000]=smg_Dxdata[i];//段选信号的“切换”语句。
		XBYTE[0xC000]=smg_Pxdata[i];//片选信号的“切换”语句。
		Delay10us();“切换”片选语句的延迟时间。
}

数码管闪烁

根据数码管动态扫描的原理,每个i都对应着一片数码管,因此,可以根据i的值来选中数码管。当i为特定值时,再让数码管消隐,即可控制指定数码管消隐。同时再用另一个变量(如times)来控制数码管消隐的时间即可。
简而言之,就是使用if语句,将i和times相与作为条件(如(i==1)&&(times<125))),判断是否需要进入数码管消隐语句块。
注意:
当指定数码管消隐时,别忘记其他未指定的数码管需要正常显示。具体请看代码中的注释。
例如:

for(i=0;i<8;i++)
	{
		XBYTE[0XE000]=0xff;//数码管消隐,段选信号。
		XBYTE[0xC000]=0;//数码管消隐,片选信号。
		XBYTE[0xE000]=smg_Dxdata[i];//段选信号不变,通过只改变片选信号让数码管闪烁。
		if(times<125)//循环的次数,当小于125次时,选中的数码管消隐。
		{		
			if((i>5)||(i<2)||((i==4)||(i==3)))//选中需要闪烁的数码管。
				{
					XBYTE[0xC000]=0;//消隐选中的数码管。
				}
			else 
				XBYTE[0xC000]=smg_Pxdata[i];//让其他数码管照常显示。
		}
		else
			XBYTE[0xC000]=smg_Pxdata[i];//当大于125次时,所有数码管都照常显示。
		Delay1ms();//数码管动态扫描延迟时间。
	}
		times++;//完成一次循环后,times加1。
		if(times==250)//当times=250时,初始化times的值。
			times=0;

按键抖动

按键按下去时有两个时间段存在抖动,分别在刚按下去的时候和松开按键的时候。所以,为了保证按键功能稳定,需要去两次抖动。
例如:

if(key==0)
{
	Delay10ms;//刚按下去时进行延时防抖,第一次去抖动。
	if(key==0)
	{
		segment1;//按下去后的相关功能模块。
		while(!key);//此处括号里内容可替换为“key==0”。等待按键释放,或判断按键是否释放。
//		Delay10ms;//刚松手时进行延时防抖,第二次去抖动。
//		while(!key);//判断按键是否完全释放。
		segment2;//按键释放之后的相关功能模块。
	}
}

/*/while 循环的条件是值为1,而其中key的值为0,因此“while(key);”语句等同于“while(0);”,是不循环的。所以对key取非,值为1,可循环,即“while(!key)”等同于“while(1)”。补充,当括号内为式子时,式子成立则为1,不成立则为0。
被注释的语句在实际编程中可以省略,省略之后也可消抖,目前尚不清楚原因。在代码中,除“独立键盘(常用方法)”未省略,其他键盘程序代码均已省略。

矩阵键盘

可参照独立键盘的编程思路,进而推理出矩阵键盘的代码。矩阵键盘可分为行扫描和列扫描(当然二者差不多)。思路和独立键盘一样,独立键盘只有一列,而矩阵键盘有四列,因此只是需要加一个条件去判断到底是哪列(或哪行)的按键按下了。
简而言之,基本原理就是8个端口只让1个端口赋值为低电平,任一按键按下,一定会有另1个端口被拉为低电平。
代码过长且复杂,为方便理解,便不在此赘述,其相关说明在“实验代码”部分中以注释的形式写出。

DS1302实时时钟

DS1302可以显示日期、星期、时间等。在使用DS1302前,先要初始化,写入初始时间等数据并启动,然后再读出实时时间数据即可。在DS1302有关日历、时间的寄存器里,每个单位都有各自的读地址和写地址。进行相关操作时,都要先传输地址。此外,在DS1302中,还有一个写保护位,在对DS1302进行任何写操作之前,都必须要解锁写保护,操作完毕后也必须要重锁写保护。
当从DS1302中读出时钟数据时,务必注意,DS1302的数据是以压缩BCD码的格式输出的(初始化数据的写入格式也是压缩BCD码)。一般情况下,要对读出的数据进行解压和转换,通常将1个字节的数据解压为2个字节。当读地址顺序为时、分、秒时,转换压缩BCD码应秉承“时低秒高”“十低个高”的原则,即在数组中,“时”的数据应放在数组低地址上,“秒”的数据应放在数组的高地址上。在只对一个单位进行转换时(如:秒),应该将秒的十位放至低字节,个位应放至高字节。
例如:

  void RTC_Init(void)//DS1302初始化函数。
{
	Write_Ds1302(0x8e,0);//解锁写保护。
	Write_Ds1302(0x84,0x23);//时。
	Write_Ds1302(0x82,0x59);//分。
	Write_Ds1302(0x80,0x50);//秒。
	Write_Ds1302(0x8e,0x80);//锁定写保护。
}
		  void RTC_Read(void)//DS1302时间读取函数。
{
	unsigned char rr1,rr2,temp;//rr1、rr2是循环参数,temp是临时缓存时间数据参数。 	
	for(rr1=0;rr1<3;rr1++)//从DS1302中读取并转换时、分、秒时间数据。
	{
	/*当DS1302读地址的顺序为时、分、秒时,转换数据的原则为“时低秒高”“十低个高”,即在数组中,转换
	顺序为时~秒,即时→低地址,秒→高地址;且在转换单个时间单位时,即十位→低字节,个位→高字节。*/
		temp=Read_Ds1302(RTC_Adr[rr1]);//读取数据。
		bcdzh[rr1*3]=(temp&0xf0)>>4;//转换十位。
		bcdzh[rr1*3+1]=temp&0x0f;//转换个位。
	}
		for(rr2=0;rr2<8;rr2++)
	{
		//将时间数据输入zfhc(字符缓存)数组,进而显示至数码管上。
		sprintf(zfhc+rr2,"%u",(unsigned int)bcdzh[rr2]);
	}
}

DS18B20温度测量

DS18B20是一个单总线通信的温度测量芯片,其通信方式十分便捷。其可通过单总线获取电源,也可通过VDD引脚外接一个电源,更加详细的介绍请参看芯片手册。
DS18B20的事件序列为:初始化→写入命令:ROM命令(紧跟任何数据交换请求)→写入命令:功能命令(紧跟任何数据交换请求)单总线上的所有事件都必须以初始化开始。当从DS18B20中读出数据时,需要注意温度数据格式的转换,其读出的温度数据为2个字节,且首先读出的数据为温度的低字节数据,其次才是温度的高字节数据。虽然蓝桥杯官方会给出DS18B20的驱动程序,但用户仍要自己编写一部分驱动代码。
用户可通过配置暂存寄存器调整测温精度(即每一个二进制位代表的温度),可分别调为0.5℃、0.25℃、0.125℃、0.0625℃。DS18B20输出的温度数据格式为16进制,需转换为十进制数,再乘以分辨率才能得到温度实际值(负数是以补码形式输出,需要将输出的数据进行取反再+1的换算),转换时务必要分清浮点数和整型数!必要时可采取“1元=100分”类似的方法来避免浮点数的计算。
例如:

//以下部分为所需编写的驱动代码。
unsigned int rd_temperature(void)//务必注意函数的类型。
{
	unsigned char low,high;//分别用于存储温度数据的低字节和高字节。
	unsigned int temperature;//最终返回值。
	init_ds18b20();//初始化。
	Write_DS18B20(0xcc);//ROM命令——跳过ROM。
	Write_DS18B20(0x44);//对温度进行模数转换。
	//Delay_OneWire(1000);//延时,等待转换完成,一般情况下可删除。
	
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0xbe);//读取RAM里温度暂存寄存器的数据。
	low=Read_DS18B20();//低字节数据传输给low。
	high=Read_DS18B20();//高字节数据传输给high。
	temperature=high;//①整合。将2个字节的char类型数据整合成1个int类型的数据。
	temperature<<=8;//②整合。
	temperature|=low;//③整合。	
  return temperature;//将数据返回给上层函数。
}
		//以下为测量转换温度的程序。
//方式一。
void get_Temperature(void)//获取实际温度数据函数。
{
	unsigned int rt;//rt(real temperature)用于存放实际的温度。
	//从ds18b20中读取温度数据,保留2位小数,因室内温度一般都高于0度,故此处不判断温度的正负。
	rt=rd_temperature();
	sprintf(zfhc,"%4.2f",rt/16.0);//除“16.0”就是乘以0.0625,“.0”一定要加!
}
//方式二。采取了“1元=100分”类似的方法进行温度转换。
		void get_Temperature(void)//获取实际温度数据函数。
{
	unsigned int rt;//rt(real temperature)用于存放实际的温度。
	//从ds18b20中读取温度数据,保留2位小数,因室内温度一般都高于0度,故此处不判断温度的正负。
	rt=rd_temperature()*0.0625*100;
	sprintf(zfhc,"%8u",(unsigned int)rt);//加“8”是为了让数码管显示的字符靠右对齐。
}

I2C协议通信

写通信格式:
单字节:主机发送起始位→主机发送元件片选写字节信号→等待从机回应→主机发送写地址→等待从机回应→主机发送第一个字节→等待从机回应→主机发送终止位
多字节:主机发送起始位→主机发送元件片选写字节信号→等待从机回应→主机发送写地址→等待从机回应→主机发送第一个字节→等待从机回应→主机发送第二个字节→等待从机回应→…→主机发送终止位
读通信格式:
单字节:主机发送起始位→主机发送元件片选写字节信号→等待从机回应→主机发送读地址→等待从机回应→主机发送起始位→主机发送元件片选读字节信号→等待从机回应→主机接收第一个字节→主机不响应从机→主机发送终止位
多字节:主机发送起始位→主机发送元件片选写字节信号→等待从机回应→主机发送读地址→等待从机回应→主机发送起始位→主机发送元件片选读字节信号→等待从机回应→主机接收第一个字节→主机响应从机→主机接收第二个字节→主机响应从机→…→主机不响应从机→主机发送停止位
注意:①请尽量将I2C通信程序放入中断服务程序中,否则中断会干扰I2C协议的通信!②若无法将I2C通信程序放入中断服务程序中,请务必要在I2C协议通信时关闭中断,以尽可能减小中断对I2C协议的影响。③有的使用I2C协议的芯片需要一定时间的缓冲,请务必在芯片缓冲完毕后再进行下一轮通信。如AT24C02芯片,每次写一字节需5ms的时间,这5ms即为缓冲时间。

PCF8591

PCF8591是一个兼A/D、D/A转换功能为一体的8位精度的芯片,其最多可接8个同类芯片。PCF8591的A/D转换原理为逐次逼近式,具有4个模拟量输入通道(可通过编程选择单端或差分输入)。其与单片机的交流方式为I2C通信,A/D转换后的数字量输出以及D/A转换时的数字量输入均通过I2C通信传输。更多详细资料请参看数据手册。

使用PCF8591的步骤可简要概括如下:①发送PCF8591片选信号字。②发送控制字。③(D/A转换)发送数字量;(A/D转换)接收数字量。以上步骤为简略步骤,具体使用时,务必注意I2C协议的通信特点。

片选信号字:该字节分为固定部分和可编程部分,高4位为固定部分(1001),低四位为可编程部分,其中,D0为读/写位,D1、D2、D3位为芯片片选信号位(根据外部硬件A0、A1、A2接线图来确定),一般情况下,若只有一个PCF8591,A0、A1、A2直接接地,故D1、D2、D3位都为0。

控制字:发送到PCF8591的第二个字节将被存储到控制寄存器,用于控制器件功能。D7和D3位为固定位,恒为0;D6位为D/A和A/D转换功能的选择位,0为A/D转换,1为D/A转换;D5和D4位为模拟量四种输入方式的选择位;D2位为通道自动增加位,为1,芯片每次A/D转换后自动将通道号+1,选择下一个通道,为0则恒保持一个通道;D1和D0位则为通道输出选择位。

D/A转换输入字:发送给PCF8591的第三个字节将被存储至DAC数据寄存器,并使用片上D/A转换器转换成对应的模拟电压。
例如:

void adzh(void)
{
	IIC_Start();//主机发送起始位。
	IIC_SendByte(0x90);//主机发送PCF8591芯片片选及写信号。
	IIC_WaitAck();//等待从机响应。
	IIC_SendByte(0x03);//主机发送控制字,模式:D/A转换,输入方式单端输入,输出通道3。
	IIC_WaitAck();//等待从机响应。
	
	IIC_Start();//主机发送起始位。
	IIC_SendByte(0x91);//主机发送PCF8591芯片片选及读信号。
	IIC_WaitAck();等待从机响应。
	v=IIC_RecByte();//主机接收电压数字量数据。
	IIC_SendAck(1);//主机发送“无应答”信号。
	IIC_Stop();//结束通信。
	v=(5*v/256)*100;//换算成实际电压的100倍,方便显示。
	sprintf(zfhc,"u%3d",(u16)v);//将电压输入字符缓存数组,用于数码管显示。
}

AT24C02

AT24C02的使用方法类似于PCF8591,因为它们都是采用I2C通信协议。
AT24C02提供2048位串行电可擦除和可编程只读存储器(EEPROM),其大小为256个字节,每个字节8位。 该装置低功耗和低压操作应用于各种场合。通过两线串行接口访问,使用I2C与单片机通信。其还具有一个写保护引脚,当该引脚接高电平时,禁止对AT24C02进行写操作,低电平则允许写操作。更多详细资料请参看数据手册。
其有“字节写”和“页写”两种写入方式和“立即地址读”、“选择性读”和“连续读”三种读取方式。通过I2C是否应答来区分它们。
其与单片机通信过程可简要概括如下:①发送AT24C02芯片片选信号字。②发送地址。③写入数据/读出数据。
片选信号字:该字节分为固定部分和可编程部分,高4位为固定部分(1010),低四位为可编程部分,其中,D0为读/写位(低电平写,高电平读),D1、D2、D3位为芯片片选信号位(根据外部硬件A0、A1、A2接线图来确定),一般情况下,若只有一个AT24C02,A0、A1、A2直接接地,故D1、D2、D3位都为0。
写操作不管是“字节写”还是“页写”,都需要传输一个存储地址给AT24C02,不同的是,对于“页写”方式来说,该地址为首地址。传输完地址后,在传输数据时,“字节写”在传输完一个字节后就等待从机回应并发送停止通信信号,而“页写”则在等待从机回应后再发送数据,每回应一次就再发送一个字节数据给AT24C02,最多可发送16字节数据,每次传输前从机都会把之前的地址+1,以接收新数据。
示例代码:

//字节写
void write_E2(void)//写入存储函数。
{
	IIC_Start();
	IIC_SendByte(0xa0);//选中AT24C02并选择写模式。
	IIC_WaitAck();
	IIC_SendByte(0);//输入写地址0x00。
	IIC_WaitAck();
	IIC_SendByte(0x34);//写入数据。
	IIC_WaitAck();
	IIC_Stop();
//	Delayms(5);//可省略,写入至少需要5毫秒的时间。
}

蓝桥杯比赛培训笔记(基于STCCT107D训练板)_第1张图片

//页写
void write_E2(void)//写入存储函数。
{
	IIC_Start();
	IIC_SendByte(0xa0);//选中AT24C02并选择写模式。
	IIC_WaitAck();
	IIC_SendByte(0);//输入写地址0x00。
	IIC_WaitAck();
	IIC_SendByte(0x34);//写入第1个数据。
	IIC_WaitAck();
	IIC_SendByte(0x35);//写入第2个数据。
	IIC_WaitAck();
	……
	IIC_Stop();
//	Delayms(5);//可省略,写入至少需要5毫秒的时间。
}

蓝桥杯比赛培训笔记(基于STCCT107D训练板)_第2张图片

读操作:“立即地址读”时,不需要输入地址,直接读取1字节数据即可,地址默认为上次读/写操作的地址。“选择性读”时,则和PCF8591一样,需先写入地址,再读出1字节数据。“连续读”则是读取完一个字节后,主机发送一个“0”应答信号告诉从机需要更多数据,AT24C02则将地址+1,继续向主机发送数据,当地址达到最大后,AT24C02将会回到0地址,继续发送数据。需要注意的是,凡是读操作,在结束时,主机都需发送一个“1”不应答信号,再发送终止传输信号
示例代码:

//立即地址读
void read_E2(void)//读取存储函数。
{
	u8 tem;
	IIC_Start();
	IIC_SendByte(0xa1);//选中AT24C02并选择读模式。
	IIC_WaitAck();
	tem=IIC_RecByte();//读取设定的参数值。
	IIC_SendAck(1);
	IIC_Stop();
}

蓝桥杯比赛培训笔记(基于STCCT107D训练板)_第3张图片

//选择性读
void read_E2(void)//读取存储函数。
{
	u8 tem;
	IIC_Start();
	IIC_SendByte(0xa0);//选中AT24C02并选择写模式。
	IIC_WaitAck();
	IIC_SendByte(0);//输入读地址0x00。
	IIC_WaitAck();

	IIC_Start();
	IIC_SendByte(0xa1);//选中AT24C02并选择读模式。
	IIC_WaitAck();
	tem=IIC_RecByte();//读取设定的参数值。
	IIC_SendAck(1);
	IIC_Stop();
}

蓝桥杯比赛培训笔记(基于STCCT107D训练板)_第4张图片

//连续读
void read_E2(void)//读取存储函数。
{
	u8 sz[8];
	IIC_Start();
	IIC_SendByte(0xa0);//选中AT24C02并选择写模式。
	IIC_WaitAck();
	IIC_SendByte(0);//输入读地址0x00。
	IIC_WaitAck();

	IIC_Start();
	IIC_SendByte(0xa1);//选中AT24C02并选择读模式。
	IIC_WaitAck();
	sz[0]=IIC_RecByte();//读取设定的参数值。
	IIC_SendAck(0);
	sz[1]=IIC_RecByte();//读取设定的参数值。
	IIC_SendAck(0);
	sz[2]=IIC_RecByte();//读取设定的参数值。
	IIC_SendAck(0);IIC_SendAck(1);
	IIC_Stop();
}

蓝桥杯比赛培训笔记(基于STCCT107D训练板)_第5张图片

实验代码

代码部分阅读说明

一、下载代码时,请务必看清代码中单片机的时钟频率为多少,以免程序运行出错。
二、因笔记在不同时间段分多次完成,故有些代码中功能相同的函数会有一些区别。
三、在按键代码中,给P3口赋值以及“key”的赋值有两种写法:①“P3=0x0f”“key=P3”。②“P3|=0x0f”“key=(P3&0x0f)”。在此说明一下,②的写法是更为谨慎的写法,其使用了位运算符号“|=”,尽量保证了P3口的初始状态不变,防止在按键与其他元件共同使用该口时,因P3口状态改变而导致元件使用出错。但①的写法更方便读者理解按键消抖的原理,便未修改。

LED灯

LED灯实验一 ↓

//实验效果:所有LED灯同时点亮,并以每100毫秒亮一次的频率不断闪烁。
#include
#include
void buzz_clr(void);//声明函数。
void led_l(void);
void Delay100ms(void);
void main(void)
{
	buzz_clr();//让蜂鸣器不动作。
	while(1)
	{
		led_l();//LED灯闪烁程序。
	}
}

void buzz_clr(void)//函数定义。
{
	P2=(P2&0x1F|0xA0);//先初始化再置数,通过38译码器选定元器件对应的锁存器。
	P0=0;//控制信号的数据传输。
	P&=0x1f;//信号锁存。
}
void led_l(void)
{
	P2=(P2&0x1F|0x80);
	P0=0; //所有LED灯亮。
	Delay100ms();//延时100毫秒。
	P0=0xFF;//所有LED灯灭。
	Delay100ms();
	P2&=0x1f; 
}
void Delay100ms(void)		//单片机的内部时钟频率为@11.0592MHz。
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

LED灯实验二 ↓

//实验预期效果:8个led灯先依次点亮。
#include
#include
void buzz_clr(void);//声明函数。
void led_l(void);
void Delay100ms(void);
unsigned char led_data1[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//LED依次点亮的信号数组。

void main(void)
{
	while(1)//该循环是不断重复整个程序的循环。
	{
		buzz_clr();//让蜂鸣器不动作。
		led_l();//LED灯闪烁程序。
	}
}

void buzz_clr(void)//函数定义。
{
	P2=(P2&0x1f|0xa0);//先初始化再置数,通过38译码器选定元器件对应的锁存器。
	P0=0;//控制信号的数据传输。
	P&=0x1f;//信号锁存。
}
void led_l(void)
{
	int i;
		for(i=0;i<8;i++)
		{
			P2=(P2&0x1F|0x80);
			P0=led_data1[i];//输入数组里的控制信号数据。
			Delay100ms();
			P2&=0x1f;
		}
}
void Delay100ms(void)//单片机的内部时钟频率为@11.0592MHz。
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

LED灯实验三 ↓

/*实验预期效果:8个led灯先依次点亮,然后再分别2个、3个、4个、5个、6个、7个依次点亮;接着,再4个4个交
替闪烁5次,最后全部闪烁5次。*/
#include
#include
void buzz_clr(void);//声明函数。
void led_l(void);
void Delay100ms(void);
unsigned char led_data1[36]={	//流水灯依次点亮的信号数组。
								0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0x3f,
								0x9f,0xcf,0xe7,0xf3,0xf9,0xfc,0x1f,0x8f,0xc7,
								0xe3,0xf1,0xf8,0x0f,0x87,0xc3,0xe1,0xf0,0x07,
								0x83,0xc1,0xe0,0x03,0x81,0xc0,0x01,0x80,0x00};
unsigned char led_data2[2]={0x0f,0xf0};

void main(void)
{
	buzz_clr();//让蜂鸣器不动作。
	while(1)//该循环是不断重复整个程序的循环。
	{
		led_l();//LED灯闪烁程序。
	}
}

void buzz_clr(void)//函数定义。
{
	P2=(P2&0x1F|0xA0);//先初始化再置数,通过38译码器选定元器件对应的锁存器。
	P0=0;//控制信号的数据传输。
	P&=0x1f;//信号锁存。
}
void led_l(void)
{
	int i=1,j=0,k;
		for(i=0;i<37;i++)
		{
			P2=(P2&0x1F|0x80);
			P0=led_data1[i];//输入数组里的控制信号数据。
			Delay100ms();
			P2&=0x1f;
		}
		for(k=0;k<5;k++)
		{
			for(i=0;i<2;i++)
			{
				P2=(P2&0x1F|0x80);
				P0=led_data2[i];
				Delay100ms();
				P2&=0x1f;
			}
		}
		for(k=0;k<5;k++)
		{
			P2=(P2&0x1F|0x80);
			P0=0;
			Delay100ms();
			P0=0xff;
			Delay100ms();
			P2&=0x1f;
		}
}
void Delay100ms(void)		//单片机的内部时钟频率为@11.0592MHz。
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

LED灯实验四 ↓

/*实验预期效果:8个led灯先依次点亮,然后再分别2个、3个、4个、5个、6个、7个依次点亮;接着,再4个4个交
替闪烁5次,最后全部闪烁5次。*/
//该实验采用了部分地址译码的方式选中锁存器,故在下载程序至单片机上之前,短接帽J13需要短接至MM位置。
#include
#include
#include
void buzz_clr(void);//声明函数。
void led_l(void);
void Delay100ms(void);
unsigned char led_data1[36]={	//流水灯依次点亮的信号数组。
								0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0x3f,
								0x9f,0xcf,0xe7,0xf3,0xf9,0xfc,0x1f,0x8f,0xc7,
								0xe3,0xf1,0xf8,0x0f,0x87,0xc3,0xe1,0xf0,0x07,
								0x83,0xc1,0xe0,0x03,0x81,0xc0,0x01,0x80,0x00};
unsigned char led_data2[2]={0x0f,0xf0};

void main(void)
{
	buzz_clr();//让蜂鸣器不动作。
	while(1)//该循环是不断重复整个程序的循环。
	{
		led_l();//LED灯闪烁程序。
	}
}

void buzz_clr(void)//函数定义。
{
	XBYTE[0xA000]=0;
}
void led_l(void)
{
	int i=1,j=0,k;
		for(i=0;i<37;i++)
		{
			XBYTE[0x8000]=led_data1[i];//输入数组里的控制信号数据。
			Delay100ms();
		}
		for(k=0;k<5;k++)
		{
			for(i=0;i<2;i++)
			{
			XBYTE[0x8000]=led_data2[i];//输入数组里的控制信号数据。
			Delay100ms();
			}
		}
		for(k=0;k<5;k++)
		{
			XBYTE[0x8000]=0;
			Delay100ms();
			XBYTE[0x8000]=0xff;
			Delay100ms();
		}
}
void Delay100ms(void)		//单片机的内部时钟频率为@11.0592MHz。
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

LED灯实验五 ↓

//实验预期效果:LED灯逐颗逐颗点亮
//利用循环移位功能实现。
#include
#include
#include
void buzz(void);
void led(void);
void Delay500ms(void);
unsigned char rol(unsigned name,char times);
void main(void)
{
	while(1)
	{
		buzz();
		led();
	}
}
void buzz(void)
{
	XBYTE[0xA000]=0;
}
void led(void)
{
	int i;
	for(i=0;i<8;i++)
	{
		XBYTE[0x8000]=rol(0xfe,i);
		Delay500ms();
	}
}
unsigned char rol(unsigned name,char times)//循环左移函数
{
	char res;
	res=name<<times;
	res|=name>>(8-times);
	return res;
}
void Delay500ms(void)		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 22;
	j = 3;
	k = 227;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

数码管

动态扫描显示0~7 ↓

//实验预期效果:数码管从左至右显示76543210。
#include
#include
#include
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char smg_Dxdata[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
void buzz(void);
void Delayus(unsigned char us);
void smg(void);
void main(void)
{
	while(1)
	{
		buzz();
		smg();
	}
}
void buzz(void)
{
	XBYTE[0xA000]=0;
}
void smg(void)
{
	int i;
	for(i=0;i<8;i++)
	{
		XBYTE[0xE000]=0xff;
XBYTE[0xC000]=0;
		XBYTE[0xE000]=smg_Dxdata[i];
		XBYTE[0xC000]=smg_Pxdata[i];
		Delayus(10);
	}
}
void Delayus(unsigned char us)		//@11.0592MHz
{
	while(us)
	{
		_nop_();
		_nop_();
		_nop_();
		us--;
	}
}

指定数码管闪烁 ↓

//实验预期效果:数码管上从左至右第1、2、4、5、7、8片数码管以每秒一次的频率闪烁。
#include
#include
#include
void Delay1ms(void);
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char smg_Dxdata[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
void smg(void);
unsigned char times=0;//times为循环的次数,用于控制数码管的闪烁频率。
void main(void)
{
	XBYTE[0xA000]=0;//初始化P0口所有元器件。
	while(1)
	{
		smg();
	}
}
void smg(void)
{
	int i;
	for(i=0;i<8;i++)
	{
		XBYTE[0XE000]=0xff;//数码管消隐,段选信号。
		XBYTE[0xC000]=0;//数码管消隐,片选信号。
		XBYTE[0xE000]=smg_Dxdata[i];//段选信号不变,通过只改变片选信号让数码管闪烁。
		if(times<125)//循环的次数,当小于125次时,选中的数码管消隐。
		{		
			if((i>5)||(i<2)||((i==4)||(i==3)))//选中需要闪烁的数码管。
				{
					XBYTE[0xC000]=0;//消隐选中的数码管。
				}
			else 
				XBYTE[0xC000]=smg_Pxdata[i];//让其他数码管照常显示。
		}
		else
			XBYTE[0xC000]=smg_Pxdata[i];//当大于125次时,所有数码管都照常显示。
		Delay1ms();//数码管动态扫描延迟时间。
	}
		times++;//完成一次循环后,times加1。
		if(times==250)//当times=250时,初始化times的值。
			times=0;
}
void Delay1ms(void)		//@11.0592MHz
{
	unsigned char i, j;

	_nop_();
	_nop_();
	_nop_();
	i = 11;
	j = 190;
	do
	{
		while (--j);
	} while (--i);
}

按键综合

独立键盘(常用方法) ↓

/*实验预期功能:数码管默认熄灭,按下S7键,最左边的数码管开始显示并+1,松开S7键,则显示0;按下S6键,数
码管开始-1,松开S6键,则会显示1。*/
#include
#include
#include
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char smg_Dxdata[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
void buzz(void);
void button(void);
void smg(unsigned char tem);
sbit s7=P3^0;
sbit s6=P3^1;
void Delayms(unsigned char ms);
void Delayus(unsigned char us);
unsigned char btdb1,btdb2,smgct;
void main(void)
{
	while(1)
	{
		buzz();
		button();
	}
}
void buzz(void)
{
	XBYTE[0xA000]=0;
}
void smg(unsigned char tem)
{
	XBYTE[0xE000]=0xff;
	XBYTE[0xC000]=0;
	XBYTE[0xE000]=smg_Dxdata[tem];
	XBYTE[0xC000]=smg_Pxdata[7];
	Delayus(10);
}
void button(void)
{
	P3=0xff;
	if(s7==0)
	{	
		Delayms(10);
		if(s7==0)
		{
			smg(smgct+1);
			smgct=smgct+1;
			while(!s7);
			Delayms(10);
			while(!s7);
			smg(0);	
		}
	}

		if(s6==0)
	{	
		Delayms(10);
		if(s6==0)
		{
			smg(smgct-1);
			smgct=smgct-1;
			while(!s6);
			Delayms(10);
			while(!s6);
			smg(1);
		}
	}
}
void Delayms(unsigned char ms)		//@11.0592MHz
{
	unsigned char i, j;
	while(ms)
	{
		_nop_();
		_nop_();
		_nop_();
		i = 11;
		j = 190;
		do
		{
			while (--j);
		} 
while (--i);
	ms--;
}
}
void Delayus(unsigned char us)		//@11.0592MHz
{
	while(us)
{
		_nop_();
		_nop_();
		_nop_();
		us--;
	}
}

独立键盘(简化方法) ↓

/*实验预期效果:依次按下S4,分别会出现:①LED全亮;②LED全灭;③蜂鸣器响;④继电器吸合;⑤蜂鸣器关闭;
⑥继电器关闭;⑦回到初始状态。按下S5,回到初始状态。S6、S7无功能。*/
//这是独立键盘的另外一种编程方式,该方式与矩阵键盘的方式相似。
#include
#include
#include
void Delay5ms(void);
unsigned char led_Data[2]={0xff,0x00};//LED控制数据。
void P0ctrl(unsigned char temp);
void button(void);
void s4ctrl(void);
unsigned char i=0x00,mtfn;//i为P0口的控制变量,mtfn(mutifunction)为按键函数的输出变量。
void main(void)
{
	XBYTE[0xA000]=0;//初始化P0口所有元器件。
	while(1)
	{
		button();
		s4ctrl();
	}
}
void P0ctrl(unsigned char temp)//P0口的控制函数。
{
	if(temp==0)
		i=i&0xbf;//关闭蜂鸣器。
	else if(temp==1)
		i=i|0x40;//打开蜂鸣器。
	else if(temp==2)
		i=i&0xef;//关闭继电器。
	else if(temp==3)
		i=i|0x10;//打开继电器。
	XBYTE[0xA000]=i;
}
void led(unsigned char teml)//led控制函数。
{
	XBYTE[0x8000]=led_Data[teml];
}
void button(void)//按键控制函数。
{
	unsigned char key;
	P3=0x0f;//给P3口赋予初值。
	key=P3;//用变量获取实际P3口的实际值,防止P3口的实际值因受影响而改变。
	if(key!=0x0f)//第一次消抖开始,消除前沿抖动,即按下抖动。
	{
		Delay5ms();
		if(key!=0x0f)
		{//第一次消抖结束。
			switch(key)
			{
				case 0x0e:break;
				case 0x0d:break;
				case 0x0b:mtfn=0;break;//S5设置为复位按键。
				case 0x07:mtfn++;break;//S4每按下一次,功能就切换一次。
			}
			while(key!=0x0f)//第二次消抖开始,消除后沿抖动,即松手抖动。
			{
				Delay5ms();
				key=P3;
			}//第二次消抖结束。
		}
	}
	if (mtfn==7)//设置循环,让功能复位。
		mtfn=0;
}
void s4ctrl(void)//S4按键功能切换函数。
{
	if(mtfn==0)
	{
		led(0);
		XBYTE[0xA000]=0;
	}
	else if(mtfn==1)
		led(1);
	else if(mtfn==2)
		led(0);
	else if(mtfn==3)
		P0ctrl(1);
	else if(mtfn==4)
		P0ctrl(3);
	else if(mtfn==5)
		P0ctrl(0);
	else if(mtfn==6)
		P0ctrl(2);
}
void Delay5ms(void)		//单片机内部晶振频率@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

用按键切换不同的数码管闪烁 ↓

/*实验预期效果:依次按下S6,从左至右,可以分别让第1、2片,第4、5片,第7、8片数码管闪烁;第四次按下,
数码管正常显示。*/
//这是独立键盘的另外一种编程方式,该方式与矩阵键盘的方式相似。
#include
#include
#include
void Delayms(unsigned char ms);
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char smg_Dxdata[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
void button(void);
void smg(void);
unsigned char times,temqh;/*times(次数)为控制数码管闪烁频率的变量,temqh(temp切换)为切换闪烁
							数码管的变量。*/
void main(void)
{
	XBYTE[0xA000]=0;//初始化P0口所有元器件。
	while(1)
	{
		button();
		smg();
	}
}
void smg(void)
{
	int i;
	for(i=0;i<8;i++)
	{
		XBYTE[0XE000]=0xff;
		XBYTE[0xC000]=0;
		XBYTE[0xE000]=smg_Dxdata[i];
		if(times<125)//将消隐变量单独提取出来,作为一个大前提。
		{
			//让i和tempqh配对,进而通过给予tempqh不同的值来选中不同的数码管。		
			if(((i>5)&&(temqh==1))||(((i==4)||(i==3))&&(temqh==2))||((i<2)&&(temqh==3)))
				{
					XBYTE[0xC000]=0;//消隐选中的数码管。
				}
			else 
				XBYTE[0xC000]=smg_Pxdata[i];//其他未选中的数码管正常显示。
		}
		else
			XBYTE[0xC000]=smg_Pxdata[i];//当times>=125时,所有数码管正常显示。
		Delayms(1);
	}
		times++;//循环完一次,times+1。
		if(times==250)//当times=250时,初始化times的值。
			times=0;//当times=0时,所有数码管均正常显示。
}
void button(void)//按键控制函数。
{
	unsigned char key;
	P3=0x0f;//给P3口赋予初值。
	key=P3;//用变量获取实际P3口的实际值,防止P3口的实际值因受影响而改变。
	if(key!=0x0f)//第一次消抖开始,消除前沿抖动,即按下抖动。
	{
		Delayms(5);
		if(key!=0x0f)
		{//第一次消抖结束。
			switch(key)
			{
				case 0x0e:break;
				case 0x0d:temqh++;break;//设置S6的功能为在不同数码管之间切换。
				case 0x0b:;break;
				case 0x07:;break;
			}
			while(key!=0x0f)//第二次消抖开始,消除后沿抖动,即松手抖动。
			{
				Delayms(5);
				key=P3;
			}//第二次消抖结束。
		}
	}
	if(temqh==4)
			temqh=0;
}
void Delayms(unsigned char ms)		//@11.0592MHz
{
	unsigned char i, j;
	while(ms)
	{
		_nop_();
		_nop_();
		_nop_();
		i = 11;
		j = 190;
		do
		{
			while (--j);
		} 
while (--i);
	ms--;
}
}

矩阵键盘(复杂方法) ↓

//实验预期效果:依次按下S4~S19,倒数第四个数码管会依次显示0~F。
//不足:除S4~S7外,剩下的按键在按下时都会出现消隐的情况。有时会出现按键与数码管的显示值不匹配的情况。
//注意:J5的短接帽一定要接到1、2口,即左边和中间。
#include
#include
#include
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char smg_Dxdata[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
							  0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
void buzz(void);
void button(void);
void smg(unsigned char tem);
void Delay5ms(void);
void Delay10us(void);
unsigned char num;
void main(void)
{
	while(1)
	{
		buzz();
		button();
		smg(num);
	}
}
void buzz(void)
{
	XBYTE[0xA000]=0;
}
void smg(unsigned char tem)
{
	XBYTE[0XE000]=0Xff;
	XBYTE[0xC000]=0;
	XBYTE[0xE000]=smg_Dxdata[tem];
	XBYTE[0xC000]=smg_Pxdata[3];
	Delay10us();
}
void button(void)
{
	unsigned char key;
	P44=0,P42=1,P3=0x7f; //8个端口只让1个端口赋值为低电平。
	key=P3;//获取实际P3口的值
	key=key&0x0f;//相与,方便之后的判断。
	if(key!=0x0f)//第一次消抖开始,消除前沿抖动,即按下抖动。
	{
		Delay5ms();
		if(key!=0x0f)
		{
			key=P3;//第一次消抖结束。
			switch(key)//第一列按键开始判断。根据Key的值,赋予num不同的值。
			{
				case 0x7e:num=3;break;
				case 0x7d:num=2;break;
				case 0x7b:num=1;break;
				case 0x77:num=0;break;
			}
			while(key!=0x0f)//第二次消抖开始,消除后沿抖动,即松手抖动。
			{
				Delay5ms();
				key=P3;
				key=key&0x0f;
			}//第二次消抖结束。
		}
	}
	
	
	P44=1,P42=0,P3=0xbf;
	key=P3;
	key=key&0x0f;
	if(key!=0x0f)
	{
		Delay5ms();
		if(key!=0x0f)
		{
			key=P3;
			switch(key)//第二列按键开始判断。
			{
				case 0xbe:num=7;break;
				case 0xbd:num=6;break;
				case 0xbb:num=5;break;
				case 0xb7:num=4;break;
			}
			while(key!=0x0f)
			{
				Delay5ms();
				key=P3;
				key=key&0x0f;
			}
		}
	}
	
	
	P44=1,P42=1,P3=0xdf;
	key=P3;
	key=key&0x0f;
	if(key!=0x0f)
	{
		Delay5ms();
		if(key!=0x0f)
		{
			key=P3;
			switch(key)//第三列按键开始判断。
			{
				case 0xde:num=11;break;
				case 0xdd:num=10;break;
				case 0xdb:num=9;break;
				case 0xd7:num=8;break;
			}
			while(key!=0x0f)
			{
				Delay5ms();
				key=P3;
				key=key&0x0f;
			}
		}
	}
	
	
	P44=1,P42=1,P3=0xef;
	key=P3;
	key=key&0x0f;
	if(key!=0x0f)
	{
		Delay5ms();
		if(key!=0x0f)
		{
			key=P3;
			switch(key)//第四列按键开始判断。
			{
				case 0xee:num=15;break;
				case 0xed:num=14;break;
				case 0xeb:num=13;break;
				case 0xe7:num=12;break;
			}
			while(key!=0x0f)
			{
				Delay5ms();
				key=P3;
				key=key&0x0f;
			}
		}
	}
}
void Delay5ms(void)		//@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}
void Delay10us(void)		//@11.0592MHz
{
	unsigned char i;

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

矩阵键盘(简化方法) ↓

//实验预期效果:依次按下S4~S19,倒数第四个数码管会依次显示0~F。
//不足:所有按键在按下时那一刹那都会出现消隐的情况,若长按,数码管一直保持消隐状态,直至松手。
//注意:J5的短接帽一定要接到1、2口,即左边和中间。
#include
#include
#include
void Delay5ms(void);
void Delay10us(void);
//↓数码管片选数据,共有八片,为共阳极数码管。
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
//↓数码管段选数据,依次为0~f。
unsigned char smg_Dxdata[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
							  0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
void buzz(void);
void button(void);
void smg(unsigned char tem);
unsigned char num;//全局变量,使用于按键函数和数码管显示函数中。
void main(void)
{
	while(1)
	{
		buzz();
		button();
		smg(num);
	}
}
void buzz(void)
{
	XBYTE[0xA000]=0;//关闭蜂鸣器。
}
void smg(unsigned char tem)//数码管显示程序,和按键函数结合使用,用于判断按下哪个键。
{
	XBYTE[0xE000]=0xff;
	XBYTE[0xC000]=0;//片选信号——消隐。
	XBYTE[0xE000]=smg_Dxdata[tem];//段选信号。
	XBYTE[0XC000]=smg_Pxdata[3];//片选信号——始终显示一片数码管。
	Delay10us();
}
void button(void)//P3.0~P3.3为行线,P3.4、P3.5、P4.2、P4.4为列线。
{
	unsigned int key;
	P44=0,P42=1,P3=0x7f;/*所有8个端口中,只令1个端口为低电平,若1个按键按下,那么肯定有另外1个端口
						  会被拉低。因此,通过扫描就可以知道是哪个按键按下了。*/
	key=P3&0x0f;//和0x0f相与,让高四位在任何情况下始终为0000,即让case后的值始终是0x0****形式。
	P44=1,P42=0,P3=0xbf;
	key=(key<<4)|(P3&0x0f);/*为保留上一行扫描的数据,故将数据左移4位。此处“P3&0x0f”是为了将高四位
							 清0,进而再进行位运算时不会清除之前的数据。*/
	P44=1,P42=1,P3=0xdf;
	key=(key<<4)|(P3&0x0f);
	P44=1,P42=1,P3=0xef;
	key=(key<<4)|(P3&0x0f);
	if(key!=0x0ffff)//判断是否和未按下按键时的值相等。
	{
		Delay5ms();//第一次消抖开始:消除按键前沿抖动,即按下抖动。
		if(key!=0x0ffff)
		{//第一次消抖结束。
			switch(key)//开始判断key的值,并依此赋予num不同的值,然后将值传递给数码管显示函数。
			{
				case 0x0fffe:num=15;break;
				case 0x0fffd:num=14;break;
				case 0x0fffb:num=13;break;
				case 0x0fff7:num=12;break;
				case 0x0ffef:num=11;break;
				case 0x0ffdf:num=10;break;
				case 0x0ffbf:num=9;break;
				case 0x0ff7f:num=8;break;
				case 0x0feff:num=7;break;
				case 0x0fdff:num=6;break;
				case 0x0fbff:num=5;break;
				case 0x0f7ff:num=4;break;
				case 0x0efff:num=3;break; 
				case 0x0dfff:num=2;break; 
				case 0x0bfff:num=1;break; 
				case 0x07fff:num=0;break; 
				default:/*倘若有的按键按下没有达到预期效果,根据这个来判断key的值是否正确。*/
				{
						XBYTE[0x8000]=0xf5;//LED亮。
						Delay5ms();
						XBYTE[0x8000]=0xff;//LED灭。
				}
			}
			while (key!=0x0ffff)//第二次消抖开始:消除后沿抖动,即松手抖动。
			{
				Delay5ms();
				P44=0,P42=1,P3=0x7f;
				key=P3&0x0f;
				P44=1,P42=0,P3=0xbf;
				key=(key<<4)|(P3&0x0f);
				P44=1,P42=1,P3=0xdf;
				key=(key<<4)|(P3&0x0f);
				P44=1,P42=1,P3=0xef;
				key=(key<<4)|(P3&0x0f);
			}//第二次消抖结束。
		}
	}
}
void Delay5ms(void)		//单片机内部晶振频率@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}
void Delay10us(void)		//单片机内部晶振频率@11.0592MHz
{
	unsigned char i;

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

定时器

利用定时器中断计时 ↓

/*实验预期效果:一上电,最右边三位数码管都亮,并且最后两位数码管从60开始减计时。减至0后,会从255开始
减。按S6键,可以更改计时方向,改为加计时。*/
//本实验采用了中断的方式进行计时,并且为方便计算,单片机频率设为12MHZ,务必注意。
#include
#include
#include
//↓数码管片选数组。
unsigned char smg_Pxdata[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
//↓数码管段选数组。
unsigned char smg_Dxdata[11]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff};
unsigned char time[8]={10,10,10,10,10,10,10,10};//显示时间的数组。
void button(void);//矩阵键盘函数。
void smg(void);//数码管函数。
void A_Ctrl(void);//地址为0xA000的一组元器件控制函数。
//↓数值转换替换函数,将值转换成单个的数字,并依次替换time数组里的元素。
void zhuanhuan(unsigned int zh);
void Delayms(unsigned int ms);//软件延时函数,以微秒为单位。
void T0zd_Init(void);//定时器T0初始化函数。
unsigned char num,t=60;//全局变量,num存储键值,t存储时间。
unsigned int c;//全局变量,用于统计中断次数,用于延长时间。

/*为何main函数里没有中断服务函数却也能执行它?因为单片机程序有两条运行路线,一个是main函数,另一个就
是中断。*/
void main(void)
{
	T0zd_Init();//初始化定时器。
	A_Ctrl();//初始化蜂鸣器等无关部件。
	EA=1;//开启总中断。
	while(1)
	{	
		button();
		smg();
	}
}
void T0zd_Init(void)//定时器T0初始化函数。
{
	AUXR &=0x7f;//使用内部时钟,即f/12模式。
	TMOD &=0xf0;//初始化TMOD,保持高四位不变,初始化低四位。
	TMOD |=0x02;//设定定时器0的工作方式为方式1。
	TH0=0x9C;//初值高位写入,总共定时100us。
	TL0=0x9C;//初值低位写入,总共定时100us。
	TF0=0;//清除标志位(硬件虽会自己清除,但保险起见,还是手动清除一下)。
	TR0=1;//启动定时器。
	ET0=1;//开启定时器0中断。
}
void T0_Service(void) interrupt 1 //定时器T0中断服务程序
{
	c++;
	if (c==10000)//即定时器计时到了1s。
	{
		if (num==2)//按下S6,改变计时方向,加计时。
		t++;
		else
		t--;//否则按下其他按键,一直都是减计时。
		c=0;//初始化c的值,开始新的1秒计时。
	}
}
void A_Ctrl(void)//地址为0xA000的一组元器件控制函数(蜂鸣器等)。
{
	XBYTE[0xA000]=0;//让该组元件全部不动作。
}
void button(void)//矩阵键盘函数。
{
	unsigned int key;
	P44=0, P3=0xff;/*因为单片机一上电复位,其所有引脚均被初始化为高电平,原只需要修改引脚值即可。
					 但P3口中P3.4脚比较特殊,需给高电平。为方便,将整个P3口都置位。*/
	key=(P3&0x0f);
	P44=1,P42=0;
	key=((key<<4)|(P3&0x0f));
	P42=1,P3=0xdf;
	key=((key<<4)|(P3&0x0f));
	P3=0xef;
	key=((key<<4)|(P3&0x0f));	
	if(key!=0x0ffff)//第一次消抖开始。
	{
		Delayms(5);
		if(key!=0x0ffff)
		{//第一次消抖结束。
			switch(key)
			{
				case 0x07fff:num=0;break;
				case 0x0bfff:num=1;break;
				case 0x0dfff:num=2;break;
				case 0x0efff:num=3;break;
				case 0x0f7ff:num=4;break;
				case 0x0fbff:num=5;break;
				case 0x0fdff:num=6;break;
				case 0x0feff:num=7;break;
				case 0x0ff7f:num=8;break;
				case 0x0ffbf:num=9;break;
				case 0x0ffdf:num=10;break;
				case 0x0ffef:num=11;break;
				case 0x0fff7:num=12;break;
				case 0x0fffb:num=13;break;
				case 0x0fffd:num=14;break;
				case 0x0fffe:num=15;break;
			}
			while(key!=0x0ffff)//第二次消抖开始。
			{
				Delayms(5);
				P44=0, P3=0xff;
				key=(P3&0x0f);
				P44=1,P42=0;
				key=((key<<4)|(P3&0x0f));
				P42=1,P3=0xdf;
				key=((key<<4)|(P3&0x0f));
				P3=0xef;
				key=((key<<4)|(P3&0x0f));	
			}//第二次消抖结束。
		}
	}
}
void smg(void)//数码管函数。
{
	unsigned char i;
	zhuanhuan(t);
	for(i=0;i<8;i++)
	{
		XBYTE[0xE000]=0xff;
		XBYTE[0xC000]=0;
		XBYTE[0xE000]=smg_Dxdata[time[i]];
		XBYTE[0xC000]=smg_Pxdata[i];
		Delayms(1);
	}
}
void zhuanhuan(unsigned int zh)//转换替换函数。
{
	//对zh值取余,再用余数除以其所在的数位,便可将其数位的值转换为单个数字。
	//因为zh是整型函数,所以会强制转换数据类型,浮点值的小数部分会被直接省略。
	time[2]=(zh%1000)/100;
	time[1]=(zh%100)/10;
	time[0]=zh%10;
}
void Delayms(unsigned int ms)//@12.000MHz,请注意频率,为了方便计算定时器初值,频率改为12MHZ。
{
	while(ms)
	{
			unsigned char i, j;

	i = 12;
	j = 169;
	do
	{
		while (--j);
	} while (--i);
		ms--;
	}
}

利用sprintf函数完成数码管左移及显示 ↓

//实验预期效果:分别按下s4、s5、s6、s7,最右边两片数码管会显示9,10,11,12。
/*实验重点:①利用定时器进行数码管动态扫描,消除了之前按键按下就会消隐的缺陷。②利用了sprintf函数实现数
值的转换以及数字左移效果。*/
//左移效果:当9变成10时,1会左移至倒数第二片数码管上。
#include 
#include 
#include 
#include 
void button(void);//独立键盘函数。
void smg(unsigned char sg);//数码管函数。
void Timer0Init(void);//定时器T0初始化函数。
void T0_Ser(void);//定时器T0中断服务函数。
void Delayms(unsigned char ms);//软件延时函数,单位为毫秒。
unsigned char i,num;//全局变量。i为T0服务程序中切换数码管段、片选信号的参数;num为存储键值的参数。
unsigned char smg_Pxdata[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//数码管片选信号数组。
unsigned char zfhc[9];/*数码管字符缓存数组,用于存储数值被分离成个、十、百等数位后的字符型数字,设
						置9个元素是因为sprintf函数会默认在数组末尾加"\0"结束符号。*/
void main(void)
{
	XBYTE[0xA000]=0;//关闭其他无关元件。
	Timer0Init();//定时器初始化函数。
	EA=1;//开启总中断。
	while(1)
	{
		button();
	}
}
void button(void)//独立键盘函数。
{
	unsigned char key;
	P3|=0x0f;
	key=(P3&0x0f);
	if(key!=0x0f)//第一次消抖开始。
	{
		Delayms(5);
		key=(P3&0x0f);
		if(key!=0x0f)
		{
			key=(P3&0x0f);//第一次消抖结束。
			switch(key)
			{
				case 0x07:num=9;break;
				case 0x0b:num=10;break;
				case 0x0d:num=11;break;
				case 0x0e:num=12;break;
				default:
					XBYTE[0x8000]=0;//用于判断键值是否出错,在程序成功运行后可删除节省单片机空间。
			}
			while(key!=0x0f)//第二次消抖开始。
			{
				Delayms(5);
				key=(P3&0x0f);//第二次消抖结束。
			}
		}
	}
	sprintf(zfhc,"%8d",(unsigned int)num);/*将整型数字转换成字符并存入zfhc数组中,加(unsigned 
											int)的原因是因为C51编译器和C编译器不一样,没有内存对
											齐。加"%8d"是为了让字符可以呈现和输入密码一样左移的效
											果,利用了函数字符不够加空格(空格在接下来的"switch"
											判断中会被判断至"default"里去)的特性。*/
}
void smg(unsigned char sg)
{
	unsigned char dx;//临时变量,用于缓存数码管段选信号。
	XBYTE[0xE000]=0xff;
	XBYTE[0xC000]=0;
	switch(zfhc[sg])//字符型数字转换为整型数字,依次为0~9。
	{
		case '0':dx=0xc0;break;
		case '1':dx=0xf9;break;
		case '2':dx=0xa4;break;
		case '3':dx=0xb0;break;
		case '4':dx=0x99;break;
		case '5':dx=0x92;break;
		case '6':dx=0x82;break;
		case '7':dx=0xf8;break;
		case '8':dx=0x80;break;
		case '9':dx=0x90;break;
		default:dx=0xff;//对其他数码管进行消隐处理。
	}
	XBYTE[0xE000]=dx;
	XBYTE[0xC000]=smg_Pxdata[sg];
}
void Timer0Init(void)		//100微秒@12.000MHz,定时器初始化函数。
{
	AUXR &= 0x7F;		//定时器时钟12T模式。
	TMOD &= 0xF0;		//设置定时器模式。
	TMOD |= 0x02;		//设置定时器模式,8位自动重装模式。
	TL0 = 0x9C;		//设置定时初值,100us。
	TH0 = 0x9C;		//设置定时重载值,100us。
	TF0 = 0;		//清除TF0标志。
	TR0 = 1;		//定时器0开始计时。
	ET0=1;			//开启定时器0中断。
}
void T0_Ser(void) interrupt 1//定时器0的中断服务程序。
{
	smg(i);
	i++;
	if(i==8)
		i=0;//让信号只在0~7之间切换。
}
void Delayms(unsigned char ms)		//@12MHz
{
	while(ms)
	{
		unsigned char i, j;

		i = 12;
		j = 169;
		do
		{
			while (--j);
		} while (--i);
		ms--;
	}
}

Ds1302实时时钟

实时时钟的显示 ↓

main.c主文件
//实验预期效果:上电后,数码管开始显示实时时间,从“21-15-21”开始计时。
//实验重点:利用sprintf函数转换段选信号执行数码管显示。
#include 
#include 
#include 
#include 
#include 
//————————————————————————————————以下为硬件驱动函数声明。
void smg(unsigned char sg);//数码管函数。
void Timer0Init(void);//定时器0初始化函数,8位重装。
void Ds1302_Init(void);//Ds1302实时时钟初始化函数。
//————————————————————————————————以下为功能函数声明。
void Timer0_Ser(void);//定时器0中断服务函数。
void Ds1302_Read(void);//读取Ds1302的时间函数。
//————————————————————————————————以下为全局变量声明。
//
unsigned char i;//全局变量。i在定时器0服务程序中,用于数码管片选、段选切换显示。
//
//————————————————————————————————以下为数组声明。
//因以另一种形式传递段码信号,故此处无数码管段选信号数组。
unsigned char smg_Pxdata[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//数码管片选信号数组。
unsigned char Ds1302_Radr[3]={0x85,0x83,0x81};//Ds1302读动作的目标地址数组。分别是时、分、秒。
unsigned char bcdzh[8]={10,10,10,10,10,10,10,10};/*压缩BCD码转换缓存数组,用于缓存从Ds1302中读出
												   来后被转换的数据。*/
unsigned char zfhc[8];//字符缓存数组,用于缓存从sprintf函数中分离转换出来的字符数字。
void main(void)
{
	XBYTE[0xA000]=0;//初始化无关器件。
	EA=1;//开总中断。
	Timer0Init();//定时器0初始化函数。
	Ds1302_Init();//Ds1302初始化函数。
	while(1)
	{
		Ds1302_Read();//不断从Ds1302元气件中读取时间。
	}
}
//—————————————————————————————————以下部分为硬件驱动函数。
void smg(unsigned char sg)//数码管函数。
{
	unsigned char dx;//用于临时缓存段选信号。
	XBYTE[0xE000]=0xff;
	XBYTE[0xC000]=0;
	if((sg==2)||(sg==5))//因为“-”不是数字,故无法通过sprintf函数转换,因此只能利用片选信号来输出。
		dx=0xbf;//第2位和第5位数码管恒定显示“-”。
	else
	{
		switch(zfhc[sg])//对字符缓存数组里的字符进行转换,转换成相应的段选信号。
		{
			case '0':dx=0xc0;break;
			case '1':dx=0xf9;break;
			case '2':dx=0xa4;break;
			case '3':dx=0xb0;break;
			case '4':dx=0x99;break;
			case '5':dx=0x92;break;
			case '6':dx=0x82;break;
			case '7':dx=0xf8;break;
			case '8':dx=0x80;break;
			case '9':dx=0x90;break;
		}
	}
	XBYTE[0xE000]=dx;
	XBYTE[0xC000]=smg_Pxdata[sg];
}
void Timer0Init(void)		//定时器0初始化函数,1毫秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式。
	TMOD &= 0xF0;		//设置定时器模式。
	TMOD |= 0x02;		//设置定时器模式。
	TL0 = 0x9C;		//设置定时初值,100us。
	TH0 = 0x9C;		//设置定时重载值,100us。
	TF0 = 0;		//清除TF0标志。
	TR0 = 1;		//定时器0开始计时。
	ET0=1;			//开启定时器T0中断。
}
void Ds1302_Init(void)//Ds1302初始化函数,用于设定时间以及启动计时。
{
	Write_Ds1302_Byte(0x8e,0x00);//解锁保护,允许数据写入。
	Write_Ds1302_Byte(0x84,0x21);//写入数据,时初始化——21时(选择24小时模式)。
	Write_Ds1302_Byte(0x82,0x15);//写入数据,分初始化——15分。
	Write_Ds1302_Byte(0x80,0x21);//写入数据,秒初始化——21秒,同时启动时间计时。
	Write_Ds1302_Byte(0x8e,0x80);//重启保护,禁止数据写入。
}
void Ds1302_Read(void)//Ds1302读取数据函数,用于将时钟数据读取出来。
{
	unsigned char ir,ir2,temp;//ir1和ir2为循环参数;temp为临时参数,用于缓存读出来的时钟数据。
	for(ir=0;ir<3;ir++)
	{
/*因为读出来的参数是压缩BCD码,因此要对其进行解压缩转换。当DS1302的读地址顺序为时、分、秒时,转换数据
的原则为“时低秒高”“十低个高”,即在数组中,转换顺序为时~秒,即时→低地址,秒→高地址;且在转换单个时间单
位时,即十位→低位,个位→高位。在转换时同时进行格式化处理,将格式改为“23059059”类型。*/
		temp=Read_Ds1302_Byte(Ds1302_Radr[ir]);//将读取出的时钟数据缓存至temp,读取顺序,时~秒。
			bcdzh[(ir*3)+1]=(temp&0x0f);//此行转换的是时、分、秒的个位,同时进行格式化处理。
			bcdzh[ir*3]=((temp&0xf0)>>4);//此行转换的是时、分、秒的十位,同时进行格式化处理。
	}
	for(ir2=0;ir2<8;ir2++)
	{
		sprintf(zfhc+ir2,"%d",(unsigned int)bcdzh[ir2]);/*将bcd转换数组里的元素转换成字符,依次
														  传递给字符缓存数组。*/
	}
}
//—————————————————————————————————以下部分为功能函数。
void Timer0_Ser(void) interrupt 1//定时器T0的中断服务程序。
{
	smg(i);
	i++;
	if(i==8)
		i=0;//让信号只在0~7之间切换。
}
Ds1302.h头文件
//该文件蓝桥杯官方会下发。
#ifndef _ds1302_h
#define _ds1302_h
void Write_Ds1302(unsigned  char temp);
void Write_Ds1302_Byte( unsigned char address,unsigned char dat );
unsigned char Read_Ds1302_Byte ( unsigned char address );
void Set_RTC(unsigned char* pucRtc);
void Read_RTC(unsigned char* pucRtc);
#endif
Ds1302.c驱动文件
//该文件蓝桥杯官方会下发。
#include 
#include 
sbit SCK=P1^7;		
sbit SDA=P2^3;		
sbit RST = P1^3;   // DS1302复位												

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_();
	SDA=0;	_nop_();
	return (temp);			
}
// 设置时钟
void Set_RTC(unsigned char* pucRtc)
{
  unsigned char temp;

  Write_Ds1302_Byte(0x8E, 0); 							// WP=0:允许写操作
  temp = ((pucRtc[0]/10)<<4)+pucRtc[0]%10;
  Write_Ds1302_Byte(0x84, temp);						// 设置时
  temp = ((pucRtc[1]/10)<<4)+pucRtc[1]%10;
  Write_Ds1302_Byte(0x82, temp);						// 设置分
  temp = ((pucRtc[2]/10)<<4)+pucRtc[2]%10;
  Write_Ds1302_Byte(0x80, temp);						// 设置秒
  Write_Ds1302_Byte(0x8E, 0x80);  						// WP=1:禁止写操作
}
// 读取时钟
void Read_RTC(unsigned char* pucRtc)
{
  unsigned char temp;

  temp = Read_Ds1302_Byte(0x85);						// 读取时
  pucRtc[0] = (temp>>4)*10+(temp&0xf);
  temp = Read_Ds1302_Byte(0x83);						// 读取分
  pucRtc[1] = (temp>>4)*10+(temp&0xf);
  temp = Read_Ds1302_Byte(0x81);						// 读取秒
  pucRtc[2] = (temp>>4)*10+(temp&0xf);
}

Ds18b20温度传感器

实时温度的测量 ↓

main.c主文件
/*实验预期效果:方案一,右边四位数码管以“21-5”的格式实时显示实际温度,其中“-”为整数和小数的分割线;方
案二,右边三位数码管以“21.5”的格式实时显示实际温度(该方案将需修改的函数以注释的形式写在最后)。*/
/*实验重点:①sprintf函数的拓展用法,解决想要移位某片数码管的问题。如将“215”格式中的“5”右移一位,在之
前的位置加上“-”,变成“21-5”的格式。②通过“或”的位运算,将小数点加入,无需再创建数组。*/
#include 
#include 
#include 
#include 
#include 
void smg(unsigned char sg);//数码管函数。
void Timer0Init(void);//定时器T0初始化函数。
void T0_Ser(void);//定时器T0中断服务函数。
void get_Temperature(void);//获取实际温度函数。
unsigned char i;//全局变量。i在定时器T0的服务函数中用于切换段选、片选信号。
unsigned char smg_Pxdata[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//数码管片选信号。
unsigned char zfhc[8];//字符缓存数组,缓存经sprintf函数转换后的数字字符。
void main(void)
{
	XBYTE[0xA000]=0;//让其他无关元件初始化。
	EA=1;//开启总中断。
	Timer0Init();//定时器初始化函数。
	while(1)
	{
		get_Temperature();//实时获取温度数据。
	}
}
//可替换函数部分开始。方案一,以“21-5”的形式实时显示实际温度。
void smg(unsigned char sg)//数码管函数。
{
	unsigned char dx;//临时缓存段选信号参数。
	XBYTE[0xE000]=0xff;
	XBYTE[0xC000]=0;
	if (sg==6)
	{
		dx=0xbf;//让第6片数码管始终显示“-”,成为整数和小数的分割线。
	}
	else
	{
		switch(zfhc[sg])//将字符缓存数组中的字符转换为相应的段选信号。
		{
			case '0':dx=0xc0;break;
			case '1':dx=0xf9;break;
			case '2':dx=0xa4;break;
			case '3':dx=0xb0;break;
			case '4':dx=0x99;break;
			case '5':dx=0x92;break;
			case '6':dx=0x82;break;
			case '7':dx=0xf8;break;
			case '8':dx=0x80;break;
			case '9':dx=0x90;break;
			default:
				dx=0xff;//消隐其他无关数组。
		}
	}
	XBYTE[0xE000]=dx;
	XBYTE[0xC000]=smg_Pxdata[sg];
}
void get_Temperature(void)//获取实际温度数据函数。
{
	unsigned int rt;//rt(real temperature)用于存放实际的温度。
	rt=read_Temperature();//从ds18b20中读取温度数据。
	rt=rt*0.625;//因室内温度一般都高于0度,因此此处不判断温度的正负。
	sprintf(zfhc,"%7d%d",(unsigned int)rt,(unsigned int)rt%10);/*重复打印实际温度的小数部分,并
																 将小数部分放在整数部分后面,再
																 一齐靠右对齐,解决第6片数码管
																 小数部分被覆盖的问题。*/
}
//可替换函数部分结束。
void Timer0Init(void)		//100微秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式。
	TMOD &= 0xF0;		//设置定时器模式,8位自动重载。
	TMOD |= 0x02;		//设置定时器模式。
	TL0 = 0x9C;		//设置定时初值,100us。
	TH0 = 0x9C;		//设置定时重载值,100us。
	TF0 = 0;		//清除TF0标志。
	TR0 = 1;		//定时器0开始计时。
	ET0=1;			//开启定时器T0中断。
}
void T0_Ser(void) interrupt 1//定时器T0中断服务函数。
{
	smg(i);
	i++;
	if(i==8)
		i=0;//初始化i的值,让段选、片选信号只能在0~7之间切换。
}
////可替换函数部分开始。方案二,以“21.5”的格式实时显示实际温度。
//void smg(unsigned char sg)//数码管函数。
//{
//	unsigned char dx;//临时缓存段选信号参数。
//	XBYTE[0xE000]=0xff;
//	XBYTE[0xC000]=0;
//	switch(zfhc[sg])//将字符缓存数组中的字符转换为相应的段选信号。
//	{
//		case '0':dx=0xc0;break;
//		case '1':dx=0xf9;break;
//		case '2':dx=0xa4;break;
//		case '3':dx=0xb0;break;
//		case '4':dx=0x99;break;
//		case '5':dx=0x92;break;
//		case '6':dx=0x82;break;
//		case '7':dx=0xf8;break;
//		case '8':dx=0x80;break;
//		case '9':dx=0x90;break;
//		default:
//			dx=0xff;//消隐其他无关数组。
//	}
//	if(sg==6)//给第6片数码管加小数点。
//	{
//		dx&=0x7f;//加小数点。
//	}
//	XBYTE[0xE000]=dx;
//	XBYTE[0xC000]=smg_Pxdata[sg];
//}
//void get_Temperature(void)//获取实际温度数据函数。
//{
//	unsigned int rt;//rt(real temperature)用于存放实际的温度。
//	rt=read_Temperature();//从ds18b20中读取温度数据。
//	rt=rt*0.625;//因室内温度一般都高于0度,因此此处不判断温度的正负。
//	sprintf(zfhc,"%8d",(unsigned int)rt);//加“8”是为了让数码管显示的字符靠右对齐。
//}
//可替换函数部分结束。
onewire.h头文件
//该文件蓝桥杯官方会下发。
#ifndef __ONEWIRE_H
#define __ONEWIRE_H

unsigned int read_Temperature(void);  //; ;

#endif
onewire.c驱动文件
//该文件蓝桥杯官方会下发。
/*
  程序说明: 单总线驱动程序
  软件环境: Keil uVision 4.10 
  硬件环境: CT107单片机综合实训平台(外部晶振12MHz) STC89C52RC单片机
  日    期: 2011-8-9
*/
#include "stc15f2k60s2.h"

sbit DQ = P1^4;  //单总线接口

//单总线延时函数
void Delay_OneWire(unsigned int t)  //STC89C52RC——注:已修正适应IAP15F2K60系列芯片。
{
	unsigned char x;
	while(t--)
	for(x=0;x<12;x++);
}

//通过单总线向DS18B20写一个字节
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);
}

//从DS18B20读取一个字节
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;
}

//DS18B20设备初始化
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;
}
unsigned int read_Temperature(void)
{
	unsigned char low,high;
	unsigned int sum;
	init_DS18b20();//初始化DS18B20。
	Write_DS18B20(0xCC);//因只有一个元件,可跳过ROM存储识别。
	Write_DS18B20(0X44);//对温度进行模数转换。
	Delay_OneWire(4000);
	init_DS18b20();//初始化DS18B20。
	Write_DS18B20(0xCC);//因只有一个元件,可跳过ROM存储识别。
	Write_DS18B20(0XBE);//读取温度暂存器。
	low=Read_DS18B20();//温度数据的低字节暂存于low中。
	high=Read_DS18B20();//温度数据的高字节暂存于low中。
	
	sum=high;
	sum<<=8;
	sum|=low;
	return sum;//将数据返回给上层函数。
}

你可能感兴趣的:(笔记)