news 2026/10/2 20:35:38

UART通信详解:从物理层电平到STM32 HAL库配置与调试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UART通信详解:从物理层电平到STM32 HAL库配置与调试实战

UART在我眼里一直是通信协议里最“亲民”的那个。它只有两根数据线,没有时钟线,协议帧结构简单到看一眼就能记住,可它承载了无数嵌入式设备从调试到量产的全过程。我最早接触单片机就是从点亮LED和printf重定向开始的,而那个printf的输出通道就是UART。后来做产品联调、写Bootloader、接GPS模块、配蓝牙模组,几乎每一站都离不开它。

这篇文章我准备把UART从物理层电平到协议层消息格式一层层拆开讲,重点放在STM32 HAL库上的实际操作,包括底层时序解读、stm32cubemx配置、HAL_UART_Transmit的阻塞机制分析,以及我实际踩过的那些坑,比如DMA收发错乱、波特率误差过大导致乱码之类的。不管你是刚入门的新手,还是已经在用串口调试但没细想过底层原理的工程师,这篇文章都值得读一遍。

1. UART的底层机制与数据帧格式

1.1 没有时钟线,怎么对得上节奏

UART全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。它跟SPI、I2C这些同步通信最本质的区别在于:没有SCLK这根时钟线。同步通信是发送方把数据线和时钟线一起拉出来,接收方跟着时钟沿采样就行;而异步通信只有TX和RX两根数据线,双方必须各自用自己的时钟来约定采样时刻。

这就诞生了一个关键概念:波特率。波特率是每秒钟传输的码元数,在UART场景下基本就等于比特率,单位是bps。通信双方必须配置成相同的波特率,比如9600bps、115200bps,否则接收方采样到的电平变化节奏对不上,收到的就是乱码。

我做个生活化的比喻。两个人在没有节拍器的情况下合唱一首歌,必须靠心里默数的速度保持一致,稍微快一点慢一点,整首歌的卡点就全乱了。UART就是这种“各唱各的但速度必须一致”的合作方式。为了让双方能找准起点,UART数据帧必须有起始位,这是异步通信的锚点。

1.2 数据帧拆解:起始位、数据位、校验位、停止位

一个标准的UART数据帧长这样:

  • 空闲状态:线路保持高电平
  • 起始位:发送方将TX拉低一个位时间,告诉接收方“数据要来了”
  • 数据位:紧接着发送5~8位数据,低位在前
  • 校验位(可选):用来做简单的奇偶校验
  • 停止位:将线路拉高并保持1~2个位时间,表示一帧结束

空闲电平是高,起始位是低,这个设计有讲究。高电平在电气上意味着“没有数据驱动的默认状态”,也方便检测线路断开。如果RX引脚一直读到低电平,那大概率是设备没接对线或者对方直接拉低了总线。

数据位最常见的是8位,也就是一个字节,配合无校验、1位停止位,缩写为8N1。这是绝大多数UART应用的默认配置。接收方怎么判断一帧结束了?靠停止位的高电平。停止位结束后,线路回到空闲高电平,等待下一个起始位的下降沿。

1.3 时序图与实际波形解读

用逻辑分析仪或者示波器抓UART线上的一帧数据,你会看到这样的信号:

空闲高电平 → 起始位(低) → D0(LSB) → D1 → D2 → D3 → D4 → D5 → D6 → D7(MSB) → 停止位(高)

举个具体例子,发送十六进制0x41,二进制是0b01000001,LSB first发送,线上实际顺序是1、0、0、0、0、0、1、0。很多人刚用逻辑分析仪看波形时容易把这8个bit看反,以为发送的是0x82,其实就是因为没搞清低位在前这个规则。

关于位时间,可以换算一下:波特率为115200时,每位时间是1/115200,大约是8.68微秒。起始位拉低8.68us,数据位每个也是8.68us。调试时如果用示波器抓波形,可以数一下位宽来判断波特率配置是否正确,比如抓一帧测出每位约104us,那波特率就是9600。

1.4 电平标准:TTL、RS-232、RS-485

UART说的是协议层逻辑,物理层电平标准有多种,这一块很容易把人绕晕。

  • TTL电平:3.3V或5V系统里的UART直接输出的电平,高电平约等于VCC,低电平为0V。单片机之间通信、USB转串口模块和MCU之间,基本都是TTL电平。
  • RS-232电平:上世纪串口标准,逻辑1是-3V到-15V,逻辑0是+3V到+15V。PC老式DB9串口就是这种电平,所以要经过MAX3232这类电平转换芯片。
  • RS-485电平:差分信号,A、B两线之间的电压差表示逻辑状态,抗干扰能力强,适合长距离多点通信。

实操中最常见的坑是:USB转TTL模块出来的就是TTL电平,不能直接怼到RS-232接口上,否则要么不工作,要么烧设备。同理,RS-485设备要用带有485收发器的模块转接。TTL电平的UART线距一般建议不超过1米,超过这个距离就得考虑换RS485或者加总线驱动器。

2. 波特率的计算逻辑与时钟配置详解

2.1 为什么波特率不能随便配

很多人直接照搬例程代码,串口助手设115200就跑起来了。但如果不知道波特率是怎么来的,遇到“为什么换成28800就乱码”这种问题就完全没方向。

UART波特率的本质是:把外设时钟分频到目标波特率对应的频率。STM32的USART外设时钟源通常是PCLK,经过USARTDIV分频后得到波特率时钟。计算关系为:

  • 过采样16倍时:波特率 = USART时钟 / USARTDIV
  • 过采样8倍时:波特率 = USART时钟 / (2 × USARTDIV)

USARTDIV是一个带小数位的分频值,存放在BRR寄存器中,整数部分写高16位,小数部分写低4位。

所以波特率误差的来源是:你想要的波特率对应到分频值后,小数部分往往不能精确表示,于是产生了误差。时钟频率越整,分频越准;时钟频率越偏,误差越大。

2.2 STM32 HAL库的波特率配置

用CubeMX配置UART时,波特率只需要在图形界面填一个数字,HAL库会在初始化时自动计算BRR寄存器值。但这里有个隐藏问题:USART的时钟源选择。

STM32F1系列,USART1挂在APB2总线上(最高72MHz),USART2和USART3挂在APB1总线上(最高36MHz)。如果你把PCLK1配成了36MHz,而PCLK2配成了72MHz,那么相同的波特率设置下,USART1和USART2的实际分频系数是不同的。这一点在CubeMX里不会直接报错,但从寄存器层面看,分频值不一样。

对于要求比较高的场景,比如需要精确的时间同步,建议把波特率误差算一遍。误差 = |实际波特率 - 目标波特率| / 目标波特率。一般要求误差低于2%,但极限情况下UART接收容忍度大约是4%左右,超过这个值就会开始丢字节。

2.3 自定义波特率的计算实例

假设STM32F407的USART时钟源是84MHz,目标波特率是115200。计算分频值:

USARTDIV = 84000000 / (16 × 115200) ≈ 45.5729

整数部分是45,小数部分0.5729 × 16 ≈ 9.17,取整为9。所以BRR寄存器写入的值约为整数45、小数9,即0x45_9。

实际分频后波特率 = 84000000 / (16 × (45 + 9/16)) = 84000000 / (16 × 45.5625) ≈ 115242.6

误差约0.037%,完全在容忍范围内。

如果同样的需求放在APB1上,比如36MHz,计算USARTDIV = 36000000 / (16 × 115200) ≈ 19.53125。小数部分0.53125 × 16 = 8.5,取整为8或9都会引入误差。取8的话实际波特率=36000000/(16×19.5)=115384.6,误差0.16%,还能接受。

实操提示:如果Project里允许,优先把USART放在主频更高的总线上,分频值更大,小数误差通常更小。

2.4 内部时钟源的选择:HSE、HSI与PLL的影响

STM32的UART时钟源可以来自SYSCLK分频后的PCLK,也可以通过USART的时钟MUX选择HSI或者LSE作为独立时钟源。比如STM32L4系列,USART可以选择LSE作为时钟源,这样即使系统主频发生变化,UART波特率也不会漂移,适合低功耗场景。

从实操角度,三句话总结:

  1. 如果系统时钟从HSE+PLL配置,UART波特率通常很准,放心用。
  2. 如果你的系统跑在HSI上,HSI的精度通常在1%~2%之间,高速UART时建议降速到9600或19200。
  3. 如果项目对时钟要求很高,必须外部温补晶振,不能靠内部RC。

3. STM32 HAL库的UART发送接收API详解与实操

3.1 阻塞式发送:HAL_UART_Transmit

先看最常用的接口:

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout);

参数含义很直观:huart是UART句柄,pData是待发送数据缓冲区,Size是要发送的字节数,Timeout是超时毫秒数。如果发送成功返回HAL_OK,超时返回HAL_TIMEOUT,参数错误返回HAL_ERROR。

注意,这个函数是阻塞式的。它会一直等待TXE(发送数据寄存器空)标志和TC(发送完成)标志,直到所有字节都发完或者超时。在实时性要求高的场景,比如10ms周期任务里发100字节,可能还没发完下个周期任务就来了,这时候就要考虑中断或DMA方式。

我实际项目里的习惯是:如果是调试打印、初始化上报、低频率命令应答,用阻塞发送没问题。如果每毫秒都有数据要发,必须用DMA。

3.2 中断式发送:HAL_UART_Transmit_IT

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size);

中断式发送不会一直占着CPU等数据发完,而是注册一个中断回调,每次TXE空中断时往数据寄存器填入下一个字节。数据发完后会调用中断回调函数HAL_UART_TxCpltCallback。

但有一个坑:HAL库在中断式发送期间,如果同一个UART对象再次调用HAL_UART_Transmit_IT,会直接返回HAL_BUSY。也就是说,你不能在还没发完上一批数据时就开始下一批。解决方案是做一个发送队列,把待发送数据缓存起来,等发送完成回调里再取下一个任务。

3.3 DMA式发送:HAL_UART_Transmit_DMA

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size);

DMA方式最省CPU,数据从内存搬运到UART数据寄存器完全不需要CPU介入。配置DMA时要注意源地址是内存,外设地址是&huart->Instance->DR(数据寄存器)。方向是内存到外设(MEM_TO_PERIPH),数据长度按字节配置。

我遇到过一个大坑:DMA发送完成后,如果直接用同一个缓冲区再次调用DMA发送,需要先调用HAL_UART_DMAAbort或者等待TC标志。因为DMA模式和阻塞模式不一样,发送完成回调只在所有数据搬完后触发,但如果之前DMA没停干净,第二次启动时会从错误位置继续搬。

3.4 如何选择发送方式:一张表说清楚

方式CPU占用实时性适用场景
阻塞发送高(全程占用)较差初始化打印、低频应答
中断发送中(逐字节中断)较好中低频通信、不定长发送
DMA发送低(搬运不占CPU)好大批量连续数据、高速通信

3.5 接收侧的关键难点:定长与不定长

接收比发送复杂得多,因为你不一定知道对方什么时候发、发多长。常见接收方案有:

  1. 定长接收:预先知道一帧固定长度,用HAL_UART_Receive阻塞接收或DMA接收固定字节数。
  2. 不定长接收:需要结合RTO(Receive Timeout)中断或者IDLE(总线空闲)中断来判定一帧结束。

HAL库最常用的不定长接收方案是:启用UART的IDLE中断和DMA接收。数据到来时DMA搬运,总线空闲时产生IDLE中断,在中断里判断接收到的长度,处理完后再重新启动DMA接收。这个方案在网上有大量代码,但很多都写不完整。核心关键点是:

  • 第一次启动DMA接收时,要调HAL_UART_Receive_DMA,设置接收缓冲区和最大长度。
  • 在IDLE中断回调中,用__HAL_DMA_GET_COUNTER获取DMA剩余计数,从而算出实际收到的字节数。
  • 处理完一帧数据后,不要重新调用HAL_UART_Receive_DMA,而是直接重设DMA计数并清除标志位,否则会因为缓冲区位置偏移导致数据错位。
  • 如果接收缓冲区是环形缓冲,最好用两个半满中断或者自定义记录写指针,避免覆盖未处理的数据。

4. 基于HAL库的UART工程实战配置流程

4.1 从零开始配置STM32CubeMX

这里我用某次实际项目里的配置步骤做示范,目标芯片是STM32F103C8T6,系统主频72MHz。

第一步,打开CubeMX新建工程,选择芯片型号,配置RCC为HSE外部晶振。如果只有HSI,也可以跑,但为了后续串口波特率稳定,推荐外接8MHz晶振。

第二步,配置时钟树。将HSE倍频到72MHz。注意:APB1总线时钟设为36MHz,APB2设为72MHz。如果打算用USART1就记住它可以跑72MHz,USART2/3跑36MHz。

第三步,在Peripherals里启用USART1,Mode选Asynchronous,参数配置如下:

  • Baudrate:115200
  • Word Length:8 Bits
  • Parity:None
  • Stop Bits:1
  • 其他用默认值

第四步,如果要用DMA接收或发送,在DMA Settings里添加USART1_TX和USART1_RX两个DMA通道,模式设为Normal。接收DMA建议开启Circular模式,方便做不定长接收的环形缓冲。

第五步,在NVIC Settings里使能USART1全局中断和DMA中断。

第六步,生成工程,代码框架会自动带初始化函数MX_USART1_UART_Init。

4.2 HAL库初始化流程的代码级解读

生成的MX_USART1_UART_Init函数本质是在填UART_HandleTypeDef结构体,然后调用HAL_UART_Init。HAL_UART_Init内部会调用HAL_UART_MspInit,这个MspInit函数里配置GPIO时钟、引脚复用、DMA通道和中断优先级。

实际踩过的坑是:HAL_UART_MspInit里如果没有正确启用GPIO时钟,或者复用功能配置错误,TX/RX引脚根本没有波形,而代码并不会报错。排查时先看这个函数,别一上来就怀疑波特率。

调试技巧:初始化后读取huart->gState和huart->RxState,如果都是HAL_UART_STATE_READY,说明初始化正常。如果状态是HAL_UART_STATE_BUSY,说明上次发送或接收没有完成。

4.3 一个可复用的UART驱动模块:EIoT86_UART

我在项目里写了一个轻量UART驱动模块,把阻塞发送、DMA发送、DMA接收、空闲中断回调都封装好,直接拿来用。模块结构大概是这样:

typedef struct { UART_HandleTypeDef *huart; uint8_t rx_buf[256]; uint16_t rx_len; uint8_t rx_complete; } EIoT86_UART_Handle;

核心函数:

void EIoT86_UART_Init(EIoT86_UART_Handle *dev, UART_HandleTypeDef *huart); void EIoT86_UART_SendBlock(EIoT86_UART_Handle *dev, const uint8_t *data, uint16_t len); void EIoT86_UART_SendDMA(EIoT86_UART_Handle *dev, const uint8_t *data, uint16_t len); void EIoT86_UART_StartReceiveDMA(EIoT86_UART_Handle *dev); void EIoT86_UART_IRQHandler(EIoT86_UART_Handle *dev);

其中ReceiveDMA初始化后,在IDLE中断回调里处理帧数据。关键代码如下:

void HAL_UART_IDLE_Callback(UART_HandleTypeDef *huart) { if (huart == &huart1) { EIoT86_UART_IRQHandler(&g_eiot86_uart1); } }

内部实现里,首先要判断接收是否真的发生了(通过RxState状态和DMA计数),拿到实际长度后置rx_complete标志,然后重新配置DMA接收以恢复监听。注意:重配DMA时只能用HAL_UART_AbortReceive加新的HAL_UART_Receive_DMA,不能直接再调用一次,否则容易丢首个字节。

4.4 环形缓冲区的工程实现思路

如果通信数据量较大,建议直接上环形缓冲区。基本结构就是一个读指针加一个写指针,写指针由DMA或中断更新,读指针由应用层消费。

UART环形缓冲的经典实现方式:

  • 开启DMA接收为Circular模式,DMA会自动绕回缓冲区头部。
  • 用空闲中断记录当前DMA写位置,应用层从读位置消费。
  • 读位置和写位置相等时表示缓冲区为空,读位置追上写位置时表示溢出,需要丢数据或置错误标志。

我在实际项目里维护过一个128字节环形缓冲,配合1kHz的协议解析任务,跑200万包数据从未丢过字节,前提是:解析任务消费速度必须大于数据到达速度。这是铁律,缓冲区再大也救不了消费太慢的应用。

5. 常用调试方法与高频问题排查实录

5.1 用逻辑分析仪/usb转串口抓波形找问题

UART调试我最常用的工具就是逻辑分析仪加串口助手。逻辑分析仪可以精确抓取波形,能看到数据帧是不是正确的8N1格式,起始位有没有被抢,停止位有没有被拉短。

抓波形的操作步骤:

  1. 逻辑分析仪的通道接UART的TX脚,地线共地。
  2. 设置波特率和你配置的一致。
  3. 软件里选UART解码,把信道极性设正常。
  4. 发送几个已知字节,比如0x55、0xAA,这些是交替电平模式,最容易看出位是否偏移。

调试时观察到的常见问题:起始位正常但后续位宽明显不均,大概率是波特率误差过大,或者对方的实际波特率和标称值不符。比如某GPS模块标称9600,实际却是10400,就会让人排查半天。

5.2 乱码的四大根源

乱码是我被问得最多的问题,归纳下来就几个原因:

第一,波特率不匹配。最常见。发送端115200,接收端9600,出来的全是乱码。确认方法:用逻辑分析仪抓一帧波形,测量位宽,反推波特率。

第二,电平不匹配。TTL接RS-232、RS-485忘了接终端电阻,都可能造成信号畸形。TTL高电平接成3.3V对5V,虽然很多时候能工作,但长期不推荐。

第三,时钟源不准。内部RC振荡器作为USART时钟时,误差随温度变化明显。解决方法是切换到外部晶振,或者降低波特率使误差在容忍范围内。

第四,寄存器配置错误。比如Word Length被改成7位,但发送端还是8位,就会出现丢位现象。检查HAL_UART_Init里的WordLength字段。

5.3 DMA接收偶尔丢头的典型案例

有一次排查一个DMA接收丢头的问题,现象是:每帧32字节,偶尔会丢第一个字节,导致整帧数据错位。查了很久,最终发现原因在于第一次调用HAL_UART_Receive_DMA之后,如果紧接着有数据到达,DMA的第一个搬运周期可能还没有完全使能,导致首个字节被丢弃。

解决方法是:在启动DMA接收后,等待一段时间或者确认DMA的EN位已经置1再开放数据发送。更稳妥的做法是在初始化完成后先调用一次HAL_UART_Receive_DMA,让DMA处于监听状态,之后第一个字节就不会丢了。

还有另外一个坑:如果使用了IDLE中断,并清了USART的IDLE标志,要注意别用__HAL_UART_CLEAR_IDLE_FLAG误清其他标志。标准做法是读SR寄存器再读DR寄存器来清除IDLE标志,HAL库里用__HAL_UART_CLEAR_IDLE_FLAG宏时要注意参数类型。

5.4 串口通信异常排查速查表

现象排查方向
完全没数据检查TX/RX是否交叉接反、GPIO复用配置、共地
乱码波特率、时钟源、电平标准、数据位/停止位配置
偶尔丢字节DMA配置、中断优先级、缓冲区溢出、消费速度慢
发送后卡死Timeout设置太短、TXE/TC标志未清除、HAL库版本差异
收发不同步接线是否正确、TTL电压域是否匹配、接线过长引入噪声

5.5 两个实际项目的排障复盘

第一个项目是环境监测设备的蓝牙升级,使用STM32L431和BLE模块通信。现象是每过几分钟就会出现一次整包数据CRC错误。用逻辑分析仪连抓了一个小时波形,发现每次出错都伴随着蓝牙模块的电源纹波变大,导致UART信号的高电平最低值跌到接收门槛附近,偶尔被采样成低电平。最后给BLE模块加了滤波电容,问题消失。这个案例说明排障不能只盯数字逻辑层,物理层供电抖动也得纳入考量。

第二个项目是工业控制板,使用RS-485连接多个从机。从机A偶尔无响应,排查发现是终端电阻没有匹配,信号反射导致边缘抖动。在总线两端加了120欧终端电阻后,问题彻底解决。UART本身是点对点短距离设计,一旦走了RS-485这种多点长距离总线,就必须按总线规范处理,不然就会踩反射问题的坑。

6. 实操中的深入心得体会

6.1 开箱验货:拿到一个UART设备先做什么

新拿到一个UART设备,比如4G模组、GPS模块、指纹模块,我建议按这个顺序做开箱验货:

  1. 查手册,确认接口电平。TTL还是RS232,3.3V还是5V。
  2. 接线前确认设备默认波特率,常见的有9600、115200、38400。
  3. 上电后先发一个AT指令之类的基本命令,看是否有应答。
  4. 如果没有应答,用逻辑分析仪抓设备TX引脚。有些设备上电后会自动发一串log,这个log就能反推出实际波特率。
  5. 如果TX有波形但串口助手收不到,优先检查接线是否交叉,以及共地。

6.2 关于cubeMX版本和HAL库版本

不同版本的HAL库在某些细节上有差异,比如USART初始化结构体里多了一个AutoBaudRate字段和OverSampling字段,低版本库可能没有。所以代码跨版本移植时不能只看函数名,还得看结构体字段。特别是C++和C混合工程里,结构体初始化方式也可能不一样。

我个人的建议是:项目章程一确定就固定HAL库版本,不要随意升级。如果必须升级,重点回归测试UART_Init、DMA中断回调、IDLE中断相关代码。

6.3 关于调试串口的额外保护

在量产板设计里,调试串口通常会加电平转换芯片或ESD保护管。UART引脚悬空时容易被静电打坏,或者调试时反复插拔杜邦线导致物理损伤。设计时至少预留测试点,建议串220欧到330欧电阻再接外部,能有效减小瞬态电流冲击。

如果你做的是低功耗产品,还要注意UART空闲时引脚的电平状态。如果外部设备将TX拉低,MCU就会一直收到起始位,导致唤醒频繁。这种情况下硬件设计上要考虑下拉电阻匹配,或者软件上在低功耗前把RX引脚配置成EXTI并检测空闲状态。

6.4 最后一个小技巧

遇到数据帧黏包和半包问题,先不要急着改代码。把协议设计成帧头+长度+数据+校验的结构,接收端只在收到长度字节后才知道需要读多少个字节。这样可以兼容各种HAL接收方式,也能在log里直观看到是哪一层的解析出错。

我习惯在每个通信模块的log里加上原始数据hex打印,比如[RX] 55 AA 1C 00 00 02 3F D0,这样无论在实验室还是现场,只要看log就能快速定位问题。这套习惯帮我在不少难缠的通信故障中节省了大量时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 20:34:57

System Prompt 膨胀:你的 AI 有多少预算给了“自我介绍“?

👋 Hi,带娃的我热爱 AI 大模型应用落地、意识解码与 AI 开发工具链 。 💡 创业路上,用技术换时间,一起把 AI 变成生产力 🚀 >System Prompt 膨胀:你的 AI 有多少预算给了"自我介绍"…

作者头像 李华
网站建设 2026/10/2 20:34:37

对象存储服务器vs数据库

一、先看结论图片可以存进普通数据库,但代价极高,几乎没人这么做。原因不是“技术上做不到”,而是数据库的设计目标与图片的存储需求根本不匹配。二、普通数据库 vs 对象存储:设计目标完全不同维度普通数据库(MySQL&am…

作者头像 李华
网站建设 2026/10/2 20:31:07

STM32定时器本质:时钟脉冲计数与时间基准推导

1. 这不是“数秒”,而是数“时钟脉冲”:STM32定时器的本质真相你写过HAL_Delay(1000),也配置过TIM2的PWM输出,甚至用过输入捕获测过超声波回波时间——但有没有哪一刻,你盯着CubeMX里那个“Prescaler”和“Counter Per…

作者头像 李华
网站建设 2026/10/2 20:30:19

大模型API价格目录开源:从计费建模到成本对比的工程实践

1. 从“查价查到头大”说起:这个开源目录到底解决了什么国内大模型 API 的价格,是我最近半年被问得最多的问题之一。不是“哪个模型最强”这种主观题,而是非常具体的:“DeepSeek 现在多少钱一百万 token?”“智谱和通义…

作者头像 李华
网站建设 2026/10/2 20:29:37

RK3588双路YOLOv5s部署:线程池调度与NPU并发实战

做嵌入式AI部署的人应该都有同感:单路跑通检测只是入门,真正折磨人的是双路甚至多路视频流同时稳定运行。香橙派5这块RK3588板子,NPU算力标称6 TOPS,单路跑一个INT8量化的YOLOv5s模型,帧率轻松破百,但你要是…

作者头像 李华
网站建设 2026/10/2 20:28:40

AI新闻日报_2026-07-07:用TaoToken统一Key追踪Agent与AI Coding动态

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华