从零手搓蓝桥杯单片机代码模板

前言

本文仅适用于已经初步了解各个模块但还没有整合的蓝桥杯单片机备赛者。

整理出自己的代码模板,是蓝桥杯单片机比赛的重要基础。我们在比赛开始,会收到官方给的数据包。里面包括STC-ISP和keil5的下载包;三个底层代码;板子的原理图等。答完客观题后,我们首先要做的就是根据题目需要,整理出自己的代码模板,这样才能灵活的调用各个模块,如键盘,数码管,ds1302,pdf8591,iic,定时器,hc138等等。那就让我们一起手搓出完整的代码模板吧!

(如有不清楚或错误,欢迎交流指正:wei_xin4538@qq.com)

构建模板思路

不论题目是怎么样的,一定是要使用LED,按键,数码管。我们是利用模块化来搭建模板。所以每个外设大致都建立一个.c文件和.h文件。

数码管的搭建要利用到延时函数和锁存器。所以第一步先写hc138()和delay()两个函数。

接着写数码管的函数。数码管的函数除了要用到ch138()和delay(),还要用到资料包的“共阳数码管段码表”。数码管写两个函数,一个是数字带点的,一个是数字不带点的。

写完数码管就写按键。按键的函数要做到按下某个键就返回相应的键值。

然后在stc-isp里面得到定时器0,1的代码,并且放到main()里初始化

接着就按照题目的要求把相应的外设写出来,别往里要利用好资料包里的三个底层代码。

有一大类外设是返回一个数,比如温度,时间,超声波测距。

模板搭建好后,就开始写main.c文件的代码了。

首先开头是各种头文件的引用

#include <STC15F2K60S2.H>
#include <delay.H>
#include <smg.H>
#include <hc138.H>
#include <time0.H>
#include <close.H>
#include <iic.H>
#include <key.H>
#include <pcf8591.H>
#include <chaoshengbo.H>

我们看到里面有一个close.h文件,这个是关闭外设的初始化,主要是关闭led,蜂鸣器和继电器,这个函数和定时器初始化的函数一起放在main()开头

接着是各种变量的定义

unsigned char mode1 = 0; //0:测距界面 1:参数界面 2:记录界面(初始化为测距界面)
unsigned char mode2 = 1; //模式值 1:按键模式 2:旋钮模式(初始化为按键模式)
unsigned char warning_time = 0;//报警次数
unsigned char low = 10;//参数下限,初始化为10
unsigned char up = 60;//参数上限,初始化为60
unsigned char distance = 0;//超声波测距
unsigned char distance_temp = 0;//储存前次距离
unsigned char ms0 = 0;//每一毫秒闪烁一次
unsigned char key_temp;//保存前一次键值
unsigned char U; //变阻器
unsigned char S8 = 0;//参数界面和旋钮模式下,按一下S8可调节low
unsigned char S9 = 0;//参数界面和旋钮模式下,按一下S9可调节up
unsigned char LED_temp = 0;//LED8 0.1秒闪烁

变量大致有几种:

1.控制模式的变量:这些变量放在键盘扫描函数中,键盘扫描函数检测到某个键按下时,就对模式变量进行操作,比如从1加到3再变成1,意义是按下按键可以切换不同模式,也就是再数码管显示不同模式的界面。

2.放在定时器里自加的变量

3.获取具体数值的变量:温度,电压,时间,距离等

4.暂存变量:储存某一变量的值,意义是当变量的值不断切换,而暂存变量保存了变量上一次的值,方便比较变化做出判断。

5.界限变量:作为临界量,有时题目要求按下某案件,某个临界量或者固定量要加上多少或减去多少。

然后就是三个函数:

LEDrun();shumaguan_run();keyrun();

这三个函数和按键函数key()都放在while()里不断执行

void main()
{
	Timer0_Init();
	Timer1_Init();
	close();
while(1)
{
	key();
	keyrun();
	LED_run();
	smg_run();
}
}

说明key()与keyrun()的区别:前者是得到键码值,后者是利用key()的键码值实现对按键操作的判断以及对案件操作后模式的变化。

简单来说就是keyrun()的作用是改变模式值

LED_run();是若干个if()判断模式值,然后里面进行灯的显示

smg_run();也是若干个if()判断模式值,然后里面根据得到的数据或者要显示的数字字母进行显示。

all in all

整个流程可以粗略归结为

0.各种初始化

1.外设获取数据

2.获取按键键值

key();

3.根据按键变化状态改变模式值

keyrun();

void keyrun()
{
	unsigned char keynum;
	keynum = key();
	
	
	if(keynum==0 && key_temp==4)
	{
		if(mode1==2)
		{
			mode1 = 0;
		}
		else
		{
			mode1++;
		}
	}
	
	
	
	if(keynum==0 && key_temp==5)
	{
		if(mode1==1)//参数界面
		{
		if(mode2==2)
		{
			mode2 = 1;
		}
		else
		{
			mode2++;
		}
		}
		if(mode1==2)//记录界面
		{
			warning_time = 0;
		}
	}
	
	
	

	if(keynum==0 && key_temp==9)
	{
		//参数界面下的按键模式
	if(mode1==1 && mode2==1)
	{	
		S9 = 0;
		if(up==90)
		{
			up=50;
		}
		else
		{
			up += 10;
		}
	}
	//参数界面下的旋钮模式
	if(mode1==1 && mode2==2)
	{	
			S9 = 1;
		U = pcf8591_ADC(0x03);
		if(U<55)             up = 50;
		if(U>=55  && U<110)  up = 60;
		if(U>=110 && U<165)  up = 70;
		if(U>=165 && U<220)  up = 80;
		if(U>=220 && U<=255) up = 90;
	}
	}

	
	
	if(keynum==0 && key_temp==8)
	{
		//参数界面下的按键模式
		if(mode1==1 && mode2==1)
	{	
		S8 = 0;
		if(low==40)
		{
			low=0;
		}
		else
		{
			low += 10;
		}
	}
	//参数界面下的旋钮模式
	if(mode1==1 && mode2==2)
	{		
		S8 = 1;
	}
	}
	
	
	//确保只有当按键8,9按下时,旋钮才会生效
	if(!(keynum==0 && key_temp==8) && !(keynum==0 && key_temp==0))S8 = 0;
	if(!(keynum==0 && key_temp==9) && !(keynum==0 && key_temp==0))S9 = 0;

	//if(!keynum)//如果要实现按键按一下mode++,就不能反复执行,要去掉if(!keynum)这句话。
	key_temp = keynum;
	
}

4.判断不同的模式值应该亮什么灯

LED_run();

5.数码管根据外设获取的数据,再不同模式值的条件下显示不同数据

smg_run();

part 1:hc138

代码示例1:

#include <STC15F2K60S2.H>


void hc138(unsigned char n)
{
	switch (n)
	{
		case 4:P2=P2&0x1f|0x80;break;//led
		case 5:P2=P2&0x1f|0xa0;break;//蜂鸣器和继电器
		case 6:P2=P2&0x1f|0xc0;break;//段选
		case 7:P2=P2&0x1f|0xe0;break;//位选
		case 0:P2=P2&0x1f;break;     //锁存
	}
}

//以上是hc138.c文件
//以下是hc138.h文件

#ifndef __hc138_H__
#define __hc138_H__

void hc138(unsigned char n);

#endif

part 2:定时器初始化函数和中断函数

第一步:在STC-ISP的定时器计算器中选中:系统频率为12.000mhz;定时时长为1毫秒;勾上‘复制代码’旁边的‘使能定时器中断’,最后复制代码,如下:

void Timer0_Isr(void) interrupt 1
{
}

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

第二步:在Timer0_Init(void)函数末尾添加"EA = 1;"然后将上述代码放在main函数前面。在main函数里面声明一下:定时器初始化;即写上:Timer0_Init();。在main函数中关闭LED灯,蜂鸣器和继电器。代码如下:

​
#include <STC15F2K60S2.H>
#include "hc138.h"



//*****************************************************************
void Timer0_Isr(void) interrupt 1
{
}

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

void main()
{
P2 = 0x00;

P0 = 0xFF;
hc138(4);    //关闭LED

P0 = 0x00;
hc138(5);   //关闭蜂鸣器和继电器

Timer0_Init();

while(1)
{

}

}

​

part 3:软件延时函数Delay();

第一步:在STC-ISP的软件延时计算器中选择12.000mhz,定时时长为1毫秒,然后复制代码,如下:

void Delay1ms(void)	//@12.000MHz
{
	unsigned char data i, j;

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

然后对该代码进行改造:

1.void Delay1ms(void)    改为 void Delay(unsigned char n)

2.在函数里面加上while(n--){},将函数里面的内容放进{}里。

3.最后生成了Delay.c文件和Delay.h文件。

代码如下:

void Delay(unsigned char n)		//@12.000MHz
{
	while(n--)
	{
		unsigned char i, j;

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

//以上是Delay.c文件
//以下是Delay.h文件

#ifndef __delay_H__
#define __delay_H__

void Delay(unsigned char xms);		//@12.000MHz

#endif

part 4:数码管显示函数

首先是添加三个头文件。然后再资料包的共阳数码管里复制数组code unsigned char Seg_Table[]

然后再写两个函数。

#include <STC15F2K60S2.H>
#include "hc138.h"
#include "delay.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
	0x88, //A
	0x83, //b
	0xc6, //C
	0xa1, //d
	0x86, //E
	0x8e //F
};


void showsmgNodot(unsigned char pos,unsigned char num)
{
	hc138(6);
	P0=0x01<<pos;
	hc138(7);
	P0=Seg_Table[num];
	
	Delay(1);
	P0=0xff;
	hc138(0);
}

void showsmgdot(unsigned char pos,unsigned char num)
{
	hc138(6);
	P0=0x01<<pos;
	hc138(7);
	P0=Seg_Table[num]+0x80;
	
	Delay(1);
	hc138(0);
}

//以上是show_SMG.c文件
//以下是show_SMG.h文件

#ifndef __showsmg_H__
#define __showsmg_H__

void showsmgNodot(unsigned char pos,unsigned char num);
void showsmgdot(unsigned char pos,unsigned char num);

#endif






part 5 独立按键与矩阵键盘

我们要写两个函数,一个是key()用于获取键值,另一个是keyrun()用于判断按键状态后的操作,包括:单击松开后,按住不动时,双击后,长按松开后 ,四种情况。

1.key()

首先是key()函数,设置为unsigned char类型,定义变量temp初始值为0,最后return temp。

然后打开资料包的硬件原理图,找到按键的图,如下:

 

P30 P31 P32 P33 理解为行  P34 P35 P42 P44 理解为列

先选择列 也就是P44 = 0; P42 = 1; P35 = 1; P34 = 1;    

再分别用四个if判断哪个是0(被按下),将对应的键值赋值给temp;

然后赋值粘贴三次,改一下数字就好了。

值得注意的是,使用了ne555的话,就可能造成P34引脚冲突,解决办法是先关闭中断

ET0 = 0;进行键值扫描,然后打开中断ET0 = 1;最后加上P3 = 0xff;

2.keyrun()

接着写keyrun()函数。keyrun()无返回值,和key()一起放在while(1){}里不断执行。

首先定义一个变量keynum接收key()的键值unsigned char keynum;keynum=key();然后定义一个变量keytemp进行前后状态的比较。

(需要注意的是,key()中temp = 0;同时key()在while里不断执行,也就是说当没有按键按下时,keyrun()中keynum的值就是0,并且当按键按下后松开的一瞬间,下一个while循环执行到key()时,temp再次变成0.)

接着进行按键消抖。if(keynum!=keytemp)Delay(10);//当前状态和前状态不相同时进行按键消抖操作。最后面再写入if(!keynum==0) keytemp=keynum;

什么意思呢?keyrun()开头keynum获得键值,结尾将本次键值保存在keytemp里。并且为了让’保存‘这个动作不出现在没有按键按下时,由于没有按键按下时keynum是0(前面那段括号有说原因),就加个if判断。

由此在中间我们就可以添加if,通过判断keynum和keytemp的状态,得到预期的效果,也就是

if(某个按键按下松开后){执行某个操作}

如if(keynum==0 && keytemp==4)
    {
        //按键4按下去松开后的操作
    }


   解释:为什么keynum==0 && keytemp==4这样写?当按键按下时第一次执行keyrun()会赋值给keytemp,松开后keynum立马变成0,keytemp保持不变。所以keynum==0 和keytemp==4同时成立时就说明是“按键4按下松开后”这个情况.

同样  if(某个按键按住不动时){执行某个操作}

if(keynum==4 && keytemp==4)
    {
        //按键4按下去不松开的操作
    }

解释:当keynum==4 && keytemp==4同时成立时,说明按键没有松开。因为松开后keynum会变成0.

长按后松开

这个要用到定时器0,


unsigned char key()
{
    unsigned char temp = 0;
    
    ET0 = 0;//消除与ne555的冲突

	P44 = 0; P42 = 1; P35 = 1; P34 = 1;	
	if(P33==0) temp = 4;
	if(P32==0) temp = 5;	
	if(P31==0) temp = 6;
	if(P30==0) temp = 7;
	
	P44 = 1; P42 = 0; P35 = 1; P34 = 1;	
	if(P33==0) temp = 8;
	if(P32==0) temp = 9;	
	if(P31==0) temp = 10;
	if(P30==0) temp = 11;
	
	P44 = 1; P42 = 1; P35 = 0; P34 = 1;	
	if(P33==0) temp = 12;
	if(P32==0) temp = 13;	
	if(P31==0) temp = 14;
	if(P30==0) temp = 15;
	
	P44 = 1; P42 = 1; P35 = 1; P34 = 0;	
	if(P33==0) temp = 16;
	if(P32==0) temp = 17;	
	if(P31==0) temp = 18;
	if(P30==0) temp = 19;

    ET0 = 1;
    P3 = 0xff;

    return temp;
}



​
void keyrun()
{
	unsigned char keynum;
	keynum=key();//这里的keynum和上面函数的keynum不是同一个哦
	if(keynum!=keytemp)//当前状态和前状态不相同时进行按键消抖操作
	{
		Delay(10);//这是一个延时函数延时10ms,用来做按键消抖
	}
	if(keynum==0 && keytemp==1)//当前状态为0,即按键没按下;前状态为1,即按键1按下——上升沿触发按键
	{
		//按键1松开做的操作
	}
	if(keynum==0 && keytemp==2)
	{
		//按键2松开做的操作
	}
	if(keynum==0 && keytemp==3)
	{
		//按键3松开做的操作
	}
	if(keynum==4 && keytemp==4)
	{
		//按键4按下去不松开的操作
	}
	if(!keynum==0)
	keytemp=keynum;//获取前一次状态
}


​



part 6:初始化 关闭外设

这个函数与定时器初始化,时钟芯片ds1302初始化等初始化函数一起放在main()函数的开头

void close()
{
P0 = 0x00;
hc138(5);  //关闭继电器蜂鸣器
hc138(0); 
P0 = 0xff;
hc138(4);   //关闭LED
hc138(0); 
hc138(6);  //关闭所有数码管
hc138(0); 
hc138(7);  //关闭数码管
hc138(0); 
}

(代码很好理解,无需多言)

part 7:DS1302

首先建立一个ds1302.c文件。然后将底层驱动代码ds1302复制粘贴进去。然后在开头添加下列代码:

#include <STC15F2K60S2.H>
#include "intrins.h"

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

记不住的话就打开硬件图找到DS1302,如下图:

然后建立读时间和写时间两个函数

unsigned char time[]={0x50,0x59,0x23};
unsigned char ds1302[]={0x80,0x82,0x84};//秒,分,时

void ds1302_init()
{
unsigned char i;
Write_Ds1302_Byte(0x8e,0x00);    //写保护地址是0x8e,关闭写保护才能往里面写数据
for(i=0;i<3;i++)
{
Write_Ds1302_Byte(ds1302[i],time[i]);
}
Write_Ds1302_Byte(0x8e,0x80);    //打开写保护
}
void ds1302_read()
{
	unsigned char i;
	for(i=0;i<3;i++)
	{
		time[i]=Read_Ds1302_Byte(ds1302[i]+0x01);
	}
}

接着将下列代码复制粘贴到main.c文件上

extern unsigned char time[];
extern unsigned char ds1302[];//秒,分,时

最后是bcd转换为十进制,显示在数码管上

​
void show_time()
{
	showsmgNodot(0,time[2]/16);
	Delay(2);
	showsmgNodot(1,time[2]%16);
	Delay(2);
	showsmgNodot(3,time[1]/16);
	Delay(2);
	showsmgNodot(4,time[1]%16);
	Delay(2);
	showsmgNodot(6,time[0]/16);
	Delay(2);
	showsmgNodot(7,time[0]%16);
	Delay(2);
		
}

​

part 8:pcf8591 AD/DA

首先建立一个iic.c文件,将底层驱动代码iic.c的代码复制粘贴到文件里。然后将下列代码添加到开头。

#include <STC15F2K60S2.H>
#include "intrins.h"

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

如果记不住P2^1;P2^0;那么打开资料包中的硬件原理图,搜索PFC8591就能找到如下图:

然后就要在底层代码后面写关于ADC和DAC两个函数:void pcf8591_DAC(unsigned char dat)

和unsigned char pcf8591_ADC(unsigned char add)。

DAC是输入参数,然后单片机就会在P34引脚输出电压,参数是0~255,电压则是0~5.函数的返回值就是电压。

ADC是光敏电阻和变阻器。输入的参数add是选择光敏电阻或变阻器。0x01是光敏电阻,0x03是变阻器。

先写DAC函数.打开资料包—竞赛板芯片资料—pcf8591,在第八页找到以下时序图:

先start。然后发送设备地址0x90,等待一下,然后发送部件地址0x40,等待一下,然后发送参数dat,等待一下,然后停止stop。(函数无返回值)

再写ADC函数:定义一个变量dat,然后先start。然后发送设备地址0x90,等待一下,然后发送部件地址(0x01是光敏电阻,0x03是变阻器),等待一下,然后重新start,然后发送设备地址0x91,等待一下,参数接收receive函数的返回值,然后I2CSendAck(1);(里面有个1),最后stop  (被忘了最后return dat;)

设备地址0x90怎么来的?

如何如果没记住0x01是光敏0x03是变阻怎么办呢?打开硬件图找到PCF8591看到有AIN0 AIN1 AIN2

搜索AIN1如图:

很明显AIN1是光敏,然后打开PCF8591,找到下图:

这是部件地址,前面六个是0,后面两个是用来选择光敏和变阻。很明显,光敏——AIN1——channel 1——01,将01和前面6个0组合就是0x01.

DAC为什么是0x40.呢?首先DAC对应AIN0——channel——00,其次是左边第二位下面有说:

意思是模拟输出电压则为1,刚好对应DAC,所以是0x40;而ADC时不用模拟输出电压,则置为0.

​


void pcf8591_DAC(unsigned char dat)
{
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	I2CSendByte(0x40);
	I2CWaitAck();
	I2CSendByte(dat);
	I2CWaitAck();
	I2CStop();
}

//0x01光敏电阻,0x03滑动变阻
unsigned char pcf8591_ADC(unsigned char add)
{
	unsigned char dat;
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	I2CSendByte(add);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0x91);
	I2CWaitAck();
	dat=I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
	
	return dat;
}


​

part 9: AT24C02 (EEPROM)

将下列代码添加到iic.c文件后面

值得一提的是这个和PCF8591的两个函数的书写有异曲同工之妙哈哈哈

void at24c02_write(unsigned char pos,unsigned char num)
{
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(pos);
	I2CWaitAck();
	I2CSendByte(num);
	I2CWaitAck();
	I2CStop();
}

unsigned char at24c02_read(unsigned char pos)
{
	unsigned char num;
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(pos);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0xa1);
	I2CWaitAck();
	num=I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
	
	return num;
}

part 10:超声波

简述原理:发射波引脚每13微秒反转电平,循环八次。发出信号后开始计时,当接收到信号(接收波引脚为0)或者超时时,停止计时,得出并返回距离或返回最大值。

1.建立csb.c文件后,导入两个头文件(第二个用于延时函数)

#include <STC15F2K60S2.H>
#include "intrins.h"

2.STC-ISP的软件延时计算器中生成13微秒的延时函数(12MHZ)

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

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

3.定义发射波引脚和接收波引脚

观察实训平台应该知道跳线帽J2是和超声波有关的。打开硬件原理图,搜索找到J2

P10是发射,P11是接收。分别命名为TX,RX

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

4.接着写发射函数

注意点:8次;反转TX;延时13微秒

void send_wave()
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		TX=1;
		Delay13us();
		TX=0;
		Delay13us();
	}
}

5.最后写接收函数

函数返回距离dis,单位厘米。所以先定义dis。然后引用发射函数,接着计时,然后while等待接收或超时,最后计算并返回距离。

unsigned char receive_wave()
{
	unsigned int dis;
	
	TL0 = 0;				
	TH0 = 0;				
	TF0 = 0;				//清除TF0标志
	TR0 = 0;				//定时器0不计时
	
send_wave();
TR0 = 1;
while(RX==1 && TH0<0x17);
TR0 = 0;
if(RX==0)
	{
		RX=1;
		dis=(TH0<<8) | TL0*0.017;
	}
	else
	{
		dis=99;
	}
	
	return dis;
}

如果不想占用定时器0和1:

unsigned char receive_wave()
{
	unsigned int time,dis;
	CMOD = 0x00;			//设置定时器模式
	CL = 0x00;				//设置定时初始值
	CH = 0x00;				//设置定时重载值
	CF = 0;				//清除CF标志
	CR = 0;				//定时器0不开始计时
	
	send_wave();
	CR=1;
	while(RX==1 && CH<0x17);
	CR=0;
	
	if(RX==0)
	{
		RX=1;
		time=CH*0x100+CL;
		dis=time*0.017;
	}
	else
	{
		dis=99;
	}
	
	return dis;
}

汇总:

#include <STC15F2K60S2.H>
#include "intrins.h"

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

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

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


void send_wave()
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		TX=1;
		Delay13us();
		TX=0;
		Delay13us();
	}
}

unsigned char receive_wave()
{
	unsigned int dis;
	
	TL0 = 0;				
	TH0 = 0;				
	TF0 = 0;				//清除TF0标志
	TR0 = 0;				//定时器0不计时

	send_wave();
	TR0 = 1;
	while(RX==1 && TH0<0x17);

	TR0 = 0;
	
	if(RX==0)
	{
		RX=1;
		dis=(TH0<<8) | TL0*0.017;//TH>>high 高四位,TL0>>low 低四位
	}
	else
	{
		dis=99;//超时返回上限
	}
	
	return dis;
}

part 11:DS18B20

创建onewire.c文件,将底层代码onewire复制粘贴进去。然后右键添加头文件#include <STC15F2K60S2.H>。

接着打开硬件原理图找到18B20,将DQ对应的引脚P1^4定义到.c文件里。然后就开始写读取温度的函数了。

首先注意返回的值的类型,由于是16位,所以用unsigned int 比较合适

然后开始书写读取温度的函数:先打开芯片资料的DS18B20,找到下图的步骤

分为三步:1.初始化,底层文件已给出

                  2.数据交换:也就是发送设备地址,找到如下图:

翻译:主服务器可以使用此命令同时处理总线上的所有设备,而不发送任何ROM代码信息。例如,主服务器可以通过发出Skip ROM命令和发出转换T [44h]命令,使总线上的所有DS18B20同时执行温度转换。

也就是说先发送0xcc,再发送0x44;就可以完成温度转换。

init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);

3.读取温度,同样要初始化和跳过发设备地址,找到如下图的读取指令地址:

也就是0xbe;

最后就是将温度赋值给temp。注意:他是先输出温度的高八位,再输出第八位,所以需要两次接收并且拼接。如下:

temp=Read_DS18B20();
temp=Read_DS18B20()*0x100+temp;

此时温度的值比正常大16倍。为什么呢?找到下图:

可以发现,每一位都比正常的少了2的四次方,赋值给temp后,就变大了。比如bit 6.是二的二次方,赋值给temp后相同位置temp是2的六次方。(注意赋值是把对应位的0或者1赋值过去。假如bit 6的位置是1,那么就是1乘以2的2次方,赋值后就变成1乘以2的六次方)

总而言之就是要除以16

但不能直接除以,因为只能得到整数部分。所以要乘以16分之一,也就是0.0625.此时得到的是四位的包含两位小数的temp

不妨将temp扩大100倍,这样就没有小数,数码管显示也好处理。

temp*=6.25;
return temp;
unsigned int ds18b20()
{
	unsigned int temp;
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0x44);
	
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0xbe);
	
	temp=Read_DS18B20();
	temp=Read_DS18B20()*0x100+temp;
	
	temp*=6.25;
	return temp;
}

.c如下:

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

unsigned int ds18b20()
{
	unsigned int temp;
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0x44);
	
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0xbe);
	
	temp=Read_DS18B20();
	temp=Read_DS18B20()*0x100+temp;
	
	temp*=6.25;
	return temp;
}

part 12:ne555

首先要知道是利用定时器0进行计数,然后才能计算并输出频率

第一步:配置定时器0

随便生成一个定时器0代码

void Timer0_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x20;				//设置定时初始值
	TH0 = 0xD1;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
}

首先AUXR不用管,我们要配置的是TMOD,TMOD是用来选择配置怎么样的定时器的。

打开手册搜索找到TMOD

如图我们知道要使用定时器0,首先高四位要置零。但是我们可能其他地方要用到定时器1,所以为了不影响高四位,我们先&=0xF0;将低四位清零,然后|=低四位,不影响高四位。再看第四位,我们要对P3^4的外部脉冲进行计数,选择16位不可重装载模式。所以是0101 

TMOD &= 0xF0;
TMOD |= 0x05;

然后让计数初值置零

TH0 = 0;
TL0 = 0;
TR0 = 1;

以下是计数器0的初始化函数:

void Timer0_Init(void)		
{
	​
    TMOD &= 0xF0;
    TMOD |= 0x05;
    TH0 = 0;
    TL0 = 0;
	TR0 = 1;			//定时器0开始计时
}

记得放在main()函数里初始化。

有了计数器,我们就要每过一秒钟计算一下计数的次数,进而算出频率。

“一秒钟”要利用定时器1

unsigned char ms1;
unsigned freq;//频率

void Timer1_Isr(void) interrupt 3
{
    ms1++;//ms1在main函数上面定义,作为全局变量
   if(ms1 / 1000 == 0)
    {
        ms1 = 0;
        freq = (TH0<<8) |= TL0;
        THO = 0;
        TL0 = 0;
    }
}

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x40;			//定时器时钟1T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x20;				//设置定时初始值
	TH1 = 0xD1;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
}

需要注意的是,ms1不能在其他地方用并清零,否则会影响频率。

最后得出的就是频率freq

未完

  • 35
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值