首页/新闻资讯/正文详情

从零用TypeScript手写轻量级Agent框架:ReAct循环与MCP工具接入实战

发布时间:2026/11/4 10:25:46 来源:云帆数科 栏目:资讯中心
从零用TypeScript手写轻量级Agent框架:ReAct循环与MCP工具接入实战
1. 为什么我要自己造一个 Agent 框架市面上现成的 Agent 框架已经不少了LangChain、AutoGen、CrewAI 这些名字随便一搜就是一大把。但我自己带团队做 AI 应用落地的过程中越来越觉得这些框架有个通病抽象层太厚调试链路太长。你写一个简单的 ReAct 循环框架内部可能帮你封装了十几层调用出了问题日志翻半天都找不到是哪一步的 prompt 拼错了。更别提版本升级带来的 breaking change上周还能跑的代码这周升个小版本就报Agent execution terminated due to error排查半天发现是某个内部依赖改了接口。所以我就动了念头用 TypeScript 从零搭一个轻量级 Agent 框架不依赖任何重型编排库核心逻辑自己写每一行代码都能说清楚它在干什么。这个框架的目标很明确——支持 ReAct 推理循环、能对接 MCP 协议的工具生态、类型安全、可调试、可扩展。适合那些跟我一样想真正搞懂 Agent 底层运转机制而不是只会调 API 的开发者。这篇文章我会把整个搭建过程拆开讲包括架构设计的取舍、ReAct 循环的具体实现、MCP 工具的接入方式、类型系统的设计以及我在实操中踩过的坑。代码全部用 TypeScript 写不依赖任何 Agent 专用框架你跟着做就能得到一个能跑的最小可用版本。2. 整体架构设计与技术选型取舍2.1 为什么不直接用现成框架先说清楚这个决策背后的逻辑。现成框架的优势是开箱即用工具生态丰富社区活跃。但劣势同样明显黑盒程度高你很难精确控制每一轮对话的上下文构造依赖膨胀一个 Agent 框架可能拖进来几十个 npm 包类型定义松散很多框架的 TypeScript 类型就是any加一层皮写起来毫无安全感。我需要的场景是一个中等复杂度的 Agent能调用 5 到 10 个工具需要多轮推理偶尔要处理流式输出。这种场景下自己写核心循环大概 300 行代码就能搞定而引入框架反而增加了理解和维护成本。当然如果你的场景是复杂的多 Agent 协作、需要大量预置工具那用现成框架更划算。选型没有绝对的对错只有适不适合。2.2 核心模块划分我把整个框架拆成四个核心模块每个模块职责单一模块之间通过明确的接口通信LLM 适配层统一不同模型提供商的调用接口屏蔽差异。我定义了一个LLMProvider接口任何模型只要实现这个接口就能接入。工具注册与执行层管理工具的注册、参数校验和执行。工具定义遵循 MCP 协议的 schema 规范这样后续接入 MCP server 时不用改代码。ReAct 推理引擎核心循环负责构造 prompt、解析模型输出、决定下一步动作、维护对话历史。类型系统与消息模型定义消息、工具调用、推理步骤等核心数据结构保证全链路类型安全。这样划分的好处是每个模块都可以独立测试。比如我想换一个模型只需要实现一个新的LLMProvider推理引擎完全不用动。2.3 技术栈选择运行时用 Node.js语言用 TypeScript这个没什么好纠结的。几个关键依赖的选择我说明一下理由依赖用途选择理由zod参数校验与 schema 定义类型推导能力强能从 schema 反推 TS 类型和 MCP 的 JSON Schema 转换方便eventsource-parser流式响应解析轻量专门处理 SSE 格式比自己写解析器靠谱tsx开发时直接运行 TS省去编译步骤改完即跑开发体验好注意我没有引入任何 HTTP 客户端库直接用 Node.js 18 内置的fetch。少一个依赖就少一个潜在的版本冲突。提示TypeScript 7.0 之后baseUrl选项会被弃用如果你在tsconfig.json里用了baseUrl做路径别名建议尽早迁移到paths配合moduleResolution: bundler的方案避免升级时踩坑。3. 核心类型系统与消息模型设计3.1 消息类型的定义Agent 的一切交互都可以归结为消息的流转。我把消息分成四种角色system、user、assistant、tool。用 TypeScript 的 discriminated union 来定义这样在 switch 分支里能自动收窄类型type MessageRole system | user | assistant | tool; interface BaseMessage { role: MessageRole; content: string; } interface SystemMessage extends BaseMessage { role: system; } interface UserMessage extends BaseMessage { role: user; } interface AssistantMessage extends BaseMessage { role: assistant; toolCalls?: ToolCall[]; } interface ToolMessage extends BaseMessage { role: tool; toolCallId: string; toolName: string; } type Message SystemMessage | UserMessage | AssistantMessage | ToolMessage;这里有个设计决策值得说为什么 assistant 消息要带 toolCalls 字段。因为模型在决定调用工具时返回的是一条 assistant 消息里面包含工具调用的意图。这条消息必须原样保留在对话历史里否则下一轮请求时模型会困惑——它看到工具返回了结果但找不到自己当初为什么要调这个工具。这是很多人自己写 Agent 时容易忽略的细节。3.2 工具调用的类型建模工具调用是 Agent 的核心能力类型定义要足够精确interface ToolCall { id: string; name: string; arguments: Recordstring, unknown; } interface ToolDefinitionT extends z.ZodType z.ZodType { name: string; description: string; parameters: T; execute: (args: z.inferT) Promisestring; }ToolDefinition用了泛型加 zod schema好处是execute函数的参数类型能自动从 schema 推导出来。你定义工具时写一遍 schema参数类型和运行时校验就都有了不用维护两份定义。这个设计灵感来自 tRPC用过的都知道这种端到端类型安全的爽感。3.3 推理步骤的记录为了可调试我把每一轮推理都记录成一个ReasoningStepinterface ReasoningStep { index: number; thought: string; action?: { toolName: string; args: Recordstring, unknown; }; observation?: string; timestamp: number; }这个结构在调试时特别有用。当 Agent 行为不符合预期时我把所有步骤打印出来一眼就能看出是哪一步的 thought 跑偏了还是工具返回的 observation 有问题。可观测性是自研框架相对现成框架最大的优势之一一定要在设计阶段就考虑进去。4. ReAct 推理引擎的实现细节4.1 ReAct 循环的本质ReAct 的核心思想是让模型交替进行推理Reasoning和行动Acting。用大白话说就是模型先想一下该干什么然后决定调不调工具调完工具看到结果再想下一步直到它认为可以给出最终答案为止。这个循环用伪代码表示就是while (未达到终止条件) { 构造 prompt包含历史消息和工具定义 调用 LLM 解析输出 if (输出包含工具调用) { 执行工具把结果加入历史 } else { 返回最终答案结束循环 } }看起来简单但魔鬼在细节里。终止条件怎么定工具调用失败怎么办模型一直循环调用同一个工具怎么办下面逐个说。4.2 系统提示词的构造系统提示词决定了 Agent 的行为模式。我的做法是把工具定义用 JSON Schema 格式注入到系统提示词里同时给出明确的输出格式要求function buildSystemPrompt(tools: ToolDefinition[]): string { const toolDescriptions tools.map(t ({ name: t.name, description: t.description, parameters: zodToJsonSchema(t.parameters), })); return 你是一个能够使用工具的智能助手。 你可以使用以下工具 ${JSON.stringify(toolDescriptions, null, 2)} 当你需要调用工具时请以如下 JSON 格式输出 {tool: 工具名, arguments: {...}} 当你已经获得足够信息可以回答用户时直接输出最终答案不要包含 JSON。 每次只调用一个工具等待结果后再决定下一步。; }这里有个关键点要求模型每次只调一个工具。虽然现在很多模型支持并行工具调用但在自研框架的初期串行调用更容易调试也更容易控制上下文长度。等框架稳定了再考虑并行优化。4.3 输出解析的健壮性处理模型输出解析是整个循环里最容易出问题的环节。模型可能返回带 markdown 代码块的 JSON可能返回格式不对的 JSON也可能在 JSON 前后加一堆解释文字。我的解析策略是分层的function parseModelOutput(content: string): ParsedOutput { // 第一层尝试直接解析 const direct tryParseJson(content); if (direct isValidToolCall(direct)) { return { type: tool_call, call: direct }; } // 第二层提取代码块中的 JSON const codeBlockMatch content.match(/(?:json)?\s*([\s\S]*?)/); if (codeBlockMatch) { const parsed tryParseJson(codeBlockMatch[1]); if (parsed isValidToolCall(parsed)) { return { type: tool_call, call: parsed }; } } // 第三层用正则找第一个完整的 JSON 对象 const jsonMatch content.match(/\{[\s\S]*?tool[\s\S]*?\}/); if (jsonMatch) { const parsed tryParseJson(jsonMatch[0]); if (parsed isValidToolCall(parsed)) { return { type: tool_call, call: parsed }; } } // 兜底当作最终答案 return { type: final_answer, content }; }三层解析加上兜底实测下来能覆盖 95% 以上的情况。剩下的 5% 通常是模型真的跑偏了这时候当作最终答案返回反而比强行解析更安全。4.4 循环控制与安全边界无限循环是自研 Agent 最容易踩的坑。我设置了三个安全边界最大迭代次数默认 10 轮超过就强制终止并返回当前状态。重复调用检测如果连续两轮调用了同一个工具且参数完全相同判定为死循环终止。单轮超时每次 LLM 调用设置 60 秒超时工具执行设置 30 秒超时。const MAX_ITERATIONS 10; const MAX_REPEAT_CALLS 2; async function runReActLoop( messages: Message[], tools: ToolDefinition[], provider: LLMProvider ): PromiseAgentResult { const steps: ReasoningStep[] []; let repeatCount 0; let lastCallSignature ; for (let i 0; i MAX_ITERATIONS; i) { const response await provider.chat(messages, tools); const parsed parseModelOutput(response.content); if (parsed.type final_answer) { return { answer: parsed.content, steps }; } const signature ${parsed.call.name}:${JSON.stringify(parsed.call.arguments)}; if (signature lastCallSignature) { repeatCount; if (repeatCount MAX_REPEAT_CALLS) { return { answer: 检测到重复调用已终止, steps }; } } else { repeatCount 0; } lastCallSignature signature; // 执行工具并记录步骤 const observation await executeTool(parsed.call, tools); steps.push({ index: i, thought: response.content, action: { toolName: parsed.call.name, args: parsed.call.arguments }, observation, timestamp: Date.now(), }); messages.push( { role: assistant, content: response.content }, { role: tool, content: observation, toolCallId: parsed.call.id, toolName: parsed.call.name } ); } return { answer: 达到最大迭代次数, steps }; }注意重复调用检测的签名里参数要做稳定的序列化。如果参数对象里 key 的顺序不固定JSON.stringify出来的字符串会不一样检测就失效了。生产环境建议用排序后的 key 做序列化。5. MCP 工具生态的接入方案5.1 MCP 协议解决了什么问题MCPModel Context Protocol本质上是一套标准化的工具描述和调用协议。在没有 MCP 之前每个 Agent 框架都有自己的工具定义格式你想把一个工具从 A 框架迁移到 B 框架得重写一遍。MCP 出现之后工具提供方只要实现一个 MCP server所有支持 MCP 的客户端都能用。对我们自研框架来说接入 MCP 意味着瞬间获得一个庞大的工具生态。文件系统操作、数据库查询、浏览器自动化、代码执行这些常见能力都有现成的 MCP server。我们不需要自己实现只要做好客户端对接就行。5.2 MCP 客户端的实现MCP 支持多种传输方式最常用的是 stdio 和 HTTP。stdio 方式下MCP server 作为一个子进程运行通过标准输入输出通信。我用 Node.js 的child_process来实现import { spawn, ChildProcess } from child_process; class MCPClient { private process: ChildProcess; private requestId 0; private pending new Mapnumber, (result: unknown) void(); constructor(command: string, args: string[]) { this.process spawn(command, args); this.process.stdout?.on(data, (data) this.handleData(data)); } private handleData(data: Buffer) { const lines data.toString().split(\n).filter(Boolean); for (const line of lines) { try { const message JSON.parse(line); if (message.id this.pending.has(message.id)) { this.pending.get(message.id)!(message.result); this.pending.delete(message.id); } } catch { // 忽略非 JSON 输出 } } } async listTools(): PromiseMCPTool[] { const result await this.sendRequest(tools/list, {}); return (result as { tools: MCPTool[] }).tools; } async callTool(name: string, args: Recordstring, unknown): Promisestring { const result await this.sendRequest(tools/call, { name, arguments: args }); return JSON.stringify(result); } private sendRequest(method: string, params: unknown): Promiseunknown { const id this.requestId; const request JSON.stringify({ jsonrpc: 2.0, id, method, params }); this.process.stdin?.write(request \n); return new Promise((resolve) this.pending.set(id, resolve)); } }这段代码是简化版实际使用还需要处理初始化握手、错误响应、进程退出重连等情况。但核心思路就是这样用 JSON-RPC 协议通过 stdio 和子进程通信。5.3 把 MCP 工具适配成内部工具MCP 返回的工具定义格式和我们的ToolDefinition不完全一样需要做一层适配function adaptMCPTool(mcpTool: MCPTool, client: MCPClient): ToolDefinition { return { name: mcpTool.name, description: mcpTool.description, parameters: jsonSchemaToZod(mcpTool.inputSchema), execute: async (args) { return await client.callTool(mcpTool.name, args); }, }; }jsonSchemaToZod这个转换函数需要自己写因为 MCP 用的是 JSON Schema我们内部用 zod。大部分基础类型string、number、boolean、object、array的转换很直接复杂类型oneOf、anyOf需要递归处理。我建议先支持基础类型遇到不支持的 schema 就退化成z.any()保证不阻塞主流程。5.4 工具命名冲突的处理当你同时接入多个 MCP server 时工具名冲突是必然的。我的处理方式是给工具名加前缀function namespaceTools(tools: ToolDefinition[], namespace: string): ToolDefinition[] { return tools.map(t ({ ...t, name: ${namespace}__${t.name}, })); }用双下划线分隔既避免了冲突又保持了可读性。在调用时把前缀去掉再转发给对应的 MCP server 就行。6. 实操中踩过的坑与排查技巧6.1 模型不按格式输出怎么办这是最常见的问题。你明明在系统提示词里写了要输出 JSON模型偏偏给你返回一段自然语言。我的经验是提示词要给出具体示例不要只说输出 JSON 格式而是给一个完整的输入输出示例。降低 temperature推理阶段用 0 到 0.3 之间的值输出稳定性会好很多。用 few-shot 示例在系统提示词里放两三个正确调用的例子效果立竿见影。如果这些都做了还是不稳定那就说明这个模型本身指令遵循能力不够考虑换模型比继续调 prompt 更划算。6.2 工具执行超时与错误处理工具执行失败是常态网络请求会超时文件可能不存在API 可能限流。关键是要把错误信息结构化地返回给模型而不是直接抛异常中断循环async function executeTool(call: ToolCall, tools: ToolDefinition[]): Promisestring { const tool tools.find(t t.name call.name); if (!tool) { return 错误未找到工具 ${call.name}; } try { const validated tool.parameters.parse(call.arguments); const result await Promise.race([ tool.execute(validated), new Promisestring((_, reject) setTimeout(() reject(new Error(工具执行超时)), 30000) ), ]); return result; } catch (error) { return 错误${error instanceof Error ? error.message : String(error)}; } }把错误信息作为 observation 返回给模型模型往往能自己纠正。比如参数格式错了它看到错误信息后会重新构造参数再试一次。这比直接中断循环智能得多。6.3 上下文长度爆炸多轮工具调用之后对话历史会变得很长。如果不加控制很快就会超出模型的上下文窗口。我的处理策略是策略适用场景实现复杂度滑动窗口对话轮次多但每轮内容短低摘要压缩历史信息重要但不能全保留中工具结果截断工具返回内容过长低分层记忆需要长期记忆的复杂场景高我目前用的是滑动窗口加工具结果截断的组合。保留最近 N 轮完整对话更早的只保留用户消息和最终答案。工具返回结果超过 2000 字符就截断加个省略标记。6.4 常见问题速查表现象可能原因排查方向Agent 一直循环不结束工具返回结果模型无法理解检查 observation 格式是否清晰工具调用参数总是错的schema 描述不清晰补充参数描述和示例值模型忽略工具直接回答系统提示词不够强硬加强工具使用的引导语流式输出解析乱码SSE 分块边界处理不当用 eventsource-parser 处理类型报错但运行正常zod schema 和 TS 类型不同步用 z.infer 统一类型来源提示调试 Agent 时把每一轮的完整 prompt 和模型原始输出都打到日志里。虽然日志量大但出问题时能省下大量猜测时间。我习惯用一个环境变量控制详细日志的开关开发时打开生产环境关掉。7. 框架的扩展方向与我的一些体会这个最小框架跑通之后扩展方向其实很多。我目前在做的是给它加一个简单的记忆模块把每次对话的关键信息存到本地文件下次对话时作为上下文注入。另外流式输出的支持也在完善中让用户能实时看到 Agent 的推理过程体验会好很多。还有一个值得投入的方向是评估体系。Agent 的行为不像传统函数那样有确定的输入输出怎么判断它这次任务完成得好不好是个挺难的问题。我现在的做法是准备一批标准测试用例每个用例有预期的工具调用序列跑完之后对比实际序列和预期序列的匹配度。粗糙但有效。说实话自己造框架这件事最大的收获不是框架本身而是对 Agent 运转机制的透彻理解。以前用现成框架时遇到问题只能猜现在每一行代码都是自己写的出问题能直接定位到根因。如果你也想真正搞懂 Agent 是怎么回事我强烈建议你动手写一遍哪怕最后不用它这个过程本身就值回票价。

相关推荐

从零用TypeScript手写Agent框架:ReAct循环与MCP工具接入实战
从零用TypeScript手写Agent框架:ReAct循环与MCP工具接入实战

1. 为什么我要自己造一个 Agent 框架 1.1 现成框架的“甜蜜陷阱” 过去大半年,我几乎把市面上能叫得出名字的 Agent 框架都摸了一遍。从 LangChain 到 AutoGen,从各种“一行代码接入大模型”的封装库,到号称开箱即用的智能体平台&#xff0c… · 2026/11/4 10:25:46

从零实现AI工程:手写神经网络与反向传播核心指南
从零实现AI工程:手写神经网络与反向传播核心指南

“ai-engineering-from-scratch”这种命名方式,我太熟了。GitHub上一搜一大把,几乎每个热门方向都会冒出来几个“from scratch”仓库,但真正能让人从头走到尾、不半路劝退的,少之又少。这个标题的核心诱惑力在于“我什么都不要&am… · 2026/11/4 10:24:45

SAP物料主数据编号范围维护全流程与避坑指南
SAP物料主数据编号范围维护全流程与避坑指南

简介:这份资源聚焦SAP系统后台物料主数据的编号范围维护,面向SAP实施顾问、运维人员及正在学习后勤模块配置的从业者,帮助解决物料类型号码组划分、内外编号段设定与冲突处理等实际问题。压缩包内共1个docx文档,约494KB&#xff0… · 2026/11/4 10:24:45

直流无刷电机AX BY CZ怎么划分?120°电角度与90°坐标系详解
直流无刷电机AX BY CZ怎么划分?120°电角度与90°坐标系详解

一个叫"ax"的热搜词,把我拽回了直流无刷电机的老本行。搜这个词的人,大概率是在某张接线图或绕线图上被AX、BY、CZ三个字母组合卡住了,随后抛出这个经典问题:直流无刷电机ax by cz怎么划分,是按照垂直轴线划… · 2026/11/4 10:57:33

ZPL标签打印预览实战:Labelary渲染PNG与参数调优指南
ZPL标签打印预览实战:Labelary渲染PNG与参数调优指南

标签打印这个行当里,ZPL 算是个绕不开的老朋友。只要用过斑马系打印机,或者对接过仓储、物流、零售后台的打印模块,大概率都跟这串以^XA开头、^XZ结尾的指令打过交道。它本质上是打印机自己能读的“母语”,描述的是标签上每一行文… · 2026/11/4 10:57:33

GOCI2波段参数与月球定标链路:辐亮度校验到ROLO模型
GOCI2波段参数与月球定标链路:辐亮度校验到ROLO模型

简介:《GOCI2 说明手册 波段信息说明》面向海洋遥感、卫星载荷标定与定量反演方向的研究人员、工程师及研究生,聚焦第二代地球同步轨道海洋水色成像仪 GOCI-II 的波段设置与辐射性能指标,帮助读者厘清短波至近红外各波段在藻华识别、悬浮颗粒… · 2026/11/4 10:57:33

从零构建AI工程能力:后端老兵的踩坑与重构实录
从零构建AI工程能力:后端老兵的踩坑与重构实录

1. 从零搭建AI工程能力:一个后端老兵的踩坑与重构实录 "ai-engineering-from-scratch"这个标题,第一次看到的时候我愣了一下。不是因为它有多花哨,恰恰相反,它朴素得有点不像这个时代的项目名。但正是这种朴素&#xff… · 2026/11/4 10:57:33

直流无刷电机三相绕组ax by cz划分与120度电角度详解
直流无刷电机三相绕组ax by cz划分与120度电角度详解

前阵子修一台直流无刷电机,端子排上贴着 ax、by、cz 三个标签,驱动板却只标了 U、V、W。我拿着表笔对着标签发愣:这 ax、by、cz 到底按什么划分出来的?有人挂在嘴边说,三相绕组就是按垂直轴线分的,一组一组… · 2026/11/4 10:57:33

无人机水质监测系统设计与实战经验分享
无人机水质监测系统设计与实战经验分享

1. 无人机水质监测的技术革新 最近在环境监测领域出现了一个有趣的现象:越来越多的科研团队开始采用无人机进行水质采样和监测。这种创新方法不仅解决了传统人工采样效率低、危险性高的问题,还能获取更全面的水域数据。作为一名长期从事环境监测的技术人… · 2026/11/4 10:56:32

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码