一、蜂鸣器
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; //切换回空闲状态
}
}
}