国信长天单片机竞赛训练之原理图讲解及常用外设原理(遗失的章节-零)

9 篇文章 2 订阅

目录

基本外设

1.1锁存器,led灯,蜂鸣器,继电器及注意事项

1.2 数码管(板载共集成8位共阳级数码管),数码管分段选和位选

1.3 矩阵键盘和独立键盘

1.4 数码管消影:

1.5 按键的长按和短按(嵌套if实现)

1.6  按键的长按和短按(状态机实现)

常用外设

2.1 DS18B20温度传感器

2.2 串口

2.3 超声波:

2.4 iic总线

2.5 NE555频率测量

2.6 DS1302实时时钟

2.7  PWM的控制



基本外设

1.1锁存器,led灯,蜂鸣器,继电器及注意事项

                单片机竞赛(蓝桥杯电子类)用的开发板的芯片是stc15f2k60s2,我第一次拿到这板子的时候,发现和其他我之前买的最小开发板最大的不同是它有锁存器,共有4个(Y4,Y5,Y6,Y7),锁存器(单片机板子上的锁存器由若干个D触发器组成)的作用是保持之前输入信号的状态!

                             -- Y4锁存器控制led灯,P0输入低 电平点亮,输入高电平熄灭

                                -- 蜂鸣器低电平响,高电由于ULN2003反相器  的 存 在,输入为低电平灭,高电平响。        

                                --- 继电器,输入低电平不工作,高电平工作

                              由上可知  Y5锁存器控制蜂鸣器 继电器等 ,ULN2003相当于一个反相器

而输入信号Y4C.Y5C,Y6C,Y7C则由以下或非门(实际就相当于非门)输出得到,而Y4,Y5,Y6,Y7则由74138译码器得到

 P2的高三位控制,所以

                        Y4:P2=P2&0x1f|0x80;

                        Y5:P2=P2&0x1f|0xa0;

                        Y6:P2=P2&0x1f|0xc0;

                        Y7:P2=P2&0x1f|0xe0;

为什么这样写:以Y4为例,P2&0x1f把高三位置为0,再位或0x80,则把高三位赋值为100,
对于的译码器即为Y4',次数Y4'输出为0有效,而自始至终其他位都未变,
实际上这个单片机只用了板载的外设,P2除高三位外,其他位都没用,大家直接赋值0x80也没问题。

        简单的说,锁存器就是一个大门,锁存器打开, 这时控制P0的电位变化,信号则被送进去,锁存器关闭,外界P0无论信号怎么变,一般来说只要该外设锁存器未打开,都不会对此外产生影响。 

注意单片机电位变化只有高低两种电平,而单片机板子上的LED灯,蜂鸣器,数码管段选和位选都是P0口控制 ,这时候大家就知道锁存器的作用了,没错,P0总不可能同时精确的控制多个外设若是设计不加锁存器,则很容易出现控制不准确,而加了锁存器后,相应的外设就对应了多个锁存器,要控制那个外设先初始化锁存器的状态和P0口的状态,然后再给P0赋值,再开对应外设的锁存器,然后再关闭锁存器。

比如点个灯:

//比如点个灯


P0=0x00;//先开P0,0有效,即为全部点亮
P2=(P2&0x1f)|0x80;
P2=P2&0x0f;
/* 
P2&0x1f把高三位置为0,再位或0x80,则把高三位赋值为100,
对于的译码器即为Y4',次数Y4'输出为0有效,而自始至终其他位都未变,
实际上单片机只用了板载的外设,P2除高三位外,其他位都没用,大家直接赋值0x80也没问题
*/


//注意:为什么要先初始化P0,如果要打开锁存器的话,P0的状态就直接输入进去了,可能会存
//在的控制不准确,这在后面多次操作数码管或是蜂鸣器更为明显
 

比如开机默认关闭led灯,数码管段位选

     P0=0xff;P2=(P2&0x1f)|0x80;P2&=0x1f;//关led
	
	 buzzer=0;relay=0;P2=(P2&0x1f)|0xa0; P2&=0x1f;//关蜂鸣器和继电器
 
	 P0=0xff;P2=(P2&0x1f)|0xc0; P2&=0x1f;//关位选
 
	 P0=0xff;P2=(P2&0x1f)|0xe0;P2&=0x1f;//关段选
 

(可以看到我每次都是先对P0进行操作,再对锁存器进行操作,这个一般用于初始化关闭蜂鸣器,led,数码管)

数码管

1.2 数码管(板载共集成8位共阳级数码管)数码管分段选和位选

段选:控制每一位数码管亮的二级管的个数,比如上面的a,b,c,d等等

比如:

0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff(低电平点亮)

0        1       2      3      4      5       6      7        8        9     全灭

位选:控制8位数码管中那几位点亮,比如com1-com8(高电平点亮)

对于静态数码管,直接选段,选位再点亮就行

对于动态数码管:利用人眼的视觉暂留,配合定时器中断,每2ms刷新一次,就能达到稳定的效果

emmmm,这里牵扯到定时器,干脆直接合着一起写了:

定时器:

对于比赛而已,算定时的时间是不可能手算的:我们有stcisp下载工具,一般定时选择12T

从这copy过去就可,使用的时候使能EA(全局总中断)和对于的定时器

注意:定时器的中断号:定时器0为1,而定时器1为3:,中断函数名可以任意写

使用:比如以下动态数码管

         
   #include "stc15f2k60s2.h"
u8 code     smg_index[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0x7f,0xbf};
u8 DT[]={10,10,10,10,10,10,10,10};
u16 s=0;
u8 i=0;
void sys_init(){
    P0=0xff;
	P2=P2&0x1f|0x80;
	P2&=0x1f;
 
		
	P0=0;	
	P2=P2&0x1f|0xe0;
	P2&=0x1f;
	 
	P2=P2&0x1f|0xc0;
	P0=0;	
	P2&=0x1f;
	 
	P2=P2&0x1f|0xa0;	
	P04=0;
	P06=0;
 
	P2&=0x1f;
  
}
void Timer0Init(void)		//1000微秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xCD;		//设置定时初值
	TH0 = 0xD4;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
EA=1;
ET0=1;
}


void smg_show(unsigned char du,unsigned char wei){//   DT[S]  s
	   
	P0=1<<wei;	  
	P2=(P2&0x1f)|0xc0;//位选
	P2=P2&0x1f;
	
	
	P0=smg_index[du];    //段选
	P2=(P2&0x1f)|0xe0;
	P2=P2&0x1f;
	 
 }
 void main(){

while(1){


    sys_init(); //系统初始化关蜂鸣器等函数
Timer0Init();//初始化定时器

//使用动态数码管
DT[0]=i++;//数码管第一位显示1
if(i>8)i=0;
 

}

}

 
void Time0() interrupt 1{
	  
	 
	 smg_show(DT[s],s);//只有0-7八位数码管变化
	 if(++s==8){
		 s=0;
	 }
	 
	 
 }

        每当DT[]数组里的数变化后,每次定时器中断则显示对于位修改的数,不修改,段选为0xff不显示。

1.3 矩阵键盘和独立键盘

独立键盘:跳线帽接 2 3 

使用:

u8 key_val=0;

P3=0xf0&P3|0xf0

//判断

if(!P30)key_val=7;

else if(!P31)key_val=6;

else if(!P32)key_val=5;

else if(!P33)key_val=4;

矩阵键盘:跳线帽 接  1 2

以状态机的方式实现:

u8 key_scan(){
	 
  static key_state=0,key_return=0;   //每次返回值和状态进来不会重置
	u8 key_press;
	switch(key_state)
	{
		case 0:     //未按按键时的状态
			key_return=0;    //返回值置零
			P3=0x0f; P42=0; P44=0; 
			key_press = P3&key_low;
			if(key_press != 0x0f)
				key_state=1;
			break;
		case 1:    //按键按下去后第一次进scan_key的状态,每按一次按键只会进来case 1一次
			P3=0x0f; P42=0; P44=0;
			key_press = P3&key_low;//key_low为0x0f 同理key_high为0xf0
			if(key_press != 0x0f)
			{
				if(key_press==0x0e) key_return=7;
				if(key_press==0x0d) key_return=6;
				if(key_press==0x0b) key_return=5;
				if(key_press==0x07) key_return=4;
				
				P3=0xf0; P42=1; P44=1;
				key_press = P3&key_high;
				if(key_press==0xe0) key_return+=12;
				if(key_press==0xd0) key_return+=8;
				if(P42==0) key_return+=4;		
				key_state = 2;
			}
			break;
		case 2:    //按键一直按着的状态
			P3=0x0f; P42=0; P44=0;
			key_press=P3&key_low;
			if(key_press == 0x0f)   //放开按键后key_state = 0回到case 0
			{
				key_state = 0;
			}
		default:
			break;
	}
	return key_return;
}

C语言不好的注意:函数内有两个static 修饰的静态局部变量

静态局部变量,在函数里定义,就只能在这个函数里使用,同一个文档中的其他函数也是用不了的。由于被static修饰的变量总是存在内存的静态区。所以即使这个函数运行结束,这个静态变量的值不会被销毁(即不会被重新定义和赋值为0,这一点很多人刚开始忘记了),函数下次使用时仍能使用。

状态机的基本原理:分为初始的状态(case0)  按下的状态(case1)已经按下的状态(case 2),以轮询的方式查按键的状态(正如代码所示),其中key_return 可以用来记录按键是否被按着未松开。

细节可以参考:

 蓝桥杯单片机之按键扫描(状态机)_昊月光华的博客-CSDN博客_基于状态机的按键扫描程序

1.4 数码管消影:

这是在之前的段选和位选上进一步消除影子,为达极致的显示:

参考理论:蓝桥杯单片机开发板的数码管的消影_昊月光华的博客-CSDN博客

 数码管消影后的动态扫描算法,与上文写的smg_play相比

void smg_play(u8 du,u8 we){
	
	
 
	P0=0xff;
	lock(7);  //消影
	
	P0=0;
	P0=1<<we;   
	lock(6);
 
	P0=0xff;
	P0=SMGINDEX[du]; 
	lock(7);
 
  
}

1.5 按键的长按和短按(嵌套if实现)


/*


功能:判断代码长按短按
长按执行临界操作或者是持续性操作
短按则执行单一操作因为不存在持续性

*/
void los(u8 keys){
	
	//有按键按下
	if(key_v==keys&&!longf){
		longf=1;
		longt=0;
	}
	if(longf==1&&(longt++>1000)){	
			 
			longf=2;
			DT[1]=9;执行长按达到临界值功能功能
	}
	if(longf==2&&(longt++%1000==0)){
		
		q++;
		DT[2]=q/10%10;
		DT[3]=q%10;
	}
	if((longf==1||longf==2)&&!key_v){//松开按键
		if(longt<=1000){//执行短按
				DT[1]=7;
			}
			longf=0;
		}
 
	
	
}

效果:短按显示7 ,长按显示9(临界操作),后每隔1s显示q++的操作

1.6  按键的长按和短按(状态机实现)


/*


功能:判断代码长按短按
长按执行临界操作或者是持续性操作
短按则执行单一操作因为不存在持续性

*/

void los(u8 keys){
	
	switch(longf){
		case 0:
			longt=0;
			DT[2]=0;
			DT[3]=0;
			if(key_v==keys)longf=1;
			break;
		case 1:
			if(key_v==keys){
				if(longt++>1000){//达到长按临界值执行长按的瞬间性操作
				DT[1]=9;
				longf=2;
			}
		}
			else {
			 if(longt<=1000){ //执行短按
					DT[1]=7;
			 }
			 longf=0;
		}
			break;
		
		case 2:
			if(key_v==keys){
				if(longt++%1000==0)q++;//0-65535
				DT[2]=q/10%10;
				DT[3]=q%10;
				if(longt==60000)longt=0;
				//执行长按的连续性操作
			}
			else 
				longf=0;
			break;
	}

}

效果:短按显示7 ,长按显示9(临界操作),后每隔1s显示q++的操作,复位后q的显示消失。

常用外设

2.1 DS18B20温度传感器

该温度传感器是基于one wire 单总线协议

参考单总线协议(1-wire)的基本原理 - 知乎 (zhihu.com)

典型的单总线命令序列如下:

  • 第一步:初始化
  • 第二步:ROM命令(跟随需要交换的数据)
  • 第三步:功能命令(跟随需要交换的数据)

初始化:

时序见图 2.25-2 主机总线 t0 时刻发送一复位脉冲(最短为 480us 的低电平信号) 接着在 t1 时刻释放总线并进入接收状态 DSl820 在检测到总线的上升沿之后 等待 15-60us 接着 DS1820 t2 时刻发出存在脉冲(低电平 持续 60-240 us) 如图中虚线所示以下子程序在 MCS51 仿真机上通过 其晶振为 12M. 初始化子程序

sbit DQ=P1^4;
bit init_ds18b20(void)
{
	bit initflag = 0;
 
	DQ = 1;//这句不加也可以 
	Delay_OneWire(12);
	DQ = 0;
	Delay_OneWire(80);//拉低延长400-960us
	DQ = 1;
	Delay_OneWire(10);
	initflag = DQ;
	Delay_OneWire(5);
 
	return initflag;
}
 

void Delay_OneWire(unsigned int t)
{
	/*传统8051单片机是12T,而15单片机是1T,理论上应该扩大延时为12倍,实际上8-12倍都可以*/
	unsigned char i;
	while (t--)
	{
		for (i = 0; i < 8; i++)
			;
	}
}

 

ROM命令

 写时间间隙:(发送命令)

当主机总线to时刻从高拉至低电平时 就产生写时间隙见图2.25.3 2.25.4 to 时刻开始 15us 之内应将所需写的位送到总线DSl820 t15-60us 间对总线采样,若低电平写入的位是0,见图 2.25.3;若高电平写入的位是1见图2.25.4。连续写2位间的间隙应大于1us

void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for (i = 0; i < 8; i++)
	{
		DQ = 0;
		DQ = dat & 0x01;//将dat的最低位数据送总线
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;//dat右移一位得次低位
	}
	Delay_OneWire(5);
}
 

读时间间隙:(读取数据)

 首先将数据线拉低然后延长4us,再将数据线拉高释放总线准备读取数据,延时10us,读数据线状态得到一个状态位并进行数据处理,延时45us,循环重复,直到读完一个字节(八位数据)


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是存取16位数据,分为高八位和低八位,高八位的高四位为符号位,低八位的低四位是小数位,其余为为整数位,另外ds18b20的资料代码中读取采样的数据是从低位开始读取。

获取温度函数编写

首先初始化设备,向单总线发送0xcc(ROM指令)指令,跳过ROM搜索,发送0x44(RAM命令如上图)指令开启温度转换。继续初始化设备,继续跳过ROM搜索,发送0xbe(RAM命令)读取命令。把读到的数据低八位赋值变量low,高八位赋值high。然后把数值转换成小数返回。


float rd_temperature(void)
{
	unsigned char low,high;/*用来存暂存器的值*/
	unsigned int temp;/*取整数*/
	float result;
	init_ds18b20();//搜寻总线上有没有DS18B20设备
	Write_DS18B20(0xcc);/*跳过rom搜索*/
	Write_DS18B20(0x44);/*开启温度转换*/
	
    init_ds18b20();//搜寻总线上有没有DS18B20设备
    Write_DS18B20(0xcc);/*跳过rom搜索*/
    Write_DS18B20(0xbe);/*告诉总线准备读取暂存器*/
	
		//DS18B20 16位 高八位的低四位和低八位的高四位为整数
	low=Read_DS18B20();//低八位
	high=Read_DS18B20();//高八位
	temp =(high&0x0f);
	temp<<=8;
	temp|=low;
	result =temp*0.0625;	
	return result;
	
	
	
	
	
	
}
 

2.2 串口

 1:首先初始化串口

void UartInit(void) //9600bps@11.0592MHz
{
	SCON = 0x50;  //8位数据,可变波特率
	AUXR |= 0x40; //定时器1时钟为Fosc,即1T
	AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
	TMOD &= 0x0F; //设定定时器1为16位自动重装方式
	TL1 = 0xE0;	  //设定定时初值
	TH1 = 0xFE;	  //设定定时初值
	ET1 = 0;	  //禁止定时器1中断
	TR1 = 1;	  //启动定时器1
	//ES=1;	//   ES EA 为中断控制位,若开启串口中断 若开启串口中断则还要写中断函数
	//EA=1; 
}

2:写串口接受和发送函数

void send_char(unsigned char chr)//发送一个字节的数据
{
	SBUF = chr;
	while (!TI)
		; //硬件发送以后自动置1
	TI = 0;
}

void send_string(unsigned char *str)//发送一个字符串
{
	while (*str)
	{
		send_char(*str);
		str++;
	}
}
void rear_char(unsigned char *p)//接受字符串
{
    char *temp;//字符临时变量
 
    if(RI){
    RI=0;
    *temp=SBUF;
     send_char(*temp);

}
 

SBUF为发送和接受数据的缓冲区

SBUF=xxx(即为SBUF写在左边为发送数据)而xxx=SBUF(SBUF写在右边为接受数据)TI为发送数据的标志位,正在发送数据 TI硬件置0,发送完后硬件置1,为了连续发送故再软件置0。

3:使用串口

通过调用串口的接受和发送函数来使用串口。

2.3 超声波:

工作原理: 

(简单的说,就是超声波配合计数器,发送方波后打开计时,然后计算时间乘以速度(340m/s)/2得距离)

需要注意的是想要计算的路程足够远,你计数器计时的时间得足够长,所有得用12T的模式,周期为1us,1T的周期为1/12us,最长时间1us*65535,最长距离

340*65535(ms)/1000000*100/2=0.017*65535(cm)(这也是计算公式)



sbit Tx=P1^0;
sbit Rx=P1^1;

void Timer0Init(void)		//1微秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0x0F;		 //设置为计数器模式
	TL0 = 0x00;		//设置定时初值
	TH0 = 0x00;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 0;		//定时器关闭计时
}
 
void csb_start(void)
{
	u8 i=8;
	EA=0;//关闭总中断,防止其他中断占用cpu导致并未连续发送方波而计算不准确
	while(i--)
	{
		Tx=1;
		Delay12us();
		Tx=0;
		Delay12us();
	}
	EA=1;
}
 
unsigned int get_dis(void)
{
	u16 dis;
	Rx=1;
	csb_start();
	TR0=1;//开启计数器
	while((Rx==1 && TF0==0));//数据未溢出或是未接受到方波时继续等待
	TR0=0;//关闭计数器
	if(TF0==1)//判断数据溢出标准位
	{
		dis=999;TF0=0;
		TH0=0;TL0=0;
	}
	else//正常接受到方波
	{
		dis=(TH0<<8)|TL0;//高八位左移八位再或上低八位再赋给16位数据dis
		TH0=0;TL0=0;
		dis=(dis*0.17)/10;
		if(dis <= 2 || dis >= 400)
			dis=999;
	}
	return dis;
}

 

2.4 iic总线

2.4.1iic总线概述:

IIC 总线只有 2 根信号线,一根是 数据线 SDA ,一根是 时钟线 SCL SDA SCL 均为 双向信号线
总线空闲 时,两根线都是 高电平 。连接到总线上的任一器件,输出低电平,都将使总线的信号变低。

       每个具有IIC接口的设备都有一个唯一的地址,也叫做设备地址

2.4.2 总线数据传输规范

在数据的传输过程中,必须确认数据传送的开始和结束。在 IIC 总线规范中,规定了起始信号和停止信号。
起始信号 :当时钟线 SCL 为高电平时,数据线 SDA 由高变低。
停止信号 :当时钟线 SCL 为高电平时,数据线 SDA 由低变高。

在起始信号之后 ,必须是器件的控制字节,也即是 设备地址 ,其中 4 是器件的类型识别符( EEPROM 的识别符为 1010 ),接着 3 是片选信号, 最后 1 是读写控制位,读操作为 1 ,写操作为 0
                (类型识别符)                                            (片选信号位)                         (读写位)

    

A0 A1 A2 24C02 片选信号 IIC 总线最多可以挂载 8 个(A0,A1,A2能表示的最多项) IIC 接口器件,通过对 A0 A1 A2 寻址,可以实现对不同的 EEPROM 操作
什么是EEPROM:
EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。(可以理解为挂在总线上的可编程只读的外设)
IIC 总线每次传送的数据字节不限, 每一个字节必须是 8 。数据传送时,必须先送最高位 (MSB) 每个数据字节后面都有一个确认位 ,也就是应答 (ACK)

         IIC总线协议规定,每传送一个字节数据后,都要有一个应答信号,以确定数据传送是否被对方收到。应答信号由接收方在数据开始后的第9个时钟周期发送,在SCL为高电平期间,接收方将SDA拉为低电平产生应答,用来结束一个字节的传输。也就是说,一帧完整的数据共有9位。      注意:当主机接收数据(也就是在读数据状态)时,它收到最后一个字节后,必须向从机发出一个结束传送的信号。这个信号是通过对从机的“非应答信号”来实现的,在SCL为高电平期间,SDA为高电平,即从机释放SDA线,允许主机产生一个停止信号。

写入时序:

读时序:

2.4.3 总线驱动程序设计

在没有硬件IIC外设的微处理器中,需要根据总线时序设计IIC接口的驱动程序。包括:起始信号停止信号产生应答等待应答发送数据接收数据6个函数。

/*
  程序说明: IIC总线驱动程序
  软件环境: Keil uVision 4.10 
  硬件环境: CT107单片机综合实训平台 8051,12MHz
  日    期: 2011-8-9
*/

#include "reg52.h"
#include "intrins.h"

#define DELAY_TIME 5

#define SlaveAddrW 0xA0
#define SlaveAddrR 0xA1

//总线引脚定义
sbit SDA = P2^1;  /* 数据线 */
sbit SCL = P2^0;  /* 时钟线 */

void IIC_Delay(unsigned char i)
{
    do{_nop_();}
    while(i--);        
}
//总线启动条件
void IIC_Start(void)
{
    SDA = 1;
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SDA = 0;
    IIC_Delay(DELAY_TIME);
    SCL = 0;	
}

//总线停止条件
void IIC_Stop(void)
{
    SDA = 0;
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SDA = 1;
    IIC_Delay(DELAY_TIME);
}

//发送应答
void IIC_SendAck(bit ackbit)
{
    SCL = 0;
    SDA = ackbit;  					// 0:应答,1:非应答
    IIC_Delay(DELAY_TIME);
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SCL = 0; 
    SDA = 1;
    IIC_Delay(DELAY_TIME);
}

//等待应答
bit IIC_WaitAck(void)
{
    bit ackbit;
	
    SCL  = 1;
    IIC_Delay(DELAY_TIME);
    ackbit = SDA;
    SCL = 0;
    IIC_Delay(DELAY_TIME);
    return ackbit;
}

//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
    unsigned char i;

    for(i=0; i<8; i++)
    {
        SCL  = 0;
        IIC_Delay(DELAY_TIME);
        if(byt & 0x80) SDA  = 1;
        else SDA  = 0;
        IIC_Delay(DELAY_TIME);
        SCL = 1;
        byt <<= 1;
        IIC_Delay(DELAY_TIME);
    }
    SCL  = 0;  
}

//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
    unsigned char i, da;
    for(i=0; i<8; i++)
    {   
    	SCL = 1;
	IIC_Delay(DELAY_TIME);
	da <<= 1;
	if(SDA) da |= 1;
	SCL = 0;
	IIC_Delay(DELAY_TIME);
    }
    return da;    
}

2.4.4 总线驱动程序的应用

2.4.4.1:

EEPROMAT24C02

AT24C02EEPROM芯片地址的固定部分为1010,片选端A2A1A0得到固定的3位编码.形成的7位编码即为该器件的地址码。

//EEPROM 写入
void AT24C02_WB(u8 ward,D)
{
	IIC_Start(); 
	IIC_SendByte(0xa0); 
	IIC_WaitAck();  
	IIC_SendByte(ward); 
	IIC_WaitAck(); 
	IIC_SendByte(D); 
	IIC_WaitAck(); 
	IIC_Stop();  
}

//EEPROM 读取
u8 AT24C02_RB(u8 ward)
{
	u8 D;
	IIC_Start(); 
	IIC_SendByte(0xa0); 
	IIC_WaitAck();  
	IIC_SendByte(ward); 
	IIC_WaitAck(); 
	IIC_Start(); 
	IIC_SendByte(0xa1); 
	IIC_WaitAck();
	D=IIC_RecByte();
	IIC_Stop();  
	return D;
}

2.4.4.2:

数模转换与模数转换(PCF8591)DAC与ADC

         PCF8591是具有IIC接口的8A/DD/A转换芯片,具有4路模拟输入一路DAC输出和一个IIC总线接口

PCF8591的设备地址包括固定部分可编程部分。可编程部分需要根据硬件引脚A0A1A2来设置。设备地址的最后一位用于设置数据传输的方向,即读/写位。

PCF8591的设备的读操作地址为:0x91;而写操作地址则为:0x90

控制寄存器:

光敏传感器接到AIN1,通道1;控制寄存器应写入:0x01
电位器Rb2接到AIN3,通道3;控制寄存器应写入:0x03

 

void write_DAC(u8 dat)
{
	IIC_Start(); 
	IIC_SendByte(0x90); 
	IIC_WaitAck();  
	IIC_SendByte(0x40); 
	IIC_WaitAck(); 
	
	IIC_SendByte(dat); 
	IIC_WaitAck();  
	IIC_Stop();  
} //DAC写入

//adc读取
u8 ReadADC(u8 ain)
{
	u8 Data;
	IIC_Start(); 
	IIC_SendByte(0x90); 
	IIC_WaitAck();  
	IIC_SendByte(ain); 
	IIC_WaitAck(); 
	IIC_Stop();  
	
	IIC_Start(); 
	IIC_SendByte(0x91); 
	IIC_WaitAck();  
	Data=IIC_RecByte(); 
	IIC_Stop();  
	return Data;
}

使用例如读取光敏传感器和RB2电位器的采样值,存取EPPROM。

AT24C02_RB(1);
AT24C02_WB(1,adc_val)//adc_val为要写入的值
ReadADC(0x03);//0x03 RB2电位器
ReadADC(0x01);//0x01 光敏传感

2.5 NE555频率测量

说明

这个NE555频率测量需要结合定时器0,并且设置为计数模式,按键,数码管等其他外设则用定时器1就行,NE555在数电中被叫做三五定时器,可用于构建单稳态电路,多谐振荡器和斯密特触发电路,在蓝桥杯(stc15f2k60s2)的开发板中,是通过调节RB3电位器的电阻值以调节脉冲频率。

使用

用竞赛板子上的P34与SIGNAL短接,用跳线帽或杜邦线(若题目不涉及超声波和红外则可借用超声波下方的跳线帽)。

void Timer0Init(void)
{
    AUXR |= 0x80;
    TMOD |= 0x05;    
    TL0 = 0x00;        //设置计数初值
    TH0 = 0x00;        //设置计数初值
    TF0 = 0;
    TR0 = 0;
    ET0 = 0;
}//初始化定时器0

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


//在main.c中

u16 fr=0;//频率 
u16 frtime=0;

TR0=1;//开始计数
while(1){


//读出1s的脉冲个数也就是频率
if(frtime==500){
frtime=0;
TR0=0;//结束计数
fr=TH0<<8|TL0;
TH0=0;
TL0=0;//清空高八位和低八位计数
fr*=2;//fr*2也就是1s中计数的脉冲个数
TR0=1;//开始下一次计数
}
//todo

}
 


void time1() interrupt 3{//定时器1中断函数 1ms产生一次中断


frtime++;

//to  do


}


2.6 DS1302实时时钟

说明

其时序图文档中都有,而且比赛提供的资料中提供了底层代码,我们只需要调用就行了。

时序图:

读写命令

 

//DS1302驱动文件提供的函数

#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 );
#endif

使用

由于DS1302读和写的数据都是BCD码,一个字节高四位表示十位,低四位的数值即为剩余的数值。比如 0x15 BCD码为  0001  0101  转换为显示的十进制为1*10+5。

u8 DatToBcd(u8 dat)//数据转BCD码
{
	u8 dat1,dat2;
	dat1 = dat / 10;
	dat2 = dat % 10;
	dat2 = dat2 + dat1 * 16;
	return dat2;
}
 
u8 BcdToDat(u8 dat)//BCD码转数据
{
	u8 dat1,dat2;
	dat1 = (dat >>4)*10;
	dat2 = (dat&0x0f) % 16;
	dat2 = dat2 + dat1;
	return dat2;
}

初始化写时间函数(如 写 年 周 月 日  时 分 秒)

void DS_INIT(){
	
	
	Write_Ds1302_Byte(0x8e,0);//清除写保护
	Write_Ds1302_Byte(0x80,DatToBcd(30));//30秒
	Write_Ds1302_Byte(0x82,DatToBcd(15));//15分
	Write_Ds1302_Byte(0x84,DatToBcd(15));//15时
	Write_Ds1302_Byte(0x86,DatToBcd(1));//日
	Write_Ds1302_Byte(0x88,DatToBcd(15));//15月
	Write_Ds1302_Byte(0x8a,DatToBcd(1));//周一
	Write_Ds1302_Byte(0x8c,DatToBcd(15));//15年
 
}

读取时间函数(比如 年 周 月 日 时 分 秒)

void Read_Time(){
				MyDst[0]=BcdToDat(Read_Ds1302_Byte(0x8d));//年
				MyDst[1]=BcdToDat(Read_Ds1302_Byte(0x8b));//周
				MyDst[2]=BcdToDat(Read_Ds1302_Byte(0x89));//月
				MyDst[3]=BcdToDat(Read_Ds1302_Byte(0x87));//日
				MyDst[4]=BcdToDat(Read_Ds1302_Byte(0x85));//时
				MyDst[5]=BcdToDat(Read_Ds1302_Byte(0x83));//分
				MyDst[6]=BcdToDat(Read_Ds1302_Byte(0x81));//秒
	
}

每隔1s定时读取时间就实现了实时时钟。

2.7  PWM的控制

参考应用

基于单片机的PWM输出对Led的常见应用_昊月光华的博客-CSDN博客

找个隐蔽的地方写:愿君能有所收获,正所谓  “何以与君识”,“无言码千行”。

  • 13
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值