1-Wire 单总线
1-Wire是一种串行通信总线协议,由美国芯片制造商Dallas Semiconductor(现为Maxim Integrated)开发。这种协议主要用于连接和通信各种设备,并在多个领域得到了广泛应用,如温度传感器、电池管理、智能卡等。
1-Wire协议基于单数据线进行串行通信,允许多个设备通过共享这一数据线与控制设备进行通信。这种通信方式极大地简化了硬件布线和设备连接的复杂性。每个1-Wire设备都具有一个唯一的64位地址,这使得系统可以轻松地识别和区分不同的设备。数据传输使用脉冲编码调制(Pulse Code Modulation, PCM)技术,通过发送不同脉冲的组合来表示不同的数据。
1-Wire技术具有节省IO资源、结构简单、成本低廉、便于总线扩展维护等优点。同时,它支持在总线上挂接多个从器件,数量基本上不受到任何限制。此外,每个1-Wire技术的从器件都具备独一无二的64位识别码,这既提高了安全性,也避免了地址冲突的问题。1-Wire技术还具备可达8kV HBM的ESD保护等级,以保证系统的稳定性。
总的来说,1-Wire技术作为一种串行通信总线协议,具有简单可靠、线路长度灵活、低功耗设计等特点,被广泛应用于各种设备和系统的通信中,为连接和通信各种设备提供了重要工具。
以上介绍来自文心一言。
我们需要知道的是1-Wire只需要一根通信线就可以进行通信,但仅适用于单主机的系统中,我们今天的主角DS18B20用的就是这个通信协议。
DS18B20
DS18B20是由DALLAS半导体公司推出的一种数字温度传感器,它采用了“一线总线”接口。这种温度传感器相较于传统的热敏电阻等测温元件,具有诸多优势,如体积小、适用电压宽、与微处理器接口简单等。
DS18B20的工作原理主要基于温度对半导体材料电阻值的影响。其内部包含一个精确的温度传感器,该传感器由一对金属电极和一个细丝电阻器组成。当温度升高时,电阻值会相应增加,反之亦然。通过测量电阻值的变化,DS18B20能够准确地确定环境温度。
在数据传输方面,DS18B20通过单线串行总线与主设备通信,仅需一根数据线即可实现数据的传输。这种通信方式不仅简化了硬件连接,还降低了成本。在通信过程中,主设备会发送指令给DS18B20,然后DS18B20将温度数据以数字形式传输回主设备。
DS18B20的温度测量范围为-55℃至+125℃,精度可达±0.5℃。它可以直接读出被测温度,并且可以通过简单的编程实现9~12位的数字值读数方式。此外,DS18B20还具备高抗干扰性,其现场温度直接以“一线总线”的数字方式传输,这大大提高了系统的稳定性。
在供电和工作模式方面,DS18B20既可以通过总线线路提供供电,也可以通过外部电源供电。其工作电压范围广泛,为3~5.5V,这使得它可以灵活地应用于各种嵌入式系统中。
DS18B20的应用领域十分广泛,包括温度监控、室内温度采集、冷链物流温度监控、工业生产温度监测等。其高精度和便捷的特点使得在各种场景下都能发挥重要作用。同时,DS18B20还具有多种封装形式,如管道式、螺纹式、磁铁吸附式等,这使得它可以根据实际应用场合的需求进行灵活选择。
总的来说,DS18B20数字温度传感器是一种功能强大、应用广泛的测温设备,其高精度、高可靠性和易用性使其在多个领域中都得到了广泛应用。
以上介绍来自文心一言。
可以看得出来DS18B20的厂商和1-Wire的开发公司是同一家,因此DS18B20自然是使用1-Wire进行通信。
上面一大段话没啥用,我们只要知道它是用来测温度的就行。
时序
初始化时序
当DS18B20接收到来自主机的探测信号之后,等待15~60us后会发出一段60~240us的低电平。
以上流程顺利的话,就算是初始化完成了。
写时序
以上为写时序,写每个bit的时序时长最短为60us,最长为120us。两个时序之间需要至少1us的恢复时间。
我们先拉低总线15us,接着发送一个bit的数据保持总线上的电平至少60us。DS18B20会在时序开始的15us~60us这个窗口进行采样。
我们重复上面的时序8次就是发送一个字节,1-Wire上发送数据是从低位到高位的。
读时序
读时序和写时序一样,都必须是最少60us,并且两个时序之间需要至少1us的恢复时间。
我们先拉低总线至少1us接着释放,这标志着读时序的开始。接着我们在15us内采样(读取总线上的电平),然后等待45us(保证一个时序至少是60us)。
命令
第二个是0xCC,忽略ROM,因为1-Wire可以挂载多个从机,因此ROM是用来确定具体是哪个从机的,但是我们一般情况下不会挂载多个,因此先发送这个指令,下一个指令就不需要ROM了,这样会比较快。
最后一个是0xBE,发送完我们就可以读取寄存器里的值了,一共是九个字节。
我们再了解一下DS18B20的寄存器。
我们需要的数据在前两个字节,因此在读完两个字节之后我们就可以停止读取了。
那么了解了上述内容之后我们就可以开始敲代码了。
完整代码(STM32)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#define DS18B20_DQ GPIO_Pin_0
void Z_DS18B20_Output(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef itd;
itd.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
itd.GPIO_Pin=DS18B20_DQ;
itd.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&itd);
}
void Z_DS18B20_Input(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef itd;
itd.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
itd.GPIO_Pin=DS18B20_DQ;
itd.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&itd);
}
void Z_DS18B20_SetDQ(BitAction val){
GPIO_WriteBit(GPIOA,DS18B20_DQ,val);
}
uint8_t Z_DS18B20_GetDQ(void){
return GPIO_ReadInputDataBit(GPIOA,DS18B20_DQ);
}
void Z_DS18B20_WriteByte(uint8_t data){
Z_DS18B20_Output();
for(uint8_t i=0;i<8;++i){
Z_DS18B20_SetDQ(0); //拉低总线15us
Delay_us(15);
Z_DS18B20_SetDQ(data&0x01); //将数据从低位开始放到总线上60us
Delay_us(60);
Z_DS18B20_SetDQ(1); //释放总线后延时1us
Delay_us(1);
data>>=1;
}
}
uint8_t Z_DS18B20_ReadBit(void){
uint8_t res=0;
Z_DS18B20_Output();
Z_DS18B20_SetDQ(0); //拉低总线1us后释放表示开始读取字节
Delay_us(1);
Z_DS18B20_SetDQ(1);
Z_DS18B20_Input();
Delay_us(10); //等待10us(在15us之内)后读取总线
res=Z_DS18B20_GetDQ();
Delay_us(50); //延时50us以保证每个时序至少有60us.
return res;
}
uint8_t DS18B20_Read_Byte(void){
uint8_t res=0x00;
for(uint8_t i=0;i<8;++i){
if(Z_DS18B20_ReadBit()==1) res|=(0x01<<i);
}
return res;
}
uint8_t Z_DS18B20_Init(void){
Z_DS18B20_Output();
Z_DS18B20_SetDQ(0); //复位
Delay_us(480);
Z_DS18B20_SetDQ(1);
Delay_us(15);
uint8_t waitTime=0;
Z_DS18B20_Input();
while(Z_DS18B20_GetDQ() && waitTime<=45){ //等待从机拉低总线
waitTime++;
Delay_us(1);
}
if(waitTime>45) return 1;
waitTime=0;
while(!Z_DS18B20_GetDQ() && waitTime<=240){ //等待从机松开总线
waitTime++;
Delay_us(1);
}
if(waitTime>240||waitTime<60) return 1;
return 0;
}
float Z_DS18B20_GetTemperture(void){
uint16_t temp;
uint8_t Hdata,Ldata;
Z_DS18B20_Init();
Z_DS18B20_WriteByte(0xCC);
Z_DS18B20_WriteByte(0x44); //温度转换指令
Z_DS18B20_Init();
Z_DS18B20_WriteByte(0xCC);
Z_DS18B20_WriteByte(0xBE); //读取寄存器指令
Ldata=DS18B20_Read_Byte();
Hdata=DS18B20_Read_Byte();
temp=Hdata;
temp=(temp<<8)|Ldata;
return temp*0.0625;
}
int main(void){
OLED_Init();
while(1){
OLED_ShowNum(1,1,Z_DS18B20_GetTemperture(),3);
OLED_ShowNum(1,5,(int)(Z_DS18B20_GetTemperture()*100)%100,2);
Delay_ms(300);
}
}
完整代码(ESP8266)
ESP8266的代码在一些细节上与上面STM32的略有不同,因为ESP8266是我最早借鉴的别人的代码修改的。
而STM32的代码是我查阅了手册之后自己根据时序图写出的。
但是两个版本都是可以正常驱动DS18B20的。
#define DS18B20_DQ_GPIO D2
void DS18B20_Input_Init(void){
pinMode(DS18B20_DQ_GPIO, INPUT_PULLUP);
}
void DS18B20_Output_Init(void){
pinMode(DS18B20_DQ_GPIO, OUTPUT);
}
void DS18B20_Reset(void)
{
DS18B20_Output_Init();
digitalWrite(DS18B20_DQ_GPIO,LOW);
delayMicroseconds(750);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
delayMicroseconds(15);
}
uint8_t DS18B20_Check(void){
uint8_t waitTime=0;
DS18B20_Input_Init();
while(digitalRead(DS18B20_DQ_GPIO) && waitTime<200){
waitTime++;
delayMicroseconds(1);
}
if(waitTime>=200) return 1;
else waitTime=0;
while((0==digitalRead(DS18B20_DQ_GPIO)) && waitTime<240){
waitTime++;
delayMicroseconds(1);
}
if(waitTime>=240) return 1;
return 0;
}
uint8_t DS18B20_Read_Bit(void){
uint8_t res=0;
DS18B20_Output_Init();
digitalWrite(DS18B20_DQ_GPIO, LOW);
delayMicroseconds(2);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
DS18B20_Input_Init();
delayMicroseconds(12);
res=digitalRead(DS18B20_DQ_GPIO);
delayMicroseconds(50);
return res;
}
uint8_t DS18B20_Read_Byte(void){
uint8_t res=0x00;
for(uint8_t i=0;i<8;++i){
if(DS18B20_Read_Bit()==1) res|=(0x01<<i);
}
return res;
}
void DS18B20_Write_Byte(uint8_t data){
DS18B20_Output_Init();
for(uint8_t i=0;i<8;++i){
if(data&0x01==1){
digitalWrite(DS18B20_DQ_GPIO,LOW);
delayMicroseconds(2);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
delayMicroseconds(60);
}else{
digitalWrite(DS18B20_DQ_GPIO,LOW);
delayMicroseconds(60);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
delayMicroseconds(2);
}
data>>=1;
}
}
void DS18B20_Start(void){
DS18B20_Reset();
DS18B20_Check();
DS18B20_Write_Byte(0xCC);
DS18B20_Write_Byte(0x44);
}
uint8_t DS18B20_Init(void){
DS18B20_Output_Init();
DS18B20_Reset();
return DS18B20_Check();
}
float DS18B20_GetTemperture(void){
uint16_t temp;
uint8_t a,b;
float value;
DS18B20_Start();
DS18B20_Reset();
DS18B20_Check();
DS18B20_Write_Byte(0xCC);// skip rom
DS18B20_Write_Byte(0xBE);// convert
a=DS18B20_Read_Byte(); // LSB
b=DS18B20_Read_Byte(); // MSB
temp=b;
temp=(temp<<8)+a;
if((temp&0xF800)==0xF800){
temp=(~temp)+1;
value=temp*(-0.0625);
}else{
value=temp*0.0625;
}
return value;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("Hello, STM32!");
pinMode(D1, OUTPUT);
digitalWrite(D1, HIGH);
DS18B20_Init();
}
int count=0;
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(D1, !digitalRead(D1));
delay(1000); // this speeds up the simulation
Serial.println(DS18B20_GetTemperture());
}
感兴趣的小伙伴也可以关注我的公众号“折途想要敲代码”回复关键词“DS18B20”,即可免费下载DS18B20相关的资料手册。