51单片机的学习下(结合中科协的个人自用笔记)

1. DS1302实时时钟

一、DS1302介绍

DS1302 是一款低功耗的实时时钟(RTC)芯片 ,你可以把它理解成一个自带 "小电池" 的电子日历 + 秒表。

  • 它的核心任务:一直精准走时间,就算设备断电也不停止计时。
  • 生产方:美国 DALLAS 公司,是单片机 / 嵌入式开发里最常用的时钟芯片之一。

二、它能做什么?

1. 基础计时功能

可以完整记录并输出:

  • 年、月、日、星期
  • 时、分、秒
  • 自带闰年补偿,不用你手动处理 2 月 29 号这种情况

2. 断电也能走的关键特性

  • 它有两个电源接口:主电源 + 备用电池(一般是 CR2032 纽扣电池)
  • 设备断电时,会自动切换到备用电池供电,用极低的功耗维持时钟运行,下次开机时间不会重置。
  • 支持涓细电流充电:部分电路设计下,主电源可以给备用电池慢慢充电,延长电池寿命。

三、它和普通定时器有啥区别?

特性 普通单片机定时器 DS1302 实时时钟
断电后 停止计时,时间重置 靠备用电池继续走,时间不丢
占用资源 需要单片机一直运行计数 独立运行,不占用 CPU 资源
精度 易受系统时钟、中断影响 靠晶振校准,长期走时更准

四、生活 / 开发里的用处

  • 单片机电子钟、万年历
  • 设备开机记录时间戳(比如日志的时间)
  • 定时开关控制(比如定时开灯、定时采集数据)
  • 智能家居、工业设备里的时间基准

五、内部信息解析

通过串行的通信协议来实现数据交互 与DS1302内的寄存器实现数据的交换 实现时间的访问和读写

两个任务(由地址命令字来完成)

  1. 在哪(秒分还是时) 写入什么

  2. 在哪(秒分还是时) 读出什么(DS1302输出的)

CE置为1 开始写入 这里的写入时由低字节到高字节的 SCLK置1 代表一个上升沿 然后置0 重复操作 先把命令字给写入 然后把实际数据写入

先发送命令字判断是读还是写(对应下面那行),命令字中发送过去包含要写或者读的位置(秒 分 时);然后如果是写的话通过MCU向时钟写入,读的话通过时钟向单片机传递相应的信息

六、DS1302时钟实现

需要用到LCD1602.c LCD1602h

DS1302.c

cpp 复制代码
#include <REGX52.H>
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;
//将这三个引脚(P3^6,4,5)定义为DS1302_...这样调用的时候就用
//DS1302_..更加直观

#define DS1302_SECOND 	0x80
#define DS1302_MINUTE	0x82
#define DS1302_HOUR 	0x84
#define DS1302_DATE 	0x86
#define DS1302_MONTH 	0x88
#define DS1302_DAY		0x8A
#define DS1302_YEAR 	0x8C
#define DS1302_WP 		0x8E


//年月日时分秒星期
unsigned char DS1302_Time[]={26,5,30,11,24,10,6};

//1.初始化 CE和SCLK要置为0
void DS1302_Init(void)
{
	DS1302_CE=0;
	DS1302_SCLK=0;
	
}

//2.写字节函数 两个返回值命令字和数据 
void DS1302_WriteByte(unsigned char Command,Data)//命令字和数据
{
	unsigned char i;
	DS1302_CE=1;//CE置为1
	for(i=0;i<8;i++)//写入八位命令字上升沿部分
	{	
		DS1302_IO=Command&(0x01<<i);//取出第0位
		DS1302_SCLK=1;//置为1
		DS1302_SCLK=0;//手动置0
	}
	for(i=0;i<8;i++)//写入八个数据上升沿部分
	{	
		DS1302_IO=Data&(0x01<<i);//取出第0位
		DS1302_SCLK=1;//置为1
		DS1302_SCLK=0;//手动置0
	}
		DS1302_CE=0;//数据写完了清零
}

//3.读字节函数
unsigned char DS1302_ReadByter(unsigned char Command)
{
	unsigned char i,Data=0x00;
	Command|=0x01;//最低位控制读和写 1就是读
	DS1302_CE=1;
	for(i=0;i<8;i++)//写入八位命令字上升沿部分
	{	
		DS1302_IO=Command&(0x01<<i);//取出第0位
		DS1302_SCLK=0;//这里要调整一下顺序 因为读的时序
		DS1302_SCLK=1;//中有一个上升沿和下降沿共用了
	}
	for(i=0;i<8;i++)//将8个命令数读取到Data中
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO) Data|=(0x01<<i);//读取IO上的数据到Data
		//如果IO上的数为1则Data为1 如果IO为0 则Data就是0
	}
		DS1302_IO=0;
		DS1302_CE=0;
		return Data;
}

void DS1302_SetTime(void) //DS1302 内部存的是【BCD 码】
{
	DS1302_WriteByte(DS1302_WP,0x00);//写保护
	//把十进制转成BCD码
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);//关闭写保护
}

void DS1302_ReadTime(void)//显示只能显示十进制
{
	//把BCD码转成十进制
	unsigned char Temp;
	Temp=DS1302_ReadByter(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByter(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByter(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByter(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByter(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByter(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByter(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
	
	
}

DS1302.h

cpp 复制代码
#ifndef __DS1302_H__
#define __DS1302_H__

extern unsigned char DS1302_Time[];

void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByter(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);
#endif

main.c

cpp 复制代码
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"

void main()
{
	LCD_Init();
	DS1302_Init();
	DS1302_SetTime();
	
	
	while(1)
	{
		
		DS1302_ReadTime();
		//Second=DS1302_ReadByter(0x81);//读出秒的数据
		//Minute=DS1302_ReadByter(0x83);//读出分的数据
		//LCD_ShowHexNum(2,1,Second,3);//这里写成数字形式显示会转成BCD码 所以要用16进制显示
		//或者写成BCD码转换成10进制
		LCD_ShowNum(1,1,DS1302_Time[0],2);
		LCD_ShowNum(1,4,DS1302_Time[1],2);
		LCD_ShowNum(1,7,DS1302_Time[2],2);
		LCD_ShowNum(2,1,DS1302_Time[3],2);
		LCD_ShowNum(2,4,DS1302_Time[4],2);
		LCD_ShowNum(2,7,DS1302_Time[5],2);
	}
}

七、DS1302可调时钟

需要用到前面学的LCD1602 以及定时器Timer0 还有DS1302

main.c主函数内有详细注释仅供参考

cpp 复制代码
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"

unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;//MODE是模式 让两个函数交替运行
//TimeSetSelect六个挡位年月日时分秒  TimeSetFlashFlag时间设置闪烁标志位
void TimeShow(void)
{
		
		DS1302_ReadTime();
		LCD_ShowNum(1,1,DS1302_Time[0],2);
		LCD_ShowNum(1,4,DS1302_Time[1],2);
		LCD_ShowNum(1,7,DS1302_Time[2],2);
		LCD_ShowNum(2,1,DS1302_Time[3],2);
		LCD_ShowNum(2,4,DS1302_Time[4],2);
		LCD_ShowNum(2,7,DS1302_Time[5],2);
}
	
void TimeSet(void)//第一个独立按键进行修改操作
{
	if(KeyNum==2)//第二个独立按键设置修改时间的挡位
	{
		TimeSetSelect++;//这代表六个挡位的调节
		TimeSetSelect%=6;//年月日时分秒 越界清零
	}
	if(KeyNum==3)//第三个独立按键对时间进行加
	{
		DS1302_Time[TimeSetSelect]++;
		if(DS1302_Time[0]>99){DS1302_Time[0]=0;}
		if(DS1302_Time[1]>12){DS1302_Time[1]=1;}
		if(DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)
		{
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		else if( DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9|| DS1302_Time[1]==11 )
		{
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		else if(DS1302_Time[1]=2)
		{
			if((DS1302_Time[0]%4==0 && DS1302_Time[0]%100 !=0 ) || DS1302_Time[0]%400==0 )
			{
				if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
			}
			else 
			{
				if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
			}
		}
		if(DS1302_Time[3]>23){DS1302_Time[3]=0;}
		if(DS1302_Time[4]>59){DS1302_Time[4]=0;}
		if(DS1302_Time[5]>59){DS1302_Time[5]=0;}
	}
	if(KeyNum==4)//第四个独立按键对时间进行减
	{
		DS1302_Time[TimeSetSelect]--;
		if(DS1302_Time[0]<0){DS1302_Time[0]=99;}
		if(DS1302_Time[1]<1){DS1302_Time[1]=12;}
		if(DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=31;}
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		else if( DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9|| DS1302_Time[1]==11 )
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=30;}
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		else if(DS1302_Time[1]=2)
		{
			if((DS1302_Time[0]%4==0 && DS1302_Time[0]%100 !=0 ) || DS1302_Time[0]%400==0 )
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=29;}
				if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
			}
			else 
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=28;}
				if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
			}
		}
		if(DS1302_Time[3]<0){DS1302_Time[3]=23;}
		if(DS1302_Time[4]<0){DS1302_Time[4]=59;}
		if(DS1302_Time[5]<0){DS1302_Time[5]=59;}
	}
	//修改完成后进行更新显示
	//只有选中并且TimeSetFlashFlag等于0的时候才会清空
	//其他就显示 这就会实现闪烁
		if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
		else{LCD_ShowNum(1,1,DS1302_Time[0],2);}
		if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
		else{LCD_ShowNum(1,4,DS1302_Time[1],2);}
		if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
		else{LCD_ShowNum(1,7,DS1302_Time[2],2);}
		if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
		else{LCD_ShowNum(2,1,DS1302_Time[3],2);}
		if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
		else{LCD_ShowNum(2,4,DS1302_Time[4],2);}
		if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
		else{LCD_ShowNum(2,7,DS1302_Time[5],2);}
		/*LCD_ShowNum(2,10,TimeSetSelect,2);
		LCD_ShowNum(2,13,TimeSetFlashFlag,2);
		/*LCD_ShowNum(1,1,DS1302_Time[0],2);
		LCD_ShowNum(1,4,DS1302_Time[1],2);
		LCD_ShowNum(1,7,DS1302_Time[2],2);
		LCD_ShowNum(2,1,DS1302_Time[3],2);
		LCD_ShowNum(2,4,DS1302_Time[4],2);
		LCD_ShowNum(2,7,DS1302_Time[5],2);	
		*/
}
void main()
{
	LCD_Init();
	DS1302_Init();
	Timer0Init();
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	DS1302_SetTime();
	
	
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)
		{
			if(MODE==0){MODE=1;}
			else if(MODE==1){MODE=0;DS1302_SetTime();}
			
		}
		switch(MODE)
		{
			case 0:TimeShow();break;
			case 1:TimeSet();break;
		}
		
	}
}
//
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;//内部静态局部变量
	TH0=0xfc;//中断之后值会溢出 溢出就变成0
	TL0=0X66;//所以我们要重新赋初值
	T0Count++;
	if(T0Count>=500)
	{
		T0Count=0;
		TimeSetFlashFlag=!TimeSetFlashFlag;//这是逻辑取反! 不是按位取反~
		//相当于把0变成1 1变成0
	
	}
}

2.蜂鸣器

🔍 核心概念

蜂鸣器是一种电信号转声音信号 的器件,在电子设备中常用来发出按键音、报警音提示音等信号,是嵌入式开发中最常见的输出元件之一。


📊 有源 vs 无源蜂鸣器对比

这里的 "源" 指的是振荡源,而非电源,这是两者最核心的区别:

特性 有源蜂鸣器 无源蜂鸣器
内部结构 自带振荡电路 仅发声组件,无振荡电路
驱动方式 直接接直流电压即可发声 需要控制器提供 2K~5KHz 的方波 / PWM 信号
声音特性 频率固定,音调不可调 可通过调整信号频率发出不同音调
控制难度 简单,仅需 GPIO 高低电平控制 稍复杂,需要定时器 / PWM 支持
典型应用 固定报警音、提示音 播放简单音乐

51单片机上的是无源蜂鸣器(用的是集成电路驱动 也是就是 五线四项步进电机)

注意 不能用无源蜂鸣器始终通电!

一个周期需要翻转两次也就是翻转频率是音符频率的两倍,所以周期是二分之一

如果板子是11.0592的调音不准的,自己搞重载装值的时候高位不要四舍五入,这个会导致较大音

11.0592的:周期/2*11.0592/12,再取整求重装载值差

3.AT24C02(I2C总线---这里的2是平方)

一、存储器分类详解

存储器整体分为易失性存储器 (RAM) 、** 非易失性存储器 (ROM)** 两大分支,核心区别:断电后 RAM 数据丢失,ROM 数据永久保存

易失性存储器(RAM 随机存取存储器,断电丢数据)

1. SRAM(静态随机存储器)

  • 原理:由触发器电路存储数据,无需刷新电路
  • 特点:读写速度极快、功耗高、集成度低、成本贵
  • 典型应用:CPU 内部高速缓存 L1/L2 Cache

2. DRAM(动态随机存储器)

  • 原理:电容存储电荷保存数据,电容漏电,需周期性刷新
  • 特点:容量大、成本低、速度低于 SRAM
  • 典型应用:电脑内存条、手机运行内存

非易失性存储器(ROM 类,断电数据保留)

1. Mask ROM(掩膜 ROM)

出厂时工厂光刻固化程序,用户无法改写 ,多用于大批量固定固件设备。

2. PROM(一次性可编程 ROM)

用户可通过专用设备单次烧录,烧录后不能修改,出错即报废。

3. EPROM(紫外线擦除 ROM)

芯片带石英窗口,紫外线照射全片擦除,可反复编程,拆装擦除不便,现已淘汰。

4. E²PROM(电可擦除 ROM)

电信号按字节擦写 ,无需拆芯片,可在线修改少量数据,多用于设备配置参数存储(如单片机小配置)。

5. Flash(闪存)

块擦写结构、大容量、低成本,是现在主流非易失芯片

  • NAND Flash:U 盘、固态硬盘 SSD、手机机身存储
  • NOR Flash:单片机固件、BIOS 芯片
6. 硬盘 / 软盘 / 光盘

机械式大容量存储介质:

  • 机械硬盘 HDD:磁盘磁头结构;光盘:光学读写;软盘已基本淘汰。

💡核心区分速记

类型 断电数据 读写特性
SRAM/DRAM(RAM) 丢失 任意地址随机读写
ROM 全系列 / 闪存 / 硬盘 保留 ROM 早期只读,Flash 可擦写

这里理解成,竖线在横线的上面,有数据的地方就在横线和竖线的那个点接一个二极管

二极管是单向的,可以从地址总线上来,但是不能从数据总线下去,这样数据总线的高电平就不会影响到其他行了

二、AT24C02 芯片解析

基础参数信息

  1. 存储类型 :\(E^2PROM\)(电可擦除 ROM,非易失存储器,断电数据永久保存,和上一张思维导图的\(E^2PROM\)对应)
  2. 存储容量 :256Byte(2Kbit,命名中02代表 2K 比特,\(2048÷8=256\)字节)
  3. 通信协议 :标准I2C 串行总线,仅 SDA、SCL 两根信号线即可和单片机通信
  4. 封装:常用 DIP-8 直插、SOP-8 贴片两种封装(图中两种实物)

8 个引脚功能详解

引脚号 引脚名 功能说明
1 A0 设备地址引脚,I2C 从地址配置端
2 A1 设备地址引脚,搭配 A0/A2 可挂载最多 8 片同型号芯片到同一条 I2C 总线
3 A2 设备地址引脚
4 GND 电源地
5 SDA I2C 串行数据线,双向收发数据
6 SCL I2C 串行时钟线,由主控单片机提供时钟信号
7 WP 写保护引脚:接 VCC 时芯片只读(禁止写入),接 GND 时可正常读写
8 VCC 电源正极,工作电压常见 2.7V~5.5V,适配 51/STM32 单片机

核心用途💡

  1. 单片机参数掉电保存:保存设备配置参数、计数值、密码、校准数据(如温控阈值、遥控器配对码)
  2. 小容量数据存储:容量小,不存大量文件,是嵌入式最常用的小容量掉电存储器件
  3. 拓展:同系列还有 AT24C04/08/16...,后缀数字越大容量越高,通信协议完全兼容 I2C

三、I2C 总线 + 电路规范

I2C 总线基础概述

1. 来源与硬件组成

I2C(Inter IC BUS)由飞利浦公司研发,仅 SCL(时钟线)、SDA(数据线)两根信号线即可完成多设备通信。

  • SCL:串行时钟,由主机(单片机)输出时序,管控传输节奏
  • SDA:串行数据,双向半双工传输数据

2. 三大通信特性

  1. 同步通信:依靠 SCL 时钟电平同步收发时序;
  2. 半双工:同一时刻 SDA 只能单向收发数据;
  3. ACK 应答:每传输 1 字节数据,接收方回复应答信号,保障传输可靠。

3. 协议优势

  • 芯片厂商:统一通信标准,省去自定义协议开发,缩短芯片设计周期、提升稳定性;
  • 开发人员:一套 I2C 驱动兼容多款外设,不用学习零散私有协议,降低开发难度。

4. 图中三款典型 I2C 外设

  • OLED 显示屏:I2C 收发显示数据,输出文字 / 数字;
  • DS3231 时钟模块:存储年月日时分秒,I2C 读写实时时间;
  • MPU6050 姿态传感器:采集加速度、陀螺仪数据。

I2C 硬件电路规范

1. 总线接线规则

  1. 总线并联拓扑:所有挂载 I2C 外设的 SCL 引脚全部接在一起、SDA 引脚全部接在一起,共用主机 SCL/SDA;
  2. 端口模式 :全部 I2C 设备的 SCL、SDA 引脚配置为开漏输出(对应右侧 MOS 管结构图,输出管下端接地);
  3. 上拉电阻配置 :SCL、SDA 总线分别外接4.7KΩ 上拉电阻到电源 VDD

2. 开漏 + 上拉电阻的原理💡

  1. 线与逻辑:开漏结构只能拉低总线电平,无法主动拉高;空闲时依靠上拉电阻把 SCL/SDA 拉为高电平;
  2. 多设备防干扰:任意设备拉低总线,整条总线电平变低;所有设备释放总线,上拉电阻统一拉高电平,完美解决多器件总线竞争、互相干扰的问题。

四、I2C时序结构 重点

I2C 起始 (S)、停止 (P) 时序

起始信号 S(左图)

  • 时序规则SCL保持高电平时,SDA 电平从高 → 低跳变
  • 作用:主机发起通信,通知总线上所有从设备准备接收数据,标志一次 I2C 传输正式开始
  • 时序要点:SCL 不能变低,只有 SCL 高电平期间的 SDA 下降沿才是合法起始

终止信号 P(右图)

  • 时序规则SCL保持高电平时,SDA 电平从低 → 高跳变
  • 作用:主机结束本次通信,释放 I2C 总线,总线回到空闲高电平状态
  • 时序要点:同样必须在 SCL 高电平下完成 SDA 上升沿

五、I2C 字节收发时序📌

主机发送 1 字节

时序规则

  1. SCL 低电平阶段 :主机更改 SDA 电平,把数据位放到数据线,高位 B7 先发、低位 B0 最后发
  2. SCL 拉高电平阶段 :SDA 电平必须保持不变,从机在 SCL 高电平时采样读取当前 SDA 数据;
  3. 重复以上 8 次,依次送出 B7~B0,完整传输 1Byte(8 位数据)。

核心约束:SCL = 高时 SDA 禁止跳变,防止从机采样出错。

主机接收 1 字节

时序规则

  1. 接收前置操作:主机提前释放 SDA 引脚(开漏模式,交出 SDA 控制权,由从机驱动电平);
  2. SCL 低电平阶段 :从机修改 SDA 电平,逐位输出数据,依旧高位 B7 优先输出
  3. SCL 拉高电平阶段:SDA 电平锁定不变,主机在高电平读取 SDA 上的数据;
  4. 循环 8 次,主机读完 B7~B0,完成单字节接收。

收发共同点 & 关键区别

项目 主机发数据 主机收数据
数据驱动方 主机控制 SDA 电平 从机控制 SDA 电平
SDA 控制权 主机占用 主机提前释放
传输顺序 固定高位 (B7)→低位 (B0) 固定高位 (B7)→低位 (B0)
采样时刻 从机在 SCL 高电平读 主机在 SCL 高电平读
电平约束 SCL 高电平 SDA 不许变化 SCL 高电平 SDA 不许变化

六、I2C 应答(ACK/NACK)时序

SA:主机发送应答(左图,主机收完 1 字节后执行)

使用场景:主机接收从机发来的 1Byte 数据之后,由主机发出 1 位应答位
  1. 时序规则:SCL 时钟脉冲期间,主机主动控制 SDA 电平
    • SDA 拉低 = 0 → ACK 应答:告诉从机,本机已收到数据,请继续发送下一字节
    • SDA 拉高 = 1 → NACK 非应答:告诉从机,不再接收后续数据,准备结束通信
  2. 时序特征:SCL 高电平时 SDA 保持既定电平,和字节收发电平约束一致。

RA:主机接收应答(右图,主机发完 1 字节后执行)

使用场景:主机向从机发送 1Byte 数据之后,主机读取从机回复的应答
  1. 前置操作:主机提前释放 SDA 引脚(让出总线控制权,由从机驱动 SDA 电平)
  2. 时序规则:SCL 拉高采样 SDA
    • SDA=0 → 从机 ACK:从机正常收下数据,可继续下发数据
    • SDA=1 → 从机 NACK:从机未收到 / 无法接收,通信异常或终止
  3. 电平特征:SDA 电平由从机驱动,主机只做读取。

两种应答区别汇总

标识 操作方 时机 SDA 控制权 电平含义
SA 发送应答 主机 主机收完数据后 主机掌控 SDA 0=ACK 继续读;1=NACK 停止读
RA 接收应答 从机 主机发完数据后 从机掌控 SDA 0 = 从机收下;1 = 从机拒收

结合 AT24C02 实例

  1. AT24C02 写流程应答 起始→主机发器件地址 (RA 读从机 ACK)→发存储地址 (RA 读 ACK)→发写入数据 (RA 读 ACK)→停止 P

每次主机发完字节,都 RA 接收从机应答,确认芯片正常就绪。

  1. AT24C02 读流程应答 重起始→器件地址 + 读→主机逐个接收存储数据 (SA 发应答:多字节读时 ACK,最后 1 字节 NACK)→停止 P

主机读完一个数据,SA 发 ACK = 继续读下一个;读完最后 1 字节 SA 发 NACK = 终止读取。

七、I2C【主机写入数据帧】笔记📝

整帧传输顺序(从左→右)

{S → 从机地址+W → RA(0) → BYTE1 → RA(0) → BYTE2 → RA(0) ...→ BYTEn → RA(0) → P}

1. S:起始信号

SCL 高电平,SDA高→低,开启本次 I2C 通信。

2. SLAVE ADDRESS+W(8 位从机寻址字节)

  • (A6~A0)(高 7 位):从机硬件地址,用来选中总线上指定外设(AT24C02 靠 A0/A1/A2 引脚修改该地址)
  • (R/W)(最低位):写标志位,代表主机要向从设备写入数据

功能:确定「要写给哪个芯片」

3. RA:接收应答(主机接收从机应答)

主机发完 1 字节数据后释放 SDA,读取从机电平:

  • RA=0 →ACK 应答:从机拉低 SDA,正常收到数据,继续传输(图中全为 0,代表设备正常就绪)
  • RA=1→ NACK 非应答:从机无响应,主机可直接发停止 P 结束通信

4. S:BYTE1~BYTEn:主机连续发送数据字节

主机占用 SDA 逐字节下发数据,每发送完 1 个字节,必须等待 1 次从机 RA 应答,收到 ACK 才继续发送下一字节;可无限拼接多字节。

AT24C02 场景:第 1 个 BYTE = 芯片内部存储地址,后续 BYTE = 需要存入的有效数据。

5. P:停止信号

全部数据发送完成,SCL 高电平,SDA低→高 ,释放 I2C 总线,本次写传输结束

一句话总结

S + 从机地址 (W):选定目标设备;后续 BYTE:下发写入的数据,实现「指定从机 + 批量写入」。

八、I2C接收一帧(读帧)

🔹整帧链路(接收一帧完整流程)

S → 从机地址 + R → RA (0) → BYTE1 + SA (0) → BYTE2 + SA (0) ... → BYTEn + SA (1) → P

一句话任务:向指定从机,读取一串数据

🔹逐段拆解

  1. S 起始:开启整帧读取 SCL 高、SDA 高→低,开启一次 I2C 通信。

  2. 从机地址 + R(寻址字节) A6~A0=7 位设备地址(选定要读的芯片) R/W=1 = 读指令 :通知从机,主机接下来要读你数据 → 完成:确定「向谁读」

  3. RA (0) 接收应答(从机回复) 主机发完地址,松开 SDA,从机拉低 SDA=RA=0 (ACK) 含义:从机收到地址,准备往外发数据;无应答直接终止帧。

  4. BYTE1~BYTEn + SA 主机接收 + 主动应答【读帧核心】

  • 数据方向:从机发数据、主机收数据
  • SA 是主机发送应答(读帧独有) ✅ 中间 BYTE:SA=0 (ACK) → 主机:数据收到,麻烦继续发下一个 ✅ 最后 1 个 BYTEn:SA=1 (NACK) → 主机:全部收完,不要再发了
  1. P 停止:结束本帧读取 SCL 高、SDA 低→高,整帧接收完毕、释放总线。

🔹关键区分(写 / 读帧)

✅ 写帧(发送一帧):W=0,应答全 RA(主机收从机信号),任务:给谁发数据 ✅ 读帧(接收一帧):R=1,应答全 SA(主机发信号给从机),任务:向谁收数据

🔹AT24C02 读一帧实操

  1. 先写寻址(不发 P):S + 器件 W 地址 + ACK + 内存地址 + ACK
  2. 重启 S + 读帧:S + 器件 R 地址 + ACK→依次读数据→最后 NACK→P,完整读完一帧数据

九、I2C 复合帧(先发后收|AT24C02 随机读专用)笔记📝

🔹整帧分为上下两段:【上段写帧 + 下段读帧】,中间不停 P,下段重新发起始 S

第一段:写入帧(用来指定要读取的内存地址)

链路:S → 从机地址+W → RA(0) → BYTE1 → RA(0) ...→ BYTEn → RA(0)

  1. S:起始信号,开启通信
  2. 从机地址+W:7 位硬件地址 +\(\boldsymbol{R/\overline{W}=0}\)写标志,选定芯片、告知接下来下发地址
  3. RA=0:从机 ACK 应答,每发完 1 字节主机都接收从机应答
  4. BYTE 内容:BYTE1 = AT24C02 片内存储地址(核心:把要读的位置写给芯片,实现寻址)

✅ 本段结束不发停止 P,总线保持占用

第二段:读取帧(重起始,读取指定地址的数据)

链路:S → 从机地址+R → RA(0) → BYTE1+SA(0) ...→ BYTEn+SA(1) → P

  1. S重复起始信号(重起始),不用释放总线,直接切换读写方向
  2. 从机地址+R:7 位地址不变、\(\boldsymbol{R/\overline{W}=1}\)读标志,通知从机准备往外发数据
  3. RA=0:从机应答,准备输出数据
  4. 数据交互:从机发数据、主机收数据
    • 中间字节:SA=0(ACK) → 主机:继续发下一个数据
    • 末尾字节:SA=1(NACK) → 主机:数据收完,停止发送
  5. P:停止信号,整帧复合传输结束

🔹一句话功能

先发地址定位存储位置,再回头读该地址数据 = AT24C02 随机读 任务概括:确定向哪个芯片、读取哪个指定地址的数据

十、AT24C02 字节写 + 随机读笔记📝

字节写(单字节存入指定地址)

传输帧顺序

S → 从机地址 + W → RA (0) → WORD ADDRESS (片内地址) → RA (0) → DATA (写入数据) → RA (0) → P

  1. S:起始信号,开启 I2C 通信
  2. SLAVE ADDRESS+W:器件地址 + 写位 (W=0),本板硬件地址:固定 1010+A0A1A2=000 → 二进制1010000,加写 0 → 0xA0
  3. RA (0):从机 ACK 应答,代表收到数据
  4. WORD ADDRESS:AT24C02 内部存储地址(0~255,共 256 字节空间)
  5. DATA:要存入该地址的有效数据
  6. P:停止信号,本次写入结束

功能:在【指定片内地址】存入 1 字节数据


随机读(读取指定地址里的数据,复合帧结构)

两段式帧结构(上段写地址,下段重读数据,中间不发 P,重发起始 S)

第一段(伪写,只发地址不存数据): S → 从机地址 + W → RA (0) → WORD ADDRESS → RA (0) 第二段(重读): S (重起始) → 从机地址 + R → RA (0) → DATA → SA (1) → P

  1. 第一段作用:把要读取的内部地址发给 AT24C02,指针定位到目标位置
  2. 重起始 S:不释放总线,直接切换读模式
  3. SLAVE ADDRESS+R:R=1 读标志,地址 0xA1
  4. RA (0):从机应答,准备输出数据
  5. DATA:从机传回目标地址存储的数据
  6. SA (1):主机 NACK 非应答,告诉芯片读完了
  7. P:停止,读取结束

功能:定位指定地址,读出该地址存储的数据

I2C 四类数据帧拟人化笔记(小明 = 主机 (单片机)、老师 = 从机 (AT24C02))

1、第一类:【主机写帧|小明给老师递纸条(单字节写入)】

流程:S→老师编号 + W→老师回话 (RA=0)→纸条页码→老师回话→纸条内容→老师回话→P

  1. S (起始):小明敲桌,示意「老师我要递消息」

  2. 设备地址 + W:小明报老师专属编号 +"我要写给你",点名锁定这位老师

  3. RA=0:老师点头(ACK):收到编号,等着收内容

  4. WORD ADDRESS:小明先说本子页码(要写在哪一页),老师再次点头确认

  5. DATA:小明写上要存的文字内容,老师点头收下


  6. P (停止):小明收笔,本次投递结束。

任务:小明在老师笔记本【指定页码】写下内容

2、第二类:【主机读帧|小明找老师要单页内容(纯读)】

流程:S→老师编号 + R→RA=0→老师发数据→小明回话 SA→末尾 SA=1→P

  1. S:小明敲桌,发起问话
  2. 地址 + R:报编号 +"我要向你借书里内容"
  3. RA=0:老师点头就绪
  4. BYTE:老师翻页读出一页文字、逐条念给小明
    • SA=0:小明听完一句点头:继续往下读(中间数据)
    • SA=1:小明听完最后一句摆手:不用再读了(末尾 NACK)
  5. P:谈话结束

任务:小明向老师索要现成数据(需要提前已经定好页码)

3、第三类:【复合随机读帧|小明先报页码、再找老师要内容(AT24C02 随机读)】

两段对话:先发地址 (问页码) + 重起始再读内容 (要正文) ① 上段(伪写,只报页码不写字) S→编号 + W→RA=0→页码→RA=0:小明:老师,我想看 XX 页码;老师点头记下页码,不结束对话、不离开座位(无 P) ② 下段(重起始,重新问话借书) S (再次敲桌)→编号 + R→RA=0→老师读内容→SA=1→P:小明:现在把这页内容读给我;老师读完,小明摆手结束聊天。

任务:先指定页码,再读取该页码内容(AT24C02 最常用随机读)

4、补充总规则(应答区分)

✅ RA(接收应答):老师说话 (小明发完信息,老师点头 / 摇头回复),写帧全程用 RA ✅ SA(发送应答):小明说话(老师发完信息,小明点头 / 摆手回复),读帧全程用 SA

  • ACK=0 = 点头:收到、继续
  • NACK=1 = 摆手:结束、别发了

精简口诀

写是小明递纸条,全程老师来回话; 读是老师念课文,全程小明定收尾; 随机读要先报页,重读之后再借书。

AT24C02 字节写、随机读笔记(对标两张时序图)

一、字节写(定点写入 1 字节)

完整时序链路

起始条件 → 器件地址 (写 W=0) → 从机 ACK → 字地址 → 从机 ACK → 写入数据 → 从机 ACK → 停止条件

  1. 起始:SCL 高、SDA 高→低,开启通信
  2. 器件地址:7 位芯片地址 + 最低位 0 (写指令),选中 AT24C02
  3. LACK(RA 应答,从机发):AT24C02 收到数据,拉低 SDA=ACK
  4. 字地址:8 位地址,选定芯片内部要写入的存储位置 (0~255)
  5. 写入数据:主机发送要保存的 1 字节数据
  6. 停止:SCL 高、SDA 低→高,结束本次写操作

图缩写释义

MB = 主机输出 (SDA);LACK = 从机应答;全程从机回复 ACK


二、随机读(指定地址读取 1 字节,复合帧)

整段分前段伪写寻址 + 后段重读数据,中间无停止 P、重发起始

① 前段(只发地址,不存数据)

起始 → 器件地址 (W=0) → ACK → 字地址 → ACK 作用:把要读的地址发给 AT24C02,内部存储指针跳到目标位置

② 后段(重复起始,切换读模式)

重复起始 → 器件地址 (R=1) → ACK → 从机出数据 → 主机 NACK → 停止

  1. 重复起始:不释放总线,直接二次起始信号
  2. 器件地址 R=1:读指令,通知芯片向外发数据
  3. 数据:AT24C02 输出目标地址存储内容
  4. NACK:主机拉高 SDA (SA=1),告诉从机读完、不用继续发
  5. 停止:结束读通信

关键特点

  • 前段全是从机 ACK (RA) ,后段末尾主机 NACK (SA)
  • 随机读核心:先写地址定位置,重读拿数据

三、读写规律总结

  1. 写操作:主机发全部数据,每次由从机回复 ACK
  2. 读操作:从机发数据,最后主机发 NACK 终止传输
  3. AT24C02 器件地址:固定 1010+A2A1A0,写 0xA0、读 0xA1
  4. !!!!将I2C_ReceiveACK();ACK=I2C_ReceiveACK();合并为ACK=I2C_ReceiveACK();!!!!!

4.DS18B02(温度传感器)

4.1 概念

一、基础概述

DS18B20 是 Maxim(原 Dallas)出品的单总线数字温度传感器,区别于热敏电阻、热电偶这类模拟传感器,数据与指令全部以数字信号传输,硬件接线精简、抗干扰优异,是嵌入式测温最常用器件之一。

二、核心参数

项目 参数详情
测温区间 -55℃ ~ +125℃;常温(-10~85℃)精度 ±0.5℃
通信协议 1-Wire 单总线,单根数据线完成双向收发
分辨率 可编程 9~12 位(12 位时最小分度 0.0625℃)
供电方式 ①外部供电 3.0~5.5V;②寄生供电(仅 DQ+GND 两根线)

4.2 引脚

一、引脚功能汇总

引脚 额定电压 功能说明
VDD 3.0~5.5V 器件工作电源正极,外部供电模式接入电源,寄生供电可悬空
GND 0V 电源参考地,必须和主控系统共地
DQ 单总线电平 双向数据通信引脚,单片机读写温度、指令交互的唯一信号线

二、图中外部供电电路解析

1. 接线逻辑

  • 引脚 1(GND):直接连接系统地 GND;
  • 引脚 3(VDD):直接接系统电源 VCC,为芯片提供工作电压;
  • 引脚 2(DQ):一路引出作为总线端口DQ接单片机 IO 口,同时通过 4.7KΩ 上拉电阻 R1 接到 VCC

2. 4.7K 上拉电阻作用💡

  1. 空闲电平钳位:总线无数据传输时,电阻把 DQ 拉至高电平,保证总线默认处于空闲;
  2. 驱动能力补充:DS18B20 只能拉低总线、无法主动拉高,单片机释放引脚后由电阻拉高电平,满足单总线时序电平规范;
  3. 多器件挂载:同一根 DQ 总线上挂多个传感器时,统一依靠单个上拉电阻完成电平恢复。

4.3 内部结构

一、左侧供电电路模块(寄生电源电路 PARASITE POWER CIRCUIT)

组成:二极管、储能电容(C_{PP})、电源检测(POWER-SUPPLY SENSE)

  1. 寄生供电模式:VDD 悬空,依靠 DQ 总线高电平通过二极管给\(C_{PP}\)充电储能,测温转换时电容放电给芯片供电,实现仅两根线(DQ+GND)工作。
  2. 外部供电模式:VDD 接 3.3~5V 电源,电源检测电路识别外部供电后关闭寄生取电回路,工作稳定性更高。
  3. DQ 外接 4.7K 上拉电阻((V_{PU})),是单总线电平基准。

二、64-BIT ROM + 1-WIRE PORT(单总线 ROM 模块)

  • 每个传感器内置全球唯一 64 位固化 ROM 编码:前 8 位家族码(固定 0x28)+48 位器件序列号 + 8 位 CRC 校验码。
  • 作用:一条总线挂载多片 DS18B20 时,单片机通过 ROM 寻址命令选中指定传感器,实现多点测温。

三、暂存器 SCRATCHPAD + 后端功能寄存器(核心数据区)

由存储器控制逻辑(MEMORY CONTROL LOGIC)统筹读写,分为高速暂存 RAM非易失 EEPROM两部分

寄存器 存储介质 功能说明
温度传感器 TEMPERATURE SENSOR 模拟测温单元 实时采集环境温度,转换后存入暂存器高低字节
(T_H)高温报警阈值 EEPROM 断电数据不丢失,超限触发报警标记
(T_L)低温报警阈值 EEPROM 同上,自定义低温警戒值
配置寄存器 CONFIG EEPROM 设置测温分辨率(9~12bit,对应精度 0.5~0.0625℃)
8 位 CRC 生成器 硬件逻辑 对读取的温度数据做校验,防止总线传输出错

💡 关键逻辑:EEPROM 参数需要手动下发拷贝指令,才会加载到 SCRATCHPAD 暂存器参与工作;修改阈值后写入 EEPROM,掉电不会丢失配置。


四、数据流简要流程

  1. DQ 下发单片机指令1-WIRE 端口解析存储器控制逻辑
  2. 温度传感器完成 A/D 转换→数据存入 SCRATCHPAD;
  3. 单片机读取暂存温度 + CRC 校验,同时可读写(T_H/T_L)、分辨率配置;
  4. 温度超出(T_H/T_L)时芯片内置报警标记,主机可批量巡检总线上所有异常传感器。

4.4 单总线

1-Wire 单总线协议

一、基础定义

单总线(1-Wire BUS)由原 Dallas(现并入 Maxim)公司推出的串行通信总线,仅一根 DQ 信号线完成双向数据交互,是 DS18B20、DS2431 等器件的标准通信协议。

核心特性

  1. 物理接线
    • 常规外部供电:**DQ + VDD + GND**三线;
    • 寄生供电模式 :省略 VDD,仅DQ+GND两根线,DQ 既传输数据又分时给从机供电,布线最简。
  2. 通信制式异步半双工通信,主从架构(单片机为主机,DS18B20 为从机),单线分时收发数据,依靠严格电平延时区分 0、1、复位时序。
  3. 总线拓扑单根 DQ 总线可并联数十个挂载带唯一 64 位 ROM 的从设备,依靠 ROM 地址点对点寻址。

二、时序分段(主机 + 从机动作拆分)

1. 主机发送复位脉冲(黑色波形:主机拉低总线)

主机主动将 DQ 总线拉低电平,持续时间 ≥480μs,完成总线复位,清空总线状态。

2. 释放总线,从机等待窗口期

主机释放 DQ 引脚(总线依靠 4.7K 上拉电阻回到高电平),之后等待 15~60μs; 挂载在总线上的 DS18B20 从机在此区间内检测复位信号,准备应答。

3. 从机发送存在应答脉冲(灰色波形:从机拉低总线)

满足等待时间后,在线的 DS18B20 把 DQ 总线拉低,持续 60~240μs,用来通知主机:设备在线、通信就绪。

4. 从机释放总线

应答结束后从机释放 DQ,上拉电阻重新拉高总线,一次初始化时序完成,主机即可下发读写指令。

图例释义:

  • 黑线:主机拉低总线 ;灰线:DS18B20 从机拉低总线 ;细线:上拉电阻维持总线高电平

三、发送一位bit位

一、写 0 时序(MASTER WRITE "0" SLOT)

  1. 主机动作 :起始瞬间将 DQ 总线拉低,低电平保持 60~120μs后释放总线,由上拉电阻恢复高电平。
  2. 从机采样 :从机在总线被拉低后约 30μs(典型采样点)读取电平 ,检测为低电平即判定主机下发数据0
  3. 时隙总要求 :单次完整写时隙时长必须>60μs,相邻两位指令间隔无强制下限。

二、写 1 时序(MASTER WRITE "1" SLOT)

  1. 主机动作 :起始瞬间拉低 DQ,仅保持 1~15μs 就立刻释放总线,总线快速被上拉电阻抬为高电平。
  2. 从机采样 :同样在拉低后 30μs 左右采样 ,此时总线已经变回高电平,判定下发数据1
  3. 时隙总要求:和写 0 一致,整段时隙时长>60μs。

三、关键细节

类型 主机拉低时长 从机采样时刻 电平判定结果
写 0 60~120μs 拉低后 30μs 低电平→0
写 1 1~15μs 拉低后 30μs 高电平→1

💡 核心原理:从机统一在下降沿后 30μs采样,依靠主机拉低的长短区分 0/1;全时隙 > 60μs 是为保证从机完成采样、电平稳定。

四、编程实操要点⚠️

  1. 单片机 IO 输出低电平后,严格卡准延时,写 0 不能短于 60μs、写 1 不能超过 15μs;
  2. 写完 1bit 后剩余时间补足至 60μs 以上,保证时序合规;
  3. 一字节(8bit)从最低位 LSB 开始逐位发送。

四、接收一位bit位

一、统一起始动作

主机拉低 DQ 总线 1~15μs 后立刻释放,作为读时隙起始触发信号;从机收到下降沿后,在接下来很短时间内把待发送的 0/1 电平驱动到总线上。

二、读 0、读 1 电平区分

  1. 读 0(MASTER READ "0" SLOT) 从机持续拉低总线 ,在主机采样窗口内总线保持低电平,主机读取到低电平 → 收到数据0
  2. 读 1(MASTER READ "1" SLOT) 从机不主动拉低总线 ,总线依靠 4.7K 上拉电阻恢复高电平,主机采样时为高电平 → 收到数据1

三、采样关键规则💡

  • 采样窗口:总线拉低后的 15μs 之内(尽量靠近 15μs 末端),是主机读取电平的最佳时间点;
  • 时隙总长:单个读位完整周期必须大于 60μs,剩余时长用来等待总线电平稳定,再开启下一位读写。

四、时序参数表格

项目 参数要求
主机拉低总线 1~15μs
主机采样区间 下降沿起 0~15μs(推荐 12~15μs)
单时隙总时长 >60μs

五、编程注意事项⚠️

  1. 主机拉低引脚后,需要精准延时再读 IO,过早 / 过晚采样都会读错电平;
  2. 读取一字节时,从最低位 (LSB) 开始逐位读取 8 次,最后拼接成完整字节;
  3. 读时序和写时序不能混用延时参数。

五、发送一个字节 接受一个字节

一、发送一字节(S:BYTE)

  1. 实现逻辑 :循环连续调用 8 次写 1 位时序函数,完成 1 字节 8 位数据发送。
  2. 位序规则:低位优先 先发最低位B_0(bit0)→依次{B_1、B_2...B_7}\),最后发送最高位{B_7}(bit7)。 例:发送0x05(00000101),发送顺序:1→0→1→0→0→0→0→0。

二、接收一字节(R:BYTE)

  1. 实现逻辑 :循环连续调用 8 次读 1 位时序函数,依次采集 8 个 bit 拼成 1 字节。
  2. 位序规则:低位优先 最先收到的电平存入{B_0},后续依次(B_1 ~ B_7\),最终组合为完整字节。

三、应用场景

DS18B20 完整通信流程:初始化复位→发送 ROM 指令→发送功能指令→读取温度字节,全部依靠字节收发函数完成指令与温度数据交互。


单总线四层函数汇总

  1. 初始化函数(复位 + 存在脉冲)
  2. 写 1 位函数
  3. 写 1 字节(循环 8 次写位)
  4. 读 1 位函数
  5. 读 1 字节(循环 8 次读位)

七、操作流程

整体固定流程:初始化复位 → ROM 指令 → 功能指令,单次测温必须严格遵照顺序执行。

一、三步基础流程

  1. 初始化 主机发送复位时序,等待从机存在应答脉冲;无应答代表器件未挂载 / 故障,终止后续通信。
  2. ROM 操作(寻址环节) 下发 5 类 ROM 指令,用来选中总线上目标传感器(单设备 / 多设备区分)。
  3. 功能操作(寄存器 / 测温环节) 下发功能指令,实现启动测温、读写暂存器、参数拷贝等实际功能。

二、ROM 指令(5 条,设备寻址用)

指令码 指令名称 使用场景
0x33 READ ROM 读 ROM 单总线仅 1 片 DS18B20,直接读取 64 位唯一 ROM 地址
0x55 MATCH ROM 匹配 ROM 多设备组网,后跟 64 位地址,精准选中指定传感器
0xCC SKIP ROM 跳过 ROM 单设备最简测温跳过寻址直接下发功能指令(最常用)
0xF0 SEARCH ROM 搜索 ROM 自动遍历总线,扫描总线上全部设备 ROM 编号
0xEC ALARM SEARCH 告警搜索 只筛选温度超(T_H/T_L)报警阈值的传感器

💡 日常单点测温首选:0xCC(跳过ROM),省去 64 位地址写入,代码最简。

三、功能指令(6 条,芯片功能控制)

指令码 指令名称 功能说明
0x44 CONVERT T 启动温度转换 触发 AD 测温转换,转换完成后温度存入暂存器 Byte0、Byte1; 寄生供电时转换期间需持续拉高总线
0xBE READ SCRATCHPAD 读暂存器 连续读取 9 字节暂存数据(温度 + 阈值 + 配置 + CRC)
0x4E WRITE SCRATCHPAD 写暂存器 写入(T_H)、(T_L)、分辨率配置 3 个参数(Byte2~4)
0x48 COPY SCRATCHPAD 暂存→EEPROM 把(T_H/T_L)配置从RAM 拷贝进 EEPROM实现参数掉电保存
0xB8 RECALL E2 EEPROM→暂存 从 EEPROM 回读参数加载到暂存器(芯片上电自动执行)
0xB4 READ POWER SUPPLY 读供电模式 查询当前器件是外部供电 / 寄生供电模式

八、DS1802数据帧

适用:单总线只挂载 1 片 DS18B20 ,全程使用SKIP ROM(0xCC)跳过 ROM 寻址

一、第一段:温度转换(AD 采样,启动测温)

指令时序:初始化 → 发送 0xCC → 发送 0x44

  1. 主机执行单总线复位初始化,等待从机应答;
  2. 主机发送一字节0xCC(SKIP ROM),跳过设备地址匹配;
  3. 主机发送一字节0x44(CONVERT T)芯片启动内部 A/D 温度转换

⏱ 转换耗时:12bit 分辨率最长 750ms,程序需延时等待转换完成,温度结果存入暂存器 Byte0、Byte1。

二、第二段:读取温度数据(读 9 字节暂存区)

指令时序:初始化 → 发送 0xCC → 发送 0xBE → 连续读 9 个字节

  1. 再次总线初始化复位;
  2. 再次发送0xCC跳过 ROM;
  3. 发送**0xBE(READ SCRATCHPAD)读暂存指令**;
  4. 连续调用 9 次读字节函数,依次读出: Byte0(温度低LSB) → Byte1(温度高MSB)→ TH → TL → 配置寄存器 → 3个保留字节 → CRC校验

三、温度数值换算(12 位分辨率默认)

  1. 拼接数据:temp = (MSB << 8) | LSB
  2. 正数(MSB 高 5 位全 0):实际温度 = temp × 0.0625 ℃
  3. 负数(MSB 高 5 位全 1):补码换算 temp = temp - 65536,再 ×0.0625

例:上电默认MSB=0x05,LSB=0x50 → temp=0x0550=1360 → 1360×0.0625=85℃

九、温度存储格式(以补码的形式存储数据)

一、16 位温度数据位定义(LSB 低字节 + MSB 高字节)

1. LSB(低 8 位,Byte0)

Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
(2^3) (2^2) (2^1) (2^0) (2^{-1}) (2^{-2}) (2^{-3}) (2^{-4})

小数位:Bit3~Bit0 对应 (0.5℃、0.25℃、0.125℃、0.0625℃),最小分度值 = 0.0625℃

2. MSB(高 8 位,Byte1)

Bit7~Bit3 (共 5 位) Bit2 Bit1 Bit0
S (符号位) (2^6) (2^5) (2^4)
  • S = 符号位:全 0 = 正温度;全 1 = 负温度(补码存储)
  • Bit2~Bit0:整数高位 (64、32、16)

通用换算公式: 拼接 16 位数据temp = (MSB << 8) | LSB 实际温度 (T = temp × 0.0625℃)


二、正负温度换算规则

① 正温(MSB 高 5 位 = 00000)

直接T = temp × 0.0625

② 负温(MSB 高 5 位 = 11111,补码)

真实数值 = temp - 65536,再 ×0.0625

相关推荐
OBiO20131 小时前
AAV在肠道研究中的应用:从血清型选择到炎症性肠病研究案例解析
学习
段一凡-华北理工大学1 小时前
工业领域的Hadoop架构学习~系列文章16:实时流处理架构 - 工业数据的实时动脉
大数据·数据仓库·hadoop·分布式·学习·架构·高炉炼铁
MartinYeung51 小时前
[论文学习]隐私保护联邦学习于入侵侦测系统之调查研究
学习
钟灵9211 小时前
C++【模板初阶】
开发语言·c++·笔记·c#
稷下元歌2 小时前
aifei学习前置基础:全套完整教程:Anaconda 安装→环境配置→YOLOv8+OpenCV 安装 + OpenCV 实操 + 标注→训练→导出→部署
opencv·学习·yolo
江屿风2 小时前
【C++笔记】vector流食般投喂
开发语言·c++·笔记
星恒随风2 小时前
Python 基础语法详解(3):顺序语句、条件语句和循环语句一篇讲清楚
开发语言·笔记·python·学习
worilb2 小时前
Spring Cloud 学习与实践(6):Nacos 配置中心
数据库·学习·spring cloud
零陵上将军_xdr2 小时前
API 签名防重放机制:基于 HMAC-SHA256 的设计与实现
java·学习·安全架构