1. 串口通信为什么是嵌入式里的“万能胶”
做了这么多年单片机开发,要说哪个外设用得最勤、最容易翻车、也最绕不开,串口绝对排第一。STC51这个芯片,到现在还在大量项目里用,原因很简单:便宜、稳定、资料多,而它的串口通信加上RS485总线,几乎承包了工业控制、环境监测、楼宇自动化里的所有数据交换需求。
这篇文章就围绕我实际调试STC51串口和RS485总线时积累的经验展开,从异步通信底层讲到寄存器配置、收发时序、硬件布线,最后是我踩过的一系列坑。适合正在学51单片机的同学,也适合做产品时想把通信做稳的工程师参考。不讲虚的,直接上干货。
1.1 从UART开始理解异步通信
串口通信本质上就是UART协议,Universal Asynchronous Receiver/Transmitter,通用异步收发器。说人话,就是一根线发数据、一根线收数据,双方约定好波特率,不用时钟线,直接按位发送。
这句话里最关键的两个词是“异步”和“约定”。
所谓异步,就是发端和收端没有共用时钟,各自用一个本地晶振来计时。A发一个字节,B在一个完全独立的时间轴上接收。那为什么双方能对上?靠的就是提前约定波特率。
我以前跟新人解释波特率,经常用打电话做类比。你约好对方晚上8点打电话,那你7点59分就开始守在手机旁边。对方8点准时拨过来,你们就对上了。如果两人手表时间不一样,一个用8点,一个用8点零3秒,就很容易错开。波特率就是双方“手表的时间”,它决定了每一位持续多长。比如9600波特率,意味着每1秒发9600个位,每一位持续约104.16微秒。初始化和接收时,这个时间基准必须严格一致——而这就是后面所有乱码问题的根源。
串口协议的数据帧长这样:
- 空闲时线保持高电平
- 发送起始位:一个低电平位,告诉接收端“准备开始”
- 然后是8个数据位,低位在前
- 可选的校验位
- 停止位:一个高电平位,表示一个字节结束
STC51的串口支持方式1和方式3,也就是8位数据或9位数据。8位模式最常见,9位模式常用于多机通信。这块后面再展开。
1.2 为什么RS485是工业现场的“标配”
UART本身只是定义了数据帧格式,并没有规定信号怎么在物理线上传输。这里有三个层次的区别:
- TTL电平:单片机引脚直接输出,0~5V,距离短,抗干扰弱
- RS232电平:正负电压表示,比TTL远一点,但只能点对点
- RS485电平:差分信号,一对A/B线,几十米甚至上千米,支持多设备挂总线
STC51的串口引脚是TTL电平,直接连接只能在同一块板子上做模块间通信。工业现场环境复杂,电机一启动,几十米的线缆上噪声电压就能让通信瞬间报废。所以实际项目中几乎都是TTL进收发器,再转成RS485差分信号上总线。
RS485为什么抗干扰?因为它靠的是两根线之间的电压差来判断逻辑,而不是像RS232那样对地电压。外部共模干扰会同时作用于A、B两根线,但差值基本不变,接收端只认差值,干扰就被抵消了。这就是效率最高、性价比也最高的工业总线方案——便宜、成熟、几乎所有PLC都支持。
2. 硬件设计要命的三件事:方向引脚、终端电阻、隔离接地
RS485的硬件设计看着简单,芯片外围就几个电阻电容,但真正出问题的全是细节。
2.1 收发器芯片与方向控制
目前用得最广泛的RS485收发器就是MAX485这一类。它内部的引脚功能很清晰:
- DI是发送数据输入端,接单片机TXD
- RO是接收数据输出端,接单片机RXD
- DE是发送使能,高电平才能往总线上发送
- RE是接收使能,低电平才能接收
- A、B是一对差分总线
注意,DE和RE一个是高有效、一个是低有效,实际接法通常把两个引脚直接短接,用一个单片机IO口来控制。这个IO叫方向控制脚,也叫DE/RE引脚。
为什么要这样设计?因为RS485是半双工总线。发送和接收共用同一对A/B线,不可能同时进行。你往总线上发数据的时候,A和B上有电压差,此时如果接收端也开门,那自己发出去的数据会原样收回来,而且会引起总线竞争。所以软件逻辑必须在发送前把门切到发送方向,发送完了再切回接收方向。
这个切换时机极其关键。很多人第一次调RS485,都会遇到“发出去的字节后面跟一个乱码”“最后一个字节丢了”这类问题。表面上以为是波特率错误,实际上就是方向切换时序不对。我后面会专门讲这块。
一个很重要的硬件细节:上电瞬间,单片机IO口还没初始化,方向脚可能是低电平。低电平意味着DE=0,发送关闭,接收打开,这通常是对的。但有些设计为了省IO,把方向脚用三极管反向控制,上电瞬间动作可能反过来,导致总线被长时间占住。我之前就遇到过一次,某个采集设备上电后整条485总线瘫痪,其他从机全部无法通信,查了半天才发现是方向控制电路在上电时异常拉了高电平,DE一直接通,设备像个话痨一样霸占总线。
2.2 终端电阻到底加不加
RS485总线设计里,每个工程师都会遇到加不加终端电阻的问题。答案很明确:长线传输必须加,短线传输可以不加。
终端电阻的作用是消除信号在总线末端产生的反射。如果总线长度超过一定范围(实践中最好在十几二十米以上),信号到了末端没被吸收,会反弹回来和其他信号叠加,导致波形畸变、数据错误。
标准做法是在总线最远的两端各并一个120欧姆电阻。为什么是120欧姆?因为RS485总线的特征阻抗一般在100~120欧姆之间,终端电阻匹配特征阻抗,吸收能量最干净。
但终端电阻不是越多越好,也不是随便串在中间。如果每个设备都加120欧,并联后总阻抗越来越小,驱动负载越来越重,反而把信号幅度压低。还有一个常见误区是把终端电阻加在收发器旁边,而不考虑它在总线上的物理位置。电阻必须加在总线的最末端,通常是主站一端和离主站最远的那个从站一端。
另一个经常被忽略的是偏置电阻。当总线上没有任何设备发送时,A和B之间没有电压差,接收端不知道是1还是0,就会随机出数据,常见表现是单片机不断收到0xFF。解决办法是在终端电阻附近加偏置——A线上拉一个电阻到5V,B线下拉到GND,让空闲状态保持一个明确电平。一般用4.7K到10K之间。注意如果设备已经有完善的偏置电路,那边上就别重复加,太多偏置电阻会加大负载,得不偿失。
2.3 地线、隔离和浪涌保护
RS485虽然用差分信号抗共模干扰,但A/B线上的共模电压并非无限大。如果两个设备相隔几百米,它们的地电位可能相差很多,甚至超过收发器允许的共模范围,轻则通信异常,重则烧芯片。
我做过一个跨楼的项目,两台设备一个在A楼、一个在B楼,中间走了近80米线。主站和从站各自用开关电源供电,两个电源的地电位差了一截。通信一开始时好时坏,最后确认是共模电压超标。解决办法是给RS485加隔离——在TTL和收发器之间加数字隔离器,电源也隔离开,把A/B线上的共模风险和单片机系统彻底分开。
更稳妥的方案是直接买带隔离的RS485收发模块,比如某类隔离485模块,内部集成了电源隔离和信号隔离,外围电路少,故障率低。另外,在A/B线上加TVS管做浪涌保护很有必要,特别是室外布线。雷击感应的瞬间,几百上千伏的尖峰沿着线缆打进来,没有TVS,收发器基本一次报废。这个钱不能省。
3. 串口初始化与波特率:所有通信问题的起点
硬件搭好了,回到软件。STC51串口初始化核心就是配置波特率,而波特率又由定时器1和晶振共同决定。这里我直接列出我常用的配置流程。
3.1 波特率公式与定时器1重载值计算
STC51的传统8051内核,最常用来产生波特率的定时器是定时器1,工作方式2,也就是8位自动重载。这一个方式有个巨大的好处:一旦启动,它自己循环计数,不需要中断干预,不会因为程序暂停而影响波特率精度。
波特率公式如下:
波特率 = (2^SMOD) × 系统时钟频率 / (32 × 12 × (256 - TH1))
其中SMOD是PCON寄存器的最高位,可以为0或1,相当于一个倍频开关。12是8051的12分频,也就是常说的12T模式。TH1是定时器重载值。
这个公式的适用范围是STC89C52这类12T内核单片机。如果你用的是STC15系列或STC8系列,它们是1T内核,公式里12要换成1,千万别直接套。
通常我们选11.0592MHz晶振,因为用这个晶振,9600波特率时TH1算出来是整数。我来算一遍:
把波特率代入9600,SMOD取0:
9600 = 11059200 / (32 × 12 × (256 - TH1))
256 - TH1 = 11059200 / (32 × 12 × 9600) = 11059200 / 3686400 = 3
所以TH1 = 256 - 3 = 253,也就是0xFD。
而如果用12MHz晶振,算出来256-TH1 = 12000000 / 3686400 ≈ 3.255,只能取整到3,实际波特率约9812,和9600误差接近2.2%。对串口双方来说,这个误差会导致长时间的连续收发出现位错位。一次传几个字节可能看不出来,传一整个数据包必乱码。
所以我的建议是:能用11.0592MHz就用11.0592MHz,不要为了省几毛钱晶振钱给自己挖坑。
3.2 寄存器配置速查
STC51串口相关寄存器其实不算多,我把最常用的几个说透:
- SCON:串口控制寄存器。SM0和SM1用来选工作方式,方式1就是SM0=0、SM1=1,8位UART。REN=1表示允许接收。TI是发送中断标志,RI是接收中断标志,这两个都要软件清零。
- PCON:SMOD位在这里。SMOD=0波特率不翻倍,SMOD=1波特率翻倍。对于11.0592MHz和9600波特率来说,SMOD=0就够了。
- TMOD:定时器模式。定时器1要设成方式2,TMOD的高四位是定时器1的配置。
- TH1/TL1:重载值。方式2下,TH1是自动重载值,TL1是当前计数值,初始化时两个一起赋同一个值。
- TR1:启动定时器1。
初始化代码我习惯写成这样:
void Uart1_Init(void) { SCON = 0x50; // 方式1,8位UART,允许接收 TMOD = (TMOD & 0x0F) | 0x20; // 定时器1方式2,保留定时器0原配置 TH1 = 0xFD; // 9600bps @ 11.0592MHz TL1 = 0xFD; PCON = PCON & 0x7F; // SMOD = 0 TR1 = 1; // 启动定时器1 ES = 1; // 开启串口中断 EA = 1; // 开总中断 }很多新手习惯直接把TMOD整体赋值成0x20,但那会把你对定时器0的配置覆盖掉。如果项目里还要用定时器0做定时,就把TMOD高四位改了,低四位用原来值保留下来。这类小习惯特别能体现一个工程师的谨慎程度。
3.3 中断处理函数的基本框架
串口收发我强烈建议用中断,不要在while循环里死等标志位。因为RS485半双工的收发切换,最要紧的就是“及时”二字。用中断处理RI和TI,主循环就能专心做协议解析和业务逻辑。
void Uart1_ISR(void) interrupt 4 { if (RI) { RI = 0; uart_rx_buf[uart_rx_cnt++] = SBUF; if (uart_rx_cnt >= RX_BUF_SIZE) { uart_rx_cnt = 0; // 防止溢出 } } if (TI) { TI = 0; } }注意一点,RI和TI是两个独立标志,进入中断必须先判断是谁触发的,而且处理后都要清零。中断里不要做太复杂的事,把SBUF读出来放进缓冲区就赶紧退出。我见过有人直接在上位机中断里解析协议、驱动数码管刷新,一帧数据来的时候整个系统卡顿严重,排查起来非常痛苦。
4. RS485方向切换:细节决定成败的关键环节
这部分是我认为整个项目中最容易出问题也最值得深入研究的地方。RS485半双工通信能不能稳定工作,80%取决于方向切换的时序。
4.1 为什么最后一个字节会被“吃掉”
很多从232转485的工程师第一次调试都会遇到一个诡异的现象:明明发送函数看起来没问题,对方收到的数据却是前面正常,最后少了一个字节,或者变换成乱码。
原因出在发送完成的标志上。标准51单片机的TI标志,在SBUF把数据写入后,一旦数据送到移位寄存器就开始置位。这意味着TI=1代表的是“SBUF已经空出来,可以装下一个字节了”,不代表“最后一位已经真正从引脚上发出去了”。
RS485通讯中,方向引脚如果过早地从发送切回接收,最后几个还在路上传输的数据位就会在总线上被截断。收端看到的是一个不完整的字节,自然就判成乱码或丢帧。
这个问题我为什么印象深刻?因为某次项目现场调试,主站周期性地读取几十个从站的数据,发现每隔一段时间就有一个站返回的数据经常会缺少最后一个字节。一开始怀疑程序逻辑,后来又怀疑晶振温漂,折腾了整整两天,最后用示波器一看波形才明白:每个字节都是从DE引脚拉低的部分被硬生生切掉了约莫半个字节长度。
所以正确的发送流程应该是这样:
- 方向引脚拉高,打开发送使能
- 写SBUF,启动发送
- 等待TI置位,清TI
- 如果还有下一字节,继续写SBUF
- 所有字节写完后,延时一小段时间
- 方向引脚拉低,切回接收状态
这个延时的长短需要根据波特率计算。9600波特率下每个字节大概1.04毫秒。发送完最后一个字节后,至少要等1个字节的时长再加一点余量,保证移位寄存器里的数据都从引脚出去了。我通常会让延时为2~3个字节的时长,比如发送完后延时3毫秒再切方向。
4.2 收到自己发送的数据
还有一种常见问题:RS485从机在发送数据时,如果RE一直处于有效状态(低电平),接收端会把本方发送的数据也收进缓冲区。结果就是主站发一帧指令,从站不仅执行了,还把主站的指令原样回显出来。如果协议解析稍不严谨,从站可能把这帧“外来数据”当成新指令去处理,造成逻辑错乱。
解决办法有几种:
- 软件上在发送期间禁止接收中断,或者发完清空接收缓冲区
- 硬件上让方向控制引脚只控制DE发送使能,RE接收使能单独长时间打开,但在发送期间关闭RE
- 最简单的方法:用io控制时把DE和RE焊在一起,发送期间这个引脚为高,DE有效,RE无效
实践中最常用的就是第三种,一个引脚同时控制两个使能端。高等于发送,以切断回环接收;低等于接收,允许对方数据进入。
4.3 主从轮询与帧间隔
RS485的典型应用是主站轮询从站。主站发指令,指定从站地址,从站响应数据。这种模式下,帧与帧之间的间隔非常重要。
想象一下主站刚发完一帧指令,就立刻把方向切回接收。此时总线上还有残存信号在衰减,从站响应也需要时间。如果主站几乎没有等待,立刻又发起下一帧,两个帧就会在总线上碰撞。轻则数据全部作废,重则主站一直在通信错误循环里死循环。
我在项目中通常会设计一个明确的帧间隔:主站发送完成后,至少留出2到5个字节时间的空闲,再准备接收。这个空闲时间不占CPU,只是用一个定时器计时。比如9600波特率下,一个字节时间约1.04毫秒,帧间隔取5毫秒比较合理。
另一个细节:协议的帧超时判断。从站收到主站指令后,如果CRC或校验不对,就不要回复。主站则要设置一个超时时间,比如50毫秒没收到响应,就重试。重试次数一般设2到3次,再多会影响整体轮询周期。
5. 完整实战示例:Modbus风格主从轮询
有了以上的原理,我现在给出一套可以“抄作业”的主从轮询代码框架。为了让例子不过于抽象,我按一个传感器采集场景来写。固定一个主站,多个RS485从站,地址从1开始。主站周期发送读取指令,从站返回传感器数据,过程全部用查询式发送加中断式接收。
5.1 主站发送指令并等待响应
主站发送部分核心代码如下:
// 发送多字节数据帧到RS485总线 void RS485_SendFrame(unsigned char *buf, unsigned char len) { unsigned char i; DIR_PIN = 1; // 发送方向开启 _nop_(); // 给收发器一点建立时间 for (i = 0; i < len; i++) { SBUF = buf[i]; while (!TI); TI = 0; } // 关键:等待最后一个字节完全从引脚发出 // 以9600bps为例,1字节约1.04ms,延时3ms足够 DelayMs(3); DIR_PIN = 0; // 切回接收方向 }从这段代码可以看到,我发送过程中每个字节都等TI清零再写下一个。发送完成后特意延时3毫秒再切方向。这个延时不是可有可无,它是整个发送流程的保护时间,宁可多浪费3毫秒,也不丢一个字节。
5.2 从站响应帧的实现
从站程序一般放在中断接收里。收到完整一帧后,校验地址和CRC,如果匹配就准备响应。
// 假设协议帧格式:地址 + 功能码 + 数据 + CRC16 void Process_Frame(void) { unsigned char addr = rx_buf[0]; unsigned char func = rx_buf[1]; unsigned int crc_read = 0, crc_calc = 0; if (addr != MY_ADDR) return; // 地址不匹配,直接忽略 crc_calc = CRC16_Calc(rx_buf, len - 2); crc_read = (rx_buf[len - 1] << 8) | rx_buf[len - 2]; if (crc_calc != crc_read) return; // CRC错误,忽略 // 处理功能码,准备响应数据 tx_buf[0] = MY_ADDR; tx_buf[1] = func; tx_buf[2] = sensor_data >> 8; tx_buf[3] = sensor_data & 0xFF; crc = CRC16_Calc(tx_buf, 4); tx_buf[4] = crc & 0xFF; tx_buf[5] = crc >> 8; RS485_SendFrame(tx_buf, 6); }这里有个很容易犯的错误:从站刚收到主站的帧就立刻回发,没有任何延时,好像没什么不对。但实际上主站发送结束后还需要切方向、释放总线,从站这边可能还在处理上一帧的余波。稳妥起见,从站应该在接收完毕后再延时一个很短的时间,比如1到2毫秒,再开始回发。
5.3 主站轮询调度框架
主站侧轮询不能简单写个for循环发指令,还要考虑每个从站的超时和重试。
#define MAX_RETRY 3 void Poll_All_Devices(void) { unsigned char i, retry; for (i = 1; i <= MAX_ADDR; i++) { for (retry = 0; retry < MAX_RETRY; retry++) { Build_Read_Frame(i, tx_buf); RS485_SendFrame(tx_buf, frame_len); uart_rx_cnt = 0; timeout = 0; start_timer = 1; // 等待超时或收到完整帧 while (!frame_complete) { if (timeout > 50) // 50ms超时 { frame_complete = 0; break; } } if (frame_complete && Check_Frame(i)) break; // 成功,跳出重试 } } }这段代码要注意一个核心原则:主站在发完指令后,接收缓冲区和帧完成标志必须清零重来。如果主站上一次轮询超时,而这次刚好从站的迟到响应到达,不清缓冲就会造成“上一个站的响应混进下一个站的结果里”。我在实际项目中吃过这个亏,后来干脆在发送前把接收相关变量全部复位。
6. 调试实录与问题排查速查表
这部分我直接整理一份排查清单,都是亲测有效的经验。你遇到问题挨个对照,基本都能解决。
6.1 波特率不匹配与晶振误差
症状:收到的数据全部是乱码,或者偶尔正常偶尔乱。排查第一步:用示波器抓TXD引脚波形,测量一个字节的每一位宽度,看实际波特率是多少。
我遇到过得最多的情况是用12MHz晶振跑9600,初始化时TH1直接抄了11.0592MHz的0xFD,实际波特率误差接近2.2%,单字节通信还好,连续几十个字节就会错位。解决办法就是换11.0592MHz晶振,或者根据12MHz重新计算TH1。
还有一个隐患是很多开发板为了省成本用了劣质晶振,频率标称11.0592MHz,实际偏差几百赫兹,短距离通信可能没事,距离长了再叠加RS485波形畸变,问题就放大了。这种只能用示波器实测,或者更换有品牌保障的晶振。
另外注意STC芯片的IRC内部时钟。某些型号不依赖外部晶振也能工作,但内部IRC时钟精度有限,通常通过串口ISP下载时校准到11.0592MHz附近。如果项目对波特率有严格要求,建议还是外接晶振。
6.2 空闲时收到随机乱码
症状:总线空闲时,单片机不断收到0xFF或随机数。原因几乎都是缺少偏置电阻。我从口头上理解,在没有发任何数据时,A和B之间没有电压差。接收端的差分比较器处于“跷跷板”边缘,任何微小的噪声都会让它随机判断成0或1。解决办法:在A线加4.7K上拉,B线加4.7K下拉,让空闲时有明确的大于200mV的差分电压。如果从站数量很多,只在主站端加偏置就够了,但要注意多组偏置可能让总线负载偏重。
6.3 延迟切换导致总线冲突
症状:发送结束后偶尔出现总线被占住,其他从站无法响应。通常是因为方向切换延时太长或太短。太短会丢尾字节,太长则总线空闲期过长,在高速轮询时浪费大量时间,更麻烦的是可能在主站切回接收前另一站就开始发送,两个站同时占线。
实践中最稳妥的方法是:发送结束后立即把方向引脚拉低,但此时数据已经完整发出。如果你担心移位寄存器没完全移完,就在方向引脚拉低前加一个最小延时即可。具体数值几百微秒到3毫秒都行,取决于波特率。
6.4 长距离通信时数据偶发错误
症状:通信距离超过50米,数据偶发错位,但不频繁。排查方向看三个点:
- 终端电阻有没有加?如果没加,反射会造成波形过冲,导致采样点错误
- A/B线有没有双绞?RS485最好用双绞线,绞合可以让两根线的噪声尽量一致,方便共模抵消
- 布线有没有和大功率动力线并行走?信号线应尽量远离电机、变频器电缆,交叉处保持垂直
我还遇到过一种奇葩情况:总线供电方式和终端电阻位置配合不当,接收端看到的波形严重畸变。后来把终端电阻从主站连接到最远端从站,波形立刻变得干净。
6.5 收发器烧毁与热插拔
RS485收发器烧毁多发生在热插拔场景。带电插拔时,A/B线上瞬间的电位冲击可能超过收发器的共模范围,直接击穿。
预防措施有三层:
- 带隔离的收发模块,隔离掉地环路冲击
- 在A/B线上并TVS管,限制浪涌电压
- 设计外壳时加防呆结构,避免用户在设备带电时插拔线缆
我有个项目因为现场环境粉尘大、插头接触不良,排查时发现收发器已经烧了两次。后来把主板上的收发器改成了带热插拔保护的模块,问题再没出现。
7. 一些补充的深入心得
RS485调试越深入越发现,它考验的不仅是软件功底,还有硬件思维。我最后补几个表格来总结关键参数和排查方法,方便你直接抄走用。
常用波特率与定时器1重载值速查(11.0592MHz)
| 波特率 | TH1(SMOD=0) | TH1(SMOD=1) |
|---|---|---|
| 2400 | 0xF4 | 0xE8 |
| 4800 | 0xFA | 0xF4 |
| 9600 | 0xFD | 0xFA |
| 19200 | 0xFE(误差略) | 0xFD |
| 57600 | 不建议 | 0xFF(误差较明显) |
注意,9600以上波特率,随着重载值接近256,定时器计数的颗粒度影响更明显,建议在长线通信中不要一味追求高速率。很多工业设备默认就是9600,稳定压倒一切。
RS485常见问题速查表
| 现象 | 可能原因 | 排查/解决 |
|---|---|---|
| 乱码 | 波特率误差、晶振偏差 | 用示波器测位宽,换11.0592MHz |
| 收尾丢字节 | 方向切换过早 | 发送完毕延时3毫秒再切 |
| 空闲收0xFF | 缺偏置电阻 | A上拉,B下拉 |
| 偶发错帧 | 终端电阻缺失、线缆不双绞 | 两端加120欧,换双绞线 |
| 收发器烧毁 | 热插拔、共模电压异常 | 加TVS、用带隔离模块 |
| 总线冲突 | 多个从站同时响应 | 检查主从帧间隔与超时逻辑 |
再补一句关于协议层的话。裸的RS485只定义物理层电气特性,不定义数据格式。你可以自己定义协议,也可以直接套Modbus RTU。Modbus RTU的帧格式是地址+功能码+数据+CRC16,轮询机制和我前面写的示例几乎一致。如果项目不复杂,建议直接上Modbus,很多现成的上位机软件、触摸屏都原生支持,省去自己写上位机协议解析的大量工作。
我自己常用的CRC16计算代码是查表法,256字节表能换来的速度提升非常明显,而且对STC51这种小内存芯片很友好。如果要组装一个完整的固件,别忘了在接收溢出时清空缓冲区,还有接收缓冲区大小要能容纳最大帧长。
我在实际使用中发现,RS485调试最磨人的不是原理看不懂,而是问题偶发、因果链长。比如某个从站偶发乱码,可能是主站方向切换延时不够,也可能是这个从站偏置电阻被旁边的人拔了,还可能是某个接头的屏蔽层碰到了A线。这种时候千万不要瞎猜,用手头能用的示波器或逻辑分析仪抓波形,一抓一个准。
最后再分享一个小技巧:调试RS485时,可以在单片机上保留一个测试用的自环模式。就是让程序发完一帧后立刻把自己收到的数据存到一个固定缓冲区,方便比较。很多收发器芯片上电后如果方向引脚悬空,还处于一种不稳定的状态,这时自环模式能帮助你快速判断是方向信号没控制好,还是线路本身有问题。这个测试习惯保持了很长时间,帮我避掉了不少潜在隐患。