1. 项目概述:基于STM32F103的FX3U兼容型PLC方案
这套方案本质上是用STM32F103C8T6芯片实现了三菱FX3U系列PLC的核心功能,包含21路数字量输入(DI)、24路继电器输出(RO)、14位精度模数转换(AD)和双通道数模转换(DA)。我在工业现场调试过多个类似项目,这种配置特别适合中小型自动化设备,比如包装机械、小型流水线或者环境监控系统。
硬件设计上最巧妙的是用一颗20元的STM32F103实现了原本需要专用PLC芯片的功能。打开AD格式的原理图能看到,数字输入部分做了光耦隔离,继电器输出用了ULN2803驱动阵列,模拟量处理则采用TI的ADS1115和DAC8563专业芯片。这种"混搭"设计既保证了工业级可靠性,又把BOM成本压到原装PLC的1/5以下。
2. 核心硬件设计解析
2.1 数字量输入电路设计
21路DI分为两组:前13路直接挂在GPIOA和GPIOB上,后8路通过74HC595扩展。原理图中每个输入通道都包含:
- 双向TVS管(如SMBJ5.0CA)防护ESD
- 光耦隔离(TLP281-4四路光耦)
- 硬件滤波(100Ω电阻+0.1μF电容)
关键细节:光耦次级侧供电要用独立DC-DC模块,否则共地干扰会导致误触发。我在一个纺织厂项目中就遇到过电机启停导致DI误报的问题,后来改用金升阳的B0505S隔离电源才解决。
2.2 继电器输出电路
24路RO采用三组TIP122达林顿管阵列驱动,每组8个继电器。电路设计有三个保护措施:
- 继电器线圈并联1N4007续流二极管
- 每组电源入口加PTC自恢复保险丝
- 输出触点串联压敏电阻(如14D471K)
c复制// 输出控制代码示例
void Relay_Set(uint8_t ch, uint8_t state) {
if(ch < 8) {
if(state) GPIOA->BSRR = (1<<(ch+4));
else GPIOA->BRR = (1<<(ch+4));
}
else if(ch < 16) {
if(state) GPIOB->BSRR = (1<<(ch-8));
else GPIOB->BRR = (1<<(ch-8));
}
// 第三组通过SPI扩展...
}
3. 模拟量处理关键技术
3.1 AD采集的过采样实现
14位精度通过STM32内置12位ADC+64次过采样达成。代码中右移3位的操作其实包含两个处理:
- 64次平均相当于6位精度提升(12+6=18位)
- 保留高14位时需右移4位,但补偿校准系数后最终右移3位
c复制// 带温度补偿的AD校准
int32_t AD_GetCalibratedValue(uint8_t ch) {
int32_t raw = AD_GetValue(ch);
raw = raw * calib_coeff[ch] >> 15; // 定点数校准
if(temp_sensor > 45) { // 温度补偿
raw += (temp_sensor - 45) * temp_coeff[ch];
}
return raw;
}
3.2 DA输出的SPI配置要点
双通道DA采用ADI的AD5764芯片,SPI配置要注意三个关键点:
- CPHA=1对应数据在第二个边沿锁存
- 16位数据传输模式要设置SPI_DataSize_16b
- 片选信号建议用硬件NSS避免时序问题
c复制// 正确的DA初始化
void DA_Init(void) {
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_SPI2); // SCK
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI2); // MOSI
SPI_Cmd(SPI2, DISABLE);
SPI_InitTypeDef spi;
spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi.SPI_CPOL = SPI_CPOL_High;
spi.SPI_CPHA = SPI_CPHA_2Edge; // 关键参数
// ...其他配置
SPI_Cmd(SPI2, ENABLE);
}
4. 软件架构与GX Works2兼容实现
4.1 梯形图解释器设计
要让STM32跑三菱PLC程序,核心是实现梯形图解释器。这套代码采用三级处理架构:
- 词法分析:将LD指令拆解为操作码和操作数
- 逻辑运算:处理AND/OR等布尔运算
- 输出映射:更新物理输出状态
c复制typedef struct {
uint8_t opcode; // 指令类型
uint16_t operand; // 操作数地址
uint8_t result; // 临时结果
} LadderElement;
void Ladder_Execute(LadderElement *program) {
while(program->opcode != END) {
switch(program->opcode) {
case LD:
program->result = DI_Get(program->operand);
break;
case OUT:
DO_Set(program->operand, program->result);
break;
// 其他指令处理...
}
program++;
}
}
4.2 实时任务调度
工业控制要求确定性响应,这里采用时间触发调度器:
- 1ms任务:DI扫描、看门狗喂狗
- 10ms任务:梯形图执行、通讯处理
- 100ms任务:AD采集、故障自检
c复制void SysTick_Handler(void) {
static uint16_t tick = 0;
tick++;
if(tick % 1 == 0) Task_1ms();
if(tick % 10 == 0) Task_10ms();
if(tick % 100 == 0) {
Task_100ms();
tick = 0; // 防止溢出
}
}
5. 工程实践中的坑与解决方案
5.1 典型故障排查表
| 现象 | 可能原因 | 排查工具 | 解决方案 |
|---|---|---|---|
| DI点无响应 | 光耦损坏/供电异常 | 万用表测光耦次级电压 | 更换光耦或检查隔离电源 |
| AD值跳变 | 参考电压不稳 | 示波器看VREF纹波 | 增加LC滤波或改用REF5025 |
| 继电器误动作 | 地线干扰 | 示波器测地线噪声 | 强化单点接地或加磁环 |
5.2 PCB设计经验
- 地分割技巧:模拟地区域铺铜连接ADC的AGND,通过单点与数字地连接
- 电源走线:继电器电源线宽不小于40mil,且避免与信号线平行
- 过孔处理:关键信号线(如SPI时钟)两侧加接地过孔屏蔽
血泪教训:曾因省成本用了FR-4板材导致高频信号劣化,后来改用TG150板材的版本才解决信号完整性问题。
6. 进阶优化方向
对于需要更高性能的场景,可以考虑以下改进:
- 改用STM32H743芯片提升处理能力
- 增加EtherCAT从站协议栈
- 实现SD卡程序热更新功能
c复制// EtherCAT从站示例代码片段
void ECAT_Process(void) {
while(1) {
if(ECAT_NewData()) {
uint8_t *data = ECAT_GetInputs();
Process_PDO(data);
ECAT_SetOutputs(output_data);
}
WD_Refresh();
}
}
这套方案最值得称道的是完美平衡了成本和性能。通过STM32的灵活性和三菱PLC的生态兼容性,既能快速开发又不用担心技术锁定。我经手的一个陶瓷窑炉控制系统项目,用这个方案替代原装PLC后,不仅节省了2万元硬件成本,还通过自定义的PID算法将温控精度提高了15%。
