
做单片机或者嵌入式开发的朋友多多少少都跟DHT11打过交道。这个温湿度传感器价格便宜、驱动简单单总线协议看着也不复杂理论上写个驱动半小时就能搞定。但只要你打算先在Proteus里做完整仿真再去焊实物就很容易被它磨掉耐心。同样的代码在开发板上跑得好好的一放进Proteus仿真就各种翻车要么元件都搜不到要么读回来全是0要么程序卡在等待应答的位置一动不动要么读出的温度湿度跟“125℃”“244%RH”这种离谱值较劲。这篇文章我把Proteus仿真DHT11温湿度传感器时最常见的5个坑整理出来每个坑都会说清楚现象、原因和解决方法最后附一套可以直接编译的完整代码包含DHT11驱动和LCD1602显示。88我还是那句话仿真踩完的坑都是给实物省下的时间。1. 为什么“仿真版DHT11”总比实物难搞1.1 先看DHT11单总线协议要点DHT11的数据通信走的是单总线一根线既做供电参考又做数据靠严格的时序来区分0和1。主机需要先拉低总线保持至少18ms作为起始信号然后释放总线DHT11响应后会先拉低80us再拉高80us表示“我准备好了”紧接着连续发送40位数据依次是湿度整数、湿度小数、温度整数、温度小数、校验和。每一位数据都遵循同样的节奏先是50us的低电平然后高电平持续26~28us表示逻辑0持续70us表示逻辑1。这组时序参数看着不难但它是整个仿真项目的核心。DHT11不像I2C或者UART那样有严格时钟全靠延时函数撑时间窗一旦微秒级的延时偏了0和1就会误判校验和自然过不去。很多人在Proteus里调不通DHT11第一反应是代码写错了但实际上问题往往出在延时精度、电路连接这些“周边环境”上。1.2 仿真环境与真实硬件的差异Proteus的仿真模型尤其是第三方做的DHT11模型对时序的容忍度和真实芯片不太一样。真实DHT11的应答窗口比较宽你拉低19ms还是22ms它基本都能响应但仿真模型可能就认死理低电平时间不够就是不理你。反过来有些模型又对“拉高后的释放时间”特别敏感主机释放总线后如果延时太短模型还没来得及输出应答程序就已经进入等待状态了。另外Proteus里的电平模型比实物更“理想”。在真实电路板上数据线悬空时可能被环境噪声干扰表现出来是随机值在Proteus里悬空的引脚往往会被模型解释成确定的0或1导致你看到“稳定但不正确”的读数。换句话说仿真环境下有些硬件层面的坑被隐藏了有些又被放大了这就解释了为什么同样的代码在不同环境下行为不一样。下面这5个坑基本覆盖了我在Proteus里调试DHT11时遇到的全部套路。2. 坑1元件库不完整搜不到DHT112.1 故障现象打开Proteus按下键盘上的P在元件搜索框里输入DHT11回车结果一条记录都没有。或者搜出来一个名字相似、引脚对不上的东西放上去一看根本不是温湿度传感器。遇到这种情况很多人第一反应是“Proteus是不是没有这个元件”然后直接放弃仿真。实际上Proteus 8以上的完整版本在Sensors分类下是可以找到DHT11的元件搜索名称通常是DHT11或DHT11 Humidity Temperature Sensor。如果你搜不到多数原因有两个一是你用的是Proteus 7这类旧版本或者精简安装包库文件不完整二是第三方库没有正确加载。2.2 手动安装DHT11元件库如果你确认自己用的版本不包含DHT11可以去电子技术论坛或软件资源站搜索“DHT11 Proteus库”下载后通常会得到两个文件一个后缀是.LIB一个后缀是.IDX。把这两个文件复制到Proteus安装目录下的Library文件夹里。以Windows系统为例常见路径是C:\Program Files (x86)\Labcenter Electronics\Proteus 8 Professional\Library。复制完成后重启Proteus再打开元件搜索框输入DHT11应该就能看到了。如果仍然看不到可以到开始菜单找到Proteus自带的Library Manager执行一次刷新索引让软件把新加入的库文件重新扫描一遍。安装第三方库这件事本身不复杂但很多下载包里的文件命名很乱有的还带版本号放进Library目录后Proteus不一定认遇到这种情况把文件名简化成DHT11.LIB和DHT11.IDX再试一次。2.3 实操心得我的建议是尽量用Proteus 8.9以上的完整安装包自带库里的DHT11模型相对稳定。第三方库虽然能用但不同作者做的模型电气特性和可调属性差别很大有的甚至不支持双击修改温湿度值调试体验会打折扣。仿真这个环节本身就是为了提前暴露问题别让元件库的问题浪费太多时间。还有个小细节即使你搜到了DHT11放置到画布后也要确认一下引脚。有一些第三方模型把引脚顺序画得和实物不一致比如DATA引脚在右侧、电源引脚在左侧。接线之前先双击元件看Pin Mapping避免后面对照电路图接线时把DATA和VCC接反。3. 坑2数据线悬空读回来的温湿度全是03.1 问题现象与分析电路连好了代码编译通过也烧录进Proteus里的单片机了结果LCD1602上显示的湿度是0%温度也是0℃程序流程看起来一切正常校验和也通过了。这种“稳定输出0”的情况非常迷惑人你甚至可能会怀疑是DHT11模型没工作。其实这个坑的根源不在软件而在硬件电路。DHT11的数据线是开漏输出结构正常工作时必须在外围接一个上拉电阻把数据线在空闲状态下拉到高电平。如果电路里没有上拉电阻数据线就处于悬空状态电平不确定。在Proteus仿真里这种悬空状态往往会被模型默认当成低电平处理而DHT11读取协议里大量判断都以“低电平结束、高电平开始”为前提最终结果就是读出来一堆0校验和却刚好是0。这个现象我在第一次仿真时踩过当时盯着代码查了一个晚上最后加上一个10k欧姆的电阻问题立刻消失。3.2 正确接线补上拉电阻正确接法是在DHT11的DATA引脚和VCC电源之间接一个电阻阻值选4.7k欧姆到10k欧姆都可以我习惯用10k欧姆。在Proteus里搜索RES元件就是普通电阻拖到画布后双击把Resistance改成10k一端接DHT11的DATA引脚另一端接到VCC电源网络。接线时还要注意DHT11本身的电源引脚VCC接5VGND接地。有的模型有4个引脚第4脚标注NC不用接。接完电阻后用电压探针或者逻辑探针点一下DATA引脚应该能看到空闲状态是高电平。如果看到的是低电平或者不稳定的电平优先检查电源和地是否接对再看上拉电阻有没有真正连到VCC上。3.3 仿真中如何快速放置电阻很多入门用户会在搜索框输入“10k电阻”结果搜出来一堆不相关的东西。在Proteus里电阻的搜索关键字就是RES放置后双击修改阻值即可。如果你想快速区分上拉电阻和下拉电阻记住一句话上拉电阻一端接信号线另一端接VCC下拉电阻一端接信号线另一端接地。DHT11的数据线需要的是上拉。另外如果电路里用了单片机的P0口接LCD1602也要注意给P0加一排上拉电阻。P0口是开漏输出内部没有上拉在Proteus里直接驱动LCD经常显示不稳定。这是另一个常见坑但它和DHT11无关属于51单片机通用常识。我一般在仿真DHT11时顺手就会把P0的上拉也加好避免显示部分出问题反过来干扰排查思路。4. 坑3微秒级延时不准数据乱跳、校验不过4.1 软件延时的“隐形误差”DHT11驱动里最核心的延时是微秒级延时比如40us的采样延时。很多教程里的代码是这样写的void Delay_us(unsigned int us) { while (us--); }这行代码在12MHz晶振、STC89C52或者AT89C52这种12T模式的单片机上一个小时能跑出多少微秒呢答案是每个循环大约5us左右而不是1us。因为Keil C51编译器会把while(us--)编译成好几条指令每条指令又要消耗若干个机器周期。12MHz晶振下1个机器周期是1us所以这个函数实际延时大约是5乘以参数值。如果你把参数传成40实际延时接近200us早就超出DHT11位0和位1的区分窗口了。更麻烦的是Keil的优化级别改了循环编译出来的指令条数也会变。同一个工程优化级别从Level 2改成Level 8延时函数的实际时间可能差了一倍。这就是为什么很多人今天调试通过第二天重新编译一下工程数据又乱了。4.2 用Proteus虚拟示波器校准延时校准延时的最好办法是直接看信号波形。Proteus自带虚拟示波器在左侧工具栏选“Virtual Instruments Mode”把OSCILLOSCOPE拖到画布上A通道接DHT11的DATA引脚地接公共地然后运行仿真。把时基调到0.1ms/div左右就能清楚看到起始信号、应答信号和数据位的波形。正常波形应该是这样的主机拉低大约18~25ms拉高一小段然后DHT11拉低80us、拉高80us接着是40位数据每位都是50us低电平加不同时长的高电平。对比一下高电平的时长大于50us的位是1小于30us的位是0。如果你看到的高电平时长整体偏大说明延时函数要的参数偏大把延时的单位数调小如果波形的低电平时长明显不够把对应延时调大。整个校准过程其实就是拿示波器当尺子把延时的“实际长度”量出来。4.3 进阶思路用定时器判断电平时长如果软件延时怎么调都觉得不踏实可以换一种思路不要用延时去卡采样点而是用定时器去测量高电平的持续时间。主机在数据位高电平开始时启动定时器等到电平变低时停止计时。如果高电平持续超过50us判定为逻辑1如果低于30us判定为逻辑0。这种方案对延时的依赖小很多只要定时器分频对了基本一次就能跑通。51单片机内部定时器做这个测量天然合适。12MHz晶振下定时器工作在12T模式每计数一次正好1us拿定时器记录高电平的计数次数就是微秒数。缺点是代码会比软件延时版本复杂一些但对于追求稳定的人来说这个复杂度完全可以接受。我在后续章节给出的代码里仍然以经典的软件延时版本为主因为这个版本更容易看懂、更适合教学如果你仿真总调不通可以考虑自己改成定时器判断版本。5. 坑4起始信号发不出去程序卡死在等待应答5.1 现象与常见原因程序运行后LCD上一直显示“Read Err”或者干脆停住不动仿真暂停后程序卡在等待DHT11应答的那段while循环里。我见过不少人遇到这个现象后直接把DHT11驱动函数里所有延时都加了一倍结果还是不行。这个问题最常见的原因有两个。第一个是上电后没有给DHT11足够的稳定时间。真实DHT11上电后内部需要大约1秒的时间完成初始化如果单片机复位后立刻去发送起始信号传感器可能还没准备好自然不会应答。第二个原因是主机拉低总线的时间不够。数据手册写的是至少18ms但在Proteus仿真里很多模型对18ms这个下限并不感冒实测下来拉低到20ms甚至25ms才稳定。5.2 解决步骤附关键代码解决方法是两件事配合起来。第一在main函数的初始化之后正式读取循环之前加一段至少1000ms的延时。第二把起始信号里的拉低时间从常规的18ms拉长到25ms。下面是修改后的起始信号函数注意DHT11_Delay_ms(25)这一行void DHT11_Start(void) { DHT11_PIN 1; DHT11_Delay_Unit(2); // 先拉高确保总线空闲 DHT11_PIN 0; // 拉低总线 DHT11_Delay_ms(25); // 保持低电平25msProteus模型下更稳 DHT11_PIN 1; // 释放总线 DHT11_Delay_Unit(6); // 拉高约30us再开始读取应答 }这段代码里的DHT11_Delay_Unit是“约5us一个单位”的延时函数后面完整代码里会给出具体实现。你在自己的工程里调整拉低时长后如果程序还是卡在等待应答可以把释放总线后的拉高时间再加大一点从30us调到40us左右。我自己在Proteus 8.15版本里测试25ms拉低加30us拉高是最稳的组合。5.3 上电稳定时间不能省很多人写单片机程序喜欢“上电就干活”这对DHT11来说其实是坑。真实芯片上电后的1秒稳定时间在Proteus仿真模型里同样被模拟了。有些模型甚至会直接在稳定期内忽略一切总线操作。所以主程序里一定要加上电延时别觉得浪费那1000ms。在代码里就是简单的一行但没加的人十个里有九个会踩到这个问题。另外读取间隔也建议控制在500ms以上。DHT11本身不支持太频繁的读取连续两次读取间隔低于1秒时有些模型会返回上一次的数据有时候还会直接不响应。这些细节在实物数据手册里有说明仿真模型也沿用了这套行为。6. 坑5采样时机不对读错位、错位、校验失败6.1 为什么“延时70us再读”是错误示范网上流传的DHT11例程里有一种写法是检测到数据位前导低电平结束后延时70us然后直接读引脚。这种写法在真实硬件上偶尔能工作但在Proteus仿真里经常翻车。原因很简单逻辑0的高电平时长只有26~28us逻辑1的高电平时长是70us。你延时70us后读引脚遇到逻辑0时电平早就变低了所以读到的全是0遇到逻辑1时电平刚好处于临界状态非常容易被误判。最终结果就是读出的二进制位要么全是0要么随机错乱校验和经常失败。这个坑的迷惑性在于它不影响程序流程编译能过、跑起来也不死机但数据就是不对。你可能会反复怀疑是不是传感器模型坏了实际上问题是采样点选错了。6.2 正确的读位函数写法正确的采样方式是先等待数据位的前导低电平结束也就是等到引脚变高然后延时大约40us再去读引脚。此时如果高电平还没结束说明这是逻辑1如果电平已经变低了说明这是逻辑0。40us这个数值刚好卡在28us和70us之间区分度最好。下面这段是我项目里一直在用的读位逻辑加入了超时保护防止因为总线异常导致程序死循环unsigned char DHT11_ReadByte(void) { unsigned char i, byte 0; unsigned int timeout; for (i 0; i 8; i) { timeout 0; while (DHT11_PIN 0) { // 等待前导低电平结束 if (timeout 300) return 0xFF; } DHT11_Delay_Unit(8); // 延时约40us后采样 byte 1; if (DHT11_PIN 1) { byte | 0x01; timeout 0; while (DHT11_PIN 1) { // 等待该位结束避免影响下一位 if (timeout 300) return 0xFF; } } } return byte; }这段代码的关键点是“等待变高后再延时40us”而不是“延时40us后再判断”。顺序反了结果完全不同。如果你在调试时发现读回来的数据偶尔对、偶尔错优先怀疑这里把40us的延时参数调一下比如把DHT11_Delay_Unit(8)改成DHT11_Delay_Unit(6)试试因为不同Proteus模型对时序的敏感度会有差异。6.3 数据校验逻辑别让错数据蒙混过关DHT11返回的40位数据里最后8位是校验和数值等于前四个字节之和的低8位。校验通过只代表“和值对得上”不代表数据一定是真实环境值。比如你把0、0、0、0四个字节读回来校验和也是0程序自然认为数据有效显示0℃、0%RH也不报错。所以只看校验和不行还要结合常识判断温度在-20℃到60℃之外、湿度在0%到100%之外大概率是读错了。我在代码里加了范围判断不在合理范围内的数据直接丢弃LCD上显示错误提示而不是把离谱数值呈现出来。7. 可直接编译的完整代码DHT11驱动 LCD1602显示7.1 Keil工程创建与配置先创建一个新的Keil工程芯片型号选择Atmel下的AT89C52或者你熟悉的STC89C52两者在指令集上完全兼容。把下面四个文件加到工程里dht11.h、dht11.c、lcd1602.h、lcd1602.c、main.c实际上main.c也会单独加入。在Options for Target的Output选项卡里勾选Create HEX File方便之后把编译产物加载到Proteus里。晶振频率务必保持一致。Keil工程里默认不关心运行频率但Proteus里双击单片机属性面板里的Crystal Frequency要设置成12MHz。DHT11驱动里的延时参数都是以12MHz、12T模式为基准写的。如果你用了11.0592MHz晶振延时参数需要按比例调整不然读位时序会偏。7.2 dht11.h 和 dht11.cdht11.h:#ifndef DHT11_H #define DHT11_H // 数据引脚定义根据Proteus实际接线修改 sbit DHT11_PIN P1^0; // 毫秒级延时供其他模块使用 void DHT11_Delay_ms(unsigned int t); // 读取一次温湿度数据 // buf长度必须为5湿度整数、湿度小数、温度整数、温度小数、校验和 // 返回1表示校验通过0表示失败 bit DHT11_ReadData(unsigned char *buf); #endifdht11.c:#include dht11.h #include intrins.h /* * 微秒级单位延时函数 * 12MHz晶振、12T模式1机器周期1us下每个循环约5us。 * 更换编译器或优化级别后建议用示波器实测校准。 */ void DHT11_Delay_Unit(unsigned char t) { while (t--) { _nop_(); _nop_(); } } /* * 毫秒级延时 * 用于主机起始信号和上电稳定允许一定误差。 */ void DHT11_Delay_ms(unsigned int t) { unsigned int i; unsigned char j; for (i 0; i t; i) { for (j 0; j 200; j) { _nop_(); } } } /* 主机发送起始信号 */ static void DHT11_Start(void) { DHT11_PIN 1; DHT11_Delay_Unit(2); // 确保总线空闲 DHT11_PIN 0; // 拉低总线 DHT11_Delay_ms(25); // 保持25ms仿真模型下更稳定 DHT11_PIN 1; // 释放总线 DHT11_Delay_Unit(6); // 拉高约30us } /* 读取一个字节 */ static unsigned char DHT11_ReadByte(void) { unsigned char i, byte 0; unsigned int timeout; for (i 0; i 8; i) { timeout 0; while (DHT11_PIN 0) { if (timeout 300) return 0xFF; } DHT11_Delay_Unit(8); // 延时约40us后采样 byte 1; if (DHT11_PIN 1) { byte | 0x01; timeout 0; while (DHT11_PIN 1) { if (timeout 300) return 0xFF; } } } return byte; } /* 读取完整40bit数据并校验 */ bit DHT11_ReadData(unsigned char *buf) { unsigned char i, sum 0; unsigned int timeout; DHT11_Start(); // 等待DHT11应答低电平 timeout 0; while (DHT11_PIN 1) { if (timeout 1000) return 0; } // 等待应答低电平结束 timeout 0; while (DHT11_PIN 0) { if (timeout 1000) return 0; } // 等待应答高电平结束 timeout 0; while (DHT11_PIN 1) { if (timeout 1000) return 0; } // 读取5个字节 for (i 0; i 5; i) { buf[i] DHT11_ReadByte(); if (buf[i] 0xFF) return 0; } // 校验和 sum buf[0] buf[1] buf[2] buf[3]; if (sum buf[4]) { return 1; } return 0; }7.3 lcd1602.h 和 lcd1602.clcd1602.h:#ifndef LCD1602_H #define LCD1602_H void LCD1602_Init(void); void LCD1602_ShowChar(unsigned char x, unsigned char y, unsigned char ch); void LCD1602_ShowString(unsigned char x, unsigned char y, unsigned char *str); #endiflcd1602.c:#include REGX52.H #include lcd1602.h sbit LCD_RS P2^6; sbit LCD_RW P2^5; sbit LCD_EN P2^7; #define LCD_DATA P0 void LCD1602_Delay(unsigned int t) { while (t--); } void LCD1602_WriteCmd(unsigned char cmd) { LCD_RS 0; LCD_RW 0; LCD_DATA cmd; LCD_EN 1; LCD1602_Delay(100); LCD_EN 0; } void LCD1602_WriteData(unsigned char dat) { LCD_RS 1; LCD_RW 0; LCD_DATA dat; LCD_EN 1; LCD1602_Delay(100); LCD_EN 0; } void LCD1602_Init(void) { LCD1602_Delay(15000); LCD1602_WriteCmd(0x38); // 8位数据、2行、5x7点阵 LCD1602_WriteCmd(0x0C); // 开显示、关光标 LCD1602_WriteCmd(0x06); // 光标右移 LCD1602_WriteCmd(0x01); // 清屏 LCD1602_Delay(1500); } static void LCD1602_SetCursor(unsigned char x, unsigned char y) { unsigned char addr; if (y 0) addr 0x00; else addr 0x40; LCD1602_WriteCmd(0x80 | (addr x)); } void LCD1602_ShowChar(unsigned char x, unsigned char y, unsigned char ch) { LCD1602_SetCursor(x, y); LCD1602_WriteData(ch); } void LCD1602_ShowString(unsigned char x, unsigned char y, unsigned char *str) { while (*str) { LCD1602_ShowChar(x, y, *str); } }7.4 main.c主流程与显示逻辑#include REGX52.H #include dht11.h #include lcd1602.h void main(void) { unsigned char buf[5]; unsigned char humi_int, humi_dec, temp_int, temp_dec; LCD1602_Init(); LCD1602_ShowString(0, 0, H: . % T: . C); // DHT11上电稳定时间 DHT11_Delay_ms(1000); while (1) { if (DHT11_ReadData(buf)) { humi_int buf[0]; humi_dec buf[1]; temp_int buf[2]; temp_dec buf[3]; // 合理范围判断 if ((humi_int 100) (temp_int 60)) { LCD1602_ShowChar(2, 0, humi_int / 10 0); LCD1602_ShowChar(3, 0, humi_int % 10 0); LCD1602_ShowChar(5, 0, humi_dec / 10 0); LCD1602_ShowChar(10, 0, temp_int / 10 0); LCD1602_ShowChar(11, 0, temp_int % 10 0); LCD1602_ShowChar(13, 0, temp_dec / 10 0); LCD1602_ShowString(0, 1, Data OK!); } else { LCD1602_ShowString(0, 1, Data Err!); } } else { LCD1602_ShowString(0, 1, Read Err!); } DHT11_Delay_ms(500); } }7.5 Proteus连线与仿真步骤Proteus里的电路连接并不复杂。单片机选AT89C52晶振设置为12MHz。P0口接LCD1602的数据引脚D0到D7P2.6接RSP2.5接RWP2.7接EN。DHT11的DATA接单片机P1.0同时在P1.0和VCC之间接10k欧姆上拉电阻。记得给单片机接好复位电路和晶振电路虽然是仿真但Proteus模型里没有这些外围电路单片机不会正常工作。布线完成后双击单片机在Program File里选择Keil编译生成的hex文件。点击运行LCD第一行应该显示“H: 40.0 % T: 25.0 C”之类的数据。如果你看到的是Data Err或者Read Err按照前面几个坑的顺序排查先看元件库是否完整再看上拉电阻有没有接然后用虚拟示波器看波形确认延时是否准确。8. 常见问题速查表与独家排错技巧8.1 一张表快速定位问题我把仿真过程中最常见的问题、原因和解决办法整理成了一张速查表调试的时候不用从头看文章直接对号入座。现象可能原因解决办法元件搜索框找不到DHT11Proteus版本过旧或精简库安装完整版或手动添加DHT11的.LIB/.IDX库文件读回数据显示0℃、0%RH数据线没有上拉电阻在DATA和VCC之间接10k欧姆电阻数据显示125℃、244%RH之类的离谱值微秒级延时偏差过大用虚拟示波器校准延时调整DHT11_Delay_Unit参数程序卡死LCD一直Read Err起始信号拉低时间不够或上电稳定时间不足拉低延长到25ms上电后延时1秒再读湿度正常温度错乱读位采样点不对改为“等待高电平后延时40us再采样”的写法校验和通过但数据不像环境值DHT11模型默认值固定双击DHT11元件修改温湿度属性值LCD1602不显示或显示乱码P0口缺少上拉电阻给P0口接一排10k欧姆上拉电阻8.2 几个提升仿真效率的小习惯第一个习惯在Proteus里双击DHT11元件属性面板里通常会有温度、湿度的初始值设置。默认值往往是温度25℃、湿度40%左右这是模型自带的不代表你的程序读错了。想在仿真里测试程序是否响应环境变化就手动修改属性里的温湿度值然后运行程序看LCD是否跟着变化。第二个习惯在DHT11的DATA引脚上放一个逻辑探针也就是Logic Probe用于观察引脚电平变化。虽然看不到具体波形但能快速判断总线是否被拉低、是否有持续翻转。如果读数据过程中逻辑探针一直常亮说明总线一直停留在高电平那大概率是起始信号没发出去如果特别暗或者常灭说明总线被拉低后没有人释放可能是代码里某个while循环卡住了。第三个习惯Proteus和Keil联调时可以启用电平输出显示和变量观察窗口。用Keil的调试模式配合Proteus VSM可以单步执行看每一步执行后DHT11引脚电平的即时变化定位卡死位置特别方便。不过联调配置比较繁琐日常调试直接烧hex文件也够用。8.3 从Proteus到实物的最后提醒Proteus仿真能把电路的连接、程序逻辑、协议解析这些框架问题提前暴露出来但DHT11的电流特性和时序细节仿真模型和真实芯片还是存在差异。我在仿真里把25ms拉低时间调得很大是因为Proteus模型对这个参数更“挑剔”到了实物上18ms就可以稳定触发。所以别把仿真里的参数原封不动搬到实物代码里拿到开发板后还是要用逻辑分析仪或示波器实测一轮以数据手册为准。我的经验是先在仿真里把通信协议、校验逻辑、显示逻辑全部跑通把代码框架固定下来再到实物上根据实际波形微调延时参数。这条路看着绕实际是踩坑最少的路径。希望这篇文章能帮你把Proteus里这几个典型的坑填平剩下的就是享受调通一瞬间的成就感了。