STM32版FX2N源码&原理图&PCB板(可直接移植) FX2N源码V3.8版的使用基本说明: 编译语言:C语言 FX2N源码V3.8版是根据三菱FX2N的PLC通信协议、通信命令以及基于STM32F103系列单片机上编写运行的程序,可以直接利用三菱编程软件编写梯形图下载运行,无需任何转换。 注:1)目前在STM32F103RD/STM32F103VC/STM32F103VD/STM32F103VE/STM32F103ZE测试通过; 2)本程序不完全支持所有梯形图指令,如果需要其他的功能,需要亲自己修改代码, 编译环境:Keil 4或者Keil5

这年头用STM32复刻PLC的项目不少,但能把三菱FX2N协议跑利索的真不多见。最近搞到个开源的FX2N_V3.8实现,实测确实能用三菱的GX Works2直接下载梯形图,这种无缝对接的骚操作必须得扒开代码看个明白。

先看硬件配置,原理图里STM32F103VE核心板外挂了SRAM和铁电存储器。重点在通信接口这块,源码里用USART1实现了三菱的编程口协议:

void PLC_Comm_Init(void)
{
    GPIO_InitTypeDef gpio;
    USART_InitTypeDef uart;
    // 485方向控制引脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    gpio.GPIO_Pin = GPIO_Pin_8;
    gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &gpio);
    
    // 串口配置:9600,8,N,1
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_Init(USART1, &USART_InitStructure);
}

这段配置有个坑点——需要外接MAX485芯片做电平转换,PCB文件里用的是PA8控制收发方向。实际调试时发现三菱编程软件对时序极其敏感,在发送应答帧前必须精准切换方向,否则分分钟握手失败。

指令解析是核心部分,项目里用状态机处理梯形图逻辑。比如处理MOV指令的代码段:

void Handle_MOV(uint16_t operand[])
{
    uint16_t src = operand[0];  // 源操作数地址
    uint16_t dest = operand[1]; // 目标地址
    uint16_t val = GetRegister(src);
    
    if(dest < D_MAX_ADDR) {     // 数据寄存器范围检查
        SetRegister(dest, val);
        UpdateIOFlag(dest);      // 触发IO更新标志
    } else {
        ExceptionHandler(ERR_ADDRESS);
    }
}

这里有个取巧的设计——用GetRegister/SetRegister统一管理寄存器访问,不管是X/Y物理IO还是D/M软元件都走这套接口。实测发现处理速度比预期快,毕竟STM32F103的72MHz主频对PLC逻辑来说绰绰有余。

STM32版FX2N源码&原理图&PCB板(可直接移植) FX2N源码V3.8版的使用基本说明: 编译语言:C语言 FX2N源码V3.8版是根据三菱FX2N的PLC通信协议、通信命令以及基于STM32F103系列单片机上编写运行的程序,可以直接利用三菱编程软件编写梯形图下载运行,无需任何转换。 注:1)目前在STM32F103RD/STM32F103VC/STM32F103VD/STM32F103VE/STM32F103ZE测试通过; 2)本程序不完全支持所有梯形图指令,如果需要其他的功能,需要亲自己修改代码, 编译环境:Keil 4或者Keil5

移植时要注意的硬件差异主要集中在IO映射。比如在IO_Config.c里有这么一段:

const X_IO_MAP x_map[16] = {
    {GPIOB, GPIO_Pin_0},  // X0
    {GPIOB, GPIO_Pin_1},  // X1
    //... 省略其他输入点配置
};

const Y_IO_MAP y_map[16] = {
    {GPIOC, GPIO_Pin_12}, // Y0
    {GPIOC, GPIO_Pin_11}, // Y1
    //... 输出点配置
};

如果目标板子的外设引脚不同,改这里比改全局宏定义更安全。之前有个老哥直接把PCB的GPIO定义全改成自己的开发板,结果下载后输出点全乱,排查半天才发现Y7接的引脚被复用成SWD接口了...

项目里最骚的操作是模拟了FX2N的RUN/STOP开关。源码里用按键长按实现模式切换:

void Check_Mode_Switch(void)
{
    static uint8_t last_state = 1;
    if(KEY_RUNSTOP_GPIO->IDR & KEY_RUNSTOP_PIN) {
        if(last_state == 0) { // 释放检测
            if(key_press_time > 2000) { // 长按2秒切换模式
                plc_mode = (plc_mode==RUN_MODE)?STOP_MODE:RUN_MODE;
                SetStatusLED();
            }
            key_press_time = 0;
        }
        last_state = 1;
    } else {
        last_state = 0;
        key_press_time += PLC_SCAN_TIME; // 累计按下时间
    }
}

这种软硬结合的设计让调试方便不少,实测长按响应比原厂PLC还灵敏,估计是STM32的时钟精度带来的意外收获。

虽然源码已经实现了大部分基础指令,但像PID、高速计数器这些高级功能确实需要自己补全。比如项目里的定时器中断服务函数留了扩展口子:

void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
        // 基础定时任务
        SysTimer++; 
        // 用户自定义硬件定时任务钩子
        if(usr_timer_hook) usr_timer_hook();
        
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

想加高速脉冲计数的话,可以在这里挂接自己的编码器处理函数。之前见过有人在这个钩子里实现了四路200kHz的AB相计数,STM32的性能果然还是能榨出不少油水。

最后说下移植到其他STM32型号的注意事项。虽然源码支持多个F103型号,但换到F4系列的话要改启动文件和时钟配置。尤其是HSE_VALUE的定义,F103用8MHz外部晶振,而F4开发板常见25MHz,不改的话分频系数算错直接启动失败。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐