新闻详情

新闻详情

首页 / 资讯中心 / 详情

STC51串口多字节接收实战:状态机、环形缓冲区与中断优化

发布时间:2026/9/28 9:37:47来源:尧图网络
STC51串口多字节接收实战:状态机、环形缓冲区与中断优化
1. 项目概述与整体设计思路1.1 STC51串口接收的本质一次只来一个字节先明确一个基础认知STC51系列单片机的UART串口硬件上接收数据时一次只会往SBUF寄存器里塞一个字节。你不可能指望硬件帮你把一帧多字节数据完整收进来它没这个本事。数据是一个字节一个字节顺着RXD引脚进来的每收完一个字节硬件会置位RI中断标志位然后你必须在下一个字节到达之前把这个字节取走否则就会被新数据覆盖。这就引出了多字节接收的第一个矛盾上位机发过来的是完整的一帧数据比如5个字节、8个字节甚至更长但单片机这边只能一个一个地接。如果你只是简单地在主循环里判断“RI 1就读SBUF”那么当你同时在处理其他任务、或者主循环代码比较长的时候稍不留神就会丢字节。更麻烦的是你不知道这一帧数据从哪儿开始、到哪儿结束如果第一个字节就是错的后面全盘皆错。我早期刚开始写51串口时也天真地以为串口接收就是“读SBUF、存数组、完事”。真到项目里才发现事情远没有那么简单。尤其是当你需要接收类似“AA 01 03 00 5C”这种带帧头、带命令、带校验的完整指令帧时如果没有一个健壮的接收状态机三天两头就会出现数据错乱。1.2 为什么多字节接收容易出问题先说结论多字节接收的难点不在“接收”本身而在“帧同步”和“缓冲管理”。什么叫帧同步就是你得知道现在收到的字节是帧头、长度、数据、还是校验位。如果每个字节都只是孤零零地被存进数组没有任何状态标记那你完全无法判断这一帧数据什么时候开始、什么时候结束、数据在第几个位置。另一个难点是处理速度。STC51的经典51内核一个机器周期要12个时钟周期12MHz晶振下大概1MIPS的指令执行能力。说实话这个速度在今天看来是比较慢的。而串口通信常用的波特率9600bps大概一毫秒多一个字节115200bps大概87微秒一个字节。在87微秒内你要完成中断响应、现场保护、数据读取、状态判断、数据存储、缓冲指针更新、可能还要做校验时间是相当紧张的。如果中断服务函数里写了太多代码或者主循环里做的事情太慢丢数据几乎是一定的。所以想要把STC51的串口多字节接收做好核心就三件事正确的状态机设计、尽量精简的中断服务函数、以及稳妥的缓冲管理。下面我会把这三点拆开来讲重点说三个最常见的坑这些坑我都在实际项目里踩过每一个都配有解决方案和完整代码。2. 坑一帧头帧尾判定不明确数据一错全错2.1 问题表现莫名其妙地收到乱码帧很多初学者写多字节接收时代码长这样uchar rx_buf[16]; uchar rx_cnt 0; void Uart_Isr() interrupt 4 { if(RI) { RI 0; rx_buf[rx_cnt] SBUF; rx_cnt; } }看着好像没问题每收到一个字节就存进数组收满16个清零。但真用起来就难受了上位机发“AA 01 02 03 04 55”中间任何一次多字节没对齐你就发现从某个字节开始全乱了。比如上位机只发了5个字节你的数组里存了上一次残留的数据这一帧数据里混着旧数据命令解析出来完全是错的。这个问题的根源就是没有帧对齐概念。每个字节只是机械地往数组里塞至于它是不是一帧的起始、是不是结束完全没有判断。一旦数据流中间丢了一个字节后面所有字节的位置全部错位而且这种错误不会自动恢复。2.2 解决方案引入状态机进行帧同步解决这个问题的标准做法是设计一个接收状态机。所谓状态机本质上就是给接收过程划分阶段每个阶段根据当前收到的字节决定下一步进入什么状态。以常见的帧格式为例帧头 数据长度 数据 校验。我来演示一个最简单的状态机方案#define FRAME_HEAD1 0xAA #define FRAME_HEAD2 0x55 #define MAX_FRAME_LEN 16 typedef enum { STATE_WAIT_HEAD1 0, STATE_WAIT_HEAD2, STATE_WAIT_LEN, STATE_WAIT_DATA, STATE_WAIT_CHECK } RX_STATE; volatile uchar rx_buf[MAX_FRAME_LEN]; volatile uchar rx_len 0; volatile uchar rx_check 0; volatile uchar rx_state STATE_WAIT_HEAD1; volatile bit frame_ready 0; void Uart_Isr() interrupt 4 { uchar tmp; if(RI) { RI 0; tmp SBUF; switch(rx_state) { case STATE_WAIT_HEAD1: if(tmp FRAME_HEAD1) rx_state STATE_WAIT_HEAD2; break; case STATE_WAIT_HEAD2: if(tmp FRAME_HEAD2) { rx_state STATE_WAIT_LEN; rx_len 0; rx_check 0; } else if(tmp ! FRAME_HEAD1) { rx_state STATE_WAIT_HEAD1; } break; case STATE_WAIT_LEN: if(tmp MAX_FRAME_LEN - 3) { rx_state STATE_WAIT_HEAD1; } else { rx_len tmp; rx_cnt 0; rx_state STATE_WAIT_DATA; } break; case STATE_WAIT_DATA: rx_buf[rx_cnt] tmp; rx_check ^ tmp; rx_cnt; if(rx_cnt rx_len) rx_state STATE_WAIT_CHECK; break; case STATE_WAIT_CHECK: if(tmp rx_check) { frame_ready 1; } rx_state STATE_WAIT_HEAD1; break; default: rx_state STATE_WAIT_HEAD1; break; } } }这段代码的思路是先用双字节帧头AA 55确认一帧的起始位置再读长度字节然后按长度收数据边收边做异或校验最后校验字节对上就置位frame_ready标志主循环里发现这个标志后开始解析。用状态机的好处非常明显任何时刻收到错数据都能回到等待帧头状态重新同步。哪怕是数据流中间丢了几个字节只要后面再出现完整的AA 55帧头依然能正确配对后续内容。我在实际项目里通常使用双字节帧头因为单字节帧头太容易被数据内容干扰。特别是当你传输的内容本身可能包含0xAA时单字节帧头就会出现假同步。双字节帧头虽然占用两个字节带宽但换来的是极其稳定的帧对齐效果这个代价完全值得。2.3 校验策略补齐异或校验基本够用上面的代码里用到的是异或校验这是一种非常轻量级但够用的校验方式。它的原理很简单把所有数据按字节异或结果作为校验字节放在帧尾。接收端同样把所有数据异或一遍如果结果与校验字节一致说明数据基本没问题。异或校验的优点是计算量极小不会给本来就紧张的中断服务函数增加太大压力。对于低速传感器数据、指令帧这类场景异或校验的可靠性已经足够。如果你传输的是关键控制数据而且波特率又高可以考虑把校验升级为CRC8或CRC16。STM32上做CRC很省事硬件外设一算就出来了但STC51没有硬件CRC只能软件模拟。软件CRC的查表法或逐位计算法都比较占资源放在中断里做会比较紧张。我的建议是如果必须用CRC先在中断里只收数据不进校验等完整一帧收完在主循环里再统一做CRC校验。这样能大幅度降低中断占用的时间代价是缓冲里要预留完整的帧数据。3. 坑二中断服务函数过于臃肿波特率稍高就丢数据3.1 问题表现高波特率下数据频繁丢失我遇到过不少读者拿代码来问“我用9600波特率收数据正常改到115200之后经常丢字节有时候甚至一个字节都收不对。”9600bps和115200bps的差距有多大9600bps收发一个字节大约需要1.04ms115200bps大约需要86.8微秒。这就意味着你的中断服务函数必须在86.8微秒内完成所有操作否则下一个字节的起始位来了你就来不及处理当前数据。再算一笔账STC51在12MHz晶振下普通指令一个机器周期大约1微秒部分增强型STC在1T模式下会快很多但基础51内核差不多就是这个量级。如果你的中断服务函数里写了上百行C代码编译优化又不到位光中断响应、现场保护、SBUF读取、状态切换这些操作可能就要消耗几十上百微秒。在这种情况下115200bps下丢字节就是必然的。3.2 解决方案中断里只管收和存解析放外面解决高波特率丢数据的核心原则我总结成一句话中断里只做最必要的事情能不做的一律不做。什么是最必要的事读SBUF、判断状态机、存数据、更新标志。除此之外的事情——数据解析、命令处理、超时判断、日志打印、LED闪烁——一个都不要放进中断函数。以状态机方案为例中断服务函数里的操作我已经尽量精简。但如果你觉得还不够快可以进一步优化把中断里的switch-case状态机换成查表法或直接跳转。虽然C代码写起来不太优雅但在老51上性能提升非常明显。下面这段是经过精简的版本思路是完全去掉复杂的switch-case用if-else直接按状态分支执行适合对中断时间要求极端的场景void Uart_Isr() interrupt 4 { uchar tmp; if(!RI) return; RI 0; tmp SBUF; if(rx_state STATE_WAIT_HEAD1) { if(tmp 0xAA) rx_state STATE_WAIT_HEAD2; return; } if(rx_state STATE_WAIT_HEAD2) { if(tmp 0x55) { rx_state STATE_WAIT_LEN; rx_check 0; rx_cnt 0; } else if(tmp ! 0xAA) { rx_state STATE_WAIT_HEAD1; } return; } if(rx_state STATE_WAIT_LEN) { rx_len tmp; rx_cnt 0; rx_state STATE_WAIT_DATA; return; } if(rx_state STATE_WAIT_DATA) { rx_buf[rx_cnt] tmp; rx_check ^ tmp; rx_cnt; if(rx_cnt rx_len) rx_state STATE_WAIT_CHECK; return; } if(rx_state STATE_WAIT_CHECK) { if(tmp rx_check) frame_ready 1; rx_state STATE_WAIT_HEAD1; return; } rx_state STATE_WAIT_HEAD1; }我在很多项目里实测过这种精简写法比switch-case版本在中断响应时间上能快20%以上。虽然switch-case在C语言层面看着很规整但编译成汇编后可能会生成跳转表或者一堆比较跳转指令相比之下if-else的逻辑路径更短。3.3 硬件与波特率配置的配合除了中断函数本身STC51的波特率生成方式也会影响接收的稳定性。STC89C52RC这类经典芯片常用的波特率发生器是定时器1工作方式28位自动重装。这种方式的好处是定时器溢出后自动重装初值不需要在中断里手动赋值不会因为赋值延迟带来定时误差。关键点是选择波特率倍数模式。STC单片机支持波特率加倍SMOD位一般我们设置为不加倍因为不加倍时定时器初值比较大对波特率误差的容忍度更好。以11.0592MHz晶振为例不加倍、波特率9600定时器1初值就是0xFD加倍后初值变为0xFA计算误差略大。看似差别不大但在多字节连续传输场景下每一个字节的累积位误差都可能影响接收正确性。另外我强烈建议用11.0592MHz的晶振做串口通信。因为这个频率是串口通信的“黄金频率”不管波特率是9600、19200、38400还是115200都能算出误差极小的定时初值。你要是用12MHz晶振跑串口波特率高的时候误差会显著增大传输不稳定几乎是必然的。这一点我在很多项目里反复验证过不想在硬件选型上吃亏的话直接上11.0592MHz。4. 坑三缓冲区溢出与数据覆盖多帧连发时数据错乱4.1 问题表现连续多发几帧后一帧覆盖前一帧很多人用的是数组存接收数据定长数组比如rx_buf[16]。如果上位机一秒钟内发了好几帧每帧之间的间隔又很短那么当主循环还没来得及把上一帧数据解析走时新一帧的数据就已经来了。此时接收状态机还在工作新数据会直接覆盖旧数组里还没处理的内容。我曾经做过一个设备上位机以50ms为周期发送状态查询指令每次发完指令后紧接着查询设备返回的数据。最开始用的就是定长数组结果经常出现解析出来的命令和返回数据对不上。后来一查原因就是缓冲区只有一份新帧来了直接覆盖旧帧而主循环还在处理旧帧的解析。这种问题的本质是接收缓冲区没有采用“先入先出”的队列结构而是单一共享区读写之间没有隔离机制。4.2 解决方案环形缓冲区FIFO解决覆盖问题环形缓冲区是单片机串口接收里最常见、最实用的数据结构。它的核心思路是用一个数组和一个读写指针写指针由中断服务函数更新读指针由主循环维护两者互不干扰。只要写指针没有追上读指针数据就不会丢失。下面是一个适用于STC51的环形缓冲区实现#define RX_BUFFER_SIZE 32 volatile uchar rx_ring[RX_BUFFER_SIZE]; volatile uchar rx_write_index 0; volatile uchar rx_read_index 0; // 中断里调用存入数据 void RxBuf_Push(uchar dat) { uchar next (rx_write_index 1) % RX_BUFFER_SIZE; if(next ! rx_read_index) // 缓冲区未满 { rx_ring[rx_write_index] dat; rx_write_index next; } // 如果满了就丢弃当前数据 } // 主循环/解析代码里调用取出一字节 bit RxBuf_Pop(uchar *pdat) { if(rx_write_index rx_read_index) return 0; *pdat rx_ring[rx_read_index]; rx_read_index (rx_read_index 1) % RX_BUFFER_SIZE; return 1; } // 查询缓冲区有多少字节未读 uchar RxBuf_Count(void) { return (uchar)(rx_write_index - rx_read_index); }注意这里的缓冲区大小是32字节对应到环形队列实际可用空间是31字节因为必须留一个格区分“满”和“空”。当写指针追上读指针时说明缓冲区满了此时我选择丢弃新数据。如果你希望满了之后覆盖旧数据只要改一下判断逻辑即可但一般来说丢新不丢旧在通信场景里更合理因为旧数据总是比新数据更接近被处理的状态。使用环形缓冲区后接收状态机和缓冲区解耦了。状态机在中断里每收到一个字节就往环形缓冲区里推数据。主循环负责从缓冲区里弹出数据再送入状态机解析。即使主循环处理稍慢只要缓冲区没满数据就不会丢。缓冲区大小可以根据实际项目调整最小一帧数据的两倍比较保险避免帧到达时缓冲区已满。4.3 主循环解析与中断接收的分工接到环形缓冲区之后整个架构就清晰了。中断服务函数只做两件事读SBUF、往环形缓冲区推数据。主循环里则集中做从缓冲区弹数据、跑状态机、解析完整帧、执行命令。这种分工的好处是把接收和解析彻底解耦。接收是实时的绝对不能丢解析是准实时的慢一点没关系。哪怕主循环刚处理到一半又来了新的一帧新数据也只会进缓冲区不会影响正在解析的那份数据。具体的解析循环写起来也不复杂void Process_Rx_Data(void) { uchar dat; while(RxBuf_Pop(dat)) { // 在这里跑帧同步状态机 // 状态机和前面写的一样把tmp换成dat即可 } if(frame_ready) { frame_ready 0; Parse_Frame(); // 真正的业务解析函数 } }在实际项目里我习惯把状态机放在一个独立的函数里由主循环周期调用而不是放在中断里。这样好处很明显中断函数极其精简就算波特率跑到115200以上也不会因为中断处理时间过长而丢字节。唯一要注意的是主循环调用Process_Rx_Data的频率要足够快建议在主循环的每次迭代里都调用一次。如果主循环里有其他耗时的阻塞操作比如等待某个传感器就绪、或者延时函数过长就会导致缓冲区堆积最终溢出丢数据。所以设计主程序时我通常会把延时做成非阻塞方式或者把串口解析放到定时器中断里周期执行。比如用定时器2做一个1ms的节拍在节拍中断里调用Process_Rx_Data这样即使主循环里有一些耗时操作也能保证串口数据被及时处理。5. 完整可运行的代码与调试工具实操5.1 一套可直接移植的STC51串口接收代码把上面几个方案整合起来这里给出一份完整的、经过我实际验证的STC89C52RC串口多字节接收工程代码。代码里包含定时器1配置串口、中断接收、环形缓冲区、状态机解析和主循环处理逻辑。你可以直接复制到Keil里建工程使用。#include reg52.h #define FOSC 11059200L #define BAUD 9600 #define FRAME_HEAD1 0xAA #define FRAME_HEAD2 0x55 #define MAX_DATA_LEN 12 typedef unsigned char uchar; typedef unsigned int uint; // 环形缓冲区 #define RX_BUFFER_SIZE 32 volatile uchar rx_ring[RX_BUFFER_SIZE]; volatile uchar rx_write_index 0; volatile uchar rx_read_index 0; // 帧状态机变量 volatile uchar rx_state; volatile uchar rx_len; volatile uchar rx_cnt; volatile uchar rx_check; volatile uchar rx_data[MAX_DATA_LEN]; volatile bit frame_ready; void Uart_Init(void) { // 波特率96008位数据1位停止位 SCON 0x50; // 模式1REN1允许接收 TMOD 0x0F; // 定时器1保持原有模式 TMOD | 0x20; // 定时器1工作方式28位自动重装 TH1 TL1 0xFD; // 11.0592MHz下9600波特率初值 PCON 0x7F; // SMOD0波特率不加倍 ES 1; // 开串口中断 EA 1; // 开总中断 TR1 1; // 启动定时器1 } void RxBuf_Push(uchar dat) { uchar next (rx_write_index 1) % RX_BUFFER_SIZE; if(next ! rx_read_index) { rx_ring[rx_write_index] dat; rx_write_index next; } } bit RxBuf_Pop(uchar *pdat) { if(rx_write_index rx_read_index) return 0; *pdat rx_ring[rx_read_index]; rx_read_index (rx_read_index 1) % RX_BUFFER_SIZE; return 1; } void Uart_Isr() interrupt 4 { uchar tmp; if(RI) { RI 0; tmp SBUF; RxBuf_Push(tmp); } } void Frame_Parser(uchar dat) { switch(rx_state) { case 0: if(dat FRAME_HEAD1) rx_state 1; break; case 1: if(dat FRAME_HEAD2) { rx_state 2; rx_len 0; rx_cnt 0; rx_check 0; } else if(dat ! FRAME_HEAD1) { rx_state 0; } break; case 2: if(dat MAX_DATA_LEN) { rx_state 0; } else { rx_len dat; rx_cnt 0; rx_check 0; rx_state 3; } break; case 3: rx_data[rx_cnt] dat; rx_check ^ dat; rx_cnt; if(rx_cnt rx_len) rx_state 4; break; case 4: if(dat rx_check) { frame_ready 1; } rx_state 0; break; default: rx_state 0; break; } } void Process_Rx(void) { uchar dat; while(RxBuf_Pop(dat)) { Frame_Parser(dat); } } void main(void) { uchar i; Uart_Init(); while(1) { Process_Rx(); if(frame_ready) { frame_ready 0; // 在这里解析处理rx_data[0] ~ rx_data[rx_len-1] // 例如把接收的帧原样回发方便调试 SBUF FRAME_HEAD1; while(!TI); TI 0; SBUF FRAME_HEAD2; while(!TI); TI 0; SBUF rx_len; while(!TI); TI 0; for(i 0; i rx_len; i) { SBUF rx_data[i]; while(!TI); TI 0; } SBUF rx_check; while(!TI); TI 0; } } }5.2 串口调试助手的正确用法代码写好了怎么测最靠谱我推荐用串口调试助手配合虚拟串口或者USB转TTL模块来做测试最常用的工具就是XCOM、友善串口助手这类软件。测试前先确认硬件连接STC51的TXD引脚接USB转TTL模块的RXDSTC51的RXD引脚接USB转TTL模块的TXDGND必须共地。很多新手的第一次串口测试失败原因就是TXD和RXD接反了或者忘了共地。打开串口调试助手后选择正确的COM口号波特率设为9600数据位8停止位1校验位None。然后在下方的发送区填入你的测试帧比如AA 55 03 11 22 33 21这里AA 55是帧头03是数据长度3个字节11 22 33是数据21是前面所有字节的异或校验结果AA^55^03^11^22^33 0x21。你可以用计算器算一下或者写个小工具自动生成校验。如果程序跑通了串口调试助手的接收区应该会显示一模一样的帧。如果显示的帧不对先看是不是接线问题再看波特率有没有配错最后再检查状态机逻辑。我自己的调试习惯是先用9600波特率把逻辑跑通确认无误后再调高波特率测试极限性能。这样能区分是代码逻辑问题还是高频传输问题。直接拿115200调试出了问题很难判断到底是哪个环节出错了。6. 常见问题与排查技巧实录6.1 问题速查表我把实际项目中经常遇到的串口接收问题整理成一张速查表你可以对照排查现象可能原因排查方法完全收不到数据TXD/RXD接反、共地缺失、波特率不匹配万用表测电平、确认接线、核对串口助手波特率收到数据但内容全乱波特率误差过大、帧格式定义不一致检查晶振频率、串口助手设置是否一致低波特率正常高波特率丢字节中断处理时间过长、主循环阻塞精简中断函数、把解析放到主循环或定时器节拍中偶尔丢失某一帧环形缓冲区溢出、主循环处理不及时增大缓冲区、缩短主循环周期、加超时保护帧内容错位但帧头能对上帧长度字段异常、状态机未正确复位检查长度字段合法性判断、确保校验失败后复位状态机能收到数据但校验总失败校验计算错误、数据中有转义字符统一校验算法、确认帧格式中是否包含帧头帧尾6.2 排查步骤从硬件到软件逐步定位串口问题排查我建议遵循“由下而上”的顺序不要一上来就怀疑代码逻辑。第一步检查电气连接。串口通信看似简单但TXD/RXD的交叉连接太容易搞错。USB转TTL模块上一般都有标注但还是建议用万用表确认一下TXD引脚和RXD引脚的电平。空闲状态下TXD和RXD都应该是高电平3.3V或5V视模块而定。第二步确认波特率误差。用示波器看单片机TXD引脚的波形测量一帧数据的位宽和理论值对比误差超过3%就要考虑换晶振或者调整波特率参数。如果没有示波器可以用串口调试助手发送0x55然后在逻辑分析仪上看波形0x55的二进制是01010101每一位都翻转能很直观地看出波特率是否准确。第三步用轮询方式验证接收。先把中断关掉在主循环里用轮询RI标志的方式接收数据。如果轮询能收到正确数据说明硬件和波特率没问题如果轮询也不行那就是硬件或配置问题。轮询验证通过后再打开中断一步步加入状态机和缓冲区这样能准确定位是哪一步导致的问题。6.3 两个实战经验中断优先级和掉电时序最后分享两个不算常见但很实用的经验。第一个经验是关于中断优先级的。STC51的串口中断默认优先级较低如果同时用了定时器中断而且定时器中断执行时间比较长串口中断就有被延迟响应的风险。在STC单片机里可以通过IP寄存器手动调整中断优先级把串口中断设为高优先级确保串口数据到达时能立刻获得响应。第二个经验是掉电和数据接收的关系。如果设备用的是电池供电在电压不足时SRAM数据可能出现位翻转缓冲区里的内容自然也就不可靠了。稳妥的做法是在检测到低压时先停止串口接收或者干脆把单片机进入掉电模式避免处理不可信的数据。当然如果项目对可靠性要求没那么高这一步可以不做。但对于工业现场设备我是建议一定要考虑供电稳定性对串口接收的影响。6.4 最终调通后的性能参考按上面的方案调通后我实测过几个常见参数下的性能表现STC89C52RC11.0592MHz晶振中断接收主循环解析波特率帧长帧间隔连续发送100帧成功率96008字节10ms100%192008字节5ms100%384008字节2ms99%以上576008字节1ms98%左右1152008字节1ms95%左右受主循环任务影响明显高波特率下性能会受主循环负载的影响如果你主循环里还有其他耗时操作115200下丢帧会加剧。这种情况下建议把Process_Rx放到定时器中断节拍里调用或者换用STC15/STC8系列1T内核的单片机性能会显著提升。说到这我再强调一件事STC51串口多字节接收真正的核心不是“接了多少个字节”而是“怎么从字节流里准确还原出完整的一帧”。只要抓住状态机、精简中断、环形缓冲区这三个关键点多字节接收就能稳定可靠。即便以后换到STM32或者其他MCU这套思路依然成立只是API不同罢了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱 2026/9/28 9:42:31

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱

快速搭建网站的工具怎么选?3个方案省下5万冤枉钱 网站做好了没人访问,这是很多老板最头疼的事。你花大价钱做的官网,设计精美、功能齐全,但打开一看,流量为零,咨询为零。这时候你才意识到,问题不在“做没做”,而在“怎么快速做出来并推向市场”。面…

阅读更多 →
昇腾910B多机分布式推理:从HCCL到MindIE的DeepSeek部署实践 2026/9/28 9:42:24

昇腾910B多机分布式推理:从HCCL到MindIE的DeepSeek部署实践

昇腾910B上跑DeepSeek多机分布式推理,很多人卡在第一眼:MindIE、HCCL、ranktable、hccn_tool,每个词都眼熟,串起来就不是那么回事。实际踩过一圈之后你会发现,真正决定能不能跑起来的不是模型代码,而是通信…

阅读更多 →
从CANoe到TSMaster:车载总线测试工具链迁移实战指南 2026/9/28 9:42:24

从CANoe到TSMaster:车载总线测试工具链迁移实战指南

搞车载总线测试的工程师,电脑里大概率都装着一套CANoe。我最早接触CANoe是刚入行那会儿,跟着前辈在项目里做网络测试,从报文发送、DBC解析到UDS诊断,基本全是靠Vector这套工具撑起来的。说实话,CANoe确实是这个行业的标…

阅读更多 →
从刷榜到用榜:GitHub Trending 的增量逻辑、项目筛选与高效落地 2026/9/28 9:42:23

从刷榜到用榜:GitHub Trending 的增量逻辑、项目筛选与高效落地

1. 日榜的"热度"到底是怎么算出来的先别急着收藏仓库。每天打开 GitHub 的 Trending 页面,你看到的是过去 24 小时内 Star 增量最高的仓库,周榜和月榜则分别看一周、一个月内的增量。官方没有公开完整排序算法,但用久了会发现&…

阅读更多 →
【Java开发MCP】SSE模式开发并集成MCP:TaoToken统一Key接入与SpringAI WebFlux配置骨架 2026/9/28 9:42:23

【Java开发MCP】SSE模式开发并集成MCP:TaoToken统一Key接入与SpringAI WebFlux配置骨架

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

阅读更多 →
OpenCompass 高效评测:Partitioner 任务切分与 Runner 执行后端实战指南 2026/9/28 9:42:23

OpenCompass 高效评测:Partitioner 任务切分与 Runner 执行后端实战指南

模型评测人工智能大模型AI 评测 【免费下载链接】opencompass OpenCompass is an LLM evaluation platform, supporting a wide range of models from OpenAI, Anthropic, Gemini, Qwen, GLM, DeepSeek, etc, across 100 datasets covering knowledge, reasoning, coding, scie…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉