news 2026/9/7 13:09:02

嵌入式Linux Modbus RTU开发实战:串口配置、协议解析与调试技巧

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式Linux Modbus RTU开发实战:串口配置、协议解析与调试技巧

嵌入式Linux下做Modbus RTU开发,听起来像是个基础活儿,但真要一次把串口调通、把传感器数据读准,坑还是不少。这个项目说白了就三件事:把嵌入式Linux的串口配置对,按Modbus RTU协议把请求帧发出去,再把从站返回的数据拆成能用的数值。无论是IMX6ULL、RK3399还是全志的板子,只要跑的是嵌入式Linux,这套思路基本通用。

这篇文章适合刚接手嵌入式Linux项目的朋友,尤其是那种“手里一块Linux开发板,要接一个温湿度变送器、风速仪或者电表”的场景。我会把串口配置、RTU报文格式、CRC校验、代码实现和调试方法全部串起来讲,顺便把我踩过的坑都列出来,你看完可以直接照着做。

1. 项目概述与整体设计思路

1.1 这个项目解决什么问题

很多工业传感器、采集模块和仪表至今仍以Modbus RTU为主要通信接口,原因很简单:RS485差分信号抗干扰强,两根线就能组网,协议帧又是公开标准,设备端实现成本极低。嵌入式Linux主板上一般自带UART,加一颗RS485收发芯片就能和现场传感器对上信号。

这个项目的目标是把“读传感器数据”这件事做成稳定、可复现的流程。比如你手里有一个Modbus RTU温湿度传感器,地址是0x01,你需要从Linux应用层定期读取温度和湿度,并处理异常情况。这里面涉及的技术点包括:

  • Linux串口设备的识别与参数配置。
  • Modbus RTU请求帧和响应帧的构造与解析。
  • CRC16校验的计算与验证。
  • 寄存器数据与真实物理量的换算,尤其是浮点数的字节序。
  • 超时、重试和错误码处理。

也就是说,这不只是调用一个read函数的问题,而是要把从硬件到协议到应用层的整条链路打通。

1.2 技术选型:为什么首选RTU而不是TCP

在项目规划阶段,很多人会纠结Modbus RTU和Modbus TCP到底选哪个。我的观点是:如果传感器与主板的物理距离在几米到几百米,且对实时性和成本敏感,RTU是更合适的方案。

Modbus RTU走串口,协议直接承载在物理层上,没有IP栈、没有TCP握手,响应延迟通常在毫秒级。而Modbus TCP虽然调试方便,但要处理IP地址、端口、连接状态,在嵌入式设备上还要保证网口稳定,复杂度上一个台阶。

另外,RTU可以通过RS485总线挂载多个从站,只要从站地址不同,一根双绞线上能带几十台设备。这个特性对现场改造很友好,很多老设备没有网口,但都有RS485接口。

回到这个项目,我们选择RTU还有一个现实原因:传感器厂商给的通信说明书几乎都是RTU格式,地址、功能码、寄存器表写得清清楚楚,照着协议来就行。

1.3 整体架构和数据流向

把它拆开看,整个系统是这样的:

  • 应用层:用户程序或者脚本,周期性发起读请求,处理结果。
  • 协议层:构造Modbus帧,校验CRC,解析响应。
  • 串口层:Linux下的tty设备节点,完成字节收发。
  • 物理层:UART TX/RX经过RS485收发器,变成差分信号,接到传感器。

数据流向很简单:应用层构造“读保持寄存器”请求帧,通过串口发送给从站,从站校验地址和CRC无误后,返回对应的寄存器数据,主站再解析。关键在于每一层都不能出错,尤其是串口参数和CRC,一出错就是数据乱码或者超时。

2. 环境准备与串口配置

2.1 硬件连线和RS485方向控制

先提醒一个新手高频问题:RS485不是直接用TTL电平和传感器对接的。嵌入式Linux开发板上出来的通常是UART引脚,一般是3.3V TTL电平,而标准RS485是差分信号,A/B两根线,电平也不同。所以必须在中间加RS485收发芯片,比如MAX485、SP3485,或者买现成的TTL转RS485模块。

接线一般是:

  • 开发板UART_TX -> 485芯片DI
  • 开发板UART_RX -> 485芯片RO
  • 控制引脚DE/RE(有的芯片是分开,有的合一)接一个GPIO,控制发送/接收方向

这里有一个很容易忽略的坑:RS485是半双工通信,发送时必须让485芯片处于发送模式,发送完再切回接收模式。在Linux应用层,方向控制通常是通过一个GPIO高低电平来切换。有些核心板已经把方向控制接在硬件上,甚至由驱动自动完成,但如果你的板子是直接外接模块,就不得不自己在代码里控制。

如果你用的是USB转RS485模块,比如CH340+485这种,一般模块内部已经把方向控制处理好了,应用层不需要关心GPIO。对于快速原型验证,USB转485模块是最省事的选择。

2.2 在嵌入式Linux下定位串口设备

接好硬件后,第一件事是确认系统识别到了串口设备。常见的设备节点有:

  • /dev/ttyS0、/dev/ttyS1:原生的UART设备
  • /dev/ttyUSB0、/dev/ttyUSB1:USB转串口设备
  • /dev/ttymxc1、/dev/ttymxc2:某些NXP平台采用的命名规则

可以通过以下命令查看:

ls -l /dev/tty* dmesg | grep tty

如果是USB转串口,插上后dmesg会打印类似ch341或cp210x的设备信息。如果设备节点没出现,先查内核驱动是否编译进去。

这里要提醒一句:嵌入式开发板的设备树和引脚复用对串口影响很大。一个串口引脚可能同时被复用在别的外设上,导致设备节点虽然存在,但收不到数据。项目里遇到“开了串口但没反应”,先排查设备树是否把UART引脚配置正确,再用示波器或逻辑分析仪看TX引脚是否有波形。

2.3 stty快速验证串口参数

串口参数必须和传感器说明书一致。常见配置是:波特率9600、8位数据位、无校验、1位停止位,即9600 8N1。也有的传感器默认115200、偶校验、2位停止位,最好先看说明书。

在命令行下可以用stty设置参数并快速验证:

stty -F /dev/ttyUSB0 9600 cs8 -cstopb -parenb -ixon raw
  • cs8:8位数据位
  • -cstopb:1位停止位,如果是2位停止位则使用cstopb
  • -parenb:无校验,若有偶校验则用parenb -parodd
  • -ixon:关闭软件流控
  • raw:原始模式,不让内核处理特殊字符

设置完参数后,可以先用一个USB转485模块配合串口助手发一帧Modbus请求,看看传感器是否有回复。这里的技巧是先把串口当透明通道测试,不急着写代码。

注意事项:如果验证时用的是minicom或picocom,一定要小心它们退出后会把串口参数改回默认配置。很多“自己写的程序读不到数据”问题,其实是之前被minicom改乱了后没有重新设置。

3. Modbus RTU协议拆解与关键细节

3.1 报文帧格式和常用功能码

Modbus RTU报文没有起始符和结束符,而是依靠时间间隔来定帧。一帧数据由从站地址、功能码、数据和CRC16校验组成。

请求帧示例(读保持寄存器):

从站地址 功能码 起始寄存器高字节 起始寄存器低字节 寄存器数量高字节 寄存器数量低字节 CRC低字节 CRC高字节 01 03 00 00 00 02 C4 0B

这里从站地址0x01,功能码0x03,起始寄存器0x0000,读取2个寄存器,CRC是0x0BC4,发送时低字节在前,所以是C4 0B。

响应帧格式:

从站地址 功能码 字节计数 数据字节1 数据字节2 ... 数据字节N CRC低字节 CRC高字节 01 03 04 xx xx xx xx CRC_LO CRC_HI

功能码方面,读传感器数据最常用的是:

  • 0x03 读保持寄存器,可读可写寄存器
  • 0x04 读输入寄存器,只读寄存器,很多传感器用这个功能码暴露实时数据
  • 0x01 读线圈状态,一般用于读取开关量
  • 0x02 读离散输入
  • 0x06 写单个寄存器
  • 0x10 写多个寄存器

多数传感器说明书中会写明温度和湿度分别在哪个寄存器地址、用哪个功能码。如果不清楚,可以先试试0x03和0x04,从寄存器地址0开始扫一遍。

3.2 CRC16计算原理

Modbus RTU的CRC16是数据完整性校验的关键。它的计算有几个特征:多项式0xA001(其实是CRC-16/MODBUS,初始值0xFFFF),按字节处理,校验值低字节在前发送。

Python计算示例:

def modbus_crc(data: bytes) -> int: crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc frame = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x02]) crc = modbus_crc(frame) print(hex(crc)) # 0x0bc4

如果你收到的响应经常出现CRC错误,问题可能是波特率不对、RS485方向切换太慢导致第一个字节被吃掉,或者线上有干扰。还有一种常见情况是传感器返回的字节顺序和你的算法不一致,表面上CRC不对,实际上是把高低字节校验值搞反了。

3.3 从报文到真实物理量

寄存器本身只存16位整数,传感器物理量和寄存器值之间往往有换算系数。比如一个温度传感器,如果说明书说“温度值 = 寄存器值 / 10”,那么寄存器读到的0x00FF就是255,温度就是25.5摄氏度。

也有不少传感器把数据用IEEE 754单精度浮点数存放,占两个寄存器,共4字节。这时字节顺序就非常关键。以常见的“大端字节序”为例,寄存器1和寄存器2分别是float的高16位和低16位。转换方法如下:

uint16_t reg1 = 0x4120; // 高16位 uint16_t reg2 = 0x0000; // 低16位 uint32_t raw = ((uint32_t)reg1 << 16) | reg2; float value; memcpy(&value, &raw, sizeof(float));

这里就是为什么很多人在同一台设备上读到的数值莫名其妙:不是寄存器读错了,而是字节序没对齐。有的传感器偏偏用“低16位在前”的存储方式,直接照搬网上代码就会踩坑。

4. 嵌入式Linux端代码实现

4.1 方案对比:libmodbus还是自己写

嵌入式Linux下实现Modbus RTU,最快的方式是用libmodbus库。它封装好了RTU帧、CRC、超时和从站地址处理,你不用自己拼CRC,代码量少很多。自己写的好处是可控性强,不依赖第三方库,尤其在某些裁剪过的内核和轻量环境里更灵活。

我的建议:快速验证用libmodbus,正式项目如果对体积和依赖有严格限制,再自己写一个最小实现。下面分别演示两种方式。

安装libmodbus:

# Debian/Ubuntu 类系统 apt-get install libmodbus-dev

交叉编译时,你可能需要下载源码自行编译。libmodbus的源码编译不复杂,configure后直接make,再把库和头文件放到工具链的sysroot里即可。

4.2 使用libmodbus读取传感器数据

libmodbus的RTU接口使用起来非常直观:

#include <stdio.h> #include <modbus/modbus.h> int main(void) { modbus_t *ctx; uint16_t regs[4] = {0}; int rc; // 串口 /dev/ttyUSB0, 波特率9600, 无校验, 8位数据, 1位停止位 ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1); if (ctx == NULL) { fprintf(stderr, "modbus_new_rtu failed\n"); return -1; } // 设置从站地址 modbus_set_slave(ctx, 1); // 设置串口和帧超时时间 struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 500000; // 500ms modbus_set_response_timeout(ctx, tv.tv_sec, tv.tv_usec); if (modbus_connect(ctx) == -1) { fprintf(stderr, "connect failed: %s\n", modbus_strerror(errno)); modbus_free(ctx); return -1; } // 读取地址0x0000开始的2个保持寄存器 rc = modbus_read_registers(ctx, 0x0000, 2, regs); if (rc == 2) { printf("reg[0]=0x%04X %d\n", regs[0], regs[0]); printf("reg[1]=0x%04X %d\n", regs[1], regs[1]); } else { fprintf(stderr, "read failed: %s\n", modbus_strerror(errno)); } modbus_close(ctx); modbus_free(ctx); return 0; }

这段代码把串口配置、连接、读寄存器、错误处理都串起来了。libmodbus的modbus_read_registers内部已经实现了请求帧构造、CRC计算和响应解析,你只需要关心寄存器地址和数量。

如果你的传感器用的是功能码0x04,也就是读输入寄存器,libmodbus提供了对应接口modbus_read_input_registers,用法完全一样。这点在选型时很关键,不要在一个只支持输入寄存器的设备上用0x03去读。

编译命令:

gcc -o modbus_reader modbus_reader.c -lmodbus

如果是交叉编译,需要指定工具链和头文件路径:

arm-linux-gnueabihf-gcc -o modbus_reader modbus_reader.c -I/path/to/libmodbus/include -L/path/to/libmodbus/lib -lmodbus

4.3 自己实现一个精简版RTU读取函数

不依赖库时,核心就是串口读写加CRC。下面是关键代码片段。

串口配置部分,使用termios:

#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <string.h> int set_serial(int fd, int baud, int data_bits, const char *parity, int stop_bits) { struct termios tty; if (tcgetattr(fd, &tty) != 0) { perror("tcgetattr"); return -1; } cfmakeraw(&tty); tty.c_cflag |= CLOCAL | CREAD; tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~PARENB; // 数据位 tty.c_cflag &= ~CSIZE; switch (data_bits) { case 8: tty.c_cflag |= CS8; break; case 7: tty.c_cflag |= CS7; break; default: return -1; } // 校验 if (strcmp(parity, "N") == 0) { tty.c_cflag &= ~PARENB; } else if (strcmp(parity, "E") == 0) { tty.c_cflag |= PARENB; tty.c_cflag &= ~PARODD; } else if (strcmp(parity, "O") == 0) { tty.c_cflag |= PARENB; tty.c_cflag |= PARODD; } // 停止位 if (stop_bits == 2) { tty.c_cflag |= CSTOPB; } else { tty.c_cflag &= ~CSTOPB; } // 波特率 speed_t speed; switch (baud) { case 9600: speed = B9600; break; case 19200: speed = B19200; break; case 115200: speed = B115200; break; default: return -1; } cfsetispeed(&tty, speed); cfsetospeed(&tty, speed); // 设置读取超时,防止卡死 tty.c_cc[VTIME] = 10; // 1秒 tty.c_cc[VMIN] = 0; if (tcsetattr(fd, TCSANOW, &tty) != 0) { perror("tcsetattr"); return -1; } return 0; }

cfmakeraw会自动关掉ECHO、ICANON、ISIG和软件流控,这对Modbus这类二进制协议至关重要。如果不用cfmakeraw而用默认的termios配置,收到的数据可能会被当成终端控制字符处理,出现“数据被吞掉”的诡异问题。

发送Modbus请求帧并读取响应:

uint16_t crc16_modbus(const uint8_t *data, int len) { uint16_t crc = 0xFFFF; for (int i = 0; i < len; i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) { if (crc & 1) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; } int read_modbus_register(int fd, uint8_t slave, uint16_t start_addr, uint16_t count, uint16_t *values) { uint8_t req[8]; uint8_t buf[256]; int len = 0; uint16_t crc; // 构造请求帧 req[len++] = slave; req[len++] = 0x03; // 功能码:读保持寄存器 req[len++] = start_addr >> 8; req[len++] = start_addr & 0xFF; req[len++] = count >> 8; req[len++] = count & 0xFF; crc = crc16_modbus(req, len); req[len++] = crc & 0xFF; req[len++] = crc >> 8; // 清空串口缓冲 tcflush(fd, TCIOFLUSH); // 发送 if (write(fd, req, len) != len) { return -1; } // 读取响应,注意长度可能为5 + count*2 usleep(50000); // 等待从站响应 int total = 0; int need = 5 + count * 2; while (total < need) { int n = read(fd, buf + total, need - total); if (n > 0) { total += n; } else { break; } } if (total < need) { return -2; // 超时或响应不完整 } // 校验地址和功能码 if (buf[0] != slave || buf[1] != 0x03) { return -3; // 地址或功能码错误 } // 校验CRC crc = crc16_modbus(buf, total - 2); if ((crc & 0xFF) != buf[total - 2] || (crc >> 8) != buf[total - 1]) { return -4; // CRC错误 } // 解析寄存器数据 for (int i = 0; i < count; i++) { values[i] = (buf[3 + i * 2] << 8) | buf[4 + i * 2]; } return count; }

注意这个函数里故意加了一个usleep(50000)等待50ms。实际项目里更推荐用select或poll做超时读取,而不是固定sleep。因为从站响应时间不固定,如果传感器掉线,固定sleep会让程序一直卡在那里。

用poll实现超时读取会更可靠:

#include <poll.h> struct pollfd fds; fds.fd = fd; fds.events = POLLIN; int ret = poll(&fds, 1, 500); // 500ms超时 if (ret > 0) { int n = read(fd, buf + total, need - total); }

超时时间设置也有讲究。RS485总线上如果挂了多台设备,主站轮询时每个从站的响应时间可能不同。建议超时设置成200ms到1s之间,太短容易误判从站故障,太长会影响轮询效率。

4.4 浮点数和字节序处理

很多传感器会把温度以IEEE 754浮点数存进连续两个寄存器。实际项目里我做过一个风速传感器,它用寄存器0x0000和0x0001存风速float,寄存器0x0002和0x0003存风向float。

读取和转换的代码大致如下:

uint16_t regs[4] = {0}; read_modbus_register(fd, 1, 0x0000, 4, regs); // 把寄存器对合并成float,假设大端字节序 uint32_t raw_wind_speed = ((uint32_t)regs[0] << 16) | regs[1]; float wind_speed; memcpy(&wind_speed, &raw_wind_speed, sizeof(float)); printf("wind speed: %.2f m/s\n", wind_speed);

如果把大端和小端搞混,读出来的数据会是一个很大的或接近0的数。遇到这种问题,先抓原始寄存器值,再对照说明书看字节顺序。不要想当然改代码,用十六进制打印寄存器:

printf("reg[0]=0x%04X reg[1]=0x%04X\n", regs[0], regs[1]);

如果reg[0]=0x4120,reg[1]=0x0000,那按大端拼出来就是float 10.0。如果你按小端拼,就会得到非常小的非规格化数,基本一眼就能判断出来。

5. 调试与问题排查实录

5.1 用Modbus Poll和Modbus Slave模拟联调

没有传感器硬件时,Modbus Slave可以在PC上模拟一个从站设备,极大方便协议验证。我经常在开发阶段这样干:嵌入式Linux板子通过USB转485接到PC的USB转485上,PC上跑Modbus Slave作为虚拟传感器,板子上的程序作为主站去读。

调制Slave时要注意设置从站地址、功能码和寄存器初始值。如果程序读到固定值,说明串口和协议链路基本没问题。再用Modbus Poll作为主站连真实传感器,可以反过来确认传感器本身是否正常。Modbus Poll这个工具能看到收发的原始报文,排查CRC错误和设备不响应非常直观。

实际联调时,我强烈建议先用逻辑分析仪或示波器看波形。Modbus RTU只是异步串口波形,如果TX信号都对,那就查RX,如果RX一直没波形,大概率是硬件接线或者地址/波特率配置问题。

5.2 常见问题速查表

下面这个表是我这两年在Linux端Modbus开发里踩过的典型问题:

现象可能原因解决方案
串口打开失败权限不足或设备节点不存在用root运行,或加入dialout组,检查dmesg
能发送但收不到响应RS485方向控制没切换发送后切GPIO为接收模式,或者用自动方向模块
收到数据但CRC错误波特率不一致、线路干扰、第一个字节被吃换低波特率,检查硬件,发送前清空串口缓冲
读到0x00或0xFF从站地址错误、寄存器地址不存在用Modbus Poll先扫一遍地址,确认寄存器表
数值明显不对字节序或者物理量换算不对抓原始寄存器值,对照说明书处理字节顺序
程序卡在read()没有设置串口超时用poll/setitimer,设置VMIN和VTIME
用USB转485后不稳定USB转串口芯片和驱动问题,或者供电不足换带隔离的USB转485,外接电源
多设备轮询时某台设备无响应从站地址冲突或者设备响应慢检查从站地址,增加设备级超时时间

这里我最想强调“第一个字节被吃”这个问题。RS485方向切换如果由GPIO手动控制,发送结束后立刻切回接收模式,但485收发芯片从发送模式切换到接收模式有短暂时间,此时如果从站响应很快,主站的第一个字节可能被吃掉。解决办法是发送完成后再延时几十微秒再切方向,或者用DE/RE信号自动控制的收发器。

5.3 项目现场实测案例

去年我做的一个环境监测项目,传感器是某品牌的MODBUS RTU温湿度变送器,地址默认0x01,波特率9600,8N1,功能码0x04读输入寄存器,温度在寄存器0x000A,湿度在寄存器0x000B。

我直接用libmodbus写了个小工具读取:

rc = modbus_read_input_registers(ctx, 0x000A, 2, regs);

结果第一次读出来温度是0x011C,换算成十进制284,但现场温度只有26摄氏度。后来看说明书才明白,这个传感器的温度寄存器值需要除以10,284 / 10 = 28.4度。湿度寄存器是0x00F6,246 / 10 = 24.6%。这个例子很典型,很多传感器不会直接给你float,而是整数加系数。

另外一次调试ESP32和Linux嵌入式板子通信,SPI屏的引脚恰好和UART3冲突,设备树里只要使能了SPI,UART3就没有数据。排查过程很痛苦,最后用/sys/kernel/debug/pinctrl下的引脚复用信息确认了冲突。这个经验说明:嵌入式Linux开发遇到串口无数据,先怀疑硬件复用,再怀疑协议。

6. 实操经验与避坑建议

6.1 串口配置的几个关键细节

第一,raw模式一定要开。很多网上的教程只是用cfsetispeedcfsetospeed设置了波特率,没有把termios设置成raw模式,结果程序收到的数据总是少字节或者乱码。cfmakeraw(&tty)这一行能帮你省去大量问题。

第二,发送和读取之间要清空缓冲区。用tcflush(fd, TCIOFLUSH)清掉之前可能残留的数据,不然上一次通信的残留字节会被当成当前响应帧的一部分,CRC自然对不上。

第三,VMIN和VTIME配置要理解清楚。我习惯设置VMIN=0,VTIME=10,表示没有数据时等待最多1秒返回。如果你设置VMIN=1,read会一直阻塞直到收到至少1个字节,这在做轮询时很危险,从站掉线后程序会卡死。

6.2 从站地址和寄存器表的管理

我在正式项目里会把传感器寄存器表抽象成配置项,而不是在代码里写死。比如用结构体描述:

typedef struct { const char *name; uint8_t slave_addr; uint8_t func_code; uint16_t reg_addr; uint16_t reg_count; float scale; } sensor_channel_t;

这样后面加传感器类型、改寄存器地址,都只需要改配置表就行,不用改通信逻辑。Modbus设备多了以后,最好有一个全局轮询表,按周期调度读取,避免两个任务同时操作同一个串口造成帧交叉。

6.3 再分享几个小技巧

  • 如果没有硬件,可以用socat创建虚拟串口对,再用Python的pymodbus写一个模拟从站,这样可以在纯Linux环境下跑通整套协议逻辑,再移植到真实硬件上。
  • hexdump或者xxd直接看串口收到的原始字节,排查问题比在程序里断点更快。
  • 写日志时把收发帧都打出来,格式化为Hex字符串,出问题时对比文档和现场抓包,能省很多时间。
  • 如果你在嵌入式设备上做轮询,建议加一个看门狗或者超时计数器,当连续多次读超时时,尝试重新初始化串口,有些USB转串口设备遇到异常后会进入僵尸状态。

最后说点个人的体会:嵌入式Linux端做Modbus开发,最大的成本往往不在写代码,而在于设备和现场环境的适配。串口参数、寄存器地址、字节序、RS485方向控制,任何一个环节的小问题都会让你排查很久。所以一定要把调试工具链配齐,Modbus Poll、Modbus Slave、逻辑分析仪、USB转485,这几样东西在你工具箱里,遇到问题就能按部就班地定位,而不是靠猜。我每次接到新设备,都会先拿Modbus Poll和真实传感器对一次寄存器表,再写代码。这个习惯看着笨,但确实能避开最大的坑。

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

单相交流调压电路Simulink仿真全流程:从建模、触发到波形验证

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

作者头像 李华
网站建设 2026/9/7 13:06:15

中配GPU集群的正确出路:本地LLM推理与视频转码实战

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

作者头像 李华
网站建设 2026/9/7 13:01:56

Coze智能体开发实战:从工作流编排到Agent落地的完整教程

最近有不少读者在后台问我&#xff1a;Coze&#xff08;扣子&#xff09;到底是什么&#xff0c;它和AI大模型、Agent、工作流这些词到底是什么关系&#xff1f;为什么大家突然都在说“用Coze搭建智能体”“Coze工作流免费下载”这类话题&#xff1f;带着这些疑问&#xff0c;我…

作者头像 李华
网站建设 2026/9/7 13:01:33

用Python实现全自动拼豆:图像像素化与自动放置实战

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

作者头像 李华
网站建设 2026/9/7 13:01:05

Claude Code 接入 DeepSeek:环境变量配置与省钱实战

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

作者头像 李华
网站建设 2026/9/7 13:01:01

本地AI工具部署前必读:硬件自查、环境准备与避坑指南

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

作者头像 李华