
简介这是一套面向工业控制与嵌入式开发者的 STM32 仿三菱 FX3U PLC 源码和原理图资料目标是提供低成本、高可定制性的 PLC 替代方案并帮助读者理解 PLC 的扫描、I/O 与通信机制。包内共 535 个文件约 82.22MB包含 C/H 源码、Keil 工程文件、hex 固件、原理图/PCB 设计文档以及日志、编译中间文件等结构清晰便于定位工程模块与复现实验环境。已有 1292 人浏览学习。内容覆盖输入输出管理、程序执行引擎、定时器/计数器、MODBUS 通信、存储管理、HMI 界面等关键模块的工程实现可结合原理图了解 STM32 与外部硬件的连接方式以及扩展模块设计思路。适合希望掌握 STM32 应用、深入认识 PLC 指令处理流程的开发者借此降低系统成本、提高定制化程度。 把三菱GX Works2打开新建工程型号选FX3U写一段起保停电路点下载——然后运行的不是哪台几千块的PLC而是一块几十块钱的STM32最小系统板。这就是这个项目最核心的东西用STM32模拟三菱FX3U的编程口协议和指令执行逻辑让三菱官方软件认为你插着的就是一台FX3U。这个思路在工控DIY圈一直很火原因很现实。FX3U系列PLC现在二手都要上千全新的更贵而且想往里面加自定义通信协议、特殊IO扩展、高速脉冲轴控制门槛非常高。STM32方案成本低、资料多、外设自由最关键的是能直接用三菱官方编程软件写梯形图不需要自己搞一套IDE。对刚入行学PLC的、做非标设备需要批量定制的、或者纯粹想玩一玩协议栈的人来说这个项目都能让你把PLC的工作原理彻底吃透。这篇文章我会把整个项目的技术栈拆开讲清楚通信协议帧怎么构造、软元件地址怎么和STM32内存映射、梯形图编译出来的指令表怎么解释执行以及我在实际调试中踩过的一堆坑。内容偏实践尽量不给空理论想动手做的照着一路做下去就能跑通。1. 项目核心思路拆解为什么STM32能“冒充”一台FX3U1.1 你要仿的到底是什么通信协议、内存模型、指令集很多人第一次听到“仿PLC”会下意识觉得特别难但实际上你只需要搞清楚三件事PLC和上位机之间怎么说话通信协议、程序和数据放在哪内存模型、梯形图编译后变成了什么指令集。这三件事搞明白一台PLC对你来说就是一段可以重写的代码。先看通信协议。三菱FX3U支持多种通信接口我们常说的“编程口”是最容易仿的物理层是RS422波特率96008数据位偶校验Even1停止位。GX Works2在连接设置里选RS-232C后实际上就是通过这个编程口协议和PLC通信。整个协议是主从式的PC发命令帧PLC回响应帧一次一问一答没有PLC主动上报花活非常适合嵌入式实现。再看内存模型。FX3U内部有一个统一的软元件空间X是输入继电器按八进制编号X0~X7、X10~X17这样排Y是输出继电器M是内部继电器D是数据寄存器还有T定时器、C计数器、S状态继电器。梯形图里写的每一个元件最终都会落到这些编号上上位机读写PLC本质上就是读写这一片地址空间。最后是指令集。GX Works2写完梯形图后点“变换”软件会把图形化的梯形图编译成一条一条的指令表类似汇编语言。LD X0、OUT Y0、MOV D0 D10、CMP K100 D0 M10全部都是操作码加操作数的结构。STM32要做的就是把这些指令一条一条取出来、解码、执行然后再走下一个扫描周期。这三件事你都吃透了那台“假FX3U”就已经成立一半了。1.2 方案选型硬件平台和通信接口要定对硬件平台我推荐从STM32F103ZET6或者F103C8T6起步。F103C8T6便宜到十几块钱Flash才64KB跑基本指令和常用功能指令够了。但如果你的梯形图程序比较大、功能指令用得多F103C8T6的内存会紧张一编译进去就超容量这时候果断上F103ZET6或者F407留出余量。通信接口这块有个小坑FX3U原生编程口是RS422电脑那边一般是RS232串口或者USB转串口。实际DIY不用完全照搬RS422直接让STM32的USART通过一个MAX3232芯片转成RS232电平再接到电脑的USB转串口上GX Works2一样能识别。注意三菱编程口协议对时序有隐含要求不能太拖所以USART中断收发必须做好别用那种阻塞式延时收发的写法。扫描周期这块PLC和单片机最大的区别就是“循环扫描模型”读输入、执行程序、更新输出、通信服务整个过程在一个周期里完成。STM32里我建议用一个定时器中断打周期标志位比如每5ms或10ms产生一次主循环检测到标志后就执行一遍完整的扫描流程。这个机制是仿PLC的命根子没有它梯形图程序的时序逻辑就跑不对。2. 核心协议与内存映射仿PLC最关键的“翻译关”2.1 三菱FX编程口协议帧格式与校验计算三菱FX编程口协议的数据帧结构其实非常固定命令分两种一个是批量读取软元件一个是批量写入软元件。读命令的请求帧长这样括号内是十六进制STX(02) 命令码(FF) 起始地址(4位ASCII) 读取点数(2位ASCII) ETX(03) 校验和(2位ASCII)响应帧则是分两步回的先回一个ACK(06)告诉上位机“我收到命令了”随后再回数据帧STX(02) 数据区 ETX(03) 校验和。注意这两个步骤中间有细微的时间间隔要求不能合成一帧一次性发给PCGX Works2对握手时序比较敏感我在实际调试中发现有些初学者就是把ACK和数据拼在一起发导致上位机直接报超时。写命令的请求帧格式类似区别是命令码变成80且后面要跟要写入的数据区。校验和的计算方式是这样的从STX后面的第一个字节开始一直累加到ETX包括ETX取累加和的低字节并将这个低字节拆成两个十六进制字符大写。举个例子如果累加和是0x5A那么发送的ASCII串就是“5A”。这里最容易算错的是累加对象——“FF”是作为两个字符0x46、0x46参与累加的不是当成字节0xFF去加。很多第一次写协议的人在这栽跟头返回的校验怎么都对不上。我贴一段简单的校验和计算函数方便你直接抄作业uint8_t calc_sum(const uint8_t *data, uint16_t len) { uint8_t sum 0; for (uint16_t i 0; i len; i) { sum data[i]; } return sum; } void append_sum(uint8_t *frame, uint16_t len) { uint8_t sum calc_sum(frame 1, len - 2); // 去掉STX和最后的校验位 uint8_t hi (sum 4) 0x0F; uint8_t lo sum 0x0F; frame[len] hi 10 ? (0 hi) : (A hi - 10); frame[len 1] lo 10 ? (0 lo) : (A lo - 10); }2.2 软元件地址编码与STM32内存映射表设计上位机读到的“起始地址”并不是X、Y、M、D这些软元件编号本身的十进制数而是一个经过偏移编码后的十六进制地址。每种软元件有各自的基址区间这是仿制时最容易出错的点我直接给你一张整理好的表软元件基址十六进制编号范围备注X输入0x0080X0~X377按八进制编号每位对应一个BITY输出0x00A0Y0~Y377按八进制编号每位对应一个BITM中间继电器0x0100M0~M7679位访问每8个点组成一个字节D数据寄存器0x1000D0~D799916位为单位T定时器0x00C0起T0~T255位/字混合C计数器0x01C0起C0~C255位/字混合这里有个关键理解三菱FX系列地址空间的单位是“字”或者“位”要看访问类型。批量读取命令里会有个点数字段如果是读M软元件点数按位算读D软元件点数按字算每个字两个字节。STM32内部千万不要用二维数组或者链表去模拟这些元件直接定义一个大型的内存池数组让所有软元件落到映射好的偏移上。我习惯的做法是这样的#define FX3U_MEMORY_SIZE 0x3000 uint8_t fx3u_mem[FX3U_MEMORY_SIZE]; uint16_t* mem_word(uint16_t addr) { return (uint16_t*)fx3u_mem[addr]; } uint8_t* mem_bit(uint16_t addr) { return fx3u_mem[addr 3]; }X、Y、M这些位元件用bit操作去读某个位D、T、C这些字元件按16位字来写。协议层接收到地址后先减去基址得到相对偏移量再通过上面这两个宏访问内存池整个读写逻辑可以做到效率很高且代码量极少。2.3 程序下载后的存储与加载机制GX Works2下载梯形图时不是直接把指令表塞给PLC而是通过写命令把数据写到软元件空间中的指定区域。在FX3U的内存映射里有一段专门用来存放用户程序的空间地址从0x0800开始叫作文件寄存器/程序区。这一点你在线监视时能看到GX Works2会弹出一个“程序存储器”的信息栏。所以你的STM32固件里要预留一块Flash或者外部EEPROM区域把接收到的用户程序在RAM里临时存好后再写入到内部Flash的预留扇区。下次断电重启从Flash里读出指令数据加载到内存池对应区域就能做到“程序断电保存”。如果不做这一步每次上电程序归零实用性就废了一大半。我在工程里是这么处理的在Link脚本里预留一块2KB区域专门存放梯形图程序启动时用memcpy把这部分数据从Flash捞到RAM程序区。需要注意STM32 Flash写入前必须先擦除整页而且擦写过程中不能响应中断否则会卡死。要先把接收完的程序暂存在RAM缓冲区里统一进行一次擦写而不是收一条写一条。3. 实操过程从串口抓包到第一段“梯形图”跑起来3.1 环境准备与最小硬件接线整个调试链路不复杂电脑上装好GX Works2和任意一款串口助手STM32开发板通过USB转TTL连电脑PA9USART1_TX接串口模块RXPA10USART1_RX接串口模块TXGND共地。这里一定要共地不共地跑通信就像两个人各说各话时序全乱。分辨率再拉高一点推荐先不要直接接GX Works2抓包而是用串口助手监听GX Works2发出的原始帧。做法是让USB转TTL模块同时接到两个设备不行串口是点对点的。我实际的操作是先用GX Works2连接真实FX3U PLC把串口模块夹在PLC和电脑中间用带监听的USB转TTL模块抓包。没有真实PLC的话也可以从网上搜现成的FX3U协议帧抓包数据先对着数据手工模拟回包把收发逻辑调通再接GX Works2。开发环境方面标准库和HAL库都行但我个人更喜欢标准库原因只有一个通信协议栈对中断响应时间有要求标准库更透明出问题好排查。串口初始化就开接收中断和空闲中断一帧收完置标志主循环里解帧、回包不要在主循环里做while等接收。3.2 用串口助手模拟GX Works2的读写命令在接GX Works2之前先用串口助手手工模拟上位机发命令验证你的协议解析是否正确这是排错效率最高的一步。比如要读取STM32模拟PLC的D0~D9共10个字的数据命令构造如下十六进制ASCII字符串02 FF 1000 0A 03 校验位这条命令解析出来是STX0x02命令码0xFF读起始地址是ASCII“1000”十六进制0x1000对应D区基址点数是ASCII“0A”十进制10个字ETX0x03后面跟着校验和。你的STM32收到后应该先回一帧“06”表示收到然后立刻回02 20字节的D0~D9数据 03 校验位这时候重点检查点数和字节数的关系读D元件时每个元件是16位10个D等于20字节很多初学者按点数直接读了10个字节回去GX Works2直接报错数据长度非法。同理写命令的时候上位机发多少数据字节你也得按2倍点数去解析。用串口助手手发这些帧调通了再接GX Works2基本上一下就通。千万别跳过这步直接连GX Works2否则你没数据链路层的抓手出了问题完全是黑盒状态。3.3 指令解释器的实现要点协议通了之后真正让梯形图“活”起来的是指令解释器。GX Works2编译输出的指令表逻辑上是一条条指令每条指令由操作码和操作数组成。操作码决定动作比如LD是“读位取入累加器”OUT是“输出到线圈”操作数是软元件编号或者立即数。我用一个状态机来执行uint16_t pc 0; while (1) { uint8_t opcode program[pc]; uint16_t operand 0; // 根据操作码决定是否需要读取操作数 switch (opcode) { case OP_LD: // 取位到累加器 acc get_bit(operand); break; case OP_AND: acc get_bit(operand); break; case OP_OUT: set_bit(operand, acc); break; case OP_MOV: // 功能指令需取双操作数 mem_word(operand2) mem_word(operand1); break; default: break; } if (opcode OP_END) break; }这个循环的复杂度和完整度完全取决于你想支持多少条功能指令。FX3U的指令系统很庞大几百条功能指令不可能全做完我建议起步阶段只实现LD、LDI、AND、ANI、OR、ORI、OUT、SET、RST、END这些基本指令再加上MOV、ADD、SUB、CMP这四五个最常用的功能指令已经能覆盖80%以上的实际编程场景了。后续再加指令也只是查表解码的事架构上不会推翻重来。指令解释器最需要小心的是程序跳转和条件分支。FX3U指令表里没有显式的跳转行号梯形图是靠“回路块”逻辑自然形成的GX Works2编译时已经把块结构理顺了。解释器只需要逐条顺序执行碰到MPS/MRD/MPP堆栈指令时用一个栈保存中间结果碰到功能指令按操作数宽度正确读取就行。4. 常见问题与调试心得我在这个项目里踩过的实战坑4.1 通信连不上GX Works2一直报超时这是项目做到一半最常见的问题。排查优先级从高到低波特率、校验位、共地、帧格式、校验和。GX Works2连接设置里选RS-232C后一定要手动确认波特率9600、偶校验这一项错了一切白搭。然后看你的串口是不是处于“阻塞等待”状态有些代码在收到STX后死等后续数据一旦某一帧丢了一个字节整个通信就卡死了。串口接收一定要有超时判断超过100ms收不齐就复位接收缓冲区。另一个案例是GX Works2选了“USB”连接模式去连STM32这走的是另外一套协议底层驱动是三菱官方USB驱动不是普通的COM口很多DIY被这坑了一晚上。记住你的STM32通过串口模块接出来在GX Works2里要选RS-232C连接并指定COM口号不能选USB。4.2 校验和总是算不对我前面提过很多人的第一个版本程序校验和的累加对象弄错了。再强调一次帧里除了STX和校验和本身其余所有ASCII字符的ASCII值都要参与累加不是十六进制字节值。比如发送命令码0xFF在帧里以字符形式存在的是“FF”累加时加的是0x46和0x46不是0xFF。调试技巧先用最简单的全0数据帧算一遍校验拿计算器和串口助手手动对一遍确认无误再套代码。或者直接用printf把每一帧的十六进制数据打出来跟网上已知正确的抓包数据逐字节比对比你在那儿干猜快得多。4.3 程序能下载但运行逻辑不对下载成功说明协议通了一半运行不对说明解释器有问题。碰到这种问题先在STM32里放一段最简单的程序一个X输入接一个常开触点直接输出到Y。GX Works2里写个最最基础的“导线直连”逻辑下载后看能不能通。如果这个都跑不对逐条打印你解释器每步执行的指令行号和操作数对照GX Works2里的指令表视图看哪条指令的解析错位了。最常见的是操作码后面跟的操作数宽度不对——三元操作数的功能指令比如CMP S1 S2 D只解析了两个导致从下一条指令开始全部错位。修复方式只有一个把所有功能指令的操作数宽度整理成一张表解码时查表决定读多少字节别用if else硬编码。4.4 地址映射串位X0变成Y0的诡异问题这是三菱PLC特定坑X和Y的编号都是八进制的X7之后不是X8而是X10。GX Works2里所有软元件的编号输入框都自动帮你按八进制处理了但你的STM32内存映射函数如果直接按十进制做位偏移就会错位。处理方式很简单解析地址时先判断基址属于哪个区间然后把软元件编号从八进制字符串转换成二进制偏移量再做位操作。同理M元件每个位对应一个地址编号M0~M7在内存池中占第一个字节M8~M15占第二个字节M编号也是连续十进制但中间隔了8个位。用上面的mem_bit函数时偏移计算要精确到“位”再除以8找字节、取模8找位号错一位就是完全不同的继电器。4.5 触摸屏和上位机扩展从仿PLC到开放的工业网关项目做到后面你会发现既然STM32能跑三菱协议为什么不能同时跑Modbus我在自己的工程里加了Freemodbus从站协议栈把所有D寄存器直接映射到Modbus保持寄存器地址上。触摸屏、组态软件、LabVIEW不用三菱协议也能直接读写这台“PLC”的数据这几乎白送了一个协议转换网关功能。这样扩展之后这个DIY设备的应用场景就完全不局限于学PLC了。非标设备里需要采集IO、跑逻辑、对接上位机一块STM32、一个485接口、一段梯形图程序搞定成本比任何方案都低。而且协议栈全部开源可见想改控制逻辑就改梯形图想改通信就直接动C代码自由度高到不行。我个人在实际做完这个项目之后最大的感受是三菱FX3U并没有比STM32多出魔法PLC的底层不过是协议、内存、指令三叠层。把这个模型在单片机里重写一遍你对PLC的理解会突破“用软件”的层面直接进入“造工具”的阶段。如果后续你想进一步拓展还可以研究一下FX3U的定位指令、原点回归、高速计数器这些特殊功能它们和普通软元件的区别无非是在解释器里挂上定时器、外部中断这些硬件外设把指令操作数变成实际的外设寄存器而已。搞懂这一层你就真的拥有一台“什么都拆得开”的PLC了。本文还有配套的精品资源点击获取