串口不够用?CH432/CH438/CH9434串口扩展芯片选型与波特率计算实战
发布时间:2026/9/28 14:16:37来源:尧图网络
搞嵌入式这些年最难缠的问题之一就是“串口不够用”。一个项目里要挂蓝牙模块、GPS、定位模块、串口屏还要给RTOS留一个调试口板子上的UART瞬间就没了。尤其ESP32这种“看着外设很多真用起来捉襟见肘”的主控默认日志还要吃掉一个通道真正能自由支配的串口往往只剩一两个。这时候我一般不去换主控而是直接加串口扩展芯片。国产方案里CH432、CH438、CH9434这三款是绕不开的选择我习惯叫它们“串口扩展三剑客”。这篇博文会把三款芯片的定位、接口差异、波特率计算方法和实测经验一次讲透。无论你是做工业网关、多路传感器采集还是给ESP32扩展四路UART都可以直接拿这份内容当选型参考。懂的人省时间刚入门的人也能少踩几个坑。1. 为什么需要串口扩展芯片ESP32用户尤其明白1.1 串口资源的“硬瓶颈”很多MCU的数据手册上写着一堆UART但真到画板子、写驱动的时候你会发现“可用的”根本没几个。举几个常见场景主控和电脑之间要一个调试串口这是不能动的底线一占就是一路。外挂设备多4G模块、GPS模组、RS485总线节点、指纹模块、串口摄像头每个都是标准UART接口。板子上还要留SPI给Flash、留I2C给传感器引脚资源被层层瓜分。有些低功耗主控的UART还和下载引脚复用你想烧录就不能接设备。尤其是ESP32虽然芯片内部有3个UART但日常开发时串口0固定连接USB转串口芯片用于下载和日志串口1常被Flash占用串口2一旦接了蓝牙音频或其他外设又不方便复用。真正能拿来扩展设备串口的通道经常只剩下一个。这个“硬瓶颈”不是靠改软件能解决的必须从硬件层面拆掉。好消息是串口扩展芯片正好解决这个痛点主控只需要提供一个SPI或并行总线就能换来2路、4路甚至8路独立UART而且每路波特率可单独配置互不干扰。1.2 三条路线软件模拟、换主控、外置扩展芯片面对串口不够用大多数人的第一反应是软件模拟UART也就是用两个GPIO自己“bit-bang”时序。这个方案不是不能用但局限性非常明显。软件模拟UART在低速场景比如9600bps或19200bps配合定时器还能凑合。可一旦上了115200bpsGPIO翻转频率要求就很高而且系统里只要有点中断抖动或任务调度延迟波形就会变形轻则误码重则整个通信链路瘫痪。对稳定性要求高的产品我基本不会考虑这条路线。第二条路线是换一个串口资源更丰富的主控。比如从STM32F103换到STM32F429或者从ESP32换到ESP32-S3。这个方法暴力有效但代价很大原有代码要移植、PCB要重画、物料成本上升、供货周期也可能变长。仅仅为了多几个UART就动主控多少有点“杀鸡用牛刀”。第三条路线就是外置串口扩展芯片。主控端只占一个SPI接口剩余繁琐的UART时序、FIFO、中断全交给扩展芯片。CH432、CH438、CH9434这三款我都实际用过各有擅长场景下面从规格到调试逐一说清楚。2. 三款芯片核心规格对比别只盯通道数2.1 CH432轻量双通道小项目首选CH432是一款双通道UART扩展芯片支持SPI和并行接口两种访问方式通信接口非常灵活。它的最高波特率做到4Mbps日常工作电压范围宽3.3V和5V系统都能用这在很多老式工控板上很吃香。我最早用CH432是在一个农用设备控制器上主控MCU只剩一组空闲SPI需要同时挂一个RS485模块和一个串口显示屏。CH432一片搞定寄存器风格简洁初始化代码几十行就能跑通。它内置了收发FIFO虽然深度不算大但对中小数据包场景足够用主控不需要在每次收发时被中断打断太多次。选CH432时要注意一点它只有两路UART适合那种“我只要再补两个串口”的项目。如果预期以后还要挂更多设备直接上4路或8路会更省事否则后期改板成本更高。2.2 CH438八通道老将多设备采集主力CH438是八通道UART扩展芯片同样支持SPI和并行接口最高波特率也能到4Mbps。它最大的特点是每个通道拥有独立的波特率发生器、独立的FIFO和独立的Modem控制信号八个通道之间基本不互相牵制。我曾经用一个CH438做工业现场采集网关下面挂8路RS485传感器每路波特率甚至都不一样有的9600有的115200有的用偶校验还有一路是9位数据模式。CH438把这些全扛下来了主控只需要通过SPI去读FIFO里的数据。相比CH432CH438的寄存器功能更完整中断处理也更复杂适合做“小网关”类产品。另外CH438的并口模式适合没有SPI总线或者SPI被占满的主控。并行接口数据吞吐更高但占用的GPIO数量会显著增加选型时需要权衡。如果你只是单纯想扩展两路串口上CH438有点大材小用成本也会上去。2.3 CH9434SPI四通道大FIFO新选择CH9434是三款里面我目前最喜欢的四路UART纯SPI从机接口最高波特率做到6Mbps。它比CH432多两路比CH438少四路但它的优势不在“数量”而在“接口清爽”和“FIFO更大”。CH9434的SPI从接口很适合ESP32这类SPI主机外设丰富的现代主控。主控上只占SCK、MOSI、MISO、CS四条线加上中断和复位总共也就六根线却能换回四路独立UART还支持RTS/CTS硬件流控。对设计紧凑的板子来说这个性价比非常高。我实测过CH9434在四路全开115200bps的情况配合大容量FIFO主控不需要每秒处理海量中断数据可以先在芯片内部缓冲等攒到一定数量再一次性读走。这种设计在RTOS场景下特别友好任务调度不慌张丢包率也低。2.4 一张表看懂三款差异参数项CH432CH438CH9434UART通道数2路8路4路主机接口SPI / 并口SPI / 并口SPI从机最高波特率4Mbps4Mbps6Mbps硬件流控视具体型号支持支持RTS/CTSFIFO特性收发FIFO深度较小每路独立FIFO大容量收发FIFO典型封装SOP类小封装LQFP类多引脚封装LQFP/QFN类紧凑封装推荐场景轻量补两路串口八路采集网关、串口服务器ESP32等主控扩展四路UART只看通道数量很容易选错。比如项目需要四路串口有人觉得CH438更划算但CH9434往往引脚占用更少、FIFO更充裕。反过来如果只差两路串口上四路或八路芯片纯属浪费成本。选型第一原则永远是“够用就好留有余量”。3. 波特率计算原理和配套工具3.1 波特率是怎么产生的为什么总是乱码很多朋友用串口扩展芯片遇到乱码第一反应就是怀疑芯片坏了。其实八成是波特率没算对。要理解这个问题先得明白UART波特率在芯片内部是怎么产生的。典型UART内部有一个波特率发生器本质是个分频器。外部给一个基准时钟比如22.1184MHz晶振或PLL合成时钟经过分频后产生串口的工作时钟。因为UART接收端要在每一位的中间采样通常会做16倍过采样所以实际波特率公式是目标波特率 基准时钟频率 / (分频系数 × 16)也就是说我们往寄存器里写的“分频值”并不是直接等于波特率而是要除以16。很多新手直接在寄存器里填115200芯片当然不可能认识结果就是输出一堆乱码。更麻烦的是如果分频系数是小数而芯片只支持整数分频那实际波特率和目标波特率之间就会有误差。当误差超过一定范围通信双方就无法准确采样。一般来说单侧误差尽量控制在1%以内双方累计误差超过3%~4%就容易出错。这也是为什么工业上特别钟爱22.1184MHz晶振——它能被很多常见波特率整数整除。3.2 手算示例22.1184MHz下面的常见波特率用22.1184MHz作为基准时钟时很多常见波特率能得到完全整数的分频系数目标波特率分频系数÷16后实际波特率误差960014496000%5760024576000%115200121152000%23040062304000%46080034608000%9216001.5737280-20%上面最后一行很关键22.1184MHz下的921600bps分频系数是1.5。如果芯片支持小数分频可以把整数位写1、小数位写0.5精准得到921600。如果芯片只支持整数分频那就只能取1或2实际波特率偏差非常大基本不能可靠通信。我建议在做串口扩展设计时先确认三件事外部时钟是多少目标波特率是多少芯片支不支持小数分频。把这三个问题搞清楚再写驱动能省下一大半调试时间。3.3 附一个能直接跑的波特率计算工具很多时候不是不会算而是算起来烦。我给项目组写过一个Python小脚本输入基准时钟和目标波特率自动输出分频系数、实际波特率和误差值。这里直接分享出来支持普通整数分频和带小数分频两种模式。#!/usr/bin/env python3 def calc_divider(clock_hz, target_baud, oversample16): 整数分频计算返回分频系数、实际波特率、误差百分比 div clock_hz / (target_baud * oversample) int_div max(1, int(div)) actual clock_hz / (int_div * oversample) error (actual - target_baud) / target_baud * 100.0 return int_div, actual, error def calc_frac_divider(clock_hz, target_baud, frac_bits2, oversample16): 小数分频计算返回整数部分、小数部分、实际波特率、误差百分比 div clock_hz / (target_baud * oversample) int_part int(div) frac_scale 1 frac_bits frac_part int(round((div - int_part) * frac_scale)) if frac_part frac_scale: int_part 1 frac_part 0 total int_part frac_part / frac_scale actual clock_hz / (total * oversample) error (actual - target_baud) / target_baud * 100.0 return int_part, frac_part, actual, error if __name__ __main__: clock 22_118_400 # 22.1184MHz for baud in [9600, 57600, 115200, 230400, 460800, 921600]: div, actual, err calc_divider(clock, baud) printf f{baud:8} | div{div:8} | actual{actual:12.3f} | err{err:.4f}% print(printf)脚本输出的分频系数最终要写成寄存器值时需要再按芯片手册做一次换算比如高字节、低字节、小数分频位分别填哪里。不同芯片寄存器布局不一样但计算公式是通用的。4. 选型决策和ESP32实战4.1 选型决策先回答四个问题别人问我选哪款我一般会让他先回答四个问题。第一个问题到底需要几路UART2路就选CH4324路优先看CH94348路直接看CH438。通道数量是硬指标别的再好也替代不了。第二个问题主控还有没有空闲SPICH432和CH438都支持并口如果你的主控SPI已经被Flash、LCD、传感器占满可以考虑走并口模式。CH9434是纯SPI从机没有SPI接口的主控就不要硬选。第三个问题波特率要求多高最高的那一路如果超过4MbpsCH9434更合适。如果只是常规115200bps三款都能胜任。第四个问题要不要硬件流控外接4G模块或者高速数传时RTS/CTS流控能有效避免FIFO溢出。CH9434在这方面做得比较完整。把这四个问题回答完选型基本就落地了。为了省成本硬上低规格芯片或者为了“以后可能用得上”盲目上高规格都是浪费钱的做法。4.2 ESP32 CH9434的接线与初始化ESP32扩展四路串口最实用的组合就是ESP32的硬件SPI加CH9434。接线方式不复杂典型连接如下ESP32引脚CH9434引脚说明GPIO5CS#片选低有效GPIO18SCKSPI时钟GPIO23MOSI主出从入GPIO19MISO主入从出GPIO17INT#中断输出接ESP32输入GPIO21RST#复位控制可复用普通IO3V3VCC电源GNDGND共地注意CH9434是SPI从机ESP32是SPI主机MOSI和MISO千万别接反我第一次焊就是接反了排查了半天才发现。INT#和RST#建议都接到ESP32上虽然不接也能跑但有中断配合时CPU开销低很多复位也方便。初始化代码我这里给一个Arduino框架的简单示意里面用到了SPI库和前面计算的波特率分频值。命令字和寄存器地址要根据CH9434手册确认我这里给的是通用框架。#include SPI.h #define CS_PIN 5 #define INT_PIN 17 #define RST_PIN 21 void ch9434_write(uint8_t reg, uint8_t val) { SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); digitalWrite(CS_PIN, LOW); SPI.transfer(0x01); // 写命令字具体以手册为准 SPI.transfer(reg); SPI.transfer(val); digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); } uint8_t ch9434_read(uint8_t reg) { uint8_t val; SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); digitalWrite(CS_PIN, LOW); SPI.transfer(0x00); // 读命令字具体以手册为准 SPI.transfer(reg); val SPI.transfer(0xFF); digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); return val; } void setup() { SPI.begin(); pinMode(CS_PIN, OUTPUT); pinMode(INT_PIN, INPUT_PULLUP); pinMode(RST_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH); // 复位CH9434 digitalWrite(RST_PIN, HIGH); delay(50); digitalWrite(RST_PIN, LOW); delay(50); digitalWrite(RST_PIN, HIGH); delay(100); // 用脚本算好的分频值配置UART通道这里示例写0x0C对应115200 ch9434_write(0x10, 0x0C); // 通道0波特率寄存器 ch9434_write(0x11, 0x00); // 通道0波特率高字节 ch9434_write(0x20, 0x03); // 使能FIFO和接收中断简化示意 } void loop() { // 业务代码 }写驱动时建议加一个“读ID寄存器”的步骤。很多扩展芯片都有厂家ID或版本寄存器初始化后先读一遍值对得上再继续配置。这个习惯能帮你快速排除SPI接线问题。4.3 硬件上容易踩的坑第一个坑是电平匹配。CH9434是3.3V芯片不能直接接5V如果主控IO是5V电平中间要加电平转换或串联电阻分压。CH432支持电压范围更宽但也别把5V电源直接往3.3V的IO上怼。第二个坑是电源电流。四路UART全双工同时收发时芯片瞬间电流不小。我习惯在VCC引脚旁边放一个0.1uF陶瓷电容加一个10uF钽电容靠近芯片放置避免电源噪声影响高频通信。第三个坑是SPI线长。CH9434的SPI从机时序对线长敏感我实测下来SCK/MOSI/MISO/CS这四条线尽量控制在10cm以内并且不要跨分割区。线太长时SPI速率要适当降低否则通信偶发错误很头大。第四个坑是中断处理。四路UART共用一个INT引脚中断服务程序里一定要通过寄存器判断到底是哪一路触发的然后再去读对应FIFO。如果漏了这个判断经常会出现某一路数据覆盖另一路的情况。5. 调试中踩过的坑与排查清单5.1 乱码和波特率不准串口扩展芯片最典型的故障现象就是“所有通道都乱码”或者“第一路正常第二路乱码”。前者基本是共用配置写错了后者多半是每路独立配置没写全。排查思路我一般按这个顺序走用示波器量扩展芯片发送引脚的TX波形看每一位的实际宽度。比如115200bps下1位起始位宽度应该是8.68us左右偏差太大就是波特率没配准。用逻辑分析仪抓RX/TX时序对比主机和从机的波特率是否一致。回头用脚本算一遍分频值看看是不是取整导致误差超限。检查外部晶振频率是不是真的符合手册要求之前遇到过有人把24MHz晶振当成22.1184MHz用的结果128000bps以下全乱。如果你用的是支持小数分频的芯片一定记得把分频的小数部分写进寄存器不要只写整数部分。很多“偶发乱码”就是因为把小数分频位漏掉了平时速率低看不出问题一旦高速就立刻暴露。5.2 中断与FIFO配置有个问题我调试时遇到过很多次启用FIFO后数据反而“卡住”了要等很久才刷出来。原因通常是接收FIFO触发阈值设置得太高小包数据一直攒不够触发线CPU就没收到中断。解决方法是把FIFO触发阈值调低一些。比如每次来一个字节就触发中断虽然CPU开销大一点但延迟最低如果数据量大可以设成FIFO半满才触发然后在中断里批量读取。这个阈值要根据实际通信包大小反复调没有绝对最优值。另外硬件流控接线要特别注意RTS和CTS的方向。设备A的RTS要接设备B的CTS两个信号是交叉的。接成直连的话流控永远失效发送端不会停止发送接收端FIFO一满就开始丢数据。这种问题用万用表都不一定看得出来最好用逻辑分析仪抓RTS/CTS的时序变化。5.3 实测经验最后说几个我自己测试出来的经验值供参考。在ESP32 CH9434的组合下SPI时钟设4MHz四路UART全部开115200bps全双工持续收发跑72小时没出现丢字节。如果把SPI时钟提高到十几兆赫兹同时信号线又比较长偶发错包率会明显上升所以稳定优先。CH438八路全开时如果各路都在低速收发比如1200bps反而容易让主控频繁进中断。因为低速波特率下每个字节的持续时间更长中断触发频率不一定低。这时候要开FIFO还要考虑在中断里同时处理多路数据别一次中断只读一个字节就退出。测试工具我用的是最土的办法串口扩展芯片的某一对TX/RX短接做回环然后通过另一个USB转串口工具往它发数据看能不能原样收回来。这个办法虽然土但能快速验证本轮驱动改动是否破坏了基础收发链路。把基础的收发链路打通之后再去验证流控、中断、FIFO这些高级功能调试效率会高很多。最后再分享一个小技巧设计PCB时给每路串口的TX/RX都留一组测试点最好还能用跳线短接。产品量产之后如果偶尔出问题可以直接在现场做回环测试不用拆开外壳再去飞线。这个习惯帮我在售后阶段省了太多事。
网站建设高端定制企业官网