
做自动售货机这类项目我一直觉得是学习工业控制系统的最好切入点它规模不大但五脏俱全。数字量输入按钮、投币信号、数字量输出电机、指示灯、模拟量处理如果有温度或压力、计数逻辑、状态机设计再加上上位机组态和通讯一个项目把PLC和WinCC最核心的活儿全练了一遍。我大学那会儿用S7-300和WinCC搭过一套三货物版的自动售货控制系统今天把这套设计从头到尾拆开讲硬件选型、I/O分配、控制逻辑、WinCC画面、联调排坑全给你捋一遍。这篇内容既适合正在做课程设计的学生也适合想从基础项目上手西门子全家桶的工程师。三货物版听起来简单但五脏俱全把它吃透了后面做几十种货道的大机器也就是在这个骨架上加货道和算法的事。1. 项目整体设计与方案选型1.1 为什么选S7-300和WinCC这套组合先聊一下方案选型。现在市面上做小型自动售货机控制用S7-200 SMART或者S7-1200的非常多因为便宜、上手快调试工具也简单。那为什么我当初偏要选S7-300和WinCC主要有几个现实原因。第一教学和实训环境里S7-300是很多学校的老牌标配设备实验室里淘汰下来的CPU 315-2DP、SM321、SM322模块一大堆成本几乎为零。第二S7-300搭配WinCC是西门子经典的中大型上位机方案能让你完整走一遍“PLC编程上位机组态通讯调试”的工业标准流程这套方法论放到以后做生产线、做SCADA系统完全通用。第三WinCC的画面组态能力、脚本扩展能力比那些小触摸屏的组态软件强太多做报警记录、趋势曲线、用户权限都很方便适合把项目做得像个正经系统。那如果你手头只有S7-1200或S7-200 SMART也没关系本文的逻辑完全通用只是硬件配置表里模块型号和地址要改一下。真正核心的控制思路和WinCC脚本逻辑换平台之后依然能直接用。1.2 三货物自动售货机的控制需求拆解三货物版顾名思义就是机器里只有三种商品。我们假设是可乐、矿泉水、薯片售价分别是3元、2元、3.5元。这样设置的好处是既有整数价格又有小数价格必须处理“元”和“角”的找零逻辑。整个系统的功能需求可以拆成下面几块投币识别与金额累计支持1元硬币、5角硬币、纸币可选的模拟输入实时计算当前投入金额。商品选择与出货执行检测三个选择按钮判断金额是否足够足够则启动对应货道的出货电机并减少库存计数。找零计算与执行系统计算“投入金额-商品价格”如果有多余的钱输出找零信号驱动退币机构。缺货检测与提示每个货道配一个检测开关可以用微动开关模拟无货时对应指示灯亮并且禁止该货道出货。库存管理与状态监控上位机WinCC实时显示每种商品的剩余数量、销售总数、当前投币金额、系统状态。手动/自动模式切换维修模式下可以通过上位机或柜内按钮直接点动测试电机方便调试。三货物版本看起来很基础但该有的状态转换一个都不能少。状态机至少包含空闲等待、投币累计、选择确认、出货执行、找零结算、故障报警这几个状态。后面编程的核心就是把这个状态机在PLC里用梯形图或者STL实现出来。2. 硬件系统配置与I/O分配2.1 S7-300硬件组态清单根据上面的需求我们先列一份硬件清单。我这次用的是传统的S7-300 WinCC 7.x经典组合通过以太网Industrial Ethernet或者MPI通讯。典型配置表如下组件订货号示例说明电源模块 PS 3076ES7 307-1EA00-0AA024V DC / 5A给PLC和传感器供电CPU 315-2 PN/DP6ES7 315-2EH14-0AB0自带PN口可走Profinet或S7通讯数字量输入模块 SM3216ES7 321-1BH02-0AA016路24V DC输入数字量输出模块 SM3226ES7 322-1BH01-0AA016路24V DC输出驱动中间继电器存储卡 MMC6ES7 953-8LJ20-0AA0用于存储程序如果实验室是老的DP口CPU比如315-2DP那和WinCC通讯就需要配CP5611通讯卡或者走DP口WinCC里驱动选“SIMATIC S7 PROTOCOL SUITE”然后选择MPI或DP信道。现在WinCC 7.x连接新CPU直接选“SIMATIC S7-300”通道填IP地址就行简单很多。模块地址分配也需要提前规划。S7-300的I/O地址是系统自动分配的默认情况下IM0开始是0号机架。我这套配置里SM321输入模块默认地址是IB0~IB1SM322输出模块是QB4~QB5具体看机架配置在HW Config里可以改。2.2 I/O点位分配与电气接线要点我们把三货物版的I/O点位完整列出来这个表同时也是接线图纸的依据。地址信号名称说明I0.0启动按钮系统上电初始化I0.1急停按钮紧急停止常闭触点I0.21元投币传感器上升沿有效投入一枚1元硬币I0.35角投币传感器上升沿有效投入一枚5角硬币I0.4商品1选择按钮可乐常开按钮I0.5商品2选择按钮矿泉水常开按钮I0.6商品3选择按钮薯片常开按钮I0.7找零执行反馈退币机构动作完成反馈I1.0货道1缺货检测无货时闭合I1.1货道2缺货检测无货时闭合I1.2货道3缺货检测无货时闭合Q4.0货道1出货电机驱动推杆电机Q4.1货道2出货电机驱动推杆电机Q4.2货道3出货电机驱动推杆电机Q4.3找零电机/退币电磁铁执行退币Q4.4运行指示灯绿色系统正常指示Q4.5故障指示灯红色缺货或故障指示Q4.6蜂鸣器出货完成/报警提示接线有几点需要注意按钮和传感器的公共端统一接M0VPLC输出端公共端接L24V即采用“源型输入、源型输出”的接法这是S7-300最常见的接线方式。投币传感器是NPN型还是PNP型必须提前确认。PNP型输出高电平有效接法是传感器正极接24V信号线接PLC输入点NPN型则刚好相反若PLC输入模块是源型输入则NPN传感器接线会很别扭需要在信号线上加上拉电阻或者更换模块。这里强烈建议一律选PNP型传感器。输出端不建议直接用SM322直接驱动电机应该接中间继电器24V DC线圈再由继电器触点去控制电机主回路。原因很简单电机启动瞬间冲击电流大可能会把输出模块的晶体管或者继电器触点烧掉换继电器成本低换模块就肉疼了。急停按钮用常闭触点串联在24V电源回路里而不是只在程序里做逻辑。这样一旦急停被按下输出模块和电机电源直接被切断硬件级安全符合工业安全惯例。3. 控制逻辑设计与程序实现3.1 存储区和数据块规划写程序之前先把数据块规划好不然写到后面一团乱麻。我习惯建两个DB块一个叫DB1_CORE存放核心数据一个叫DB2_STAT存放统计数据和WinCC需要读取的变量。DB1_CORE核心数据块建议包含以下变量变量名类型作用TotalMoneyREAL当前投入总金额Goods1_PriceREAL商品1价格3.0Goods2_PriceREAL商品2价格2.0Goods3_PriceREAL商品3价格3.5Goods1_StockINT商品1库存Goods2_StockINT商品2库存Goods3_StockINT商品3库存Change_MoneyREAL需要找零的金额State_CurrentINT当前状态机编号State_FlagBOOL出货执行标志Change_FlagBOOL找零执行标志DB2_STAT统计块主要是销售额、各商品销量、总销售次数等供WinCC做报表和曲线显示。把数据块拆分的好处是核心控制程序读DB1上位机只读DB2两者互不干扰也方便后续扩展。3.2 状态机设计与梯形图实现三货物版的控制逻辑虽然简单但我还是建议用状态机的思路来组织。原因很简单状态机能让程序结构异常清晰排查问题的时候一眼就能看出当前执行到哪一步。状态编号分配如下0空闲状态1投币累计状态2选择确认状态3出货执行状态4找零结算状态5故障报警状态下面逐个状态说一下关键梯形图思路。投币累计。这是最基础的一块。核心是利用上升沿指令检测I0.2和I0.3然后把对应金额累加到TotalMoney里。梯形图里就是两个并联的上升沿分支分别对TotalMoney做加法。有一点要特别注意一个1元硬币投下来信号保持时间可能只有几十毫秒到几百毫秒但PLC扫描周期是毫秒级如果不做上升沿触发同一个硬币会被累计十几次。所以必须用“边沿检测”要么用上升沿指令要么自己写一个“前一个周期值比较”的算法。这是所有投币设备程序的血泪教训。选择确认与金额判断。在总金额大于等于商品价格的前提下按下选择按钮进入出货执行。梯形图里用三个选择按钮的常开触点和比较指令累计金额 商品价格串联然后置位出货标志。这里我用了带保持的置位位而不是普通输出线圈。为啥因为按下按钮的瞬间到电机启动、商品掉落、检测反馈整个过程需要一定时间普通线圈一旦松开按钮就复位了根本撑不到出货完成。所以出货标志用置位指令等出货完成后由检测反馈或者定时器复位。出货执行。出货执行是用定时器控制电机运行时间。比如产品从货道掉落到取货口推杆电机需要运行1.5秒程序里就启动一个TON定时器定时时间到自动复位出货标志。同时出货过程中要把相关标志位之间的互锁关系处理好例如商品1正在出货时商品2和商品3的选择请求必须被忽略否则会出现多路电机同时运行的混乱状态。这个互锁的写法既可以在梯形图里用常闭触点互锁也可以在程序开头加一个统一的Gate条件即State_Current 3时所有选择输入都不响应。找零计算与执行。这个逻辑稍微有点绕。出货完成后计算应找零金额Change_Money TotalMoney - 商品价格。如果Change_Money大于0.01避免浮点误差搞出莫名其妙的0.0000001则置位找零执行标志Q4.3输出驱动退币电机动作。退币电机运行时间和硬币面值有关比如退一枚1元硬币电机转0.6秒退一枚5角硬币转0.4秒。所以找零不是简简单单退一次而是要循环判断找零金额能否分解成1元和5角然后循环退币直到Change_Money小于最小面值。这本质上是一个零钱组合算法用STL写起来比纯梯形图舒服很多。这里也分享一个STL的片段思路用一个循环当Change_Money 1.0时置位退1元币输出运行0.6秒后复位然后Change_Money Change_Money - 1.0当Change_Money 0.5时执行退5角币。程序是把同样的判断块复制两份分别对应1元和5角退币电机也可以用一个DB里的计数器来控制循环次数避免无限循环。缺货检测与故障处理。在启动出货之前程序必须检查对应货道的缺货传感器。如果I1.0闭合表示货道1没货了那么即使投币金额足够、按下选择按钮也不能执行出货而是应该把故障指示灯Q4.5置位状态机切换到故障报警状态。同时WinCC画面上对应货道的图标要变灰或者闪烁提示工作人员补货。3.3 实现一个“二次确认”功能的思路这里插一个很实用的小功能。WinCC的的热搜词里有“c脚本实现对某变量置位复位二次确认功能”。这个在很多场合都会用到比如在WinCC画面上按“手动出货”按钮为了防止误触要弹出一个确认对话框确认后才真正置位PLC变量。实现方式是这样的在WinCC的按钮M事件鼠标按下里写C脚本调用系统对话框函数用户点“确定”之后才调用置位函数置位变量点“取消”则不动作。同时可以用一个内部二进制变量记录确认状态如果不为真就直接返回。这套逻辑用在自动售货机的“管理员补货测试”功能上特别合适防止巡检人员误点了出货按钮导致商品掉出。4. WinCC上位机组态与画面设计4.1 WinCC变量管理与PLC通讯建立WinCC和S7-300通讯经典做法是使用“SIMATIC S7 PROTOCOL SUITE”通道。新建连接时填写PLC的MPI地址或者IP地址。我这里以以太网通讯为例在WinCC项目管理器的“变量管理”里右键“添加新驱动”选择“SIMATIC S7-300/400”或者“S7 PROTOCOL SUITE”。在通道单元下新建连接填入CPU的IP地址比如192.168.0.1。在连接下新建变量变量类型记得和PLC里的DB块变量严格对应REAL对应浮点数32位INT对应16位整数。WinCC变量的刷新周期可以单独设定一般状态值用250ms刷新控制指令用事件触发或者100ms刷新别把刷新周期设得太短否则网络通讯负载会增大画面卡顿。有一点需要提醒WinCC里新建的变量地址比如“DB1.DBD0”对应PLC里的DB1.DBD0必须保持数据类型和偏移量完全一致差了半个字节都可能读成乱码。常见的坑是PLC侧数据块里加了一个BOOL变量占了一个字节但网上WinCC变量地址没跟着偏移导致后续所有REAL数值全部错位读出来都是天文数字。这个我踩过不止一次解决办法就是建立DB块时手动分配偏移量把BOOL变量集中放在DB块开头后面连续的地址全部留给数组或数值变量。4.2 画面布局与对象动画效果WinCC画面设计我的经验是做三个画面就够主监控画面、库存报表画面、手动调试画面。主监控画面布局上部是系统状态栏运行指示灯、故障指示灯、当前系统状态文本。中间是三个商品展示区每个商品区放置商品图片、库存数字、价格标签、投币金额显示、选择按钮。下部是投币信息区和找零信息区显示当前累计投币金额、应找零金额、退币动画。动画效果是WinCC画面最出彩的部分。比如投币时画面上的硬币图标从投币口移动到计数区这个可以用对象的“水平移动”动画功能关联一个内部变量或者直接关联PLC的投币计数位。又比如出货电机动作时商品图片从上往下移动给人一种商品掉落的感觉这个用垂直移动动画就能实现。实际项目中如果PLC输出的出货标志位Q4.0置位WinCC里对应的画面对象就做一次平移加闪烁过程非常直观。组态时注意动画触发变量要选“BOOL类型”并且勾选“更新周期”为“有变化时”避免信号变化不及时导致动画延迟。4.3 C脚本实现置位、复位、二次确认WinCC的C脚本是这个项目的加分项。以“手动出货”按钮为例完整的C脚本逻辑如下#include apdefap.h int g_bConfirm 0; // 确认标志放全局变量或静态变量 void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName) { // 第一次点击弹出确认框 if (g_bConfirm 0) { int nResult; nResult MessageBox(NULL, 确定要执行手动出货吗, 二次确认, MB_YESNO | MB_ICONQUESTION); if (nResult IDYES) { g_bConfirm 1; // 记录已经确认 SetTagBit(ManualStart_Flag, 1); // 置位手动出货标志 SetTagBit(ManualStart_Flag, 0); // 根据工艺需要延迟后复位 } else { g_bConfirm 0; } } else { // 第二次以后点击直接复位并重置确认标志 SetTagBit(ManualStart_Flag, 0); g_bConfirm 0; } }当然前面这段脚本是为了演示“置位、复位、二次确认”的写法。实际工程中我一般不会用全局变量做确认标志而是用画面对象属性或WinCC内部变量来做这样画面关闭再打开状态不会丢失。另外要强调的是置位和复位之间到底要不要延迟取决于PLC侧是怎么处理这个标志位的。比较稳妥的做法是PLC侧定义“SinglePulse”逻辑即WinCC置位一个启动脉冲PLC扫描到上升沿后自行执行完整流程并复位。让PLC来主导时序WinCC只负责发脉冲这样即使画面卡顿也不会导致控制逻辑出错。4.4 数据报表与库存历史曲线WinCC自带的“变量记录”和“表格控件”可以做销售统计。我在这个项目里把每次出货的销售记录写到一个SQL Server数据库里WinCC曲线控件直接读数据库生成小时销量趋势图。如果你不想碰数据库也可以把销售数据保存在PLC的DB2_STAT数据块里WinCC的表格控件绑定DB2变量历史数据只能保存最近的若干条。对学生作品或者初级项目来说PLC存储方案已经完全够用不需要上数据库。5. 联调过程与常见问题排查实录5.1 离线模拟调试的完整流程写好的PLC程序不可能直接上真机就成功我习惯先做离线模拟。S7-300可以用PLCSIM西门子PLC仿真器做完整的程序调试。调试步骤大概是打开PLCSIM插入CPU下载程序。在仿真器里手动置位I0.2模拟投币观察DB1.TotalMoney是否是预期值。置位I0.4选择商品1观察Q4.0是否有输出出货标志是否按定时器时序复位。手动修改DB1.Goods1_Stock为0再试选择商品1观察是否能够正常禁止出货并触发缺货报警。检查找零逻辑投入5元选择3.5元的商品3观察Change_Money是否等于1.5退币动作是否按1元、5角分解执行。PLCSIM调完之后换到真实PLC上这个递进过程能帮你节省大量现场时间。把问题留在仿真里解决到现场只需要验证传感器接线和输出回路。5.2 实战中遇到的几个典型故障与排查方法结合我看过的很多类似项目的反馈以及我自己调设备时踩过的坑这里列了一个常见问题速查表算是排障经验总结。故障现象可能原因排查与解决办法投币后金额不增加传感器接线反了PLC输入点损坏梯形图没做上升沿万用表测传感器信号线是否有24V强制输入点测试检查程序是否用上升沿指令选择按钮按下无响应总金额判断条件不满足货道缺货信号误触发状态机不在允许状态监控TotalMoney和缺货信号强制置位缺货信号确认输入正常检查状态机当前编号电机不转或转一下就停输出继电器触点烧蚀电机启动电流过大定时器时间设得太短换中间继电器串电流表实测启动电流把TON时间从0.5秒调到1.5秒测试WinCC连不上PLCIP地址不在同一网段驱动选择错误未下载通讯配置在WinCC诊断器里查看通道状态ping PLC的IP检查连接配置的机架号和插槽号WinCC读取的DB数值乱码变量类型或地址偏移不匹配刷新周期太短对比PLC侧数据块偏移和WinCC变量地址BOOL类型变量单独集中放置刷新周期调到200ms以上找零金额错误浮点数精度问题找零分解逻辑缺项把浮点比较改成“绝对值小于0.01”判断打印Change_Money中间值用整数“分”为单位存储金额这里单独说一下浮点精度问题。PLC的REAL是32位浮点存储3.5这样的十进制小数时本身就有误差。如果你写的是“IF Change_Money 0.0 THEN”这类相等判断十有八九会失败。正确做法是判断绝对值小于一个很小的阈值比如0.001。另一个更激进的办法是所有金额都换算成“分”整数1元就是100分3.5元就是350分全程用INT或DINT计算彻底避开浮点误差。我后来在做商业项目时全部改成了整数分存储这个经验特别建议大家抄走。5.3 抑制电磁干扰的几个实用措施自动售货机里电机启停频繁继电器通断会产生火花这在工业现场是典型的电磁干扰源。如果WinCC画面上监控值偶尔跳变、PLC输入信号误触发八成是干扰问题。我总结几个低成本高收益的防干扰措施PLC电源和电机电源分开供电不从同一个开关电源出线。输入信号线用屏蔽线屏蔽层单端接地PLC柜内接地端子不要两端接地避免形成地环路。电机和继电器线圈两端并联续流二极管直流负载或RC吸收电路交流负载。传感器信号线尽量远离动力线间距至少20厘米如果做不到就穿金属软管。24V电源线的正负极在PLC柜内采用双绞线减少压降和电磁耦合。同学们做项目时最容易忽略的是第二条“屏蔽层单端接地”很多人把屏蔽层直接剪掉不用结果现场干扰问题层出不穷。其实这根线接好了大多数信号抖动都能解决。6. 扩展方向与个人经验总结三货物版做完以后再往大了做其实就是一个扩展思路的问题。这里分享几个我后来在正式项目中用到的扩展方向供大家在原有系统基础上改进。第一货道扩展和统一寻址。三货物版是三个Q点直连三个电机但如果要做几十个货道就必须引入“矩阵式驱动”方案用继电器矩阵先选中某一行货道再选中某一列通过行列组合来激活指定电机。这样几十个电机只需要十几个输出点就够了成本大幅下降但程序里需要设计一个寻址逻辑本质是把“货道编号”映射成“行列组合”。这个思路和PLC控制数码管动态扫描很像理解了扫描两个字就懂了。第二支付方式扩展。现代自动售货机早就不是投硬币的年代了扫码支付、刷脸支付已经是标配。PLC层面并不直接对接微信、支付宝而是对接一个支付网关板。用户扫码后云端服务器返回支付成功信号网关板通过串口Modbus RTU或者网口发给PLC一个“允许出货”的指令。PLC程序里需要增加一个“订单状态”的数据结构包括订单号、金额、支付状态收到确认信号后校验金额再出货。这样一来之前的投币逻辑就要重构为“金额池支付来源”的通用框架。第三远程运维。这个是工业物联网的玩法了PLC通过以太网上传到边缘网关网关把数据转发到云平台手机端就能看到每台售货机的库存和故障状态。S7-300因为本身PN口支持Profinet和TCP/IP通讯配合S7通讯协议很容易实现数据上云。WinCC上的那些统计变量都可以同步映射到云端数据库做更复杂的销售预测和补货调度。最后说点实际的个人体会。做这个项目的过程中我最大的收获不是“学会了S7-300的指令”或者“会组态WinCC画面”而是建立了一套完整的调试思维任何功能都要先从硬件和信号层面验证再谈软件逻辑任何软件逻辑都要先从状态机角度设计再动手写指令任何通讯问题都要先用最小系统分层排查而不是上来就重新下载程序。这套思维比你记几万个指令都有用。另一个建议是做项目时尽量把注释写完整、把中间变量集中管理哪怕只是一个小小的课程设计也要养成工程习惯。因为等你到现场调试的时候面对着一堆“M0.0、M1.3”这样的地址没有完善的注释三天后你自己都看不懂自己写了什么。这套三货物控制系统的设计过程就是我理解工业自动化最扎实的一课。把它认真做好胜过草草跑通十个“看起来高级”的大项目。