尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F103实现三菱PLC扫描周期与梯形图执行

STM32F103实现三菱PLC扫描周期与梯形图执行 简介本资源是一套基于STM32F10X系列MCU实现三菱PLC核心功能的嵌入式源代码工程面向嵌入式初学者、自动化专业学生及工业控制开发者解决在低成本硬件平台上理解与复现PLC扫描机制、I/O逻辑、定时计数等关键控制功能的学习痛点。压缩包共216个文件含60个头文件h定义寄存器与接口、36个C源码c实现梯形图逻辑解析与循环扫描调度、32个目标文件o及17个链接配置xcl等完整覆盖IAR Embedded Workbench开发环境下的编译、调试与烧录全流程包体大小为2.64MB。已有167人学习下载资源结构清晰包含uart.c、stm32f10x_tim.c、stm32f10x_adc.c等典型外设驱动模块以及plc_xe.icf链接脚本、.bat自动化构建脚本和.hex可执行镜像便于读者快速上手、逐层剖析PLC仿真架构与STM32底层控制逻辑。1. 这不是PLC但能跑出PLC的逻辑STM32F10X上实现三菱PLC扫描周期与梯形图语义你手头那块STM32F103C8T6最小系统板插上USB-TTL就能当PLC用——不是接线模拟而是真正在裸机上复现GX Works里拖拽触点、线圈、定时器后生成的执行行为。这个“STM32F10X仿三菱PLC源代码”项目本质是把PLC的三阶段循环扫描模型Input Scan → Logic Execution → Output Update拆解为可移植、可调试、可扩展的C语言模块运行在IAR EWARM环境下不依赖RTOS、不调用HAL库、不封装抽象层所有GPIO操作直写寄存器所有定时器中断服务函数ISR严格对齐PLC典型扫描周期如10ms/50ms。它解决的不是“怎么让单片机亮灯”而是“如何让单片机按PLC时序规范响应输入变化、保持输出锁存、触发TON/TOF延时动作、执行RST复位逻辑”。适合两类人刚学完《PLC原理》但没摸过真实设备的学生能通过源码反推GX Developer编译后的字节码映射关系以及已有STM32项目经验的工程师想快速验证控制逻辑而无需采购PLC硬件或部署Codesys Runtime。注意这不是一个完整HMI编程器套件而是一套可嵌入现有固件的PLC内核引擎——你只需把plc_xe.icf.bak链接脚本导入IAR替换掉原工程的.icf文件再将stm32f10x_tim.c和stm32f10x_adc.c中的外设初始化逻辑与你的硬件引脚定义对齐就能启动扫描循环。2. 从寄存器级IO映射到PLC地址空间GPIO配置与I/O映射表设计2.1 STM32F10X GPIO寄存器操作与PLC地址映射原理PLC的I/O地址如X000、Y001、M100在物理层面必须映射到MCU的端口寄存器。本项目采用位带别名区Bit-Band Alias 预定义宏展开方式实现零开销位操作。例如X000对应PA0输入其读取逻辑并非调用GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)而是直接访问位带别名地址// 在 stm32f10x_gpio.h 中定义 #define PERIPH_BASE ((uint32_t)0x40000000) #define BITBAND_OFFSET (0x02000000) #define BITBAND_SRAM_BASE (PERIPH_BASE BITBAND_OFFSET) #define BITBAND_PERIPH_BASE (PERIPH_BASE 0x02000000) // PA0 的位带别名地址计算(0x40000000 0x02000000) (0x40010800 - 0x40000000) * 32 0 * 4 // 简化为宏 #define GPIOA_IN_BITBAND(x) (*((__IO uint32_t *)(BITBAND_PERIPH_BASE (((uint32_t)GPIOA-IDR) - PERIPH_BASE) * 32 (x) * 4))) #define X000 GPIOA_IN_BITBAND(0) // 直接读取PA0状态提示位带操作比BSRR/BSRR寄存器置位/清位更节省指令周期且支持if(X000)这样的布尔表达式与梯形图触点逻辑完全一致。但需确保IAR编译器启用--enable_bitbanding选项否则位带地址访问会触发HardFault。2.2 I/O映射表与PLC地址空间划分项目未使用动态地址解析而是通过静态结构体数组固化I/O映射关系。关键文件plc_xe.icf.bak中定义了三个核心段段名起始地址大小用途对应PLC元素PLC_INPUTS0x20000000128字节输入映像区X000–X177共128点PLC_OUTPUTS0x20000080128字节输出映像区Y000–Y177共128点PLC_FLAGS0x20000100512字节内部继电器区M0–M511共512点该布局直接对应三菱FX系列PLC的内存结构。例如X000位于PLC_INPUTS[0]的bit0Y001位于PLC_OUTPUTS[0]的bit1M100位于PLC_FLAGS[12]的bit4因每字节8位100÷812余4。这种硬编码映射避免了运行时查表开销但要求开发者在修改I/O点数时同步调整.icf链接脚本和plc_core.c中的数组索引计算逻辑。2.3 输入滤波与去抖动实现基于TIM2的硬件消抖PLC输入必须抵抗工业现场的电气噪声本项目采用定时器捕获软件计时器双重确认机制。以X000为例流程如下PA0配置为浮空输入上升沿触发EXTI0中断EXTI0 ISR中启动TIM2预分频72-1自动重装载999即1ms定时TIM2更新中断中检查PA0电平是否仍为高若连续3次3ms稳定则置位PLC_INPUTS[0]bit0若期间电平跳变TIM2计数器清零重启。// 在 stm32f10x_tim.c 中 TIM2_IRQHandler 实现 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { static uint8_t x000_stable_cnt 0; if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) { x000_stable_cnt; if (x000_stable_cnt 3) { PLC_INPUTS[0] | (1 0); // 置位X000 x000_stable_cnt 0; } } else { x000_stable_cnt 0; // 电平回落重置计数 } TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }注意此实现将消抖逻辑绑定到具体IO点若需扩展至X001–X007需为每个EXTI线分配独立计数器变量并在EXTIx_IRQHandler中调用统一消抖函数避免代码膨胀。3. 扫描周期调度与梯形图逻辑执行引擎3.1 主循环扫描架构SysTick 定时器协同控制PLC的核心是确定性扫描周期。本项目采用SysTick作为主时钟源TIM3作为扫描周期计时器的双定时器方案SysTick配置为1ms中断用于驱动所有毫秒级任务如ADC采样、通信接收TIM3配置为自由运行模式ARR扫描周期值如50ms对应ARR49999每次更新中断触发一次完整扫描扫描过程严格遵循三阶段Input Refresh批量读取所有GPIO输入写入PLC_INPUTS映像区Logic Execution遍历用户梯形图编译后的指令序列存储在plc_program.bin中逐条执行LD/AND/OR/OUT等指令Output Update将PLC_OUTPUTS映像区内容批量写入GPIO输出寄存器。// 在 main.c 中的 TIM3_IRQHandler void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { // 阶段1输入刷新 plc_input_refresh(); // 调用 gpio.c 中函数读取全部输入端口 // 阶段2逻辑执行关键 plc_execute_cycle(); // 解析 plccode[] 数组执行LD X000, AND M100, OUT Y000等指令 // 阶段3输出更新 plc_output_update(); // 将 PLC_OUTPUTS[] 写入 GPIOx_BSRR/BSRR 寄存器 TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } }3.2 梯形图指令集实现LD/AND/OR/OUT指令的C语言映射用户编写的梯形图经GX Developer编译后生成二进制指令流本项目中为GC.bin其格式为[OPCODE][OPERAND_HIGH][OPERAND_LOW]。例如LD X000对应0x01 0x00 0x00OUT Y001对应0x05 0x00 0x01。plc_execute_cycle()函数通过switch-case解析opcode// plc_core.c 中关键逻辑片段 void plc_execute_cycle(void) { uint16_t pc 0; // 程序计数器 uint8_t rlo 0; // RLOResult of Logic Operation梯形图当前行结果 while (plccode[pc] ! 0xFF) { // 0xFF 为程序结束符 uint8_t opcode plccode[pc]; uint16_t operand (plccode[pc] 8) | plccode[pc1]; pc 2; switch (opcode) { case 0x01: // LD (Load) rlo get_plc_bit(operand); // 根据operand查X/Y/M区 break; case 0x02: // AND rlo get_plc_bit(operand); break; case 0x05: // OUT set_plc_bit(operand, rlo); // 将rlo写入Y/M区 break; case 0x08: // TON (On-Delay Timer) ton_timer_exec(operand); // 调用 ton.c 中函数处理定时器逻辑 break; } } }get_plc_bit()函数根据operand高位字节判断地址类型0x00X, 0x01Y, 0x02M低位字节计算字节偏移和位偏移最终返回对应bit值。这种设计使指令执行时间恒定O(1)满足PLC实时性要求。3.3 定时器与计数器模块TON/TOF/CTU指令的寄存器级实现三菱PLC的TON通电延时、TOF断电延时、CTU加计数器指令在本项目中由独立模块ton.c、tof.c、ctu.c实现全部基于TIM4定时器的1ms中断服务TON定时器当输入RLO为1时启动计时器达到设定值SV后置位输出位并停止计时输入为0时复位当前值CVTOF定时器输入RLO由1变0时启动计时CV递增CV≥SV时置位输出输入恢复为1则立即复位CV和输出CTU计数器检测CU信号上升沿CV1当CV≥PV时置位输出R信号为1时CV清零。// ton.c 中 TON_000 的实现对应T0 typedef struct { uint16_t sv; // 设定值单位ms uint16_t cv; // 当前值 uint8_t en; // 使能标志 uint8_t q; // 输出位 } ton_t; ton_t ton_inst[16] {0}; // 支持16个TON实例 void ton_timer_exec(uint16_t addr) { ton_t *t ton_inst[addr]; // addr为T0→0, T1→1... if (rlo) { // 输入有效 t-en 1; if (t-cv t-sv) t-cv; // 未到设定值继续计时 if (t-cv t-sv) t-q 1; // 置位输出 } else { if (t-en) { // 输入失效但之前使能过 t-en 0; t-cv 0; // 复位当前值 } } }关键参数说明sv值由GX Developer编译时写入plccode[]例如TON T0 K5050ms对应sv50cv存储在PLC_FLAGS区固定偏移位置保证掉电不丢失需外挂EEPROM本项目暂存于SRAM。4. 串行通信协议解析与GX Works仿真联机调试4.1 三菱PLC通信协议基础SC-09电缆与RS-232帧格式本项目通过USART1模拟SC-09通信接口支持与GX Works进行在线监视Online Monitor和程序下载。协议采用ASCII帧格式关键字段包括字段长度说明STX1字节0x02SOHCMD2字节命令码如00读软元件、01写软元件、02读PLC状态ADDR4字节地址如D000→44303030ASCII D,0,0,0COUNT2字节读写点数如0001DATA变长数据内容十六进制ASCII编码ETX1字节0x03ETXSUM2字节校验和STX至ETX所有字节异或// uart.c.bak 中串口接收中断处理 void USART1_IRQHandler(void) { static uint8_t rx_buf[64]; static uint8_t rx_len 0; uint8_t data; if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { data USART_ReceiveData(USART1); if (data 0x02 rx_len 0) { // STX起始 rx_buf[rx_len] data; } else if (data 0x03 rx_len 0) { // ETX结束 rx_buf[rx_len] data; if (validate_checksum(rx_buf, rx_len)) { parse_mitsubishi_frame(rx_buf, rx_len); // 解析命令 } rx_len 0; } else if (rx_len sizeof(rx_buf)-1) { rx_buf[rx_len] data; } } }4.2 GX Works联机调试实操步骤与常见故障排查要实现GX Works识别该STM32设备需完成以下配置硬件连接USB-TTL模块TX→PA10(RX), RX→PA9(TX)GND共地电平匹配3.3V TTLGX Works设置工程→PLC类型→选择“FX1S/FX1N/FX2N/FX2NC”在线→传输设置→端口选择对应COM号波特率设为9600数据位8停止位1无校验在线→PLC登录→选择“PLC读取”点击“连接”固件响应当GX Works发送02 30 30 44 30 30 30 30 30 31 03 xx xx读D000时固件需返回02 30 30 30 30 30 30 30 30 30 03 yy yyD0000。常见故障“无法连接PLC”检查USART1时钟是否使能RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_USART1, ENABLE)、GPIOA9/A10是否配置为复用推挽输出/浮空输入“读取数据错误”校验和计算遗漏STX/ETX字节或ASCII转HEX时未忽略空格“在线监视无变化”GX Works默认每500ms轮询一次需确保plc_execute_cycle()在TIM3中断中稳定执行且PLC_INPUTS/OUTPUTS映像区被正确更新。5. 梯形图逻辑移植技巧从GX Developer到STM32F10X的指令转换表5.1 典型梯形图元件到C指令的映射规则将GX Developer中绘制的梯形图转换为plccode[]数组需遵循以下转换表。注意本项目仅支持基本指令复杂功能如PID、浮点运算需自行扩展。GX Developer元件对应指令码操作数格式示例GX中plccode[]序列常开触点 X0000x010x00 0x00LD X00001 00 00常闭触点 X0010x030x00 0x01LDI X00103 00 01输出线圈 Y0000x050x01 0x00OUT Y00005 01 00定时器触点 T00x060x08 0x00LD T006 08 00上升沿检测 PLS0x0A0x02 0x64PLS M1000A 02 64M100地址为0x0264下降沿检测 PLF0x0B0x02 0x64PLF M1000B 02 64关键技巧GX Developer编译后的.bin文件如GC.bin可用十六进制编辑器直接查看指令流。例如LD X000 → AND X001 → OUT Y000对应01 00 00 02 00 01 05 01 00 FFFF为结束符。将此序列复制到plccode[]数组中即可在STM32上运行相同逻辑。5.2 ADC模拟量输入接入将AD值映射为PLC的D寄存器PLC的D寄存器数据寄存器用于存储模拟量值。本项目通过stm32f10x_adc.c实现12位ADC采样并将结果写入PLC_FLAGS区的指定位置如D000→PLC_FLAGS[0]和PLC_FLAGS[1]组成16位值// adc.c 中 ADC采样完成后调用 void ADC1_2_IRQHandler(void) { uint16_t ad_val ADC_GetConversionValue(ADC1); // 映射到D000高字节存PLC_FLAGS[0]低字节存PLC_FLAGS[1] PLC_FLAGS[0] (ad_val 8) 0xFF; PLC_FLAGS[1] ad_val 0xFF; // 触发GX Works读取D000时返回PLC_FLAGS[0]和PLC_FLAGS[1]组合值 }此时在GX Works中读取D000将得到0–4095范围的ADC原始值。若需工程单位转换如0–10V→0–100.0℃可在plc_execute_cycle()中插入缩放计算指令例如MOV D000 K100后接DIV D000 K40实现除法运算需扩展MOV/DIV指令支持。5.3 错误处理与诊断HardFault定位与PLC状态寄存器当程序异常如非法内存访问、未定义指令触发HardFault时本项目在stm32f10x_it.c中重定向HardFault_Handler将关键寄存器值写入PLC_FLAGS[511]开始的诊断区void HardFault_Handler(void) { __asm volatile ( movs r0, #0\n\t // r0 0 str r0, [r0, #0]\n\t // 强制写0地址触发BusFault仅用于调试 ldr r0, 0x200001FC\n\t // PLC_FLAGS[511]地址 mrs r1, psp\n\t // 获取进程栈指针 str r1, [r0]\n\t // 存入诊断区 ldr r1, 0xE000ED28\n\t // AIRCR地址 ldr r2, [r1]\n\t // 读取异常原因 str r2, [r0, #4]\n\t // 存入诊断区4 bkpt #0\n\t // 断点便于J-Link调试 ); }GX Works可通过读取D511–D512获取故障信息辅助定位问题。实际部署时建议将诊断数据通过UART发送至上位机日志系统而非依赖PLC读取。本文还有配套的精品资源点击获取
返回列表