news 2026/10/16 20:02:03

STM32 Modbus RTU从站移植实战:RS-485通信与寄存器映射

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 Modbus RTU从站移植实战:RS-485通信与寄存器映射

简介:面向嵌入式开发者的STM32F10x标准外设库Modbus通信工程包,适合需要移植Modbus主从协议、完成STM32与上位机或传感器数据联调的工程师与学生。核心程序经过作者多次实测,确认稳定可用,可直接在Keil工程中打开、编译和烧录,适合作为学习模板或项目改造基础。压缩包共107个文件、大小约4.69MB,主体由48个C源文件与46个头文件组成,覆盖USART、定时器、ADC、I2C、CAN、RCC等常见外设驱动,另有汇编启动文件、uvprojx/uvoptx工程文件、hex烧录固件、批量清理脚本、CRC计算助手exe以及PDF/txt辅助文档,目录划分较清晰。目前已有3457人学习下载,被不少开发者用作STM32+Modbus的入门借鉴。除了完整Modbus通信示例,包内还集成FreeModbus 1.6协议栈与CRC校验工具,配合作者博客的教程说明,可快速打通外设配置、协议移植、报文解析到校验计算的整条链路,有效降低上手门槛和排错成本。

1. 这个 zip 到底能干什么:STM32 上的 Modbus 从站模板

拿到一份叫stm32 for modbus.zip的代码包,别急着把它当“官方例程”往工程里拖。它通常是一套完整的 Modbus RTU 从站工程模板:串口收发、CRC 校验、功能码分发、寄存器读写都替你封装好了。但能不能在你的板子上跑起来,取决于你愿不愿意把帧边界、串口时序和 485 方向切换这几个问题重新过一遍。

它能解决的事情很具体:让一块 STM32 通过 RS-485 被上位机、触摸屏或 PLC 当做一个标准从站来读写寄存器。适合正在做设备联网、传感器采集、工业屏对接的开发者,也适合刚把 UART 调通、想往协议层走一步的初学者。我见过不少人把时间耗在“协议写不出来”上,真正卡住的却往往是 DE 引脚和串口中断之间的那十几毫秒时序。这篇就按工程落地的顺序,把每个环节讲透。

2. 工程结构与移植入口:先分清协议层和硬件适配层

很多人把一个模板用不好,是因为不知道哪部分代码可以原样保留,哪部分必须重写。拿到 zip 之后第一步不是编译,而是先做职责拆解。

2.1 一个 Modbus 从站工程的三个层次:协议层、寄存器层、硬件层

Modbus 从站代码按职责可以切成三层,这个分层思路比任何具体文件命名都重要。

协议层负责帧格式、CRC 计算、功能码解析。它跟芯片型号没有关系,只要编译器支持 uint8_t、uint16_t 就能跑。只要是标准 RTU 帧,无论 F1 还是 F4 还是 G0 系列,这一层代码完全一致,所以 zip 里这一部分可以直接保留。

寄存器层是你暴露给外部世界的数据模型。Modbus 从站本质上就是一组地址连续的 16 位寄存器,外部主站通过功能码去读或写。你的设备有哪些参数、每个参数放在哪个地址、是只读还是可写,都在这一层决定。这一层必须按你的实际设备字段重新设计,不能照抄。

硬件层就是串口初始化、GPIO 配置、485 收发器的方向控制、时钟树。这一层跟你的板子强相关,引脚不同、晶振不同、485 芯片不同,代码都要改。移植时最容易翻车的也集中在这一层,尤其是时钟频率不对导致波特率偏差。

选型上,如果 zip 里同时给了 RTU 和 ASCII 两套实现,直接选 RTU。ASCII 帧里有起始符结束符、数据用十六进制字符表示,同样传 32 个字节的数据帧,ASCII 要多付出一倍的字节开销,而且大多数工控屏和 PLC 默认优先支持 RTU。TCP 当然更干净,但它需要以太网硬件,不属于这个 zip 的适用范围。

2.2 拿到 zip 后先做三件事:按型号裁剪文件、确认时钟、确认引脚

第一步,看压缩包里的工程是不是按芯片系列分目录。常见做法是按照 F1、F4、L4 等系列分别建工程,因为外设库或 HAL 库版本不同。你要是用 F1 却打开了 F4 的工程,编译报错会先出现在头文件包含上,而不是协议代码本身。裁剪原则是:只保留与你芯片系列匹配的启动文件和驱动文件,协议层文件全部保留,寄存器层文件暂时保留默认实现,等跑通后再改。

第二步,确认时钟树。串口波特率是靠外设时钟分频出来的,如果系统主频不是预期值,实际波特率会偏。比如外部晶振没焊接,代码却按外部晶振配置 PLL,系统时钟可能跌到内部 RC 的几分之一,485 总线上表现为“时通时断、CRC 偶尔错”。先打开调试器看 SystemCoreClock,或者直接看时钟配置寄存器,确保数值与预期一致,再碰 Modbus。

第三步,确认 485 收发器的 DE/RE 引脚。大多数 RS-485 收发器芯片把 DE 和 RE 并成一个方向控制脚,高电平发送、低电平接收。你只需要一个 GPIO。有些板子为了省 IO 把这两个脚分开接,那就要发两个 GPIO 信号,并且注意 RE 是低电平有效。确认完这三件事,再开始动代码。

做移植时我一般会按职责拆文件,不管 zip 里原来的命名是什么,按职责对号入座:

职责典型文件是否要改
协议帧解析与 CRCmodbus.c / modbus.h一般不改
寄存器数据模型regs.c / regs.h必须改
串口与 GPIO 驱动usart.c / gpio.c必须改
主循环调度main.c按需改

一个容易忽略的点:如果板子的串口引脚做了重映射,比如 F1 上 USART1 的 TX/RX 默认在 PA9/PA10,但你的板子把它们重映射到了 PB6/PB7,那必须开启 AFIO 重映射并配置对应引脚。串口数据能自发自收,但 485 对端收不到,多半就是这个原因。调这种问题不要先怀疑协议,先用示波器或逻辑分析仪看 TX 引脚上有没有波形。

3. 协议核心实现逻辑:帧边界、CRC 与寄存器映射

协议层是整个 zip 里最值得逐行读的部分。Modbus RTU 的难点不在协议本身复杂,而在它太简单,简单到任何细节出错都会被上层误判成设备故障。

3.1 RTU 帧格式与 3.5 字符间隔的真正含义

RTU 一帧的排列是:从站地址 1 字节、功能码 1 字节、数据区 N 字节、CRC 校验 2 字节。地址 0 是广播地址,从站收到后要执行但不回复;地址 1 到 247 是单播地址,从站必须回复。数据区的长度由功能码决定,比如读保持寄存器 0x03 的请求帧固定是 8 字节,数据区里是起始地址 2 字节加寄存器数量 2 字节。

这里最关键的是“帧结束”怎么判断。RTU 规定,一帧之内相邻两个字节的间隔不能超过 3.5 个字符时间,超过就算上一帧结束、新帧开始。字符时间的计算不是简单用 1/波特率,一个字节在串行线上要发 11 位:1 位起始位、8 位数据位、1 位校验位、1 位停止位。所以在 9600 波特率下,3.5 个字符时间约等于 38.5 位时间,也就是 4.01 毫秒;在 115200 波特率下只有约 0.334 毫秒。

这个数值是你在做接收超时判断时的唯一依据。不要用固定 10 毫秒、20 毫秒去套,那是在赌,不是在写协议。波特率变了,这个值必须跟着变。

3.2 CRC 校验与帧解析代码:通过校验再执行

Modbus RTU 的 CRC 是 CRC-16/MODBUS 变体:初值 0xFFFF,多项式 0x8005 反向写成 0xA001,输出结果要异或 0xFFFF。下面是标准的位运算法,不依赖查表,也很好验证。

static uint16_t crc16_modbus(const uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; /* MODBUS CRC 初值固定为 0xFFFF */ while (len--) { crc ^= *data++; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; /* 0xA001 是 0x8005 的反向多项式 */ } else { crc >>= 1; } } } return crc; }

这段代码的逻辑:每个字节先与 CRC 低 8 位异或,然后循环 8 次,每次判断最低位,如果为 1 就右移并异或多项式,为 0 就直接右移。len是参与计算的字节数。对 Modbus RTU 来说,CRC 计算范围是从站地址到数据区最后一个字节,不包含 CRC 本身。发送时 CRC 低字节在前、高字节在后,这一点很多人在组响应帧时写反。

验证 CRC 用一组已知向量最省事:对报文01 03 00 00 00 01,CRC 结果应该是0x840A,发送时先发0x0A再发0x84。你要是手头没有现成工具,就用这组向量验。

帧解析部分,我习惯把“收完一整帧”和“处理这一帧”分成两段。接收端只负责按帧边界攒齐一帧,攒齐后调用解析函数。解析函数先做三件事:地址过滤、长度过滤、CRC 校验,全部通过才允许进入功能码分发。

void modbus_frame_handler(uint8_t *buf, uint16_t len) { uint16_t crc_calc; uint16_t crc_recv; if (buf[0] != 0 && buf[0] != dev_addr) { return; /* 既不是广播也不是本机地址,整帧丢弃 */ } if (len < 4) { return; /* 最短帧也要 地址+功能码+2字节CRC */ } crc_calc = crc16_modbus(buf, len - 2); crc_recv = (uint16_t)buf[len - 1] << 8 | buf[len - 2]; if (crc_calc != crc_recv) { return; /* CRC 不过,直接丢,不回异常帧 */ } switch (buf[1]) { case 0x03: read_holding_regs(buf[2], buf[3], buf[4], buf[5]); break; case 0x06: write_one_reg(buf[2], buf[3], buf[4], buf[5]); break; case 0x10: write_multi_regs(buf); break; default: send_exception(buf[1], 0x01); /* 非法功能码 */ break; } }

dev_addr是工程里的从站地址变量,一般在 1 到 247 之间。read_holding_regs的四个参数是起始地址高字节、低字节、寄存器数量高字节、低字节,因为 Modbus 里 16 位字段都是大端传输,高字节在前。这里有个容易踩的细节:如果 CRC 不对,千万不要回异常帧。工业现场总线上可能挂着多台从站,CRC 错的帧大概率是干扰或别人家的帧,回了异常帧反而会把总线搞乱。

功能码分发之后的行为要遵守一条原则:读操作越界或数量为 0,回异常码 0x02;写操作地址合法但值超出范围,回异常码 0x03;功能码不认识,回异常码 0x01。异常响应帧的格式是地址、功能码最高位置 1、异常码、CRC。

3.3 寄存器映射:用数组还是地址偏移

寄存器层设计决定了你的设备在别人眼里“好不好用”。最常见、也最推荐的做法是直接用uint16_t数组做保持寄存器映射。外部看到的是 40001 开头的地址,内部数组下标从 0 开始,转换关系就是:PLC 地址减 40001 得到数组下标,也就是协议地址。比如外部地址 40003,协议地址是 2,直接访问holding_regs[2]。

用数组的好处是功能码 0x03、0x06、0x10 的实现都变成简单的下标读写,代码路径统一。结构体也很直观,但结构体有对齐和填充问题,而且你要是想动态计算某个参数的 Modbus 地址,还得自己维护偏移表。除非你的寄存器数量很少且固定,否则我一般不推荐在 Modbus 从站里直接用结构体做映射。

#define HOLDING_REGS_NUM 64 static uint16_t holding_regs[HOLDING_REGS_NUM]; static void read_holding_regs(uint16_t start, uint16_t count) { uint8_t resp[256]; uint16_t idx; if (start + count > HOLDING_REGS_NUM) { send_exception(0x03, 0x02); /* 越界,回非法数据地址 */ return; } resp[0] = dev_addr; resp[1] = 0x03; resp[2] = (uint8_t)(count * 2); /* 数据字节数 */ for (idx = 0; idx < count; idx++) { resp[3 + idx * 2] = holding_regs[start + idx] >> 8; resp[4 + idx * 2] = holding_regs[start + idx] & 0xFF; } modbus_send(resp, 3 + count * 2); }

start + count > HOLDING_REGS_NUM这一步必须用大于而不是大于等于,因为 Modbus 协议里起始地址和数量都是基于 0 的,地址 0 + 数量 64 正好覆盖到下标 63,是合法的。实际工程里寄存器数量 64 个已经能覆盖很多设备了,超过 125 个就要考虑 Modbus 协议本身对单次读写数量 0x7D 的限制。不要把数组开得过大,每个寄存器 2 字节,256 个也就是 512 字节 RAM,但协议帧长度限制会先卡住你。

4. 收发通路与 485 方向切换:中断、DMA 和那个 GPIO

协议层写完后,真正的工程量在收发通路。串口配置错一个参数,协议写得再对也白搭。

4.1 串口参数怎么设:波特率、校验位与帧间隔的计算

Modbus RTU 常用串口参数组合是 8 位数据位、1 位停止位、无校验,也就是 8N1。也有一部分设备用偶校验 8E1,你需要在工程里留一个宏配置。注意 Modbus 协议里的 CRC 负责整个帧的完整性校验,串口校验位只是针对单个字节的奇偶校验,两者不冲突,但千万不要开成 9 位数据位,那会让帧字节错位。

参数推荐值备注
波特率9600 / 19200 / 1152009600 抗干扰最好,115200 响应快
数据位8固定
停止位1 或 2常用 1
校验位无或偶校验通过宏切换
帧间隔3.5 字符时间必须随波特率计算

帧间隔的计算直接在代码里写成宏,不要写死毫秒数。9600 波特率下大约是 4 毫秒,115200 下大约是 0.33 毫秒。你如果做的是主从轮询,还要同时考虑主站的轮询周期,从站帧间隔算错会导致主站把一帧拆成两帧处理,表现就是“第一条指令偶尔没响应,重发就好了”。

4.2 两种收帧方案:字节中断 + 定时器 vs DMA + 空闲中断

收帧方案有两种主流做法。第一种是每个字节都进串口接收中断,在中断里判断与上一字节的间隔是否超过 3.5 字符时间,超过就认为上一帧结束。这个方案简单直观,适合帧不长、CPU 负载不重的场景。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* 与上一字节间隔超过 3.5 字符时间,说明上一帧已经结束 */ if (time_now - last_rx_time > char_time_3_5) { rx_cnt = 0; /* 新帧从当前字节开始 */ } rx_buf[rx_cnt++] = rx_tmp; last_rx_time = time_now; HAL_UART_Receive_IT(&huart1, &rx_tmp, 1); /* 继续接收下一字节 */ } }

time_now一般用系统滴答定时器的毫秒计数,char_time_3_5是按当前波特率算出的帧间隔阈值。这个代码的关键在判断顺序:必须先把当前时间存到last_rx_time,再继续使能接收,否则中断里一旦有更高级的中断抢占,时间基准会漂。rx_tmp是单字节缓冲区,rx_buf开 256 字节足够,因为 Modbus RTU 最长报文就是 256 字节。

第二种方案是 DMA + 串口空闲中断。DMA 把数据搬运到缓冲区,空闲中断表示总线上超过一个字符时间没有新数据,借此判定一帧结束。这个方案对 CPU 开销小,但代码复杂度高一些,而且空闲中断标志的清除顺序有讲究:要先读串口状态寄存器再读数据寄存器,顺序反了会漏掉下一帧的开头。对于 256 字节以内的 Modbus 帧,DMA 的优势其实没有想象中那么大,因为每个字节都要被解析,DMA 节省的只是中断次数。我个人的原则是:项目简单就用字节中断,帧长、中断频繁或者主循环任务重再上 DMA。

4.3 485 方向切换:DE/RE 引脚别等错标志位

485 是半双工,同一时刻只能有一个节点驱动总线。从站平时处在接收态,DE 引脚拉低;要回复时切到发送态,DE 拉高;发完再切回接收态。这个切换的时机比你想的更严格。

void modbus_send(uint8_t *data, uint16_t len) { modbus_rs485_dir(1); /* 先拉高 DE,切换到发送 */ HAL_UART_Transmit(&huart1, data, len, 100); /* 关键:必须等 TC,而不是等 HAL_UART_Transmit 返回 */ while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET) { } modbus_rs485_dir(0); /* 确定发完,再切回接收 */ }

HAL_UART_Transmit是阻塞发送,但它内部判断的是 TXE(发送数据寄存器空),意思是数据已经从数据寄存器搬到了移位寄存器。最后一个字节此刻还在移位寄存器里一位一位往外送,如果你在这时候把 DE 拉低,总线上最后两个字节的 CRC 会被硬生生掐断。我见过一次调试现场,现象是主站每次都要重发三次才能收到完整响应,逻辑分析仪一看,从站响应帧最后总是缺两个字节。

正确做法是等TC(发送完成)标志。TC 表示移位寄存器已经全部送出,总线已经回到空闲电平,这时候切方向才安全。还有两个细节:DE 拉高到真正开始发送数据之间,最好加一小段延时,让总线稳定,一般几微秒到几十微秒;从发送态切回接收态之后,也不要立刻使能接收中断,先等总线电平完全释放,否则可能把回声当成数据收进来。

5. 避坑清单:从站程序最常见的 6 个故障点

这一章有价值的部分全在现象和解决里。每一条都是我实际调试中遇到过的,按频率排序。

坑一:固定延时判断帧结束,导致间歇性超时 现象:上位机发指令,从站有时正常回复,有时完全没反应,重发一次或几次后又正常。 原因:接收代码里用固定 10 毫秒或 20 毫秒判断帧结束。9600 波特率下 3.5 字符时间才 4 毫秒,10 毫秒勉强能用;切到 115200 后,3.5 字符时间变成 0.33 毫秒,一帧内字节间隔稍微大一点就被误判成新帧,帧被拆碎,CRC 永远过不了。 解决:帧间隔阈值按 38.5 位时间除以波特率计算,代码里用宏定义并根据当前波特率赋值。

坑二:CRC 多项式用错,从站不回帧 现象:手动用串口助手发标准请求帧,从站毫无反应;用调试器看,接收缓冲里的数据确实到了,CRC 校验就是不通过。 原因:把标准 CRC-16 当成 Modbus CRC 用,或者位运算法方向搞反。标准 CRC-16 的初值和多项式与 Modbus 变体不同,计算出来的校验值当然对不上。 解决:确认初值 0xFFFF、多项式 0xA001、输出异或 0xFFFF。用向量01 03 00 00 00 01验证,CRC 应为0x840A,发送顺序是0x0A 0x84。

坑三:485 切回接收太早,CRC 尾字节被截断 现象:主站收到的响应帧比预期短两个字节,或者最后一两个字节乱码,CRC 校验失败。 原因:发送函数在HAL_UART_Transmit返回后立刻拉低 DE,没等 TC 标志。数据还在移位寄存器里,总线就切成了接收态,发送方向的三态门被关闭,剩余位送不出去。 解决:发送循环等待UART_FLAG_TC置位后再拉低 DE。检查代码里是不是只用了HAL_UART_Transmit的返回值做判断。

坑四:上位机能连上,寄存器值却全是乱的 现象:主站能正常读到从站响应,数据长度、CRC 都没问题,但数值对不上,比如发的 0x1234 读回来是 0x3412。 原因:字节序问题。Modbus 规定 16 位数据高字节在前,但有些工程代码在组帧时先发了低字节,或者你的寄存器里同时存在 16 位和 32 位数据,32 位数据的字序没有统一。 解决:在工程里加一个字节序配置宏,默认按大端组帧。对 32 位浮点或长整型,额外约定字序,在协议文档里写清楚,别让上位机去猜。

坑五:上电后第一次通信失败,之后一切正常 现象:设备上电后,主站发的第一条指令没有响应,第二条开始正常。 原因:GPIO 初始化顺序问题。如果 DE 引脚在串口初始化之后才被配置,或者复位后引脚默认为高电平,485 收发器会在上电瞬间处于发送态,此时从站还没准备好接收。还有一种情况:发送完响应后没有可靠切回接收态,下一次请求到来时从站还停在发送方向。 解决:初始化顺序固定为:先配置 DE/RE 引脚并拉低,再初始化串口,最后使能接收中断。发送函数末尾必须等 TC 后再拉低 DE。

坑六:中断里做 CRC 和功能码解析,拖垮主循环 现象:通信功能正常,但主循环里的其他任务变慢,比如按键响应迟钝、脉搏计数值跳变,严重时看门狗复位。 原因:字节接收中断里不仅搬运数据,还跑了 CRC 位运算、功能码分发,甚至偶尔写 Flash。Modbus 帧长时中断占用率飙升,主循环得不到调度机会。 解决:中断里只做字节搬运和时间戳记录,帧攒齐后置一个标志,CRC 校验和功能码处理放到主循环里执行。如果实时性要求高,就丢给一个低优先级任务处理。

6. 从从站到主站:验证方法与一个扩展技巧

这套模板跑通从站后,你可以把它改造成主站,用来测试其他从站设备,这个过程反过来会加深你对帧格式的理解。

6.1 用串口工具加 USB 转 485 做完整回归

最可靠的验证链是:USB 转 485 模块接到你的板子 A-B 线,电脑端串口助手设置与从站完全一致的波特率和校验位。先手动发一帧完整的读请求,比如地址 01、功能码 03、起始地址 0000、数量 0001、CRC 按公式算。看从站是否正确回 7 个字节。然后依次验证写单个寄存器、写多个寄存器、越界地址、非法功能码这几类场景。越界时从站应回异常码,非法功能码也回异常码,这两条能确认你的异常处理路径是通的。

6.2 把模板改成主站的三个改动点

从站变主站,核心是三个改动:一是构造请求帧并主动发送;二是发送后进入等待响应状态,用超时定时器做重试;三是从站地址和功能码不能写死,要由应用层传入。

static void master_send_read_holding(uint8_t slave_addr, uint16_t start, uint16_t count) { uint8_t req[8]; uint16_t crc; req[0] = slave_addr; req[1] = 0x03; req[2] = start >> 8; req[3] = start & 0xFF; req[4] = count >> 8; req[5] = count & 0xFF; crc = crc16_modbus(req, 6); req[6] = crc & 0xFF; req[7] = crc >> 8; modbus_send(req, 8); }

请求帧的组装逻辑和从站响应帧是镜像关系:起始地址和数量都是大端,CRC 低字节在前。主站收到响应后,同样先用地址过滤、再验 CRC、再解析。超时重试的时间要按波特率算,9600 波特率下从站响应一帧最长可能接近 30 毫秒,主站超时至少留 50 到 100 毫秒,别用 10 毫秒这种激进值。我的习惯是把超时做成可配置参数,现场调试时根据实际总线负载调整,这是这个方向里最值得花时间摸清的一个参数。希望帮到你。

本文还有配套的精品资源,点击获取

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

Java生态SVG转换实战:Batik与FOP搞定PNG/PDF/印刷格式

SVG转换这件事&#xff0c;我在几个项目里反反复复踩过不少坑。最近负责一个导出模块&#xff0c;业务方提的需求很直接&#xff1a;一张SVG加工后&#xff0c;要能在浏览器出PNG预览、下载JPG高清图、生成PDF报表交付&#xff0c;还要给印厂提供EPS或PS源文件用于印刷&#xf…

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

Struts2验证框架原理与实战:拦截器链、XML配置与调试

1. 项目概述&#xff1a;为什么今天还要谈 Struts2 验证框架&#xff1f;Struts2 验证框架的基本实现——看到这个标题&#xff0c;不少刚接触 Java Web 开发的新手会下意识皱眉&#xff1a;“Struts2 不是早就过时了吗&#xff1f;Spring Boot 都卷到自动装配和响应式编程了&a…

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

ACGAN原理深度解析:解耦生成与属性控制的隐空间机制

1. 为什么ACGAN不是“GAN的升级版”&#xff0c;而是解决了一个根本性错位问题很多人刚接触ACGAN时&#xff0c;第一反应是&#xff1a;“哦&#xff0c;这是DCGAN加了分类标签的版本&#xff0c;属于GAN家族的常规迭代。”我最初也这么想&#xff0c;直到在某跨平台系统里用它…

作者头像 李华
网站建设 2026/10/16 19:11:36

BrowserSkill 深度解析:AI Agent 浏览器操作技能与 CLI 实践

1. 从 BrowserSkill 这个标题说起&#xff1a;它到底想解决什么问题第一次看到 "BrowserSkill" 这个词&#xff0c;我的直觉是&#xff1a;这大概率是一个把浏览器操作能力封装成"技能"的项目&#xff0c;而且从热搜词里同时出现了 CLI、AI agents、Chrome…

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

测试工程师必学Git:从安装配置到分支合并与冲突解决

团队里的测试用例文档已经换到了第四个版本&#xff0c;文件名从 test_case_v1.xlsx 一路涨到 test_case_really_final_v9.xlsx&#xff0c;里面还有一堆"改了就是最终版""再也不改了"的批注。如果你也经历过这种命名地狱&#xff0c;那 Git 版本控制就是解…

作者头像 李华