UART(外设)

(通信主机)之间进行通信?
GND:
并行通信:一次性将多条数据传输
优点:效率高
缺点:单片机引脚资源有限,并行通信方法占用大量的应用层资源
解决方法:按位序依次发送部分数据
串行通信:同一时刻传输一个bit
例:USB
串口通信既可以是全双工的,也可以是半双工或单工的,这取决于具体的硬件设计和通信协议。
-
全双工:最常见的串口(如RS-232、RS-422)支持全双工通信,即通信双方可以同时发送和接收数据。这是因为它们使用独立的发送线(TX)和接收线(RX),两条路信号传输互不干扰。例如,计算机与外部设备通过RS-232串口通信时,双方可同时收发数据。
-
半双工:某些串口设计(如RS-485)默认工作在半双工模式,发送和接收共用同一组线路,因此同一时间只能单向传输(要么发送,要么接收),需要通过控制信号切换方向。
-
单工:少数特殊场景下(如某些简单传感器的数据发送),串口可能被设计为单工模式,只能单向传输(要么只能发送,要么只能接收)。

半双工通信:

单工通信:

串口通信:
串口通信是一种特殊的串行通信,属于全双工通信
接收信号:RXD
发送信号:TXD
在串口通信中,RXD和TXD是最核心的两根数据线,用于明确数据的传输方向,具体含义如下:
1. RXD(Receive Data):接收信号线
- 功能:用于接收外部设备发送过来的数据。
- 例:当计算机与单片机通信时,计算机的RXD线连接单片机的TXD线,以接收单片机发送的数据;反之,单片机的RXD线连接计算机的TXD线,以接收计算机发送的指令。
2. TXD(Transmit Data):发送信号线
- 功能:用于向外部设备发送数据。
- 例:单片机通过自身的TXD线,将采集到的传感器数据发送到计算机的RXD线;计算机也通过自身的TXD线,将控制指令发送到单片机的RXD线。

&
空闲位:高电平
起始位:低电平
发送顺序:低位先行
奇偶校验正确率:50%
奇校验:
偶校验:
无校验:
停止位:高电平
波特率bps:1200,2400,4800,9600,115200...(bit/s)
传输1bit所需时间:1/波特率

根据所给参数计算每秒传输有效字节数:
-
波特率定义:9600波特率表示每秒传输9600个二进制位(bit)。
-
串口数据帧结构(按你的参数):
- 起始位:1bit(固定,用于标识数据开始)
- 数据位:8bit(约定的传输比特数)
- 校验位:0bit(n:无奇偶校验)
- 停止位:1bit(约定的停止位长度)
- 每帧总长度:1+8+0+1 = 10bit
-
每秒传输字节数计算:
- 1字节(Byte)= 8bit(数据位,不包含控制位)
- 每秒可传输的帧数:9600bit ÷ 10bit/帧 = 960帧
- 每帧包含1个有效字节(8bit数据位),因此每秒传输字节数 = 960字节
关键说明
- 停止位"不固定"是指可设置为1bit、1.5bit或2bit(根据协议约定),若你的场景中停止位为1bit,计算完全成立。
- 若停止位为2bit,结果则为9600÷(1+8+0+2)=872字节/秒,需根据实际配置调整。
综上,在"9600波特率、8数据位、无校验、1停止位"的配置下,每秒可传输960字节

1. 同步通信与异步通信的核心区别(以SCL为标志)
-
同步通信 :
通信双方共用一条独立的时钟信号线(如I2C的SCL),发送方通过SCL线向接收方发送时钟信号,双方以该时钟的节拍同步数据传输(即"时钟同步")。
例如:I2C、SPI协议,均通过SCL(或SCK)时钟线实现同步,数据在时钟的上升沿/下降沿被采样。
-
异步通信 :
没有独立的时钟信号线(无SCL),通信双方需提前约定相同的波特率(数据传输速率),通过数据信号中的起始位、停止位等"约定规则"实现同步。
例如:串口通信(UART),发送方和接收方靠预设的波特率(如9600、115200)保持节奏一致,无需额外时钟线。
2. 串口通信的本质:异步串行全双工
- 异步:如上述,串口通信没有SCL时钟线,依赖波特率约定和数据帧格式(起始位、数据位、校验位、停止位)实现同步,属于异步通信。
- 串行:数据按位依次传输(而非并行传输多位),仅通过一根数据线(如TX/RX)即可完成单向数据传输。
- 全双工:标准串口(如RS-232)通过两根独立的数据线(TX发送线、RX接收线)实现双向通信,双方可同时发送和接收数据(如计算机与串口设备通信时,可一边发送指令,一边接收反馈)。
3. 对比I2C(含SCL/SDA)与串口通信
协议 | 时钟线(SCL) | 数据线(SDA/TX/RX) | 通信方式 | 典型应用场景 |
---|---|---|---|---|
I2C | 有(必需) | 1根(双向复用) | 同步串行半双工 | 芯片间短距离通信(如传感器与MCU) |
串口(UART) | 无 | 2根(TX发送、RX接收) | 异步串行全双工 | 设备间远距离通信(如PLC与上位机) |
简言之,SCL的有无是区分同步与异步通信的关键标志:有SCL(如I2C)为同步通信,无SCL(如串口)为异步通信 。而串口通信凭借独立的收发线,同时具备了"全双工"能力,是工业控制、设备互联中最常用的异步串行通信方式。
以下是关于主机间通信及相关标准的清晰总结:
一、通信中的基础问题
单片机:使用TTL
- 核心问题:导线存在内阻,导致电压随传输距离增加而衰减,同时易产生串扰(信号相互干扰失真)。
- TTL电平限制 :
- 电压值与芯片相关(如51单片机为5V,2440为3.3V)。
- 5V TTL通信距离通常仅10-20米,超过则信号不稳定。
二、RS232标准(解决短距离通信)

- 电平定义 :
- 逻辑高电平:-3V ~ -15V
- 逻辑低电平:+3V ~ +15V
- 线路结构:收线(RX)、发线(TX)、地线(GND),共3根线。
- 通信方式:全双工(双方可同时收发数据)。
- 传输距离:理论20-30米。
三、RS485标准(解决长距离通信)
- 信号传输 :通过A、B两根信号线,以两者的电压差识别信息:
- 正电压差(A > B):高电平
- 负电压差(A < B):低电平
- 电压范围:±7V ~ ±12V
- +7+12v:表1;-7-12v表0;
- 抗干扰能力:差分信号设计,抗干扰性强。
- 通信方式:半双工(同一时间只能单向传输)。
- 传输距离:可达1200米,适合大范围数据传输。
三种方案对比:
类型 | 通信方式 | 传输距离 | 抗干扰性 | 适用场景 |
---|---|---|---|---|
TTL | 全双工 | 10-20米 | 较弱 | 短距离板间通信 |
RS232 | 全双工 | 20-30米 | 中等 | 短距离设备连接 |
RS485 | 半双工 | 可达1200米 | 强 | 长距离总线通信 |
实操:

需要使用到定时器1
设置定时计数器1初值:
设置为8位自动重载模式
定时器1初值计算:

SMOD:0/1
focs:晶振频率: 单位MHZ
256-21*晶振频率(12)*106/32/设定的波特率(1200)/12=204;
怎么计算不同串行口工作方式下的波特率?
TB8:奇偶校验
TI:手动清零,发送中断请求标志位,用于中断其他请求待信息发送完毕后清零
RI:手动清零,软件复位
总结:要做到发送数据:
1:打开中断开关(定时器一)
2:设置定时器一的工作模式
3:确定SCON下串行口的工作方式
4:修改SCON下的各位置一/零情况
收发数据寄存器:

写发送代码:
1200 n,8,1
1:确认工作方式:
8位:数据位比特数
9位:数据位比特数加上一比特的的奇偶校验位
所以我们选择方式一
为避免电路故障:
c
//1:初始化函数
void init_uart(void)
{
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6);//使用方式一:SM1置1
SCON=t;
}
REN置1,
c
void init_uart(void)
{
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
}
让SMOD置1,波特率加倍:
c
void init_uart(void)
{
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
//设置定时器一:
}
设置定时器一:
使用单片机晶振为11.0596MHZ
c
//1:初始化函数
void init_uart(void)
{
//设置串口:
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
//设置定时器一:
t=TMOD;
t&= ~(3<<4);
t |= (2<<4);
t &= ~(3 << 6);
TMOD = t;
TH1 = 208;
TL1 = 208;
TCON |= (1 << 6);
}
发送数据:
c
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送
SCON &= ~(1 << 1);//手动置0
}
总代码:发送字符
c
#include<reg52.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{
//设置串口:
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
//设置定时器一:
t=TMOD;
t &= ~(3<<4);
t |= (2<<4);
t &= ~(3<< 6);
TMOD = t;
TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208
TL1 = 208;
TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1
}
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送
SCON &= ~(1 << 1); //手动置0
}
int main(void)
{
init_uart();
while(1)
{
send_char('A');//65
delay(0x9FFF);
}
return 0;
}
发送字符串:(不能使用printf,但是可以使用sprintf)
记得包string.h和strlen.h库
c
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{
//设置串口:
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
//设置定时器一:
t=TMOD;
t &= ~(3<<4);
t |= (2<<4);
t &= ~(3<< 6);
TMOD = t;
TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208
TL1 = 208;
TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1
}
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送
SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{
while(len--)
{
send_char(*p++);
}
}
int main(void)
{
const char *s="HELLO WORLD!";
int n=10,m=20;
xdata char buffer[32];
init_uart();
while(1)
{
// send_char('A');//65
sprintf(buffer,"%d+%d=%d我去",m,n,m+n);
send_buff(buffer,strlen(buffer));
delay(0x9FFF);
}
return 0;
}
51单片机:大端字节序
接收数据:
IE |= (1<<7)|(1<<4); //打开允许中断寄存器
void uart_handler(void) interrupt 4
{
if((SCON & (1<<0)) != 0)
{
P2 = SBUF;
SCON &= ~ (1<<0);//手动(软件)置0
}
}
c
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{
//设置串口:
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
IE |= (1<<7)|(1<<4); //打开允许中断寄存器
//设置定时器一:
t=TMOD;
t &= ~(3<<4);
t |= (2<<4);
t &= ~(3<< 6);
TMOD = t;
TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208
TL1 = 208;
TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1
}
void uart_handler(void) interrupt 4
{
if((SCON & (1<<0)) != 0)
{
P2 = SBUF;
SCON &= ~ (1<<0);//手动(软件)置0
}
}
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送
SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{
while(len--)
{
send_char(*p++);
}
}
int main(void)
{
int a,b,c,d,f,g;
const char *s="HELLO WORLD!";
int n=10,m=20;
xdata char buffer[32];
a=sizeof(int); //2
b=sizeof(char); //1
c=sizeof(short); //2
d=sizeof(long); //4
// e=sizeof(longlong);//
f=sizeof(float); // 4
g=sizeof(double);// 4
init_uart();
while(1)
{
// send_char('A');//65
sprintf(buffer,"size=%d\n",g);
send_buff(buffer,strlen(buffer));
delay(0x9FFF);
}
return 0;
}
主从应答:
c
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{
//设置串口:
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
IE |= (1<<7)|(1<<4); //打开允许中断寄存器
//设置定时器一:
t=TMOD;
t &= ~(3<<4);
t |= (2<<4);
t &= ~(3<< 6);
TMOD = t;
TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208
TL1 = 208;
TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1
}
//定义片外缓冲区
xdata char rcv_buffer[64];
int pos = 0;//接收字符个数
void uart_handler(void) interrupt 4
{
if((SCON & (1<<0)) != 0)
{
rcv_buffer[pos++] = SBUF;
SCON &= ~ (1<<0);
}
}
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送
SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{
while(len--)
{
send_char(*p++);
}
}
int main(void)
{
init_uart();
while(1)
{
// send_char('A');//65
if(pos !=0)
{
delay(0xFFFF);
send_buff(rcv_buffer,pos);
}
}
return 0;
}
规定应答:
xdata char rcv_buffer[64]={0};//初始化
memset(rcv_buffer,0,sizeof(rcv_buffer));//记得清空缓冲区!!!
c
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{
//设置串口:
unsigned char t;
t=SCON;
t &=~(3<<6);//先将第SM0和SM1清零
t |=(1<<6)|(1<<4);//使用方式一:SM1置1
SCON=t;
PCON |=(1<<7);//SMOD置1
IE |= (1<<7)|(1<<4); //打开允许中断寄存器
//设置定时器一:
t=TMOD;
t &= ~(3<<4);
t |= (2<<4);
t &= ~(3<< 6);
TMOD = t;
TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208
TL1 = 208;
TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1
}
//定义片外缓冲区
xdata char rcv_buffer[64]={0};
int pos = 0;//接收字符个数
void uart_handler(void) interrupt 4
{
if((SCON & (1<<0)) != 0)
{
rcv_buffer[pos++] = SBUF;
SCON &= ~ (1<<0);
}
}
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送
SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{
while(len--)
{
send_char(*p++);
}
}
int main(void)
{
init_uart();
while(1)
{
// send_char('A');//65
if(pos !=0)
{
delay(0xFFFF);
if(strcmp(rcv_buffer,"china")==0)
{
send_buff("ok",2);
}
else if(strcmp(rcv_buffer,"hello")==0)
{
send_buff("confirm",7);
}
pos = 0;
memset(rcv_buffer,0,sizeof(rcv_buffer));//记得清空缓冲区!!!
}
}
return 0;
}
传递数据时常用hex:十六进制数传递
modbus协议:
通常上位机一个,下位机若干个
上位机生成协议数据:

下位机读取执行
执行完毕后下位机回复数据给上位机:

注意功能码最高位的0转为1!!
关于DS18B20的概念

释放总线
上拉电阻:VCC
上拉电阻作用:双方在释放总线时总线能够达到高电平
下拉电阻:GND
1. 释放总线
总线是多个设备(如芯片、模块)共享的通信线路,同一时间通常只允许一个设备"占用"总线发送信号,其他设备需处于"接收"或"空闲"状态。
"释放总线"指的是:当一个设备完成数据发送后,停止对总线的驱动(不再主动输出高电平或低电平),让总线恢复到"空闲状态",以便其他设备可以占用总线进行通信。
简单说,就是设备"放手"总线,不再控制它的电平状态。
2. 上拉电阻:VCC
上拉电阻是一端连接总线,另一端连接电源(VCC,高电平)的电阻。
- 连接方式:总线 ←→ 上拉电阻 ←→ VCC(电源正极)。
- 作用核心:当所有设备都"释放总线"(不再驱动总线)时,上拉电阻会将总线"拉到"高电平(因为电阻导通微弱电流,总线电平接近VCC),让总线保持一个稳定的空闲状态。
3. 上拉电阻作用:双方在释放总线时总线能够达到高电平
这是上拉电阻的核心功能之一。在多设备共享总线的场景中(比如两个设备A和B通信):
- 当A发送完数据、释放总线,且B也未发送数据(同样释放总线)时,总线本身没有设备驱动,理论上电平可能不稳定(受干扰、寄生电容等影响)。
- 上拉电阻此时会通过微弱电流将总线"拉向"VCC,确保总线在空闲时稳定为高电平,避免状态混乱,为下一次通信提供明确的初始状态。
4. 下拉电阻:GND
下拉电阻是一端连接总线,另一端连接地(GND,低电平)的电阻。
- 连接方式:总线 ←→ 下拉电阻 ←→ GND(电源负极)。
- 作用核心:与上拉电阻相反,当所有设备释放总线时,下拉电阻会将总线"拉到"低电平,确保总线空闲时稳定为低电平。
上拉电阻或下拉电阻的阻值越小,对总线的影响主要体现在以下几个方面:
-
增大静态功耗
电阻越小,根据欧姆定律(I=U/R),当总线被拉到高电平(上拉)或低电平(下拉)时,流过电阻的电流越大,导致电路的静态功耗增加,尤其在总线长期处于稳定状态时,这种功耗浪费更为明显。
-
增强总线驱动能力(抗干扰)
小阻值电阻能提供更强的拉电流(上拉)或灌电流(下拉)能力,当总线上存在外界干扰(如噪声、瞬间低电平/高电平信号)时,更能快速将总线拉回稳定的高/低电平,减少干扰对总线状态的影响,提高总线的抗干扰能力。 -
延长总线状态切换时间
总线上通常存在分布电容(导线、器件引脚等形成的寄生电容),电阻越小,RC时间常数(τ=R×C)越小,理论上状态切换更快?这里可能存在误解:实际中,当外部器件驱动总线改变状态时(如从高电平拉到低电平),小阻值上拉电阻会与驱动器件形成竞争------驱动器件需要提供更大的电流来克服上拉电阻的拉电流,反而可能延缓总线从高到低的切换速度;同理,下拉电阻过小时,也可能延缓总线从低到高的切换。因此,过小的电阻可能增加状态切换的延迟。
-
可能损坏驱动器件
若上拉/下拉电阻过小,当外部驱动器件试图将总线拉到与电阻相反的电平(如上拉电阻接VCC,驱动器件想将总线拉到低电平)时,会形成较大的电流回路(VCC→上拉电阻→驱动器件→GND),过大的电流可能超过驱动器件的最大允许电流,导致器件损坏。
综上,上拉/下拉电阻的阻值需根据总线速度、功耗要求、驱动能力及抗干扰需求综合选择,并非越小越好。

上拉电阻:4.7~10k
DS18B20接VCC,电阻为上拉电阻
1:判断DS18B20温度传感器是否存在并复位该传感器:

这是单总线(1 - WIRE BUS)通信中 DS1820 器件的初始化流程,核心是"复位脉冲(Reset Pulse)"和"存在脉冲(Presence Pulse)"的交互,用于主设备(Master)与 DS1820 建立通信前的"握手"。
1. 流程阶段与时间参数
-
主设备发送复位脉冲(Master Tx "reset pulse") :
主设备主动将总线拉低(Bus master active low
,黑色实线)【通过】,持续时间最小 480μs,最大 960μs。目的是"复位"总线上的 DS1820,让器件进入待响应状态。 -
DS1820 等待与发送存在脉冲 :
主设备释放总线后,总线通过**上拉电阻(Resistor pull - up,黑色细实线)**回到高电平(接近 VCC)。
DS1820 检测到总线由低变高后,等待
15 - 60μs
,然后主动将总线拉低(DS1820 active low
,灰色实线),发送"存在脉冲",持续时间60 - 240μs
。这是 DS1820 向主设备"报告自己存在"的信号。 -
主设备接收存在脉冲(Master Rx) :
主设备需在释放总线后,至少等待
480μs
来检测 DS1820 的存在脉冲,确认 DS1820 已准备好通信。
2. 线型图例(LINE TYPE LEGEND)
Bus master active low
(黑色实线):主设备主动将总线拉低的阶段。DS1820 active low
(灰色实线):DS1820 主动将总线拉低的阶段(存在脉冲)。Resistor pull - up
(黑色细实线):总线无设备主动拉低时,由上拉电阻拉至高电平的阶段。
3. 核心逻辑
单总线是"一主多从"的通信方式(一条线同时传数据和供电),初始化的"复位 - 存在脉冲"是为了:
- 主设备通过复位脉冲"唤醒/同步"从设备(DS1820);
- DS1820 通过存在脉冲"应答"主设备,确认自身在线且可通信。
只有完成这个交互,主设备才能后续向 DS1820 发送读写命令(如温度转换、数据读取等)。
代码:
c
#include<reg52.h>
#include<intrins.h>
#include"delay.h"
#define DS18B20_SET (P3 |= (1<<7))//将P37电平拉高
#define DS18B20_CLEAR (P3 &= ~(1<<7))//将P37电平拉低
#define DS18B20_TST ((P3 & (1<<7))!=0)//判断P37电平的电平高/低状态
// 11.0592MHz晶振版本 - 优化版
void Delay10us(unsigned int n) //针对晶振频率为11.0592MHz的情况,函数功能:延迟n*10us
{
unsigned char data i;
_nop_();
_nop_();
_nop_();
i = 2 * n;
while (--i)
{
_nop_();
_nop_();
}
}
int reset_ds18b20(void)
{
int t;
DS18B20_CLEAR; // 主设备拉低总线,发送复位脉冲
Delay10us(70); // 拉低持续约 700μs(符合"最小 480μs,最大 960μs")
DS18B20_SET; // 释放总线,由上拉电阻拉到高电平
Delay10us(4); // 等待 DS18B20 响应
t = 0;
// 检测 DS18B20 是否拉低总线(存在脉冲的开始)
while(DS18B20_TST && t < 30)
{
Delay10us(1);
++t;
}
if(t >= 30)
{ // 超时,未检测到 DS18B20 拉低总线
return 0;
}
t = 0;
// 检测 DS18B20 是否释放总线(存在脉冲的结束,总线回到高电平)
while(!DS18B20_TST && t < 30)
{
Delay10us(1);
++t;
}
if(t >= 30)
{ // 超时,DS18B20 未释放总线
return 0;
}
return 1; // 复位成功,检测到 DS18B20 存在
}
int main(void)
{
if(reset_ds18b20()!=0)
{
P2=0;
delay(0xFFFF);
P2=0xFF;
}
while(1)
{
}
return 0;
}
温度传感器存在并且复位的标志:LED灯亮一段时间后熄灭。
向温度传感器写入数据:

这是单总线(1 - WIRE BUS)通信中,主设备向 DS1820 写入"0"和"1"的时序图,核心是两种"写时隙(Write Slot)"的时序规范,用于主设备向 DS1820 传输二进制数据(0 或 1)。
1. 左侧:主设备写"0"时隙(MASTER WRITE "0" SLOT)
主设备向 DS1820 写"0"时,需遵循以下时序:
- 主设备拉低总线 :主动将总线拉低(图中黑色实线部分),持续时间要求
60 μs < T_X "0" < 120 μs
(即拉低时间在 60 微秒到 120 微秒之间)。 - DS1820 采样 :在主设备拉低总线后,DS1820 会在特定窗口内采样总线电平:
- 从拉低开始,先等待
15 μs
,然后在15 μs(典型值)
的窗口内采样(采样窗口总跨度约15 + 30 = 45 μs
)。 - 由于此时总线被主设备持续拉低,DS1820 采样到低电平,即接收到"0"。
- 从拉低开始,先等待
2. 右侧:主设备写"1"时隙(MASTER WRITE "1" SLOT)
主设备向 DS1820 写"1"时,时序与写"0"有明显区别:
- 主设备短暂拉低后释放总线 :
- 先将总线拉低,但拉低时间小于 15 μs (图中黑色短实线部分,标注
>1 μs
,实际需足够短)。 - 然后释放总线 ,总线通过上拉电阻回到高电平(图中带斜线的上升沿部分,最终接近 VCC)。
- 先将总线拉低,但拉低时间小于 15 μs (图中黑色短实线部分,标注
- DS1820 采样 :DS1820 同样在拉低后的特定窗口内采样:
- 从拉低开始,等待
15 μs
后,在15 μs(典型值)
的窗口内采样。 - 此时总线已被上拉电阻拉到高电平,DS1820 采样到高电平,即接收到"1"。
- 从拉低开始,等待
核心逻辑
单总线是"一主多从"的通信方式,主设备通过控制总线拉低的持续时间,让 DS1820 区分"0"和"1":
- 写"0":持续拉低总线,让 DS1820 采样到低电平。
- 写"1":短暂拉低后释放,让 DS1820 采样到上拉后的高电平。
这种时序设计确保了主设备能向 DS1820 可靠传输二进制数据(如配置命令、地址等)。
c
void ds18b20_write(unsigned char n) // 写入1字节(8位)数据
{
int i;
for(i = 0; i < 8; ++i)
{
if(n & 0x01) // 写"1"时隙
{
DS18B20_CLEAR; // 拉低总线
Delay10us(1); // 拉低时间约10μs(<15μs,符合写"1"要求)
DS18B20_SET; // 释放总线(由上拉电阻拉到高电平)
Delay10us(); // 保持高电平至少50μs,确保整个时隙≥60μs
}
else // 写"0"时隙
{
DS18B20_CLEAR; // 拉低总线
Delay10us(7); // 拉低时间约70μs(60~120μs,符合写"0"要求)
DS18B20_SET; // 释放总线
Delay10us(1); // 短暂延时,确保时隙间隔
}
n >>= 1; // 右移,处理下一位
}
}
c
#include<reg52.h>
#include<intrins.h>
#include"delay.h"
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#define DS18B20_SET (P3 |= (1<<7))
#define DS18B20_CLEAR (P3 &= ~(1<<7))
#define DS18B20_TST ((P3 & (1<<7))!=0)
void init_uart(void)
{
unsigned char t;
t = SCON;
t &= ~(3 << 6);
t |= (1 << 6) | (1 << 4);
SCON = t;
PCON |= (1 << 7);
IE |= (1 << 7) | (1 << 4);
t = TMOD;
t &= ~(3 << 4);
t |= (2 << 4);
TMOD = t;
TH1 = 208;
TL1 = 208;
TCON |= (1 << 6);
}
// 11.0592MHz晶振版本 - 优化版
void Delay10us(unsigned int n) //@11.0592MHz
{
unsigned char data i;
_nop_();
_nop_();
_nop_();
i = 2 * n;
while (--i)
{
_nop_();
_nop_();
}
}
void delay_1ms(unsigned int n)
{
while(n--)
{
Delay10us(100);
}
}
xdata char rcv_buffer[64] = {0};
int pos = 0;
void uart_handler(void) interrupt 4
{
if((SCON & (1 << 0)) != 0)
{
rcv_buffer[pos++] = SBUF;
SCON &= ~(1 << 0);
}
}
void send_char(char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0);
SCON &= ~(1 << 1);
}
void send_buffer(const char *p, int len)
{
while(len--)
{
send_char(*p++);
}
}
int reset_ds18b20(void)
{
int t;
DS18B20_CLEAR; // 主设备拉低总线,发送复位脉冲
Delay10us(70); // 拉低持续约 700μs(符合"最小 480μs,最大 960μs")
DS18B20_SET; // 释放总线,由上拉电阻拉到高电平
Delay10us(4); // 等待 DS18B20 响应
t = 0;
// 检测 DS18B20 是否拉低总线(存在脉冲的开始)
while(DS18B20_TST && t < 30) {
Delay10us(1);
++t;
}
if(t >= 30) { // 超时,未检测到 DS18B20 拉低总线
return 0;
}
t = 0;
// 检测 DS18B20 是否释放总线(存在脉冲的结束,总线回到高电平)
while(!DS18B20_TST && t < 30) {
Delay10us(1);
++t;
}
if(t >= 30) { // 超时,DS18B20 未释放总线
return 0;
}
return 1; // 复位成功,检测到 DS18B20 存在
}
void ds18b20_write(unsigned char n) //0x01010 110 & 0000 0001
{
int i;
for(i = 0;i < 8;++i)
{
if(n & 0x01) // 1
{
DS18B20_CLEAR;
_nop_();
_nop_();
_nop_();
_nop_(); //不能同函数调用,单片机性能太弱。
DS18B20_SET;
Delay10us(5);
}
else
{
DS18B20_CLEAR;
Delay10us(6);
DS18B20_SET;
}
n >>= 1;
}
}
unsigned char ds18b20_read(void)
{
unsigned char ret = 0;
int i;
for(i = 0;i < 8;++i)
{
DS18B20_CLEAR;
_nop_();
_nop_();
_nop_();
_nop_();
DS18B20_SET;
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
if(DS18B20_TST)
{
ret |= 1 << i;
}
Delay10us(5);
}
return ret;
}
float get_temperatuer(void)
{
unsigned char tl, th;
short t;
xdata char s[24];
reset_ds18b20();
ds18b20_write(0xCC);
ds18b20_write(0x44);
ds18b20_write(0x4E);
delay_1ms(750);
reset_ds18b20();
ds18b20_write(0xCC);
ds18b20_write(0xBE);
delay_1ms(750);
tl = ds18b20_read();
th = ds18b20_read();
t = tl;
t |= th << 8;
return t * 0.0625;
}
int main(void)
{
xdata char s[24];
init_uart();
if(reset_ds18b20()!=0)
{
P2=0;
delay(0xFFFF);
P2=0xFF;
}
while(1)
{
float f;
f = get_temperatuer();
sprintf(s,"%f" , f);
send_buffer(s , strlen(s));
}
return 0;
}