单总线介绍与总结
- 单总线介绍
- 单总线时序图
- DS18B20的操作流程
- 代码 读温度
- 代码思路
- 代码实现
单总线介绍
单总线应用案例:Ds18B20、温湿度传感器用到的就是这个,这里Ds18B20从当的角色是从机部分,而开发板充当的部分人是主机部分。
Ds18B20内部结构图
Ds18B30存储器结构
单总线的电路规范
从上图可知 单总线的电路规范和IIC有很大的相似之处,也可以想象为上篇文章中拉杆子
的案例。想输出0 就把杆子拉下来 1的话就释放杆子。
上图第一个是非寄生供电的模式,第二个他图是寄生的供电模式。
单总线时序图
开始初始化
注意
这里从机应答需要有返回值来确定从机的应答的,并且从机拉低也是自动完成的,我们不需要在代码里面释放从机,我们写的代码是针对主机而言的。
发送时序图 (一位)
接收时序图 (一位)
发送与接收一个字节 8位
DS18B20的操作流程
读=接收 写=发送
DS18B20数据发送执行
注意:
跳过ROM必须只有一个设备时才可以跳过设备。
温度是两个字节组成的吧。
R:Temperature LSB 温度的低八位 一个字节
R:Temperature MSB 温度的高八位 一个字节
温度存储格式
代码 读温度
代码思路
代码实现
oneWire.c
#include <REGX52.H>
sbit oneWire_DQ=P3^7;
//AT89C52开发板
/**
* @brief oneWire单总线初始化
* @param 无
* @retval ackBit 从机的响应,0代表响应,1代表不响应
*/
unsigned char oneWire_init()
{
unsigned char i;
unsigned char ackBit;
//以下时序图均看自己总结的csdn
oneWire_DQ=1;
oneWire_DQ=0;
i = 247;while (--i); //延时 500us 在STC-ISP软件 软件延时里设置 @12MHZ,Y1
oneWire_DQ=1;
//等待的这15-60里主机不用操作,从机会在释放总线的15-60内响应主机发出应答位,
//持续60-240,也就是说从机最慢最慢60之后就会发出应答位,所以取70时来读取,这时候到达低电平
i = 32;while (--i); //延时70us
//下面看时序图可知 是从机的响应 这里是从机控制, 我们不需要拉低或者释放 ,交给从机自己释放
ackBit=oneWire_DQ;//得到从机响应
i = 247;while (--i); //延时 500us ,理论延时 60~240us 但是可以多延时一会,从机自动释放
return ackBit;//返回应答位
}
/**
* @brief oneWire发送一位数据
* @param Bit 一位数据
* @retval 无
*/
void oneWire_sendBit(unsigned char Bit)
{
unsigned char i;
oneWire_DQ=0;//由于执行过 init()函数后 oneWire_DQ=1了 此时直接拉低即可 当然也可以先置1然后再置0
i = 4;while (--i);//延时10us 这里和时序图不太一样。这样写可以省去if判(Bit为0和1时)
// 延时10us后让bit直接给DQ,如果bit是0,则在10us后仍然是0,之后再延时 50us 也就达到了60~120us的范围了
//如果此时参数Bit=1,那么由时序图可知 在1~15us内需要拉低。执行此函数第一句DQ=0,
//然后延时了10us,也在1~15us内,然后使得DQ=Bit,
//此时DQ=1,相当于释放了,之后和0时一样再延时50us 满足时间切片为60us
oneWire_DQ=Bit;//为bit1时代表释放 bit为0时oneWire_DQ继续为0
i = 24;while (--i);//延时50us
oneWire_DQ=1;//bit为0的释放,bit为1的保持高点电平不变
}
/**
* @brief oneWire接收一位数据
* @param 无
* @retval Bit 接收来自从机的一位数据
*/
unsigned char oneWire_receiveBit(void)
{
unsigned char i;
unsigned char Bit;
//主机先拉低
oneWire_DQ=0;
//规定在1~15us内拉低电平 这里在5us
i = 2;while (--i);//延时5us
//释放电平 主动权交给从机
oneWire_DQ=1;
i = 2;while (--i);//延时5us
//读取从机的电平
Bit= oneWire_DQ;
i = 24;while (--i);//延时50us走完时间片
return Bit;
}
/**
* @brief 发送一个字节 8位数据
* @param byte 要发送的字节数据
* @retval 无
*/
void oneWire_sendByte(unsigned char byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
oneWire_sendBit(byte&(0x01<<i));//这里解释下:发送一个字节 8位 调用上面的8次发送一位的函数
//因为从低到高,一位一位的取,故而byte&(0x01<<i)
//假如byte是0x55,则即是 0101 0101 ,&上0x01 也即是 0101 0101 & 0000 0001 =00000001第一位1便取出来了
//若想取出低四位的第二位,0101 0101 & 0x02=0101 0101 & 0000 0010 =0000 0000第二位便是0
//如此循环八次便取出来了 八位数据
}
}
/**
* @brief oneWire接收一个字节 8位数据
* @param 无
* @retval byte 主机接收来自从机的一个字节的数据
*/
unsigned char oneWire_receiveByte()
{
unsigned char i,Data;
unsigned char byte=0x00;//提前将byte=0x00,初始化
for(i=0;i<8;i++)
{
Data=oneWire_receiveBit();//假如完整的八位数据的Data是 0x52,也即是 0101 0010
if(Data) //如果接收到的是1才进入if判断
{
byte=byte | (0x01<<i);
//i =0 时 Data位0x52 取最低位为 0,此时Data为0 ,不进入循环,此时byte=0x00
//i =1 时,取Data低二位为 1,此时 此时因为Data是1,进入if,byte= 0x00 |(0x01<<1)= 0x00 | 0x02=0000 0000 | 0000 0010 =0000 0010
//i =2 时,Data为0,不进入循环,此时,byte仍为0000 0010
//i =3 时,Data也是0 同上
//i =4 时,Data为1,进入if byte=0000 0010 , 0x01<<4= 0001 0000 ,byte|0x10,也即是 0000 0010 | 0001 0000=0001 0010
//i =5 时,Data为0,byte仍保持上面的值
//i =6 时,Data=1,再次进入if,byte=0001 0010 ,0x01<<6= 0100 0000=0x40 ,byte|0x40=0101 0010
//i =7 时,不进入循环,继续保持0
//结束后 byte的值就是八位的Data,也就和oneWire_receiveBit()的值一样了
}
}
return byte;//把数据返回出去
}
DS18B20.c
#include <REGX52.H>
#include "oneWire.h"//引入oneWire
#define ds18B20_skipRom 0xcc //跳过rom指令
#define ds18B20_convertT 0x44 //温度转化
#define ds18B20_readScratchpad 0xBE //读暂存器
/**
* @brief DS18B20开始温度变换
* @param 无
* @retval 无
*/
void ds18B20_convertTemp()
{
oneWire_init();//先进性初始化,观察上面的时序图
oneWire_sendByte(ds18B20_skipRom);//跳过rom,发送一个字节
oneWire_sendByte(ds18B20_convertT);
}
/**
* @brief DS18B20开始读取温度
* @param 无
* @retval 返回真实温度 小数点四位
*/
float ds18B20_readTemp()
{ int temp;//临时变量存温度
float T;//存真正的温度
unsigned char TLSB,TMSB;//观察上图可知 此处读的是温度 温度是由 TLSB 和TMSB组成
oneWire_init();//初始化 可由上述时序图可知,需要先初始化
oneWire_sendByte(ds18B20_skipRom);
oneWire_sendByte(ds18B20_readScratchpad);
TLSB=oneWire_receiveByte();//接收低字节数据
TMSB=oneWire_receiveByte();//接收高字节数据
temp=(TMSB<<8) | TLSB;
//这里先把整体转为int类型,再把TMSB向左移八位,再放或上 TLSB,例如TMSB是11111001,TLSB为 1000 1100,
//TMSB<<8=11111001<<8=11111001 00000000 再或上TLSB 1000 1100=11111001 1000 1100
//变为16位数据,但注意,上图TLSB中后四位是小数,转化为int后,小数部分就被转化了,最低位就变成了2^0的那位了,没有小数了
//这时候需要把数据再转为带小数的
T=temp/16.0; //2^4 右移4位把小数的数据拿回来
return T;//返回真实带有1小数点的温度
}
LCD1602.c
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "oneWire.h"
#include "DS18B20.h"
#include "delayXms.h"
float T;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Temperature:");
//ack=oneWire_init();
//LCD_ShowNum(2,1,ack,3);//拔掉ds18b20断电重启显示 1 未应答,插上后断电重启显示0 代表应答成功
ds18B20_convertTemp();
delayXms(1000);
while(1)
{
ds18B20_convertTemp();//先转换温度
T= ds18B20_readTemp();
if(T<0)//如果温度是负数
{
LCD_ShowChar(2,1,'-');//显负号
T=-T;//把负数的温度转为正数
}
else
{
LCD_ShowChar(2,1,'+');
}
LCD_ShowNum(2,2,T,3);
LCD_ShowChar(2,5,'.');//小数点
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//因为不能取小数 故而先降温度*10000再对10000取余数便得到了小数
//由因为*10000后数据超过范围 故而强制类型转为 unsigned long类型
}
}