多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

曼彻斯特编码原理与STM32编解码实战:相位约定、波形调试与排错

曼彻斯特编码原理与STM32编解码实战:相位约定、波形调试与排错 逻辑分析仪上抓回来的波形一半以上的人第一次看都会愣一下每个比特的正中间都有一道跳变跳变的方向还一会儿朝上一会儿朝下可数据手册里明明写着1 Mbps 比特率你按 1 Mbps 去采样却发现全是乱码。这时候你遇到的很可能就是曼彻斯特编码。它不是什么玄学本质就一句话把时钟信号硬塞进数据流里用每个比特中点的跳变来给接收端对表。代价是波特率翻倍、带宽需求翻倍换来的是极强的抗时钟漂移能力和天然的直流平衡。这篇文章面向三类人正在用 STM32 或类似 MCU 做自定义编码通信的嵌入式工程师、在调试 125 kHz 读卡器或红外遥控这类自带曼彻斯特码流的设备的人、以及准备面试或者复习通信基础的在校同学。我会从为什么需要它讲到两套相位约定怎么区分再到 STM32 上怎么发、怎么收、怎么排错尽量把数据手册里不写的那些细节说透。1. 为什么每个比特中间都必须跳一下1.1 一根线上的连0会把接收端逼疯先看不用曼彻斯特的情况。假设你用最朴素的 NRZ不归零编码高电平代表 1、低电平代表 0一根线一路往外发。数据流里只要出现连续 8 个 0线就老老实实趴在低电平上不动。接收端这时候面临一个尴尬的问题它不知道这段低电平到底代表 8 个 0还是 7 个 0 加一个还没到来的 0。接收端当然有自己的本地时钟理论上按固定的比特周期去采样就行了。但问题在于两个时钟永远不可能完全一致。常见的无源晶振精度是 ±20 ppm再算上温度漂移和负载电容带来的偏差两端时钟差个 ±100 ppm 很常见。100 ppm 听起来微不足道但如果你连续收到 10000 个比特都没有一次边沿可以校准累积误差就达到了一个完整比特周期——直接错位。这就是 NRZ 在长连 0 或长连 1 场景下的致命伤。传统的补救办法是加扰码比如 4B/5B、8B/10B把数据打散保证一段窗口内跳变足够多。这是另一条技术路线以太网从百兆开始就走这条路了。而曼彻斯特走的是另一条路它不改变数据的统计特性而是干脆规定每个比特内部必须有一次跳变从物理层直接保证边沿密度。1.2 边沿就是时钟时钟就藏在数据里曼彻斯特编码的核心规则非常简洁把一个比特周期 T 劈成前后两个半位half-bit每个半位电平固定。前半个半位是高、后半个半位是低这整个比特算作一种值反过来前低后高算作另一种值。这么一规定产生了两个立竿见影的效果。第一每个比特的正中间必定有一次电平翻转。接收端只要盯着这次翻转就能把本地时钟的相位重新对齐一次误差根本来不及累积——它是逐比特重同步的不是整帧重同步的。第二比特值不再由当前电平高低决定而是由这次翻转的方向决定抗干扰的维度从幅度维度换到了时间维度。这里要特别强调一个概念上的区分曼彻斯特编码里的半位和比特是两套时间尺度。很多刚接触的人会误以为波特率和比特率是一回事结果配置定时器的时候直接把 ARR 按比特周期算了发出来的波形整整慢了一倍。1.3 顺带白拿的直流平衡除了自同步曼彻斯特还有一个经常被忽略的好处每个比特内部高低电平各占一半时间所以不管数据内容是什么这段码流的平均直流分量严格等于零。这个性质在很多物理链路里非常值钱。变压器耦合的链路、电容耦合的链路、光耦隔离的链路它们都传不了直流。如果码流有直流分量耦合电容会充电、变压器会饱和接收端直接工作点漂移。曼彻斯特天然没有这个问题所以你在隔离通信、RFID 的负载调制、长线缆传输这些场景里会反复看到它。同时把能量从低频推到高频也有副作用线缆和连接器的插入损耗在高频段更大电磁辐射也更难压。这就是曼彻斯特的代价也是为什么高速链路最终都换成了块编码而不是继续用它。对比项NRZ曼彻斯特差分曼彻斯特每比特边沿不保证至少 1 次中点至少 1 次中点 起始可选波特率/比特率1:12:12:1直流分量随数据变化恒为零恒为零抗极性反转不抗不抗抗典型场景UART、SPI10BASE-T、DALI、RFID令牌环、部分工业总线2. 0和1到底谁对应哪个跳变方向2.1 两套约定互为反相抄错了全盘皆错这是我在实际项目里见过最多的一类事故代码逻辑全对、时序全对、波形也漂亮就是解出来的数据全是反的。原因只有一个——相位约定选反了。历史上流传下来两套主流约定它们互为镜像约定比特值前半位后半位中点跳变方向G.E. Thomas1高低下降沿G.E. Thomas0低高上升沿IEEE 802.30高低下降沿IEEE 802.31低高上升沿看清楚了Thomas 约定下下降沿是 1IEEE 802.3 约定下下降沿是 0两者正好反着来。IEEE 802.3 这套被 10BASE-T 沿用所以以太网相关的资料大多是这一套而很多 RFID、红外遥控、工业总线的芯片手册用的是 Thomas 那一套。我踩过的坑是某次调试一个 125 kHz 读卡器参考了一份网上的开源解码代码抄过来发现数据全反。查了半天以为是天线极性接反了最后翻原厂 datasheet 才发现人家用的是 Thomas 约定而开源代码是 802.3 约定。提示永远不要靠网上都这么说来确定相位约定。判断顺序是先看芯片或协议的原厂手册手册没写就抓一段已知数据的波形对着数实在不行就在软件里加一个取反开关现场试。2.2 一个字节的波形长什么样以 0xB5二进制 1011 0101为例按 Thomas 约定展开成半位序列比特序号比特值前半位后半位与上一比特边界是否有跳变11高低—20低高无低接低31高低无高接高41高低有低接高50低高无低接低61高低无高接高70低高无高接高81高低无高接高完整的高低位序列是高 低 低 高 高 低 高 低 低 高 高 低 低 高 高 低。对着这张表看你会发现一个关键规律比特边界上有没有跳变是完全随机的上面这个字节里 7 个边界只有 1 个出现了跳变。所以解码的时候绝对不能靠数边界跳变来判断比特位置唯一可靠的锚点是每个比特中点的那次跳变。这也是为什么曼彻斯特的解码器必然带一个半位状态机。2.3 差分曼彻斯特多了一层保险差分曼彻斯特Differential Manchester有些文献叫条件双相码在结构上做了一点小改动比特中点的跳变永远存在但它的方向不再携带信息只负责提供时钟比特的值改由比特起始位置有没有跳变来决定。常见约定是起始处有跳变代表 0无跳变代表 1同时中点必定有跳变。这么改的好处很直接链路中如果存在反相器、光耦、或者差分接收端的极性接反普通曼彻斯特会全盘反转而差分曼彻斯特完全不受影响因为信息在有没有跳变里不在往哪个方向跳里。代价是发送端的编码逻辑复杂一点而且在某些数据模式下跳变次数更多、占用的带宽略高。差分曼彻斯特最著名的应用是 IBM 令牌环网另外在一些电力线载波和工业总线的物理层里也能见到。2.4 一个被反相器坑掉的下午讲个真实的小故事。有个项目需要在两块板子之间传曼彻斯特码流中间加了一级 NPN 三极管做电平转换。调试的时候接收端能正确同步半位宽度也测得很准就是解出来的字节和发送的字节每一位都反了。第一反应是编码约定搞错了翻了半天代码没发现问题。后来直接抓接收端引脚上的波形跟发送端比较发现整条波形在时间轴上完全一致只是高低电平整体翻转了——三极管的集电极输出本来就是发射极输入的反相这是最基础的共射极电路特性我自己配的电路自己忘了。解决办法有两个一是改用同相驱动比如加一级或换成 MOSFET 加推挽二是在软件里把解析结果取反。当时工期紧我选了后者在解码状态机的输出上加了一个编译期开关。但从工程角度说软件取反是个补丁不是设计因为它把反相这个事实藏进了代码里后面接手的人根本看不出来。后来我还是把硬件改成了同相驱动同时在代码注释里写明了本链路无极性反转。3. 速率、带宽和效率这笔账要算清楚3.1 波特率永远是比特率的两倍曼彻斯特最容易被忽略的一个量就是半位时间。给定比特率 R比特周期 T 1/R半位周期就是 T/2。生成的波形里最小的重复单元是半位所以调制速率波特率等于 2R。定时的精度要求其实落在半位上。以 1 Mbps 为例T 1 µs半位 500 ns如果接收端允许 ±25% 的判决容差那么每个半位的测量误差必须控制在 125 ns 以内。这个数字在 72 MHz 的 MCU 上相当于 9 个时钟周期用定时器捕获来做是相当宽松的但如果你的代码在中断里做了一堆浮点运算那就不好说了。应用场景比特率半位时间载波 / 备注10BASE-T 以太网10 Mbps50 ns双绞线差分Cat3 即可DALI 照明总线1200 bps416.7 µs16 V 总线容错极强RC5 红外遥控≈562 bps889 µs36 kHz 载波调制EM4100 系列 RFID125000/64 ≈ 1953 bps256 µsRF/64半位 32 个载波周期看这张表能得出一个很实用的结论低速场景下曼彻斯特几乎是白送的。RFID 的半位宽达 256 µsDALI 更是 416 µs这种量级用 GPIO 轮询都能解出来。而 10 Mbps 的以太网半位只有 50 ns就必须靠专用 PHY 芯片了。你的 MCU 方案能不能扛住先算一下半位时间对应多少个 CPU 周期心里就有数了。3.2 频谱能量从 R/2 铺到 R曼彻斯特的频谱形状很有特点。考虑两种极端的数据模式连续相同的比特比如全 1只有中点跳变波形是周期为 T 的方波基频 1/T R这是最高基频。交替比特1010…中点跳变加边界跳变叠加波形是周期为 2T 的方波基频 1/(2T) R/2这是最低基频。所以曼彻斯特的主要能量集中在 R/2 到 R 这个区间而直流附近几乎没有能量。这跟 NRZ 完全相反——NRZ 的能量集中在直流到 R/2。10BASE-T 选曼彻斯特的一个直接原因就在这里它 10 Mbps 的比特率对应 R/2 5 MHz 到 R 10 MHz 的频段正好落在 Cat3 双绞线 10 MHz 带宽的能力范围之内而且低频被压掉了变压器耦合好做。代价也清楚同样的比特率曼彻斯特占用的带宽是 NRZ 的两倍左右。这就是为什么高速以太网果断放弃了它——100BASE-TX 用的是 4B/5B效率 80%千兆用的 8B/10B效率也是 80%曼彻斯特的效率只有 50%。用一半的带宽换来自同步和直流平衡这笔买卖在低速场景很划算在高速场景就划不来了。3.3 抗时钟漂移的能力到底有多强这里可以给一个量化的感觉。NRZ 链路里接收端在两次边沿之间的时间越长累积相位误差越大。假设两端时钟差 100 ppm连续 500 个比特没有边沿累积偏差就是 500 × 100 ppm 0.05 个比特周期看起来还能忍但如果连续 5000 个比特没有边沿偏差就到了 0.5 个比特周期处在采样点边缘误码率飙升。所以 NRZ 必须配扰码。曼彻斯特的情况完全不同每个比特周期内至少有一次边沿所以相位误差的最大累积时间就是一个比特周期。100 ppm 的时钟差在一个比特周期内只造成 0.0001 个周期的偏移完全可以忽略。这就是它逐比特重同步的真正含义——不是理论上的漂亮话是可以直接算出来的余量。反过来说这也解释了为什么曼彻斯特的容差主要不是被时钟精度限制的而是被边沿抖动、传输线反射、比较器迟滞和中断延迟这些因素限制的。在低速场景里这些因素往往比时钟本身大一个数量级。4. 在 STM32 上把曼彻斯特码流发出去4.1 先说清楚 STM32 能做什么、不能做什么有一个常见误解需要先澄清STM32 的大部分型号里USART 和 SPI 都不带硬件曼彻斯特编解码器。有些厂商的串口控制器内置了 Manchester 模式比如部分工业级 MCU 和某些带专用通信外设的芯片但 STM32 主流系列没有这个功能。所以你在 STM32 上做曼彻斯特基本是软件编码 外设搬运的路子。好消息是 STM32 的定时器加 DMA 组合非常适合干这件事可以做到零 CPU 占用地连续发送也能做到高采样率地把引脚状态搬进内存。下面按实现难度从低到高给三条路线。4.2 路线一用 SPI 的两位换一位小技巧这是最省事、最优雅的一条路思路非常巧妙。既然 Thomas 约定下 1 是高然后低、0 是低然后高那不就是把每个比特展开成两个比特吗1 展开成 100 展开成 01。把整个字节展开成 16 个位然后用 SPI 以两倍的比特率发出去MOSI 上出现的就正好是曼彻斯特码流。/* 把 1 个字节展开成 16 个半位电平 * thomas 1 采用 G.E. Thomas 约定1 - 高,低 * thomas 0 采用 IEEE 802.3 约定0 - 高,低 * 返回值按发送顺序排列最高位第 1 个半位在 bit15 */ uint16_t man_encode(uint8_t b, int thomas) { uint16_t out 0; for (int i 7; i 0; i--) { int bit (b i) 1; int first, second; if (thomas) { first bit; second !bit; } /* 1 - 1,0 */ else { first !bit; second bit; } /* 0 - 1,0 */ out (uint16_t)((out 2) | (first 1) | second); } return out; } /* SPI1 时钟频率配置为 2 x 比特率MSB First8 位数据帧 */ void man_send_spi(const uint8_t *buf, uint32_t len, int thomas) { for (uint32_t i 0; i len; i) { uint16_t code man_encode(buf[i], thomas); spi_write_u8((uint8_t)(code 8)); /* 先发高 8 个半位 */ spi_write_u8((uint8_t)(code 0xFF)); /* 再发低 8 个半位 */ } }这条路线的配置要点有三条都是实际调过才会注意到的第一SPI 时钟必须严格等于 2 倍比特率。为什么因为 SCK 的每个周期对应两个半位SCK 的频率就是半位率的……等一下这个说法容易绕。正确的理解是SPI 每发送一个位对应一个半位所以 SCK 频率 半位率 2 × 比特率。如果目标是 100 kbps 的曼彻斯特比特率SPI 时钟就是 200 kHz。第二连续发送不能有间隙。用阻塞式的spi_write_u8循环发送时两个字节之间会出现几个时钟周期的空隙这段空隙会插入一段固定电平破坏半位序列的连续性。如果接收端做的是按半位对表的解码就会错位。要解决这个问题把 SPI 配成 DMA 模式一次性发一整帧。第三SPI 空闲时 MOSI 会保持最后一个位的电平。如果接收端依赖静默电平来判断帧起始这个特性会造成干扰。解决办法是在帧尾手动把 MOSI 拉回静默电平或者在协议里约定帧头和帧尾的固定模式让接收端靠模式匹配同步而不是靠静默电平。4.3 路线二定时器触发 DMA 直接翻 GPIO如果你不想被 SPI 的各种边角特性烦到最可控的方案是把整帧数据预先展开成一串半位电平放在数组里然后让定时器的更新事件触发 DMA把每个半位对应的 BSRR 值写进 GPIO 寄存器。这里的巧思在于STM32 的 BSRR 寄存器天然支持写 1 置位、写 0 复位——低 16 位写 1 是置位高 16 位写 1 是复位。所以你可以预先算好两种 BSRR 值存进一个小表DMA 搬运的时候只要搬 32 位整数就行完全不用做判断。#define PIN_POS 6u /* PA6 */ #define BSRR_SET (1u PIN_POS) /* 低 16 位置位 */ #define BSRR_CLR (1u (PIN_POS 16u)) /* 高 16 位复位 */ static const uint32_t bsr_lut[2] { BSRR_CLR, BSRR_SET }; /* 把一帧数据展开成 BSRR 值序列 */ /* half[] 里每个元素是一个半位的电平0 或 1 */ static uint32_t dma_buf[HALF_MAX]; void man_build_frame(const uint8_t *buf, uint32_t len, int thomas) { uint32_t n 0; for (uint32_t i 0; i len; i) { uint16_t code man_encode(buf[i], thomas); /* 前导码可以在这里插比如先塞一段 10101010 让接收端练手 */ for (int k 15; k 0; k--) { dma_buf[n] bsr_lut[(code k) 1]; } } /* 帧尾把线拉回静默电平多塞几个半位 */ dma_buf[n] bsr_lut[1]; hal_dma_start(DMA1_Stream5, (uint32_t)GPIOA-BSRR, (uint32_t)dma_buf, n); hal_tim_start(TIM2); /* ARR 已经设成半位周期 - 1 */ }定时器的关键参数就一个ARR 1要等于半位周期的定时器时钟数。举个具体例子定时器挂在 72 MHz 的 APB1 定时器时钟上目标是 1 Mbps 比特率 → 半位 500 ns → 需要 36 个计数 →ARR 35PSC 0。算的时候别忘了 STM32 的定时器实际频率常常是 APB 时钟的 2 倍这个坑我踩过不止一次配置完先量一下波形周期对不对。DMA 配置上有两点要注意。方向是内存→外设外设地址固定不递增内存地址递增数据宽度 32 位。模式选 Normal发完一帧就停配传输完成中断来标记帧结束并关掉定时器如果选 Circular 就会无限循环发送同一帧有时候这是你要的比如持续发送但多数情况下不是。4.4 路线三PWM 改 CCR不推荐还有一种思路是用定时器的 PWM 模式把 ARR 设成半位周期通过动态修改 CCR 或者切换输出极性来制造中点跳变。这条路我不推荐原因是它把电平状态和定时器状态耦合在了一起每发一个半位都要在中断里改一次寄存器CPU 开销大而且容易出现时序抖动。更麻烦的是如果 CCR 恰好等于 ARR 或者等于 0PWM 输出会出现毛刺某些型号上甚至产生多余的窄脉冲这在曼彻斯特里是致命的——一个多余的脉冲可能被解码器理解成一个合法的半位跳变。除非你有非常特殊的同步需求比如要求输出波形严格锁相到某个外部参考否则老老实实用路线二。4.5 发送端必须验证的三件事不管走哪条路线上电之前请对着示波器确认这三件事第一半位周期精度。量一段连续的相同数据比如发 0x00 或 0xFF这时候波形是规则的方波周期就是 T/2量 10 个周期取平均误差控制在 1% 以内。如果偏了回头检查定时器时钟树。第二首比特的相位对齐。从静默态切换到最后一次跳变之间的时间应该正好是半个比特周期不多不少。很多实现的问题就出在第一帧的第一个比特上后面全对。原因是把 GPIO 的初始状态和第一个半位搞混了导致第一个比特被吃掉半个。第三帧间静默电平。抓一帧完整的波形看帧尾到下一帧头之间是否有一段稳定的静默电平长度是否符合协议约定。接收端往往靠这段静默来做帧同步和超时复位。5. 解码才是真正的难点发送端是确定的接收端是概率的。解码要做的事情是在只有一串边沿时间戳的情况下反推出比特边界再反推出比特值还要在噪声和抖动里判断什么时候该报错。5.1 过采样加 DMA最省心也最好调如果 MCU 的定时器资源允许我强烈建议用过采样 DMA的方案。做法是用定时器以 N 倍半位率触发 DMA把 GPIO 的 IDR 寄存器不断搬进内存的一块环形缓冲区然后在缓冲区半满或者全满的中断里跑软件解码。采样率一般取半位率的 8 到 16 倍就够。/* 定时器以 16 x 半位率触发DMA 循环搬运 GPIOA-IDR 到双缓冲 */ static uint32_t sample_buf[2][512]; /* 两个 512 样本的块 */ void dma_half_complete_cb(void) /* 第一块满了 */ { man_decode_block(sample_buf[0], 512); } void dma_complete_cb(void) /* 第二块满了 */ { man_decode_block(sample_buf[1], 512); }这个方案的最大好处是可回放。解码结果不对的时候你可以把 sample_buf 的内容导出在 PC 上重新跑一遍解码算法反复调参数不用反复上电抓波形。相比之下中断驱动的解码一旦出错你只能看到结果错了这个事实看不到过程。这个可回放的特性在实际调试中价值极高我后来的几乎所有编码通信项目都用了这套结构。另一个好处是解耦。采样完全由硬件完成DMA 搬运不需要 CPU 参与所以即使系统里其他中断很忙采样数据的时间精度也不会受影响。用外部中断加软件打时间戳的做法就没这个优势——中断延迟直接变成时间戳误差。有个细节要算清楚采样率不能太高否则 DMA 带宽和内存都会被吃满。以 125 kHz RFID 为例半位率约 3906 Hz16 倍过采样就是 62.5 kHz512 个样本只覆盖 8 ms一帧 64 比特的数据长度是 32.8 ms所以缓冲区得开大一点或者用更多的块。反过来如果是 1 Mbps 的链路半位率 2 MHz16 倍采样就是 32 MHz这个采样率对 DMA 来说压力不小可能得降到 8 倍甚至改用输入捕获方案。5.2 输入捕获方案用硬件打时间戳如果采样率要求高到过采样不现实那就换定时器输入捕获。STM32 的定时器有一个很好用的特性可以把同一个引脚通过 TI1 的间接输入 TI1FP1 和 TI1FP2同时映射到两个捕获通道上一个通道配上升沿触发、一个配下降沿触发各自写自己的 CCR 寄存器。这样一次配置就能同时获得上升沿和下降沿的硬件时间戳。更进一步还可以把定时器配成从模式复位Slave Mode Reset上升沿到来时把计数器清零。这样一个通道捕获的就是两次上升沿之间的间隔另一种做法是通道直接读出的 CCR 就是相邻边沿之间的脉宽。手册里管这叫 PWM 输入模式原理完全一样。用硬件捕获的前提是定时器时钟要够快。同时测量半位 T/2 和整位 T分辨率至少要优于 T/8也就是定时器时钟至少 8 倍于半位率。以 1 Mbps 比特率算半位率 2 MHz定时器至少 16 MHz用 72 MHz 的定时器绰绰有余。5.3 位同步怎么在边沿上对表拿到采样数据或者边沿时间戳之后第一件事是恢复半位时钟。做法很简单但要理解背后的逻辑才不容易写错。核心事实每个比特的中点必定有跳变比特边界上可能有也可能没有。所以两个相邻边沿之间的间隔只有两种可能T/2一个是中点边沿、一个是边界边沿或者 T两个都是中点边沿中间没有边界跳变。解码算法的骨架是这样在采样序列里扫描找出所有电平翻转的位置得到边沿位置列表。计算相邻边沿的间隔按采样率换算成半位个数。用一个自适应阈值把间隔分类接近 1 个半位还是一组违法内容不能编码。5.4 成对判决和非法码型恢复出半位电平序列之后判决就非常直接了/* 成对判决a 是前半位电平b 是后半位电平 * 返回 0 或 1 表示比特值返回 -1 表示非法码型 */ static int man_pair_to_bit(int a, int b, int thomas) { if (a 1 b 0) return thomas ? 1 : 0; /* 高 - 低 */ if (a 0 b 1) return thomas ? 0 : 1; /* 低 - 高 */ return -1; /* 1,1 或 0,0出错了 */ }那两个-1的分支是解码器的健康指示器用处非常大。正常情况下永远不会出现半位电平相同的组合一旦出现说明至少发生了下面几种情况之一漏掉了一个边沿可能是信号幅度不够接收端没识别出跳变也可能是采样率太低某个窄脉冲整个被跳过了。多出了一个边沿干扰毛刺造成的。开关电源的尖峰、电机换向的火花、附近大电流走线的串扰都能制造出窄毛刺。半位相位跑偏了一整位同步状态机进入了错误的状态把比特边界当成了中点。我通常的做法是连续出现 3 个以上非法码型就强制重新搜索帧头而不是继续硬解。因为一旦失步后面的解码只会越来越离谱硬解下去只会产生一堆看似合理的垃圾数据。这个主动放弃并重新同步的设计在长期运行的设备里比尽量多解出几个比特重要得多。5.5 容差阈值怎么定判决阈值不能拍脑袋得按最坏情况推。假设半位的理论采样数是 88 倍过采样那么判决门限可以这么设候选半位数判决中心允许范围±25%186 ~ 1021612 ~ 2032418 ~ 30±25% 这个数不是随便定的。它需要覆盖两端时钟差≤±5%、边沿抖动几纳秒到几百纳秒、比较器迟滞带来的额外延迟、以及过采样的量化误差±0.5 个采样周期。加起来通常远小于 25%所以留有余量。如果实测发现必须把容差放宽到 ±40% 才能解出来那说明链路上有更大的问题应该去修硬件或者降速而不是把阈值放得更松——阈值越松错误码流越容易被误判成合法码流。注意曼彻斯特本身不带帧边界信息。你必须在协议层加前导码比如先发若干个 1010 交替的半位模式让接收端先把相位锁定然后再跑一个固定的同步字类似以太网的 SFD。指望从任意位置开始都能正确解码是不现实的那不是编码层该解决的问题。6. 硬件链路里那些数据手册不会写的事6.1 开漏输出加弱上拉边沿会被 RC 吃掉这是低速曼彻斯特最常见的一类事故而且它的表现形式很迷惑协议全对、代码全对、空闲时波形也正常就是偶尔冒出几个非法码型。问题出在上升沿上。开漏输出配合上拉电阻上升沿的恢复过程是一条 RC 充电曲线。假设上拉电阻 10 kΩ线缆加引脚的寄生电容 100 pF时间常数 τ 1 µs。如果半位宽度只有 500 ns那么从这个半位开始到它结束电平根本爬不到高电平的数字判决门限接收端就可能在这一段的采样点上读到低电平。算这笔账的方法很简单要求 3τ 远小于半位宽度。3τ 是 RC 充电到 95% 所需的时间。10 kΩ 100 pF 的 3τ 3 µs要求半位宽度至少 12 µs对应比特率不能超过 41 kbps。想跑更快要么把上拉电阻降到 1 kΩτ 变成 100 ns3τ 300 ns能支持到 1.6 Mbps 左右要么换成推挽输出。这里有个取舍要说明降低上拉电阻会增加静态功耗。如果你的设备是电池供电的10 kΩ 和 1 kΩ 之间的差别可能就是几个月的续航。所以实际做法往往是分时切换——通信时用低阻上拉空闲时切回高阻。6.2 示波器上该看什么调试曼彻斯特示波器的用法和调普通数字信号不太一样。三个技巧第一用余晖模式或者叫无限余辉、数字荧光看波形重叠。把所有比特周期叠加在一起正常情况你会看到两条清晰的轮廓和中间一个眼如果眼图边缘模糊或者有分叉说明抖动超标。第二测高电平宽度和低电平宽度的分布直方图。在示波器上打开测量统计看高电平宽度的最小值、最大值、平均值。理想情况下宽度只有两个值T/2 和 T还有整数倍的静默期。如果出现一堆介于两者之间的宽度说明边沿有噪声或者驱动能力不足。第三在帧间静默期盯紧有没有毛刺。很多解码器的同步逻辑会被帧间的毛刺骗到以为新的一帧开始了。用示波器的单次触发加窄脉宽触发抓一下静默期的异常。如果手边有逻辑分析仪就更方便了主流的逻辑分析仪软件比如开源的 sigrok / PulseView都内置了曼彻斯特和差分曼彻斯特的解码器配置好半位时间就能直接看到解码结果。不过要注意这些工具的默认相位约定不一定是你的2019 年前后我用的那个版本就只有一种约定遇到反相的码流得自己写个转换。另外逻辑分析仪的解码器通常是离线解码对噪声的容错比实时解码器弱所以它能解出来不代表你的 MCU 能解出来反之亦然。6.3 症状和根因的对照表症状大概率根因验证方法数据整体取反链路存在反相级或相位约定选反抓波形对比手册试加取反开关只有第一个比特错起始相位对齐问题或帧头没算进去单独看帧头波形每隔几十帧错一帧中断延迟或 DMA 竞争导致的采样点漂移检查采样缓冲区里的数据是否连续偶发乱码位置随机干扰毛刺或电源噪声用示波器窄脉冲触发抓毛刺高位数据对、低位全错字节序或者半位顺序搞反用规律数据0xAA、0x55测试低速能通、提速就错RC 上升沿不够快或采样率不足算 3τ 和半位宽度的关系温度高了才出错晶振温漂或线缆参数变化高低温箱里跑长时间误码测试6.4 中断优先级里藏着的坑如果你的解码是靠外部中断打时间戳那中断优先级设置会直接影响解码质量。我遇过一个案例解码偶发失败错误率大概千分之一。查了很久最后发现是 ADC 的 DMA 中断优先级比 EXTI 高而 ADC 中断里有一段浮点运算偶尔会阻塞几十微秒。对于 1 Mbps 的链路几十微秒就是几十个比特周期时间戳直接崩了。解决办法有两个方向一是把通信相关的中断提到最高优先级二是干脆用硬件捕获代替软件打时间戳从根上消除中断延迟的影响。如果你对时间精度的要求到了微秒量级就不要指望软件打时间戳STM32 的输入捕获外设就是为这个场景设计的。7. 它到底用在哪些地方7.1 10BASE-T把曼彻斯特推到极限的经典案例10BASE-T 是曼彻斯特在工业界最大规模的落地。10 Mbps 比特率、100 ns 比特周期、50 ns 半位跑在普通的非屏蔽双绞线上最远 100 米。它能用 Cat3 这种低规格线缆跑起来曼彻斯特功不可没低频能量被压掉双绞线之间的差分传输只需要应对 5 到 10 MHz 这个相对窄的频段线缆的衰减和串扰都在可控范围内。同时10BASE-T 还在曼彻斯特之上叠加了一层链路脉冲Link Pulse机制用周期性的窄脉冲做链路检测。脉冲的宽度和位置都有严格定义物理层的自检做得相当完备。这套设计思路现在看依然很值得学习编码层解决时钟恢复物理层解决链路状态各管一摊。7.2 125 kHz RFID读卡器怎么把卡看清楚125 kHz 的感应卡EM4100 系列是最典型的用的是 RF/64 的曼彻斯特编码。所谓 RF/64意思是每个比特占用 64 个载波周期那么每个半位就是 32 个载波周期。125000 ÷ 64 ≈ 1953 bps 的比特率。这个设计有个很聪明的点读写器把 125 kHz 的载波发出
返回列表