
1. 从一根串口线说起UART、RS232、COM、SCI 到底谁是谁刚接触嵌入式的时候我对着开发板上那排排针发懵丝印写着 TX、RX、GND旁边还标了个 UART1。可翻原理图又看到 RS232、COM、SCI 这些词文档里还混着「波特率 115200」「8N1」这种缩写。到底哪个是硬件、哪个是协议、哪个只是接口名字如果你也有同样的困惑这篇就按我踩过的顺序把 UART 基础知识从概念到能跑通的回环收发讲一遍顺带演示怎么用 TaoToken 的统一 Key 通道把串口日志采集和校验串起来。先把最容易绕晕的四个词摆正位置。UART 是 Universal Asynchronous Receiver/Transmitter通用异步收发器它描述的是一块硬件逻辑或者芯片里的一个外设模块的工作方式把并行数据转成串行位流发出去再把收到的串行位流拼回并行字节。它管的是「怎么把 0 和 1 一位一位地送出去、怎么在接收端把位重新对齐」属于数据链路层这一层的行为。RS232 则是物理层的电气标准。它规定了用多高的电压表示逻辑 1、多高的电压表示逻辑 0接口用什么样的连接器信号线叫什么名字。UART 模块吐出来的是 TTL/CMOS 电平比如 3.3V 表示 1、0V 表示 0而 RS232 用的是负逻辑-3V 到 -15V 表示逻辑 13V 到 15V 表示逻辑 0。所以单片机要和 PC 的 RS232 口通信中间必须加一颗电平转换芯片把 TTL 电平和 RS232 电平互相翻译。COM 口是 PC 上的叫法。早期 IBM PC 把外部串行接口按 RS232 标准做出来操作系统里就把它枚举成 COM1、COM2 这样的设备名。所以你在 Windows 设备管理器里看到的 COM 口本质上就是一个遵循 RS232 电气规范的串行接口背后由 UART 芯片经典的是 8250、16550现在多是 16550 兼容的 IP驱动。SCI 是 Serial Communication Interface串行通信接口最早由 Motorola 的微控制器命名很多芯片手册里用它来指代「芯片内部那个可编程的串口控制器」。你可以把 SCI 理解成 UART 在具体芯片里的实现名字它内部包含波特率发生器、收发移位寄存器、状态与控制寄存器。ARM 的 S3C2410X 内嵌三个串行接口控制器Nios 软核可以选 UART IPPC 上常用 16650、16750叫法不同干的事是一回事。一句话收束UART 是收发机制RS232 是电气标准COM 是 PC 侧的设备名SCI 是芯片侧的模块名。它们经常被混着说但层次不一样。搞清这一点后面配波特率、接电平转换、选串口号就不会乱。2. 波特率、数据位、校验位UART 帧格式与参数配置实战UART 是异步通信发送端和接收端没有共享时钟线靠的是双方事先约定好同样的波特率各自用自己的时钟去采样。所谓波特率就是每秒传输的二进制位数单位是 bps。常见的标准值有 9600、19200、38400、57600、115200。注意它和「字节率」不是一回事一个字节在线上往往要占 10 位甚至更多因为除了 8 个数据位还要加上起始位、校验位、停止位。一个典型的 UART 帧长这样空闲时线路是高电平发送开始时先拉低一个位时间这是起始位告诉接收方「注意字符来了」接着是 5 到 8 个数据位通常是 8 位低位先发然后是可选的奇偶校验位最后是 1 位、1.5 位或 2 位停止位把线路拉回高电平。停止位之后如果还有数据就继续下一帧没有就保持空闲高电平。接收端怎么知道每一位在哪它检测到起始位的下降沿后启动内部采样时钟通常在一个位时间内采样 16 次取中间三次的值做多数表决这样即使双方时钟有轻微偏差也能采到正确的位值。这也是为什么异步通信对时钟精度要求没那么苛刻但偏差不能太大一般要求双方波特率误差在 2% 到 3% 以内。参数配置里最常写的「8N1」就是8 个数据位、无校验None、1 个停止位。如果写成「8E1」就是 8 数据位、偶校验Even、1 停止位。校验位只能发现奇数个位翻转不能纠错所以很多场景干脆不用靠上层协议做 CRC。下面给一段可以直接抄的 STM32 HAL 库串口初始化片段参数是 115200、8N1UART_HandleTypeDef huart1; void MX_USART1_UART_Init(void) { huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; huart1.Init.OverSampling UART_OVERSAMPLING_16; if (HAL_UART_Init(huart1) ! HAL_OK) { Error_Handler(); } }如果你用的是 Linux 下的 termios 配置等价写法是这样struct termios tty; tcgetattr(fd, tty); cfsetospeed(tty, B115200); cfsetispeed(tty, B115200); tty.c_cflag (tty.c_cflag ~CSIZE) | CS8; tty.c_cflag ~PARENB; tty.c_cflag ~CSTOPB; tty.c_cflag | (CLOCAL | CREAD); tty.c_lflag ~(ICANON | ECHO | ECHOE | ISIG); tty.c_iflag ~(IXON | IXOFF | IXANY); tty.c_oflag ~OPOST; tcsetattr(fd, TCSANOW, tty);这里有个容易忽略的点OverSampling设成 16 还是 8会影响波特率寄存器的计算。STM32 在过采样 16 时USARTDIV fCK / (16 * BaudRate)过采样 8 时公式不同。如果你手算寄存器和 HAL 库算出来的对不上先检查这个参数。再补一个 RS232 电平转换的常识。单片机 TX 接转换芯片的 TTL 输入转换芯片的 RS232 输出接 DB9 的 2 脚RXD注意是交叉的单片机 RX 接转换芯片的 TTL 输出转换芯片的 RS232 输入接 DB9 的 3 脚TXD。GND 对 GND。如果你用万用表量 DB9 的 2、3 脚空闲时应该能量到负电压约 -9V这就是 RS232 负逻辑的直观体现。3. 用 TaoToken 统一 Key 通道采集串口日志可复制配置串口调通之后下一步往往是把日志抓下来做校验。传统做法是开个串口助手手动复制或者写脚本读/dev/ttyUSB0。但如果你同时还在调模型接口、跑 Agent 任务Key 和通道散落各处会很乱。我现在的做法是用 TaoToken 把模型调用和日志分析统一到一个 Key 通道上串口这边只负责把原始日志吐出来分析交给模型。先拿 Key。打开 https://taotoken.net/api-keys 登录后创建一个 API Key复制出来形如sk-xxxxxxxx。这个 Key 同时能用于模型对话和 Coding Plan不用为不同工具分别申请。接着在调试工具里配置。以 Cline 为例它的 MCP 配置和模型配置是分开的。模型侧填三件套{ provider: openai-compatible, baseUrl: https://taotoken.net/api, apiKey: sk-你的Key, model: claude-sonnet-4-20250514 }如果你用的是 Claude Code配置在~/.claude/settings.json里写法是{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }Codex 的话改~/.codex/auth.json{ OPENAI_BASE_URL: https://taotoken.net/api, OPENAI_API_KEY: sk-你的Key }模型 ID 按你实际要用的填上面只是示例。Base URL 统一是https://taotoken.net/api注意不要多加斜杠或路径后缀。串口日志采集这边我用 Python 的 pyserial 读口把每行日志攒够一批后发给模型做异常检测。核心片段import serial import requests ser serial.Serial(/dev/ttyUSB0, 115200, timeout1) buffer [] while True: line ser.readline().decode(utf-8, errorsignore).strip() if not line: continue buffer.append(line) if len(buffer) 50: payload { model: claude-sonnet-4-20250514, messages: [ {role: user, content: 以下是嵌入式设备串口日志请找出异常行并说明原因\n \n.join(buffer)} ] } resp requests.post( https://taotoken.net/api/v1/chat/completions, headers{Authorization: Bearer sk-你的Key}, jsonpayload, timeout30 ) print(resp.json()[choices][0][message][content]) buffer.clear()这段代码把串口原始日志按 50 行一批送进模型模型返回异常行和原因。实测下来对于「启动阶段反复重启」「校验和错误」「超时重传」这类模式模型能比较快地指出来比人一行行翻快很多。如果你要长期跑这种采集加分析的任务建议用 Coding Plan额度更划算适合 Agent 类持续调用。入口在 https://taotoken.net/coding-plan 。4. 回环收发验证从发一个字节到确认成功配置写完必须验证串口本身是通的。最省事的办法是回环测试把开发板的 TX 和 RX 用杜邦线短接发什么就应该收到什么。这一步能排除波特率、数据位、停止位配置错误也能确认引脚复用没搞错。先看 STM32 的回环代码uint8_t tx_data 0x55; uint8_t rx_data 0; HAL_UART_Transmit(huart1, tx_data, 1, 100); HAL_UART_Receive(huart1, rx_data, 1, 100); if (rx_data tx_data) { printf(loopback ok: 0x%02X\r\n, rx_data); } else { printf(loopback fail: sent 0x%02X, got 0x%02X\r\n, tx_data, rx_data); }发 0x55 是因为它的二进制是 01010101高低电平交替用示波器看波形最直观也容易发现波特率偏差导致的位宽不对。Linux 侧的回环验证更简单用stty配好参数后直接读写stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb raw printf \x55 /dev/ttyUSB0 xxd -l 1 /dev/ttyUSB0如果短接了 TX/RX第二条命令写进去的 0x55 会立刻被第三条命令读回来xxd输出55。如果读回来是00或者超时先查线有没有接对再查波特率。成功之后把回环去掉接上真实的对方设备用同样的参数收发。这时候如果出现乱码八成是波特率不匹配如果收到固定错位的数据可能是数据位或停止位设错如果偶尔丢字节检查是不是没开流控但发送速度太快。我试过在 115200 下用 1 米杜邦线做回环稳定换成 921600 之后线稍微长一点就开始出错这时候要么降速要么换带屏蔽的线要么加流控。串口这东西速率和线长是互相制约的。5. 常见报错排查401、local proxy failed、reading choices、OAuth串口和 API 两边都可能出问题这里按我遇到过的真实报错逐个说。401 UnauthorizedTaoToken 返回 401基本是 Key 错了或者没带上。检查Authorization: Bearer sk-xxx里的 Key 有没有多余空格Base URL 是不是写成了https://taotoken.net/api/末尾斜杠有时会导致路径拼接错误。另外确认 Key 没有过期或被删。local proxy failed这个报错通常出现在本地工具试图走一个不存在的本地代理端口时。检查你的环境变量里有没有HTTP_PROXY、HTTPS_PROXY指向了127.0.0.1:xxxx但那个端口没服务。清掉这些变量或者确认代理服务确实在跑。注意这里说的是本地开发环境的代理配置问题不是让你去搞什么网络穿透。reading choices 报错形如KeyError: choices或reading choices说明返回的 JSON 里没有choices字段。常见原因是请求体格式不对比如messages写成了字符串而不是数组或者model字段填了一个不存在的模型 ID。先用 curl 发一个最小请求确认curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:claude-sonnet-4-20250514,messages:[{role:user,content:hi}]}如果 curl 通了但代码不通就是代码里的序列化问题。OAuth 相关报错Claude Code 或某些工具会走 OAuth 流程如果你已经用 API Key 配置了就不需要再走 OAuth。报错里出现OAuth token expired之类检查是不是同时配了 OAuth 和 API Key两者冲突。清掉 OAuth 缓存只保留ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL。串口侧的报错也列几个。Permission denied: /dev/ttyUSB0是当前用户不在 dialout 组sudo usermod -aG dialout $USER后重新登录。Device or resource busy是串口被别的进程占着lsof /dev/ttyUSB0找到进程杀掉。Input/output error多半是 USB 转串口线接触不良或者芯片驱动问题换个口试试。排查顺序建议先确认物理连接和电平再确认串口参数再确认 API Key 和 Base URL最后看请求体格式。一层一层往下别跳。6. 把串口调试接进日常开发流串口调通、日志能采、模型能分析之后这套流程可以固化下来。我的做法是写一个serial_logger.py启动时读环境变量里的 Key 和串口号后台跑采集前台用模型做实时异常提示。Key 从 TaoToken 拿模型对话入口在 https://taotoken.net/models 接入文档在 https://taotoken.net/doc 需要新建 Key 就去 https://taotoken.net/api-keys 。如果你还在用 Claude Code 做嵌入式项目的代码补全和重构把settings.json按第 3 节配好它就能在同一个 Key 通道下工作不用来回切账号。长期跑 Agent 任务的话Coding Plan 的额度比按次调用更省心。最后留一个实用技巧串口日志里如果混着二进制和文本读的时候用errorsignore会丢信息改成errorsreplace能看到乱码位置方便定位是哪个字节开始不对。这个细节在调协议的时候特别有用。