我要成为嵌入式高手之4月9日51单片机第四天!!
————————————————————————————
DS18B20温度传感器
单总线数字温度计
异步的半双工的串行通信
测量范围从-55℃ ~ +125℃,增量值为0.5℃
要用DS18B20采集温度,就要实现与单片机之间的通信,因此有两个问题
Bus master active low:主机执行低电平
DS1820 active low:DS18B20执行低电平
Both bus master and DS1820 active low:全都释放低电平
Resistor pull-up:电阻拉高
1、如何向DS18B20发送数据
sendChar(发送单字节的数据)
主机(单片机)向DS18B20写入(发送)0:
空闲时总线上呈现高电平,如果要发送0,就把总线电平拉低:至少(MIN)拉低15微秒,典型(TYP)拉低30微秒,最多(MAX)拉低60微秒。这样一段时间就表示单片机向DS18B20写入一个0;(由于是异步传输,故0 和 1只能靠控制时间长短来表示)
主机(单片机)向DS18B20写入(发送)1:
空闲时总线上呈现高电平,如果要发送1,就把总线电平拉低,拉低时间大于1微秒后,立刻又将电平拉高,拉高时间为:至少(MIN)拉高15微秒,典型(TYP)拉高30微秒,最多(MAX)拉高60微秒。
2、如何从DS18B20读取数据
readChar()
MASTER SAMPLES:主机采样(读引脚电平)
主机(单片机)从DS18B20读取(接收)0:
空闲时总线上呈现高电平,DS18B20会将电平拉低,在拉低后的15微秒以内,单片机进行采样,若为低电平,说明收到的是0;
主机(单片机)从DS18B20读取(接收)1:
空闲时总线上呈现高电平,DS18B20会将电平拉低,在拉低后1微秒之后且15微秒以内,单片机进行采样,若为高电平,说明收到的是1;
以上两图即为DS18B20数据收发原理
———————————————————————————————————————————
DS18B20采集流程
复位
1、单片机向DS18B20发送0xCC
2、单片机向DS18B20发送0x44(启动温度变换)
3、延时(将温度模拟转换为数字需要时间,大约为700ms)
复位
4、单片机向DS18B20发送0xCC
5、单片机向DS18B20发送0xBE(读取温度)
6、单片机连续从DS18B20读取两个字节
如何将读取到的字节转换为浮点型(float)?
读出的两个字节是一个补码,需要 * 0.0625(2 ^ -4)得到温度
涉及到的问题:
单片机P37和DS18B20总线连接起来,要是P37要拉低电平,总线要拉高电平,那到底是高电平还是低电平?
答:一定是低电平,因为有任一方拉低,相当于该总线接地,那么就是低电平。
那么如何保证该总线一定是高电平呢?
答:在该总线上外接一个上拉电阻(4.7k ~ 10k)(因为51的内部构造,导致51的上拉电平非常弱,因此需要这个电阻来添一把力,保证它是高电平),这样在单片机和DS18B20同时拉高时,该总线一定是高电平。
这俩表现出一种特性:线与特性
拉高总线的操作称为:释放总线
拉低总线的操作称为:占有总线
初始化过程"复位和存在脉冲"
黑色线表示单片机拉低,此时DS18B20释放总线(最少480最多960微秒);然后单片机释放总线在60~240微秒之内DS18B20要拉低总线,这样就能检测到低电平,在240微秒之后,DS18B20再次释放总线,此时总线上必然会检测到高电平,这样就表示该信号存在可以被使用
延时10us函数
//延时10us
void delay10us(unsigned int n) //@12.000MHz
{
unsigned char data i;
while (n--)
{
i = 2;
while (--i);
}
}
初始化
#include "ds18b20.h"
#define DS18B20CLR (P3 &= ~(1 << 7))
#define DS18B20SET (P3 |= (1 << 7))
#define DS18B20TST ((P3 & (1 << 7)) != 0)//判断总线是否为高电平,1为高电平,0为低电平
void delay10us(unsigned int n) //@12.000MHz
{
unsigned char data i;
while (n--)
{
i = 2;
while (--i);
}
}
int rest18b20(void)
{
int t = 0;
DS18B20CLR;//单片机拉低
delay10us(70);//延时
DS18B20SET;//释放总线
while (t <= 24 && DS18B20TST)//240us之内,DS18B20为高电平
{
delay10us(1);
++t;
}
if (t >= 24)//超过240us
{
return 0;//说明初始化有问题
}
t = 0;
while (t <= 24 && !DS18B20TST)//240us之内,DS18B20为低电平
{
delay10us(1);
}
if (t > 24)
{
return 0;//一直为低电平,跳不上高电平,初始化有问题
}
return 1;//在240us之内出现低电平,初始化正确,返回1
}
向18b20发送数据
/*******************
*向18b20发送数据
*一次发送一个char
*先发低位
******************/
void sendChar(unsigned char n)//LSB先行,先发低位
{
int i = 0;
for (i = 0; i < 8; ++i)
{
if ((n & 0x01) != 0)//判断要发的位不为0,此时应该找1的时序图
{
DS18B20CLR;
_nop_();
_nop_();
DS18B20SET;
delay10us(3);
}
else
{
DS18B20CLR;
delay10us(5);
DS18B20SET;
}
n >>= 1;
}
}
从18b20接收数据
/********************
*从18b20接收数据
*一次发送一个char
*从低位开始接收
*返回接收到的数据
********************/
unsigned char readChar(void)//接收18b20的数据,每次接收一个字符
{
unsigned char ret = 0;
int i = 0;
for (i = 0; i < 8; ++i)
{
DS18B20CLR;//拉低总线
_nop_();//空指令,但是消耗了一个指令周期,1us
_nop_();
DS18B20SET;//释放总线
_nop_();
_nop_();
_nop_();
//采样:DS18B20TST表示P37是(1)否(0)为高电平,若接收到1,左移或运算可以将指定位置1(第i位为1,其余位为0)
ret |= DS18B20TST << i;
delay10us(5);
}
return ret;
}
获取温度(按照流程)
float getTemerature(void)
{
unsigned char t1, t2;
int ret = 0;
//接收温度的总流程
rest18b20();
sendChar(0xCC);
sendChar(0x44);
delay10us(60000);
rest18b20();
sendChar(0xCC);
sendChar(0xBE);
t1 = readChar();//低位
t2 = readChar();//高位
//将字符型转换为浮点型
ret = t2 << 8 | t1;
return ret * 0.0625;
//
}
main.c
如何查看获取到的温度的数据呢?
这里采用串口调试查看(uart->send_buffer)
#include "ds18b20.h"
#include "uart.h"
int main(void)
{
float f;
char s[16];
uart_init();
while (1)
{
f = getTemerature();
sprintf(s, "%f", f);
send_buffer(s, strlen(s));
}
return 0;
}
MODBUS协议框架
帧:
起始字节 | 地址码 | 功能码 | 数据长度 | 数据码 | 数据码 | 校验码 | 结束码 | ||||||
0xAA | 0x01 |
| 02 | xx | xx | xx | 0x0D |
例如:
读温度:
上位机发出:AA 01 01 00 ** 0D 下位机回复:AA 81 01 04 xx xx xx xx ** 0D
0000 0001 1000 0001
第一位0代表上位机发给下位机,第一位1代表下位机发给上位机
读秒数:
上位机发出:AA 01 02 00 ** 0D 下位机回复:AA 81 02 xx xx ** 0D
两个字节的数值,大端发送