上文复习了51单片机的基础模块,本篇将介绍一些外设,因为是复习的原因,本文不会涉及,蓝桥杯国赛的内容,红外线,超声波等会单独一篇写出来。
参考文章:https://blog.csdn.net/ohy3686/article/details/86716456
在初学的时候会用驱动就可以,因为是复习,把理论也了解以下
以下的理论知识,几乎都是来自上述文章。(赛高赛高)
IIC总线是PHLIPS公司推出的一种串行总线,是具备多主机系统所需的包括总线裁决和高低速器件同步功能的高性能串行总线.
IIC总线只有两根双向信号线。一根是数据线SDA,一根是时钟线SCL.
IIC总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,连接总线的器件输出级必须是集电极或漏极开路,以形成线“与”功能。
每个具有IIC接口的设备都有一个唯一的地址,也叫做设备地址。
注意:在多主机系统中,可能同时有几个主机企图启动总线传送数据。为了避免混乱, I2C总线要通过总线仲裁,以决定由哪一台主机控制总线。在80C51单片机应用系统的串行总线扩展中,我们经常遇到的是以80C51单片机为主机,其它接口器件为从机的单主机情况。
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态
连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
接收器件收到一个完整的数据字节后,有可能需要完成一些其它工作,如处理内部中断服务等,可能无法立刻接收下一个字节,这时接收器件可以将SCL线拉成低电平,从而使主机处于等待状态。直到接收器件准备好接收下一个字节时,再释放SCL线使之为高电平,从而使数据传送可以继续进行。
在起始信号之后,必须是器件的控制字节,也即是设备地址,其中高4位是器件的类型识别符(EEPROM的识别符为1010),接着3位是片选信号,最后1位是读写控制位,读操作为1,写操作为0。
小结:对于EEPROM器件来说,如果片选信号为000,则:
读操作的设备地址为:0xA1。
写操作的设备地址为:0xA0。
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)
IC总线协议规定,每传送一个字节数据后,都要有一个应答信号,以确定数据传送是否被对方收到。应答信号由接收方在数据开始后的第9个时钟周期发送,在SCL为高电平期间,接收方将SDA拉为低电平产生应答,用来结束一个字节的传输。也就是说,一帧完整的数据共有9位。
注意:当主机接收数据(也就是在读数据状态)时,它收到最后一个字节后,必须向从机发出一个结束传送的信号。这个信号是通过对从机的“非应答信号”来实现的,在SCL为高电平期间,SDA为高电平,即从机释放SDA线,允许主机产生一个停止信号。
I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
组合方式:
a、主机向从机发送数据,数据传送方向在整个传送过程中不变:
注:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。 A表示应答, A非表示非应答(高电平)。S表示起始信号,P表示终止信号。。
b、主机在第一个字节后,立即从从机读数据
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
Delay_IIC(5);
SDA = 0; //在SCL高电平期间,SDA由高变低
Delay_IIC(5);
SCL = 0;
}
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
Delay_IIC(5);
SDA = 1; //在SCL高电平期间,SDA由高变低
Delay_IIC(5);
}
void IIC_Ack(unsigned char ackbit)
{
if(ackbit)
SDA = 0; //产生应答信号
else
SDA = 1; //产生非应答信号
Delay_IIC(5);
SCL = 1;
Delay_IIC(5); //第9个时钟周期
SCL = 0;
SDA = 1; //释放SDA线
Delay_IIC(5);
}
bit IIC_WaitAck(void)
{
SDA = 1;
Delay_IIC(5);
SCL = 1;
Delay_IIC(5);
if(SDA) //在SCL高电平期间,SDA为高电平,从机非应答。
{
SCL = 0;
IIC_Stop();
return 0;
}
else //在SCL高电平期间,SDA为低电平,从机有应答。
{
SCL = 0;
return 1;
}
}
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0;i<8;i++) //循环发送8位数据
{
if(byt & 0x80) //数据位是高电平
{
SDA = 1;
}
else //数据位是低电平
{
SDA = 0;
}
Delay_IIC(5);
SCL = 1; //SCL高电平期间,SDA的数据要保持稳定
byt <<= 1; //发送的数据左移,准备发送下一位
Delay_IIC(5); //等待SDA的数据被读取
SCL = 0;
}
}
unsigned char IIC_RecByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++)
{
SCL = 1;
Delay_IIC(5); //在SCL高电平期间,读取SDA的数据
da <<= 1;
if(SDA)
da |= 0x01;
SCL = 0;
Delay_IIC(5);
}
return da;
}
理论的地方主要了解,关键是用
WP 写保护 如果WP 管脚连接到 Vcc 所有的内容都被写保护,只能读。当 WP 管脚连接到Vss或悬空,允许器件进行正常的读/写操作。
24C02的设备地址包括固定部分和可编程部分。可编程部分需要根据硬件引脚A0、A1和A2来设置。设备地址的最后一位用于设置数据传输的方向,即读/写位。
读操作的设备地址为:0xA1。
写操作的设备地址为:0xA0。
写: 24C02在接收到起始信号和从器件地址之后响应一个应答信号,如果器件已选择了写操作,则在每接收一个8位字节之后响应一个应答信号;
读:在发送一个 8 位数据后释放SDA线并监视一个应答信号,一旦接收到应答信号,24C02继续发送数据,如主器件没有发送应答信号器件停止传送数据且等待一个停止信号。
应答时序:
第一行(主机时钟),第二行(发送器输出的数据),第三行(接收器输出的数据)
字节写操作:
24C02接收完设备地址后,产生应答信号;然后接收8位内存字节地址,产生应答信号,接着接收一个8位数据,产生应答信号;最后主机发送停止信号,字节写操作结束。
写入时序:
页写:
24C02可以一次写入 16 个字节的数据,
页写操作的启动和字节写一样,不同在于传送了一字节数据后并不产生停止信号,主器件被允许再发送 P=15个额外的字节;
注意:每发送一个字节数据后24C02产生一个应答位并将字节地址低位加 1 高位保持不变 ,如果在发送停止信号之前主器件发送超过P+1个字节 地址计数器将自动翻转,先前写入的数据被覆盖。
页写时序
24C02 读操作的初始化方式和写操作时一样,仅把 R/W 位置为 1 ,有三种不同的读操作方式:立即地址读、选择读和连续读。
立即地址读:
AT24C02的地址计数器内容为最后操作字节的地址加 1。也就是说,如果上次读/写的操作地址为 N,则立即读的地址从地址 N+1开始。
选择性读:
选择性读操作允许主器件对寄存器的任意字节进行读操作,主器件首先通过发送起始信号、从器件地址和它想读取的字节数据的地址执行一个伪写操作。
连续读:
连续读操作可通过立即读或选择性读操作启动,在24C02发送完一个 8 位字节数据后,主器件产生一个应答信号来响应,告知24C02主器件要求更多的数据,对应每个主机产生的应答信号,24C02将发送一个 8 位数据字节,当主器件不发送应答信号而发送停止位时结束此操作。
单片机向24C02的add地址上,写入char型的数dat
void wrbyte_24c02(unsigned char add,unsigned char dat)
{
// Device Address 1010 000 R/W
iic_start();
iic_sendbyte(0xa0);
iic_waitack();
iic_sendbyte(add);
iic_waitack();
iic_sendbyte(dat);
iic_waitack();
iic_stop();
delay(10);
}
写入多个char型的数
void wrbyte_24c02(uchar add, uchar dat0, uchar dat1,……)
{
// Device Address 1010 000 R/W
iic_start();
iic_sendbyte(0xa0);
iic_waitack();
iic_sendbyte(add);
iic_waitack();
iic_sendbyte(dat0);
iic_waitack();
iic_sendbyte(dat1);
iic_waitack();
… …
iic_stop();
delay(10);
}
单片机从24C02的add地址上读取一个数
unsigned char rdbyte_24c02(unsigned char add)
{
// Device Address 1100 000 R/W
unsigned char da;
iic_start();
iic_sendbyte(0xa0);
iic_waitack();
iic_sendbyte(add);
iic_waitack();
iic_start();
iic_sendbyte(0xa1);
iic_waitack();
da = iic_recbyte();
iic_ack(0);
iic_stop();
return da;
}
读取多个:
unsigned char da[]={0,0,0, … … };
iic_start();
iic_sendbyte(0xa0);
iic_waitack();
iic_sendbyte(add);
iic_waitack();
iic_start();
iic_sendbyte(0xa1);
iic_waitack();
da[0] = iic_recbyte();
iic_ack(1);
da[1] = iic_recbyte();
iic_ack(1);
… …
da[n] = iic_recbyte();
iic_ack(0);
iic_stop();
return da;
}
参考文章:https://www.cnblogs.com/ALittleBee/p/9427165.html
单总线数字温度传感器
DS18B20的基本驱动的编写,不在这里讲述,直接拿来用,感兴趣的看官可以自己探究。
DS18B20的库文件,里面有传感器复位、写字节和读字节三个函数。基本操作流程要知道。
1.初始化
2.ROM操作指令(识别有多少只,什么型号的DS18B20挂在总线上,识别哪些器件符合报警条件,定位要操作的DS18B20等)
3.DS18B20功能指令
每一次DS18B20的操作都必须满足以上步骤
初始化不用说了,说一下rom指令和功能指令,红色是常用的
1.[F0h] 搜索(多只)
2.[33h] 读取(单只)
3.[55h] 匹配
4.[CCh] 忽略
5.[ECh] 报警搜索
1.[44h] 温度转换指令
2.[4Eh] 写暂存器指令
3.[BEh] 读暂存器指令
4.[48h] 拷贝暂存器指令
5.[B8h] 召回EEPROM指令
6.[B4h] 读电源模式指令
(来自小蜜蜂老师)
<1> 主机对DS18B20进行复位初始化。
<2> 主机向DS18B20写0xCC命令,跳过ROM。
<3> 主机向DS18B20写0x44命令,开始进行温度转换。
<4> 等待温度转换完成。
<5> 主机对DS18B20进行复位初始化。
<6> 主机向DS18B20写0xCC命令,跳过ROM。
<7> 主机向DS18B20写0xBE命令,依次读取DS18B20发出的从第0一第8,共九个字节的数据。如果只想读取温度数据,那在读完第0和第1个数据后就不再理会后面DS18B20发出的数据即可,或者通过DS18B20复位,停止数据的输出。
代码:
uint temperature(void)
{
uint temp;
uchar HI_temp,LOW_temp;
Init_DS18B20(); //DS18B20初始化
Write_DS18B20(0xcc);//跳过ROM
Write_DS18B20(0x44);//温度转换
delay(1000); //延时等待
Init_DS18B20();
Write_DS18B20(0xcc);//跳过ROM
Write_DS18B20(0xbe);//读温度命令
LOW_temp = Read_DS18B20();//读低8位
HI_temp = Read_DS18B20(); //读高8位
temp = HI_temp;
temp = temp << 8;
temp |= LOW_temp;
temp = temp * 0.0625 * 100;
return temp;
}
注意需要×0.0625:
从DS18B20读取的二进制必须先转换成十进制,才能用于字符的现实,DS18B20的转换精度为9~12位可选,为了提高精度采用12位,在采用12位转换精度是,温度寄存器里的值是以0.062为步进的,即温度值为温度寄存器里的二进制值乘以0.0625,就是实际的十进制温度值。12位的最低位为权为1/16,即0.0625
这个要涉及AD DA转化的问题。
具体原理可以看这两个普中官方的视频:
https://www.bilibili.com/video/BV1h4411y7Gk?p=41
https://www.bilibili.com/video/BV1h4411y7Gk?p=42
因为篇幅原因,就不再赘述了,在51中,主要就是改变占空比
直接用代码,来讲述
1. 小蜜蜂,蓝桥杯的例题
代码:
#include
typedef unsigned char u8;
typedef unsigned int u16;
sbit S7 = P3^0;
sbit L1 = P0^0;
void Delay(u16 t)
{
while(t--);
}
void InitHc138(u8 n)
{
switch (n)
{
case 4:
P2 = (P2 & 0X1F) | 0X80;
break;
case 5:
P2 = (P2 & 0X1F) | 0Xa0;
break;
case 6:
P2 = (P2 & 0X1F) | 0Xc0;
break;
case 7:
P2 = (P2 & 0X1F) | 0Xe0;
break;
}
}
/////////////////////////////---¶¨Ê±Æ÷---////////////////////////
u8 count_T0 = 0;
u8 PWN_duty = 0;
void InitT0()//0.1ms * 100 = 10ms
{
TMOD = 0X0010;//×Ô¶¯ÖØ×°ÔØ
TH0 = (65535 - 100) / 256;
TL0 = (65535 - 100) % 256;
EA = 1;
ET0 = 1;
}
void Service_T0() interrupt 1
{
count_T0++;
if(count_T0 <= PWN_duty)
{
L1 = 0;
}
else if(count_T0 < 100)
{
L1 = 1;
}
else if(count_T0 == 100)
{
L1 = 0 ;
count_T0 = 0;
}
}
/////////////////////////////--End_T0--//////////////////////////
u8 stat = 0;
void Scan_Key()
{
if(S7 == 0)
{
Delay(100);
if(S7 == 0)
{
switch (stat)
{
case 0:
TR0 = 1;
L1 = 0;
PWN_duty = 10;
stat = 1;
break;
case 1:
PWN_duty = 50;
stat = 2;
break;
case 2:
PWN_duty = 90;
stat = 3;
break;
case 3:
L1 = 1;
PWN_duty = 0;
TR0 = 0;
stat = 0;
break;
}
while(S7 == 0);
/////////
}
}
}
void main()
{
InitHc138(4);
L1 = 1;
InitT0();
while(1)
{
Scan_Key();
}
}
2. 呼吸灯
https://blog.csdn.net/ohy3686/article/details/88372244
暂时先总结3个外设,还有DS1302时钟芯片,红外通信,等外设,因为复习考核的缘故,等博主考核完再来补上。下一篇复习一下 硬件知识。