51单片机——蜂鸣器、AT24C02、定时器键盘、PWM、AD/DA转换、红外遥控器

本文介绍了蜂鸣器的工作原理、有源与无源的区别,以及如何用单片机通过定时器控制无源蜂鸣器的频率。还详细讲解了AT24C02E2PROM存储器的I2C接口、PWM的应用、AD/DA转换器和红外遥控器的编码与接收。此外,文中涉及了51单片机的定时器键盘和红外遥控解码的实现,以及外部中断的使用。
摘要由CSDN通过智能技术生成

一、蜂鸣器

1、将电信号转换为声音信号的器件

2、有源蜂鸣器&无源蜂鸣器

(1)有源蜂鸣器:内部自带振荡源,直接通电即可发声,频率固定

(2)无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,可发出不通过频率声音

3、音符与频率对照表:

以6号音为基准(440),不难看出中音6为低音6的两倍,高音6是中音6的两倍。每种音从1到7为等比数列关系,即两个音之间呈2^(1/12)的等比关系

4、单片机原理

(1)周期(us)=1/频率(kHz)

(2)定时器计数次数=周期/2(取整数)

(3)重装载值=2^16(65535)+1-定时器计数次数 

5、无源蜂鸣器的普通工作代码:

sbit Buzzer=P2^5;

unsigned char KeyNum;
unsigned int i;
void main()
{
    while(1)
    {
        KeyNum=key();
        if(KeyNum)
        {
            for(i=0;i<500;i++)
            {
                Buzzer=!Buzzer;    //翻转
                Delay(1);
            }
            //高电平和低电平都为1ms,周期为2ms,以500HZ的频率响500ms
            num(1,KeyNum);
        }
    }
}

6、利用定时器来进行频率的控制:

(1)设定不同的重装载值可发出不同频率的声音

(2)设定不同的延时

(3)通过关闭定时器可控制声音是否发出:TR0=0——关闭定时器0

注意事项:当你的数组太大了,但51单片机的RAM只有512个字节,这时你可以将变量存在ROM里(8K)。即原本的unsigned char xxx[ ]变为unsigned char code xxx[ ]。但是存在Flash里的后果是无法修改数组里的内容了

二、AT24C02

1、AT24C02介绍:

(1)是一种可以实现掉电不丢失的存储器,用于保存单片机运行时想要永久保存的数据信息

(2)存储介质:E2PROM

(3)通讯接口:I2C总线

(4)容量:256字节

2、AT24C02原理图:

3、I2C总线(SCL、SDA通信线)

(1)I2C电路规范 

·所有I2C设备的SCL连在一起、SDA连在一起

·设备的SDL和SDA均要配置成开漏输出模式

·SCL和SDA各自添加一个上拉电阻,一般为4.7kΩ

(2)I2C时序结构

·起始条件:SCL高电平期间,SDA从高电平切换到低电平

·终止条件:SCL高电平期间,SDA从低电平切换到高电平

·发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环8次该过程即可接收一个字节

·接受一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次即可接收一个字节(主机在接收之前需要释放SDA,将SDA置为1)

·发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

·接收应答:在发送完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

#include <REGX52.H>

sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;

void I2C_Start()
{
    I2C_SDA=1;
    I2C_SCL=1;    //确保在任何操作之后,SDA和SCL线都为高电平,方便后续操作
    I2C_SDA=0;
    I2C_SCL=0;
}

void I2C_Stop()
{
    I2C_SDA=0;
    I2C_SCL=1;
    I2C_SDA=1;
}

/*------I2C发送一个字节-------*/
void I2C_SendByte(unsigned char Byte)
{
    unsigned char i;
    //SCL低电平期间,主机把数据放在SDA线上
    for(i=0;i<8;i++)
    {
        I2C_SDA=Byte&(0x80>>i);    //取出Byte的最高位给SDA
        SCL=1;
        SCL=0;
    }
}

/*------I2C接收一个字节-------*/
unsigned char I2C_ReceiveByte()
{
    unsigned char i, Byte=0x00;
    I2C_SDA=1;        //释放SDA:控制权由主机交给了从机
    for(i=0;i<8;i++)
    {
        I2C_SCL=1;    //开始从机上的SDA读取
        if(I2C_SDA){Byte|=(0x80>>i);}
        /*若SDA数据线上为1,则将1写入Byte一位上
          若SDA数据线上为0,则保持Byte该位为0*/
        I2C_SCL=0;    //回归原始状态
    }
    return Byte;
}

/*-------发送应答位(应答0,非应答1)-------*/
void I2C_SendAck(unsigned char AckBit)
{
    I2C_SDA=AckBit;
    I2C_SCL=1;        //拉高读取
    I2C_SCL=0;        //回归原始状态
}

/*-------接收应答位(应答0,非应答1)-------*/
unsigned char I2C_ReceiveAck()
{
    unsigned char AckBit;
    I2C_SDA=1;        //释放SDA:控制权由主机交给了从机
    I2C_SCL=1;        //开始读取从机上的SDA
    Ack=I2C_SDA;
    I2C_SCL=0;        //回归原始状态
    return AckBit;
}

发送和接收时序组合:

#include <REGX52.H>
#include "I2C.h"

#define AT24C02_ADDRESS 0xA0
//注意向一个地址写完数据之后,需要延时5ms方可读出数据,不可马上读出数据
void AT24C02_WriteByte(unsigned char WordAddress,unsigned char Data)
{
    I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS);
    //发送地址后,主机会发送应答位0到SDA线上
    I2C_ReceiveAck();
    I2C_SendByte(WordAddress);
    I2C_ReceiveAck();
    I2C_SendByte(Data);
    I2C_ReceiveAck();
    I2C_Stop();
}

unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
    unsigned char Data;
    //写入——需要读的地址
    I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS);    //0xA0为AT24C02写地址
    I2C_ReceiveAck(); 
    I2C_SendByte(WordAddress);
    I2C_ReceiveAck();
    //读出改地址下的值:需要重新Start
    I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS|0x01);    //0xA1为AT24C02读地址
    I2C_ReceiveAck();
    Data=I2C_ReceiveByte();
    I2C_SendAck(1);        //发送非应答为:不读了
    I2C_Stop();
    return Data;
}

 注意:每次向一个地址写入数据后,不能马上读出来,而是要过了写周期5ms之后,才能读出来。因此写完数据后需要延迟5ms之后才能读数据。

三、定时器键盘

1、定时器:使用定时器键盘用于防止按键按下后进入while循环中,不利于单片机的功能运行。定时器每隔一定时间扫描按键状态,我们结合按键前后状态(设置两个变量,一前一后,只有当前按后松时,才判断按下),来实现判断。

四、PWM

1、PWM:脉冲宽度调制,在具有惯性的系统中,可以通过调制脉冲宽度来控制有效电压的输出

2、重要参数:

·频率:频率越高,控制效果越好

·占空比:高电平所占周期比例

·精度:占空比变化步距

3、51单片机中,由于没有硬件PWM,因此我们需要再定时器中设置占空比

五、AD/DA转换

1、AD:模拟-数字转换;DA:数字-模拟转换

2、XPT2046

(1)定义引脚:

sbit XPT2046_CS=P3^5;
sbit XPT2046_DCLK=P3^6;
sbit XPT2046_DIN=P3^4;
sbit XPT2046_DOUT=P3^7;

(2)模拟时序:

参数:Command各位:

·MODE:8位最大为255,12为最大为4080(越大越精细)

#define XPT2046_XP        0x9C
#define XPT2046_YP        0xDC
#define XPT2046_VBAT    0xAC
#define XPT2046_AUX        0xEC

unsigned int XPT2046_ReadAD(unsigned char Command)
{
    unsigned char i;
    unsigned int ADValue;
    XPT2046_DCLK=0;
    XPT2046_CS=0;
    
    for(i=0;i<8;i++)    //依次把八位发送出去
    {
        XPT2046_DIN=Command&(0x80>>i);
        XPT2046_DCLK=1;
        XPT2046_DCLK=0;
    }
    for(i=0;i<16;i++)        //依次读取十六位数据
    {
        XPT2046_DCLK=1;
        XPT2046_DCLK=0;
        if(XPT2046_DOUT){ADValue|=(0x8000>>i);}    //相当于把DOUT的值赋给了ADValue
    }
    XPT2046_CS=1;    //结束时序
    return (ADValue>>8);
}

 六、红外遥控器

1、红外遥控:

(1)通过红外光进行通信,由红外LED将调制后的信号发出,由专用的红外接收头进行解调

(2)通信方式:单工(只允许一端发和一端收),异步

(3)通信协议:NEC标准

2、基本发送与接收:

(1)空闲状态:红外LED不亮,接收头输出高电平

(2)发送低电平:红外LED 以38KHz频率闪烁发光,接收头输出低电平

(3)发送高电平:红外LED 不亮,接收头输出高电平

3、NEC编码

(1)编码说明:

·每段命令都有一定的时间 

·Data格式中的Address用于区分不同的遥控器,Command用于接受键位码(注意是低位在前,高位在后

·该编码下的逻辑0和逻辑1与我们之前的理解不同:这里将低电平560us+高电平560us定为逻辑0,将低电平560us+高电平1690us定为逻辑1

(2)键码值:

注意:键位码上的数字为十六进制,因此表达的时候应该带有前缀0x

3、51单片机的外部中断:

(1)外部中断寄存器0的外部中断号为0,外部中断寄存器1的外部中断号位2

 (2)外部中断寄存器

 4、代码:

(1)定时器0:

#include <REGX52.H>

void Timer0_Init()
{
	TMOD &=0xF0;
	TMOD |=0x01;
	TL0 =0x18;
	TH0=0xFC;
	TF0=0;
	TR0=0;	//初始化后定时器还处于关闭状态
}

void Timer0_SetCounter(unsigned int Value)	//设定定时器计数的初始值
{
	TH0=Value/256;
	TL0=Value%256;
}

unsigned int Timer0_GetCounter(void)
{
	return (TH0<<8)|TL0;
}

void Timer0_Run(unsigned char Flag)	//Flag为1——开始计时,Flag为0——停止计时
{
	TR0=Flag;
}

(2)外部中断:

#include <REGX52.H>

void Int0_Init()
{
	IT0=1;	//选择触发条件:1-下降沿触发,0-低电平触发
	IE0=0;	//
	EX0=1;	//连接通道
	EA=1;	//连接通道
	PX0=1;	//
}

/*
中断函数:
void Int0_Routine()	interrupt 0
{
	
}
*/

(3)红外遥控解码:(细读注释)

#include <REGX52.H>
#include "Timer0.h"
#include "Int0.h"

unsigned int IR_Time;	//用来存储时间
unsigned char IR_State;	//用于区别状态

unsigned char IR_Data[4];//用来存储32位数据
unsigned char IR_pData;	//用来区分IR_Data[4]中不同的元素

unsigned char IR_DataFlag;//数据标识符
unsigned char IR_RepeatFlag;//重复标识符
unsigned char IR_Address;
unsigned char IR_Command;

void IR_Init()
{
	Timer0_Init();
	Int0_Init();
}

unsigned char IR_GetDataFlag()
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}
unsigned char IR_GetRepeatFlag()
{
	if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

unsigned char IR_GetAddress()
{
	return IR_Address;
}

unsigned char IR_GetCommand()
{
	return IR_Command;
}

//根据时间间隔来区分信息
void Int0_Routine(void)	interrupt 0
{
	if(IR_State==0)			//低电平期间
	{
		Timer0_SetCounter(0);
		Timer0_Run(1);	//开始计数
		IR_State=1;
	}
	else if(IR_State==1)	//一旦高电平触发
	{
		IR_Time=Timer0_GetCounter();//获取时间
		Timer0_SetCounter(0);		//获取完时间后应立即清零以便下个中断计时
		if(IR_Time>13000 && IR_Time<14000)	//区分时间来判断是Start还是Repeat
		{
			IR_State=2;	//Start信号
		}
		else if(IR_Time>10750 && IR_Time<11750)
		{
			IR_RepeatFlag=1;
			Timer0_Run(0);//停止计数
			IR_State=0;	//Repeat信号
		}
		else
		{
			IR_State=1;
		}
	}
	else if(IR_State==2)	//开始解码
	{
		IR_Time=Timer0_GetCounter();//获取时间
		Timer0_SetCounter(0);		//获取完时间后应立即清零以便下个中断计时
		//区分时间来判断是0还是1
		/*IR_Data[]中每个元素存8位数据,IR_pData从0加到8,没有到8的倍数时
		(IR_pData%8)仍为0~8,当到了8时,一个元素的8位字节写满了,需要跳到
		下一个字节,此时[IR_pData/8]增加1,而(IR_pData%8)变为0,尽管IR_pData
		仍在继续往后面加,但%仍能保证(IR_pData%8)为0~8*/
		if(IR_Time>620 && IR_Time<1620)	//0
		{
			IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));
			IR_pData++;	
			/*~让0000 0001变为1111 1110,&=让第IR_Data[IR_pData/8]个元素的
			第(IR_pData%8)位字节必为0*/
			/*一套操作下来使得IR_Data[IR_pData/8]为0*/
		}
		else if(IR_Time>1750 && IR_Time<2750)	//1
		{
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
			IR_pData++;
			/*0000 0001,|=让第IR_Data[IR_pData/8]个元素的
			第(IR_pData%8)位字节必为1*/
			/*一套操作下来使得IR_Data[IR_pData/8]为1*/
		}
		else	//数据出错:重新读取数据
		{
			IR_pData=0;
			IR_State=1;
		}
		if(IR_pData>=32)	//这时已经获取完数据了
		{
			IR_pData=0;
			/*来验证数据是否正确:NEC编码要求
			第2个数据为第1个数据(遥控器地址)的反码,第4个数据为第3个数据(命令)的反码*/
			if(IR_Data[0]==~IR_Data[1] && IR_Data[2]==~IR_Data[3])
			{
				IR_Address=IR_Data[0];
				IR_Command=IR_Data[2];
				IR_DataFlag=1;	//表示已经获取完数据了
			}
			Timer0_Run(0);	//使定时器停止计数
			IR_State=0;		//切换回空闲状态
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值