简介:一套基于STM32F103VET6实现国产三菱FX3U PLC的完整工程,面向PLC嵌入式开发与工业控制学习者。包内含AD09设计的硬件资料,包括原理图、PCB、封装库等,以及完整的STM32软件源码,代码展示了PLC输入输出配置、RTC时钟、D8000~D8126初始化、断电数据恢复、485通信等关键模块,便于理解FX3U兼容方案的软硬件设计思路。压缩包共525个文件,涵盖schdoc、pcbdoc、c/h源文件、uvprojx工程配置及hex烧录文件等,总大小22.36MB,目录结构清晰,适合作为国产PLC开发的设计参考与二次开发基础。目前已有524人浏览学习,可辅助快速搭建实验平台并对照源码进行功能调试。
1. 基于STM32F103VET6单片机设计实现国产三菱FX3U全套资料AD09设计硬件:这套包能复刻出一台FX3U兼容机吗
老产线上那台FX3U用了七八年,输出模组报故障,备件商一句“停产”就把人堵死。这时候看到“基于STM32F103VET6单片机设计实现国产三菱FX3U全套资料AD09设计硬件(原理图PCB)+STM32软件源码”这种压缩包,第一反应是:与其买一台电路黑匣子一样的兼容整机,不如自己把原理图、PCB和固件都握住。这套方案的核心,是用STM32F103VET6这颗Cortex-M3芯片,按照三菱FX3U的端子定义、安装尺寸和指令习惯,从零搭一套硬件加固件。AD09负责原理图和PCB,STM32软件源码负责跑梯形图逻辑和串口通讯。它适合两类人:想给产线备件做替代方案的电气工程师,以及想搞自动化产品软硬件一体开发、拿真PLC练手的单片机工程师。但我要先把丑话说前面:通信协议兼容是个慢功夫,别指望解压后一天跑通。
2. AD09硬件设计:把STM32F103VET6的100个引脚按FX3U端子排规划成原理图与PCB
2.1 先做引脚规划:100个引出脚怎么分给输入、输出和通信口
STM32F103VET6是LQFP100封装,100个引脚,72MHz主频,512KB Flash,64KB SRAM。这个资源量拿来跑一台小型PLC的逻辑绰绰有余,真正费神的是I/O规划。
常见的DIY方案把目标定在FX3U-32MT这种规模:16路数字量输入、16路数字量输出,外加1路RS485通信口和1路编程口。FX3U的输入端子是24V有源输入,而STM32是3.3V逻辑,所以每一路输入都得过光耦隔离再进引脚。输出侧如果做晶体管输出,则用ULN2803这类达林顿阵列驱动24V负载。我做这类板子时的引脚划分大致如下:
| 功能 | STM32引脚 | 说明 |
|---|---|---|
| X0-X7 数字量输入 | PA0-PA7 | 经光耦隔离,24V输入经限流电阻转3.3V |
| X10-X17 数字量输入 | PB0-PB7 | 第二组输入,同样走光耦 |
| Y0-Y7 晶体管输出 | PC0-PC7 | 经ULN2803驱动,集电极开路输出 |
| Y10-Y17 晶体管输出 | PD0-PD7 | 第二组输出,加续流二极管 |
| RS485 A/B | USART1 PA9-PA10 | 外接隔离收发器,做Modbus RTU |
| 编程口 | USART2 PA2-PA3 | 引到DB9或RJ45,物理上与FX3U编程口对接 |
| SWD调试口 | PA13/PA14 | 烧录和调试专用,别复用其他功能 |
这个分配表的逻辑是:把两组8路输入输出分别放在同一个GPIO端口上,固件里读写时一次就能刷完8个位,扫描周期更短。模拟量输入如果要做,尽量用PC0-PC3等ADC引脚,但FX3U本体一般不带模拟量,模拟量靠扩展模块,所以第一版不建议把模拟量做进主板。
2.2 原理图设计的四个必须抠的细节:电源、光耦、驱动和通信
原理图不是把单片机最小系统画完就结束了,PLC板子的命门在对外接口。我有几个习惯,做成产品也一直沿用。
电源部分:外部24V进来,先过保险丝和TVS二极管,再用DC-DC降到5V,最后用低压差稳压器出3.3V。5V给光耦输出侧和通信收发器供电,3.3V给STM32。模拟量如果要,VDDA要单独加LC滤波,不要直接从数字3.3V上拉线,不然ADC读数漂到你怀疑人生。
输入隔离:这是“pcb原理图分析”时最容易看漏的地方。很多新画手把光耦输出直接连STM32引脚,省略上拉电阻。光耦输出侧是集电极开路,必须接上拉电阻到3.3V,否则引脚浮空,输入信号乱跳。每路输入串470Ω到1kΩ的限流电阻,再反向并联一个稳压二极管,把24V侧的浪涌挡在光耦前面。
输出驱动:ULN2803内部是达林顿管,灌电流驱动负载。输出端子接负载到24V,负载另一端接ULN2803输出脚,GND回流到板子的电源地。每一路输出都要在负载侧并联续流二极管,感性负载(继电器、电磁阀)关断时反向电动势靠它泄放,不然一开关,整块板子上的通信全部掉线。
通信接口:RS485用隔离方案,芯片选带隔离的ADM2483,或者数字隔离器加普通收发器。很多DIY板子省掉隔离,短距离自测没问题,一接到工业现场就丢包。FX3U原机编程口是RS422,STM32的USART是TTL电平,中间需要加RS422收发器,这部分原理图要单独核对,不能直接拿RS485电路顶替。
2.3 从原理图到PCB:Layout顺序与地线处理
PCB板子电路原理图新入门,最容易卡住的不是画线,而是先理解电源分区。我的布局顺序是:电源入口和DC-DC先摆,然后是STM32和晶振,再往两边排光耦和输出驱动,最后是通信接口和接线端子。这个顺序保证大电流路径不绕圈,小信号线不被电源斩波干扰。
两层板够用,但要注意几点:STM32的8MHz晶振要靠近OSC_IN/OSC_OUT引脚,底下不要走数字信号线;24V电源线和输出驱动线尽量加宽,至少1mm,过孔不要小于0.5mm,否则大电流时会发热;光耦输入侧和输出侧的地要分开,24V侧的GND和3.3V侧的GND在板子一点连接,而不是大面积直接连通。
地线处理是最玄学也最现实的地方。我见过一块板子,原理图完全正确,LAYOUT也看不出毛病,但一接上位机就重启,最后发现是RS485的地和输出驱动的地串在一起,输出一动作地电位被抬高了2V。解决办法是输出驱动回流的GND直接走电源负端,通信芯片的地单独一块,两边在电源入口处单点汇合。
2.4 AD09导出Gerber:打样前的文件检查清单
Altium Designer 09虽然老,但生成生产文件的能力一点不过时。AD09导出Gerber的路径是File → Fabrication Outputs → Gerber Files,进去之后要逐项确认:
单位选毫米(mm),精度至少选2:3,如果能选2:5更好。图层勾选Top Layer、Bottom Layer、Top Overlay、Bottom Overlay、Top Solder Mask、Bottom Solder Mask,如果板子有贴片元件还要勾Top Paste Mask。钻孔信息单独出,File → Fabrication Outputs → NC Drill Files,单位必须和Gerber一致,否则板厂对不上孔位。
出完Gerber别直接发板,先找一个Gerber查看器打开看一遍。重点检查三样:丝印有没有压焊盘、钻孔层和焊盘中心对不对得上、Keep-Out Layer有没有包含在输出里。很多打样订单出问题,不是设计错,而是AD09导出Gerber时漏了Keep-Out层,板厂拿到的外形尺寸是错的。这类开源资料包的结构,和你见过的rtl-sdr原理图PCB文件下载、sx1268原理图PCB那些项目一样,原理图、PCB、固件各占一个目录,先读懂目录结构再动手改,能省掉一半返工时间。
3. STM32软件源码:用状态机和Modbus把FX3U指令表跑成扫描周期
3.1 固件框架与扫描周期:为什么用状态机而不是裸奔延时
PLC的工作方式就是循环扫描:读输入、执行用户程序、写输出、处理通信。STM32裸机程序如果到处用HAL_Delay,扫描周期会被卡死,通信响应自然跟不上。我一般用SysTick产生1ms时基,主循环跑一个状态机,状态切换由定时标志驱动。
typedef enum { PLC_ST_IDLE = 0, PLC_ST_READ_INPUT, PLC_ST_EXEC_PROGRAM, PLC_ST_WRITE_OUTPUT, PLC_ST_COMM_TASK } PlcState; volatile uint32_t sysTickCount = 0; void SysTick_Handler(void) { sysTickCount++; } void Plc_MainLoop(void) { PlcState state = PLC_ST_READ_INPUT; while (1) { switch (state) { case PLC_ST_READ_INPUT: Plc_ReadInputs(); // 读GPIO并写入X寄存器 state = PLC_ST_EXEC_PROGRAM; break; case PLC_ST_EXEC_PROGRAM: Plc_ExecUserProgram(); // 逐条执行指令表 state = PLC_ST_WRITE_OUTPUT; break; case PLC_ST_WRITE_OUTPUT: Plc_WriteOutputs(); // 按Y寄存器刷新GPIO state = PLC_ST_COMM_TASK; break; case PLC_ST_COMM_TASK: Plc_PollModbus(); // 非阻塞轮询通信 state = PLC_ST_READ_INPUT; break; default: state = PLC_ST_IDLE; break; } } }这段代码的核心是四个动作按顺序循环,任何一步都不能阻塞。Plc_PollModbus必须用非阻塞方式,串口收数据靠中断,主循环只查标志位,绝不能在这里面用while等一帧数据收完,否则输入响应会被拖到几十毫秒以上。
参数上要注意三个:SysTick中断优先级要设为最低,防止它打断Modbus接收的串口中断;主循环频率决定了扫描周期,一般做到1kHz循环,也就是1ms一圈;如果用户程序步数多,执行时间会变长,要预留看门狗喂狗位置,喂狗放主循环靠后,防止程序跑飞后狗不叫。
3.2 指令解释器:从指令表到内存对象的映射
FX3U的梯形图本质上是一张网表,编译后可以变成指令表,比如LD X0、AND X1、OUT Y0。这些指令在STM32固件里要变成可执行的操作码。一个轻量级做法是:把每条指令拆成1字节操作码加2字节操作数,存在一个用户程序数组里,运行时用switch逐条解释。
#define INS_LD 0x01 #define INS_AND 0x02 #define INS_OR 0x03 #define INS_OUT 0x10 #define INS_SET 0x11 #define INS_RST 0x12 #define INS_TMR 0x20 #define INS_END 0xFF typedef struct { uint16_t X[4]; // 64路输入按位存储 uint16_t Y[4]; // 64路输出按位存储 uint16_t M[32]; // 512路中间继电器 uint16_t D[1024]; // 1024路数据寄存器 uint16_t T_Counter[16]; // 定时器当前值 uint16_t T_Preset[16]; // 定时器设定值 } PlcMemory; PlcMemory plcMem; uint8_t userProgram[4096]; // 指令表字节码 uint32_t progLen = 0;这个结构体把FX3U的软元件映射到STM32的RAM里。X和Y按位存储,每个uint16_t管理16路输入输出,读写时用位操作。D寄存器是字类型,做数据运算时直接当uint16_t数组用,和Modbus保持寄存器的地址天然对应,上位机读写D0时直接映射到plcMem.D[0],省去一层转换。
uint32_t pc = 0; uint8_t op = 0; uint16_t opnd = 0; while (pc < progLen) { op = userProgram[pc]; opnd = (uint16_t)((userProgram[pc + 1] << 8) | userProgram[pc + 2]); switch (op) { case INS_LD: plcAcc = PlcGetBit(opnd); pc += 3; break; case INS_AND: plcAcc = plcAcc && PlcGetBit(opnd); pc += 3; break; case INS_OUT: PlcSetBit(opnd, plcAcc); pc += 3; break; case INS_TMR: if (plcAcc) { plcMem.T_Counter[opnd]++; if (plcMem.T_Counter[opnd] >= plcMem.T_Preset[opnd]) { PlcSetBit(opnd + 2000, 1); } } else { plcMem.T_Counter[opnd] = 0; PlcSetBit(opnd + 2000, 0); } pc += 3; break; case INS_END: pc = progLen; break; default: pc += 3; break; } }这个解释器的关键参数是操作数位数。我用3字节表示一条指令,操作码1字节,操作数2字节,这样操作数范围能覆盖0到65535,够D寄存器和位软元件的寻址。定时器用累加计数实现,每次扫描周期加1,所以定时精度和扫描周期相关,如果扫描周期不是严格1ms,定时器误差会累积。要做精确定时,必须在定时器中断里做加1操作,主循环只负责判断是否超时。
PlcGetBit和PlcSetBit这两个函数要处理位寻址和字寻址的差异。我定义了一个软元件地址编码规则:X的软元件号从0到63,Y从64到127,M从128到639,D从1000到2023。这样所有软元件都在一个地址空间里,指令操作数直接用软元件号,解释器内部判断范围再定位到对应数组,梯形图转换成指令表时会方便很多。
3.3 串口通信:Modbus RTU从站与LabVIEW和三菱FX3U通讯的配合
三菱FX3U的编程口协议是RS422,帧格式是专用协议,硬啃这个协议做GX Developer直连,工作量和收益不成正比。国产兼容PLC最常见的做法是:编程口保留FX3U物理接口,逻辑上跑Modbus RTU,上位机用组态软件或LabVIEW和三菱FX3U通讯时都走Modbus,反而更通用。LabVIEW的VISA串口模块直接支持RTU帧,NI也有Modbus库,不需要自己解析三菱协议帧。
Modbus RTU从站实现要关注CRC校验和帧间隔。帧间隔是RTU和ASCII最大的区别,RTU要求接收一帧数据时字符间间隔小于1.5个字符时间,大于3.5个字符时间就认为帧结束。串口中断里必须用定时器辅助判断帧超时:
#define MB_BUF_SIZE 256 uint8_t mbBuf[MB_BUF_SIZE]; uint16_t mbLen = 0; uint8_t mbFrameDone = 0; void USART1_IRQHandler(void) { uint8_t data = USART1->DR; if (mbLen < MB_BUF_SIZE) { mbBuf[mbLen++] = data; } // 收到一个字节就重置超时定时器,超时到达视为帧结束 MB_Timer_Restart(); } void MB_Timer_Update(void) { static uint16_t idleCnt = 0; if (mbLen > 0) { idleCnt++; if (idleCnt > 10) { // 10个字符时间没有新数据 mbFrameDone = 1; idleCnt = 0; } } }帧超时计数器的阈值要按波特率折算。9600波特率时一个字符约1.04ms,10个字符时间就是10.4ms;115200波特率时一个字符约0.087ms,10个字符不到1ms。所以这个阈值不能写死,要按波特率配置成定时器重载值。我一般用定时器的输入捕获或外部中断辅助计时,让超时判断不受主循环速度影响。
CRC16用查表法或逐位法都行,逐位法代码短,查表法速度快。对1kHz扫描周期的PLC来说,逐位法已经够用,没必要为了节省几十微秒去建一个256字节的查表。
uint16_t MB_CRC16(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; for (uint16_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t bit = 0; bit < 8; bit++) { if (crc & 1) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; }CRC计算完后,发送时低字节在前、高字节在后,这是Modbus RTU的规定。LabVIEW里如果做校验检查,也要按这个字节序,否则一直报CRC错误。很多调试时发现通信不上、但示波器看波形又正常的人,八成就是CRC字节序反了。
3.4 源码结构与关键参数:时钟、优化等级和HAL库选型
拿到STM32软件源码后,不要急着编译,先确认三件事:系统时钟是不是72MHz、APB1和APB2分频对不对、优化等级选的是多少。
STM32F103VET6最大主频72MHz,时钟源用外部8MHz晶振,PLL倍频9倍。SystemInit里如果配错PLL,UART波特率会整体偏移。比如目标9600实际跑到10400,通信就是间歇性成功。排查方法很简单,用示波器量一下USART的TX引脚波形,看单帧宽度是否符合波特率。
编译优化等级,我建议Debug用-O0,Release用-O2。解释器代码里的大量位操作和switch跳转,在高优化等级下编译器会做跳转表优化,执行速度能快30%。但-O3在某些编译器版本下会把循环展开到代码爆炸,Flash占用变大,STM32F103VET6的512KB虽然够用,也不建议无脑开-O3。
HAL库还是标准外设库,这是老话题。AD09时代的资料包通常用标准外设库写的,代码风格是直接操作寄存器或库函数。如果你更熟HAL库,移植时要注意SPL和HAL的GPIO初始化方式完全不同,尤其是AFIO重映射和中断配置。我一般建议保留原工程的库版本,不要混用,混了之后最典型的问题是串口中断进不去,因为两个库对NVIC的配置函数不一样。
4. 硬件烧录与整机联调:从AD09打样到点亮FX3U兼容面板的最短路径
4.1 裸板回板后的上电检查顺序
PCB打样回来,先别急着焊STM32。我习惯先焊电源部分,包括输入端子、保险丝、DC-DC、LDO、滤波电容。上电前用万用表电阻档打一下24V和GND之间有没有短路,正常应该在几百欧以上,如果接近0,检查电解电容极性有没有接反,TVS管方向对不对。
电源正常后,先焊STM32最小系统部分,包括晶振、复位电路、BOOT引脚、SWD调试口。此时不焊外围电路,单独给板子供3.3V,用示波器看晶振引脚有没有8MHz振荡,NRST引脚电压是否接近3.3V。确认后再焊光耦和输出驱动,最后焊通信芯片。分步焊接的好处是,出问题能快速缩小范围,不用一次面对一整块黑匣子一样的故障板。
上电前先看BOOT0和BOOT1的电平状态。BOOT0必须通过10kΩ电阻下拉到GND,BOOT1同样下拉。如果BOOT0悬空,STM32上电后可能随机进到系统存储器启动模式,表现就是SWD能连上但程序跑飞。这个坑我在调试时踩过,焊完之后测量BOOT0引脚电压,发现是1.6V浮动电平,直接导致芯片不执行Flash里的程序。
4.2 固件烧录:用ST-LINK和OpenOCD把程序灌进F103VET6
固件编译出来是bin或elf文件。用ST-LINK烧录最稳定的方式是OpenOCD命令行,烧完自动复位运行:
# 连接ST-LINK和STM32F103VET6,烧录bin到0x08000000 openocd \ -f interface/stlink.cfg \ -f target/stm32f1x.cfg \ -c "program build/plc_f103.bin 0x08000000 verify reset exit"这条命令里的每个参数都有实际意义。interface/stlink.cfg是ST-LINK驱动配置,target/stm32f1x.cfg是STM32F1系列的调试配置,里面定义了内核类型、Flash地址、RAM地址。program后面跟bin文件路径和烧录地址,0x08000000是STM32F103VET6内部Flash的起始地址。verify表示烧录后回读校验,reset exit是复位并退出。
如果电脑上装的是STM32CubeProgrammer,用命令行也行:
STM32_Programmer_CLI --connect port=SWD mode=NRST \ --write firmware.elf --verify --go --reset这里mode=NRST是指连接时用NRST引脚复位芯片,防止目标芯片正在跑程序、SWD端口被复用导致连接失败。如果芯片里的程序把SWD引脚改成了普通GPIO,直接connect会失败,先按住复位键再点连接,或者用mode=NRST强制复位后连接。
成功烧录后,最标准的验证方法是断开ST-LINK,重新上电,看板子上如果有LED,是否按照固件初始化流程点亮。不要用ST-LINK供电跑整机测试,ST-LINK的3.3V输出能力只有几十毫安,带不动光耦和通信芯片,勉强供电会导致STM32不断复位。
4.3 串口烧录:BOOT0跳线和Flash Loader的备用方案
没有ST-LINK时,STM32F103的ROM引导程序可以走串口烧录,但前提是BOOT0引脚能跳线。把BOOT0拉高到3.3V,BOOT1拉低,复位芯片,芯片就进入系统存储器模式,内置的USART1引导程序等待接收数据。
在Windows上用STMicroelectronics Flash Loader Demonstrator,选择USART1对应的串口号,波特率设为115200,晶振频率填8,连接后选择要烧录的hex文件。这个方式虽然慢,但救砖时非常好用,芯片SWD锁住或调试口被禁用时,串口引导是最后一个后悔药。
烧完把BOOT0跳线恢复为低电平,复位上电,程序从0x08000000启动。
提示 BOOT0跳线短接时,STM32上电后不执行用户程序。烧录完成后必须断开BOOT0再复位,否则板子看起来就像没烧录一样。
4.4 IO点位测试:逐路验证输入和输出的映射关系
固件烧进去后,第一件事是测IO映射。很多资料包自带IO自检模式,设置某个D寄存器为特定值后,固件会把所有输出点按顺序强制置位。如果没这个功能,就自己写一个简单的测试固件,或者用Modbus写线圈的方式逐路控制。
输入测试步骤:把24V电源接到COM端,用短接线把每个输入点和COM端短接,观察固件上报的X寄存器对应位。正常时12V到24V电平能被光耦可靠识别,低于10V可能丢信号。输出测试步骤:用万用表二极管档量Y端子和COM端之间是否有0.6V左右压降,或者直接接一个24V指示灯负载。
逐路测试时记一个点位表,哪个X对应哪个PA引脚、哪个Y对应哪个PC引脚,调程序时对照这个表。点位错一个是PLC项目最隐蔽的错误,因为硬件上电不报错,但一接外部设备就动作乱套。
4.5 与LabVIEW和三菱FX3U通讯的联调配置
IO没问题后,开始通信联调。用LabVIEW和三菱FX3U通讯,走Modbus RTU是最顺的路。LabVIEW里用VISA串口,或者直接装NI Modbus库,配置参数按固件默认值来:
| 参数 | 值 | 说明 |
|---|---|---|
| 串口号 | COM4或实际端口 | 用USB转RS485线连接板子步 |
| 波特率 | 9600或115200 | 固件里MB_BAUD宏定义决定 |
| 数据位 | 8 | 固定 |
| 校验位 | None | Modbus RTU用CRC校验,不需要奇偶校验 |
| 停止位 | 1 | 固定 |
| 从站地址 | 1 | 固件里MB_SLAVE_ADDR宏定义 |
先在LabVIEW里发03功能码读保持寄存器,比如读D0开始的10个字,判断返回帧长度和数值是否符合预期。能读到后,再发05功能码写单个线圈,控制Y0输出,看板子上的指示灯跟着变。
如果通信失败,先看串口调试助手能返回什么。板子上电后发一个03功能码请求,如果板子回了一个异常码,说明从站逻辑在工作,只是寄存器地址不对。如果完全无响应,检查RS485的A、B线是不是接反了,差分信号极性反了是收不到数据的典型原因。
Modbus从站地址和波特率在固件里一般是宏定义,位置通常在modbus.c或config.h里,改完要重新编译烧录:
#define MB_SLAVE_ADDR 1 #define MB_BAUD 115200这两个参数要提前和上位机约好,尤其做LabVIEW监控界面时,从站地址写错一个数字,整条通信链路都跑不通。
5. 调试避坑点:原理图PCB分析时最容易翻车的五个故障
5.1 现象:3.3V正常,NRST电压正常,但板子就是不启动
原理图PCB分析时,很多新手盯住电源和晶振,忽略了BOOT。板子不启动,第一反应是STM32坏了,换了芯片还是老样子。用示波器量NRST引脚,发现电压1.8V左右,低于芯片要求的复位高电平阈值2.0V,系统一直处于复位状态。
原因:复位电路的上拉电阻用到了10kΩ,且外部复位电容太大,正常应该是10kΩ上拉加100nF电容。有些资料把电容加到1μF,上电瞬间RC充放电时间太长,3.3V稳定后NRST还没有爬升到高电平阈值。另外,如果复位引脚还被其他外设电路占用,比如接了外部看门狗芯片,也会出现类似现象。
解决:把复位电容改回100nF,确认NRST只接上拉电阻和电容,不要接额外负载。用示波器单次触发看NRST上电波形,正常应该是0V到3.3V的干净爬升,如果爬升中间有台阶,查一下是不是有芯片在拉低这个引脚。
5.2 现象:ST-LINK连接一次成功一次失败,时好时坏
调试固件时最恼火的就是连接不稳。连着连着就报Cannot access Target,重启又好了。用示波器量SWDIO波形,发现波形边沿有明显振铃。
原因:SWD排针到ST-LINK的杜邦线太长,超过15cm后信号反射严重;或者PCB上SWDIO和SWCLK走线没有包地,被相邻的晶振信号干扰。还有可能是STM32的调试端口在程序里被复用了,跑起来后引脚变成普通GPIO,再连接时芯片正跑着程序,SWD端口功能被切走了。
解决:杜邦线控制在10cm以内,越短越好。PCB上SWDIO、SWCLK并排走线,旁边加一圈地线包裹。连接时按住板子复位键,ST-LINK提示连接瞬间松开复位,能稳定连上。如果程序里确实把调试口复用成了GPIO,必须在程序初始化最后加上SWD引脚重新使能的代码,或者烧录时用mode=NRST强制复位连接。
5.3 现象:输出驱动管一动作,RS485通信立刻掉线
整机测试时发现,Y0带负载,RS485通信就中断;去掉负载,通信恢复正常。万用表量RS485收发器的VCC和GND,输出动作瞬间有幅度接近1V的毛刺。
原因:输出驱动的大电流回流走了通信芯片的地,或者电源端没有做好去耦。ULN2803导通瞬间,负载电流从24V电源流过负载和ULN2803回到电源地,如果这个回路的阻抗偏大,地电位就被抬起来。RS485收发器检测的是A、B线之间的差分电压,但对地和芯片电源之间的共模范围有限,地电位瞬变超过收发器容限就直接误判。
解决:输出驱动的GND单独回流到电源负端,不要在通信芯片附近大面积铺铜连通。UDN2803的电源引脚加100nF陶瓷电容和10μF电解电容,抵消开关瞬间的电流冲击。RS485部分用隔离方案,比如B0505S隔离电源加ADM2483隔离收发器,把通信和主控电路的地彻底分开,这是治本的做法。之后我再没遇到过输出干扰通信的问题。
5.4 现象:AD09导出Gerber后,板厂说钻孔文件对不上
AD09导出Gerber的坑还不少。发出的压缩包里Gerber文件打开正常,钻孔文件也有,但板厂反馈钻孔偏移或文件缺失。在Gerber查看器里看,发现孔位和焊盘明显错位,要么全部偏了一个方向。
原因:Gerber文件导出时单位选的是英制inch,NC Drill文件导出时选的是公制mm,或者反过来。两个文件单位不一致,板厂导入时钻孔层和铜层对不上,整体偏移。另一个常见原因是钻孔文件漏导了,Gerber层里有焊盘但TXT文件没有对应钻孔,板厂只能猜孔位。
解决:AD09导出Gerber时,单位固定用mm,精度选2:3;导出NC Drill文件时,单位也选mm。导完后把.GTL、.GBL、.GTO、.GBO、.GTS、.GBS、.TXT这些文件一起放到一个文件夹,用Gerbv打开看一遍,确认钻孔层与焊盘重合。文件命名最好按板厂的规范来,比如顶层铜箔叫xxx.GTL,钻孔文件叫xxx.TXT,避免板厂识别不了。
5.5 现象:模拟量输入读数稳定但偏差大,校准后温度一变又漂
做了模拟量输入通道后,用电压源标定,读数和实际值误差不到0.1V,觉得还行。但板子在工作环境里跑半天,再和精密表对,差了0.3V,而且每次开机数值都不一样。
原因:模拟量参考电压没有用高精度基准。STM32F103的VREF引脚如果直接接VDDA,而VDDA又来自数字3.3V,LDO输出随温度变化漂移,ADC自然跟着漂。还有一个隐蔽原因是采样时间设置太短,STM32的ADC输入阻抗较高,信号源内阻大时,采样电容来不及充满,读数偏小且不稳定。
解决:VREF引脚接TL431或REF3030这类高精度基准源,VDDA通过LC滤波后再供电。ADC采样时间不要用默认的1.5周期,至少设到55.5周期以上,稳定一些用239.5周期。采样后用中值滤波或移动平均,去掉随机噪声。做模拟量精度要求高的板子,布局时把模拟输入走线远离输出驱动区域,给模拟部分单独包地。
6. 进阶验证技巧:抓帧、IAP升级和生产测试这三件事最值得先做
6.1 用抓帧代替猜协议
板子通信不正常,很多人直接怀疑固件代码,其实先抓波形最省事。用逻辑分析仪夹住RS485的A、B线,或者接在RS485收发器芯片的RO和DI引脚上,串口波特率设为和板子一致,直接看发出的帧和收到的帧。Modbus RTU协议本身很简单,抓出来的帧一眼就能看出地址、功能码、CRC对不对。
我调试LabVIEW和三菱FX3U通讯时,也遇到过LabVIEW发出的读请求和板子能处理的帧不一致。抓帧后发现LabVIEW库把从站地址默认设成了0,而板子固件从站地址是1,一个字节的差异,花了一晚上才定位。抓帧工具是逻辑分析仪或者一个USB转TTL的小板子,便宜且好用,值得常备。
6.2 给固件预留IAP升级功能
PLC固件和普通单片机程序不一样,它运行在现场环境里,不可能每次升级都把设备拆下来插ST-LINK。STM32F103VET6有512KB Flash,完全放得下一个Bootloader加一个应用程序。
Bootloader放在起始地址0x08000000,占用32KB;应用程序从0x08008000开始。Bootloader里写一个简单的串口或Modbus协议接收数据,写入Flash指定地址,然后跳转运行。应用程序里则要改向量表偏移:
#define APP_FLASH_ADDR 0x08008000 void JumpToApp(void) { uint32_t stackTop = *(volatile uint32_t *)APP_FLASH_ADDR; void (*appEntry)(void) = (void (*)(void))(*(volatile uint32_t *)(APP_FLASH_ADDR + 4)); if ((stackTop >= 0x20000000) && (stackTop < 0x20020000)) { __set_MSP(stackTop); appEntry(); } }栈顶地址的检查很关键,它判断从Flash读出的前4字节是否在SRAM地址范围内。STM32F103VET6的SRAM是64KB,地址范围0x20000000到0x20010000,检查条件不能写错,否则跳到非法地址会直接HardFault。应用程序工程里,链接脚本的FLASH起始地址要改成0x08008000,中断向量表偏移用VECT_TAB_OFFSET设为0x8000,否则中断进不去。
6.3 批量产测的最小方案
如果板子不只做一套,而是做个十几套自己用或小范围出货,产测不能只靠人手点灯。我做的产测流程是:给板子接24V电源,用USB转RS485连到电脑,跑一个测试脚本,依次做三件事。
第一,读设备信息和固件版本,确认烧录正确。第二,Modbus写线圈,把Y0到Y15依次置位,测试工装上对应的LED逐个点亮,同时用光电传感器确认输出真的动作了。第三,短路X0到X15每个输入点,上位机读X状态,确认输入到固件的链路通。全部通过就贴标,不过直接退回维修台。这套流程不复杂,但能把“看着能跑”变成“每台都能跑”。
我一直的习惯是,PCB板和固件都留出测试点,哪怕是业余项目也好。没有测试点,回板后故障定位全凭万用表戳引脚,效率低几倍。做兼容PLC这个方向,值得投入,但别把宝押在别人的协议上,自己的固件、自己的引脚映射、自己的测试流程,这三样掌握住,才真正把资料包变成自己的家底。希望帮到你。
本文还有配套的精品资源,点击获取