十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于STM32复刻三菱FX3U PLC:硬件设计、软件内核与协议解析

基于STM32复刻三菱FX3U PLC:硬件设计、软件内核与协议解析 简介本资源是一套面向嵌入式开发者与工业自动化学习者的国产化PLC替代方案设计资料基于STM32F103VET6单片机完整复现三菱FX3U系列PLC的核心功能涵盖硬件设计、固件开发与数据保持机制适用于工业控制原型验证、课程设计及国产PLC二次开发参考。压缩包共525个文件含原理图SchDoc、PCBPcbDoc、元件库PcbLib/SchLib、Keil工程uvprojx/uvoptx、C源码49个.c文件含ladder.c、stm32f10x_tim.c等、头文件48个.h、编译中间文件o/d/crf/log及Hex烧录镜像总大小22.36MB结构完整、模块清晰便于从电路设计到逻辑编程全流程研读。已有520人学习下载资源中包含RTC时钟配置、PLC输入输出初始化、D寄存器区D8000–D8126数据初始化与断电保持PLC_ReadKeepData、RS485通信bsp_Init及梯形图逻辑核心框架为理解小型PLC底层实现提供了难得的开源实践样本。1. 项目概述从零到一复刻工业PLC核心最近在整理过去的项目资料翻出了一个挺有意思的“老古董”——基于STM32F103VET6单片机完整复刻三菱FX3U系列PLC的硬件与软件项目。这个项目最初源于一个实际需求在特定的小批量、定制化设备上使用原装进口PLC成本过高且后期维护、功能二次开发受制于人。于是就有了这个“国产化替代”的探索。项目包里包含了用Altium Designer 09AD09设计的全套硬件原理图PCB以及运行在STM32上的完整软件源码。这不仅仅是一个简单的单片机程序而是一个试图在ARM Cortex-M3内核上实现一套兼容FX3U指令集和通讯协议的微型PLC系统。对于从事工业控制、嵌入式开发特别是对PLC底层原理感兴趣的朋友来说这个项目就像一份详细的“解剖报告”能让你看清一个成熟PLC的内脏是如何工作的。2. 核心设计思路与架构解析2.1 为什么选择STM32F103VET6作为核心在项目启动时MCU的选型是第一个关键决策。为什么最终锁定了STM32F103VET6这颗如今看来有些“经典”的芯片这背后是一系列工程化的权衡。首先性能与资源的匹配度。FX3U系列是小型PLC中的标杆其处理速度、I/O能力、通讯功能都有明确指标。STM32F103VET6基于Cortex-M3内核主频72MHz拥有512KB的Flash和64KB的RAM。这个资源规模为移植一个精简的PLC运行时Runtime和用户程序存储提供了可能。512KB Flash可以划分出一部分用于存储系统固件另一部分模拟PLC的用户程序存储区相当于EEPROM。64KB RAM则用于运行时的各种变量区、通讯缓冲区等。其次丰富的外设接口。这是复刻PLC硬件基础的关键。VET6型号拥有多达80个GPIO这为扩展大量的数字量输入输出DI/DO提供了物理基础。它内置了多个USART、SPI、I2C接口这对于实现PLC的编程口通常基于RS232/RS485、扩展模块通讯如FX系列专用的扩展总线以及后续可能添加的以太网、CAN等高级功能模块至关重要。此外其内置的ADC、定时器、PWM等功能也为模拟量处理、高速计数等PLC高级功能提供了硬件支持。最后生态与成本。在当时乃至现在STM32F1系列的开发资料、社区支持、芯片价格和供货都处于一个非常理想的平衡点。这对于一个需要验证可行性、并可能小批量试产的项目来说极大地降低了技术风险和物料成本。当然以现在的眼光看可以选择性能更强的F4或H7系列但在项目当时F103VET6是性价比和实现难度之间的最优解。注意选型时切忌盲目追求高性能芯片。PLC的核心在于实时性、稳定性和可预测性而非纯粹的运算速度。过高的主频可能带来更大的功耗和更复杂的时钟树、电源设计问题。对于小型PLC资源够用、外匹配、稳定可靠才是第一原则。2.2 整体系统架构设计这个复刻项目的目标不是做一个“形似”的玩具而是追求一定程度的“神似”即在功能和行为上尽可能接近原机。因此整个系统架构需要分层设计硬件抽象层HAL这是最底层直接对接STM32的固件库当时使用的是标准外设库现在也可以用HAL库重写。它负责将PLC的概念映射到具体的MCU资源上。例如将PLC的“输入继电器X0”映射到STM32的某个GPIO输入引脚将“输出继电器Y0”映射到某个GPIO输出引脚并通过光耦或继电器驱动电路控制外部负载将PLC的“数据寄存器D0”映射到STM32 RAM中的一个特定地址。PLC运行时内核Runtime Kernel这是系统的核心大脑。它需要实现一个循环扫描的工作机制即周而复始地执行“输入采样 - 用户程序执行 - 输出刷新”这三个阶段。这个内核需要包含一个软解释器能够解析并执行从编程软件如GX Works2下载下来的、符合FX3U指令集的用户程序通常是编译后的中间代码而非原始的梯形图。同时它还要管理定时器、计数器、辅助继电器等PLC内部软元件。通讯协议栈这是PLC与外界交互的桥梁。必须实现三菱的专用编程协议如MC协议常用于编程软件连接以及一些常用的无协议通讯用于与触摸屏、变频器等设备通信。这部分通常运行在USART上采用RS232或RS485电平。协议栈的实现要严格遵循三菱的文档确保上位机软件能像连接真机一样连接和调试这个STM32 PLC。用户程序存储与管理需要设计一个可靠的机制将上位机下载的用户程序存储到STM32的Flash中并在上电后由运行时内核加载到RAM中执行。这里涉及Flash的读写、擦除操作以及程序校验、备份等安全机制。这种架构确保了系统的模块化和可维护性硬件更换比如换用其他STM32型号只需调整HAL层功能升级如增加新指令主要修改运行时内核通讯扩展则修改协议栈。3. 硬件设计详解与关键电路分析3.1 核心板最小系统与电源设计原理图的设计始于STM32F103VET6的最小系统。这包括电源电路PLC通常需要24VDC工业电源输入。因此首先需要一个宽电压输入的DC-DC降压模块如MP2451将24V转换为5V或3.3V为后续电路供电。然后使用LDO如AMS1117-3.3将5V转为稳定的3.3V供STM32核心及部分外围芯片使用。这里有个坑工业环境电源噪声大必须在输入级加入TVS管、共模电感、大小电容组成的π型滤波电路否则后续MCU很容易因电压毛刺而死机或复位。时钟电路采用8MHz外部高速晶振和32.768kHz外部低速晶振。高速晶振用于系统主时钟低速晶振用于RTC如果实现时钟功能和低功耗模式。匹配电容的容值需参考晶振手册和STM32数据手册通常需要在22pF附近微调以保证起振可靠。复位电路简单的RC复位电路10k电阻100nF电容加上一个手动复位按钮。建议在复位引脚附近预留一个测试点方便用示波器抓取上电和复位时序。调试接口必须引出标准的JTAG/SWD接口使用20pin或10pin的IDC插座这是开发阶段下载、调试程序的唯一通道。同时最好也引出一个USART转为TTL电平的串口用于打印调试信息。3.2 数字量输入DI电路设计工业PLC的输入电路核心是隔离与抗干扰。我们的设计采用光耦隔离方案。典型电路外部24V信号正极通过一个限流电阻如2kΩ/0.5W接入光耦如TLP181的阳极阴极接外部信号的公共端COM。光耦输出侧集电极接STM32的GPIO引脚配置为上拉输入和3.3V电源发射极接地。关键参数计算限流电阻R (输入电压 - 光耦正向压降Vf) / 光耦推荐工作电流If。假设24V输入Vf1.2VIf取5mA则R ≈ (24-1.2)/0.005 4560Ω取标准值4.7kΩ。需要核算电阻功率P I^2 * R (0.005)^2 * 4700 0.1175W选用1/4W电阻足够。注意事项每个输入点最好并联一个双向TVS管如SMBJ24A到地用于钳位高压浪涌。在光耦输入侧并联一个反向二极管如1N4148防止反接电源损坏光耦。在STM32的GPIO引脚处对地接一个几十pF的小电容可以滤除高频干扰但容值不宜过大否则会影响输入信号的边沿速度。3.3 数字量输出DO电路设计输出电路根据负载类型继电器、晶体管有不同的设计。这里以最常用的继电器输出为例。驱动电路STM32的GPIO配置为推挽输出驱动能力有限通常需要增加一级驱动。我们使用三极管如SS8050或MOSFET如SI2302来驱动继电器线圈。GPIO通过一个基极电阻如1kΩ连接到NPN三极管的基极继电器线圈接在集电极和电源5V或12V根据继电器型号之间发射极接地。继电器线圈两端必须反向并联一个续流二极管如1N4007以吸收线圈断电时产生的反向电动势保护驱动管。隔离与输出继电器本身提供了强电负载侧与弱电控制侧的电气隔离。继电器触点一端接负载另一端接外部电源如220VAC或24VDC。在触点两端建议并联RC吸收回路如100Ω电阻串联0.1μF电容或压敏电阻以抑制感性负载如电机、电磁阀通断时产生的电弧和电压尖峰。状态反馈为了增加可靠性可以在继电器驱动电路上增加一个光耦将实际的输出状态反馈给另一个STM32 GPIO实现输出回读用于诊断输出点是否故障。3.4 PCB布局布线要点用AD09画PCB时工业控制板的布局布线有其特殊要求分区明确将板子清晰地划分为几个区域电源区DC-DC、LDO、滤波电容、数字核心区STM32、晶振、JTAG、输入隔离区光耦阵列、输出驱动区继电器、驱动管和通讯接口区RS232/485芯片。区域之间用地线或电源线进行隔离。电源走线优先电源路径尤其是24V转5V5V转3.3V要尽量粗、短。在每个芯片的电源引脚附近必须放置一个0.1μF的陶瓷去耦电容并尽可能靠近引脚。大电流路径如继电器线圈电源要用铺铜处理。模拟地与数字地虽然主要是数字电路但如果板上有ADC用于模拟量输入则需要做模拟地和数字地的分割。单点通过磁珠或0欧电阻连接连接点通常选在电源芯片的接地端。隔离与爬电距离光耦、继电器等隔离器件两侧的电路在PCB上要保持足够的电气间隙Creepage和爬电距离Clearance。对于24V电路建议至少保持2mm以上对于220V电路要求更高可能需要开槽。散热考虑继电器、DC-DC芯片、LDO都是发热大户。需要在它们的底部或附近预留足够的铜皮散热必要时在顶层丝印层标注“散热区”。4. 软件系统实现与核心代码剖析4.1 PLC运行时内核的构建这是软件部分最复杂、最核心的模块。其本质是一个任务调度器加一个指令解释器。1. 主循环与扫描周期void PLC_Runtime_Main(void) { System_Init(); // 初始化硬件、软元件区 while (1) { uint32_t scan_start_tick HAL_GetTick(); // 记录扫描开始时间 // 阶段1输入采样 DI_Scan_All(); // 将所有物理输入点的状态一次性读入到“输入映像区” // 阶段2用户程序执行 PLC_Program_Execute(); // 解释执行用户程序运算结果写入“输出映像区” // 阶段3输出刷新 DO_Update_All(); // 将“输出映像区”的状态一次性输出到物理输出点 // 扫描周期处理与通讯任务 uint32_t scan_time HAL_GetTick() - scan_start_tick; if (scan_time TARGET_SCAN_TIME_MS) { // 扫描时间未达到目标周期则执行后台任务如通讯处理 Communication_Task(); // 然后延时剩余时间 HAL_Delay(TARGET_SCAN_TIME_MS - scan_time); } else { // 扫描超时可置位超时报警标志 g_scan_timeout_flag 1; // 超时后依然处理通讯但可能降低优先级或限制时间 Communication_Task_Limited(); } } }关键点TARGET_SCAN_TIME_MS是目标扫描周期例如5ms或10ms。这决定了PLC的响应速度。必须确保在最复杂的用户程序下扫描时间也不会远超此值否则系统实时性无法保证。2. 软元件管理在RAM中开辟一系列数组来模拟PLC的各种软元件// 定义软元件存储区 uint8_t InputImage[MAX_INPUT_POINTS / 8 1]; // 输入映像区位 uint8_t OutputImage[MAX_OUTPUT_POINTS / 8 1]; // 输出映像区位 uint16_t DataRegister[MAX_D_REGISTERS]; // 数据寄存器D字 uint8_t AuxiliaryRelay[MAX_M_RELAYS / 8 1]; // 辅助继电器M位 // ... 定时器、计数器等结构体数组对这些软元件的访问需要封装统一的读写函数例如Get_X(uint16_t index)、Set_Y(uint16_t index, bool value)、Read_D(uint16_t index)、Write_D(uint16_t index, uint16_t value)。3. 指令解释器用户程序通常被编译成一系列紧凑的指令码Opcode和操作数。解释器就是一个大的switch-case循环void PLC_Program_Execute(void) { uint8_t *program_ptr g_user_program_start_addr; while (*program_ptr ! END_INSTRUCTION) { uint8_t opcode *program_ptr; switch (opcode) { case LD: // 取常开触点 operand Fetch_Operand(program_ptr); bool bit_value Get_SoftElement_Bit(operand); Push_Bool_to_Stack(bit_value); // 将触点状态压入运算栈 break; case AND: // 串联常开触点 operand Fetch_Operand(program_ptr); bool bit_value Get_SoftElement_Bit(operand); bool stack_top Pop_Bool_from_Stack(); Push_Bool_to_Stack(stack_top bit_value); // 与运算后压栈 break; case OUT: // 输出线圈 operand Fetch_Operand(program_ptr); bool result Pop_Bool_from_Stack(); Set_SoftElement_Bit(operand, result); // 弹出栈顶结果写入软元件 break; case MOV: // 传送指令 src_operand Fetch_Operand(program_ptr); dst_operand Fetch_Operand(program_ptr); uint16_t word_value Get_SoftElement_Word(src_operand); Set_SoftElement_Word(dst_operand, word_value); break; // ... 实现其他指令如OR、LDI、ANI、ORI、SET、RST、ADD、SUB等 default: // 遇到未知指令跳转到错误处理 Handle_Illegal_Instruction(); return; } } }实操心得指令解释器的效率直接影响扫描周期。优化方法包括使用函数指针跳转表代替switch-case将常用指令如LD、AND、OR的操作数读取和软元件访问内联优化运算栈的实现使用静态数组和索引指针而非动态内存分配。4.2 通讯协议栈的实现要实现与三菱编程软件如GX Works2的联机必须实现其MC协议一种基于串行通讯的二进制协议。协议帧格式通常为帧头固定值、目标站号、命令码、数据区、帧尾校验和。1. 协议解析状态机由于串口数据是流式的必须使用状态机来可靠地解析帧。typedef enum { COM_STATE_WAIT_HEADER1, COM_STATE_WAIT_HEADER2, COM_STATE_WAIT_STATION, COM_STATE_WAIT_CMD, COM_STATE_READ_DATA_LEN, COM_STATE_READ_DATA, COM_STATE_WAIT_CHECKSUM } ComState_t; void USART_Rx_IRQ_Handler(uint8_t rx_byte) { static ComState_t state COM_STATE_WAIT_HEADER1; static uint8_t rx_buffer[MAX_FRAME_LEN]; static uint16_t data_index 0; static uint16_t expected_data_len 0; switch (state) { case COM_STATE_WAIT_HEADER1: if (rx_byte FRAME_HEADER1) state COM_STATE_WAIT_HEADER2; break; case COM_STATE_WAIT_HEADER2: if (rx_byte FRAME_HEADER2) state COM_STATE_WAIT_STATION; else state COM_STATE_WAIT_HEADER1; // 同步失败重置 break; case COM_STATE_WAIT_STATION: if (rx_byte LOCAL_STATION_NO) { state COM_STATE_WAIT_CMD; rx_buffer[data_index] rx_byte; } else { state COM_STATE_WAIT_HEADER1; // 站号不匹配丢弃 data_index 0; } break; case COM_STATE_WAIT_CMD: rx_buffer[data_index] rx_byte; state COM_STATE_READ_DATA_LEN; break; case COM_STATE_READ_DATA_LEN: rx_buffer[data_index] rx_byte; expected_data_len rx_byte; // 假设数据长度在固定位置 if (expected_data_len 0) { state COM_STATE_WAIT_CHECKSUM; } else { state COM_STATE_READ_DATA; } break; case COM_STATE_READ_DATA: rx_buffer[data_index] rx_byte; if (data_index (expected_data_len FIXED_HEADER_LEN)) { state COM_STATE_WAIT_CHECKSUM; } break; case COM_STATE_WAIT_CHECKSUM: if (Validate_Checksum(rx_buffer, data_index, rx_byte)) { // 校验通过将完整帧送入处理队列 Enqueue_Frame(rx_buffer, data_index); } // 无论校验是否通过都重置状态机准备接收下一帧 state COM_STATE_WAIT_HEADER1; data_index 0; break; } }2. 命令处理在主循环的通讯任务中从队列取出完整的帧进行解析和处理。常见的命令包括读软元件如读X、Y、M、D的值根据命令码和地址从对应的软元件数组中取出数据组装成响应帧发回。写软元件将数据写入指定的软元件地址然后返回成功响应。远程控制RUN/STOP改变PLC运行时内核的状态机运行/停止。程序读写将用户程序块写入Flash或从Flash读出。踩坑记录三菱的协议对时序有要求。发送响应帧不能太慢否则上位机会认为超时。最好在收到有效帧后立即准备响应数据并在一个较短的时间窗口内发送出去。同时要处理好“粘包”问题状态机是解决此问题的标准方法。4.3 用户程序存储与文件系统模拟FX3U的用户程序存储在板载的EEPROM或电池备份的RAM中。在STM32上我们用内部Flash来模拟。1. Flash扇区规划STM32F103VET6的512KB Flash被划分为256页每页2KB。我们需要规划出一块区域专门存放用户程序和相关参数。扇区0地址0x08000000开始存放Bootloader和PLC系统固件。扇区1~扇区N作为用户程序区。可以将多个扇区组合成一个“虚拟EEPROM”。最后一个扇区存放系统参数如站号、通讯波特率、密码等。2. 简易的磨损均衡与掉电保护为了防止频繁擦写同一个扇区导致Flash损坏可以设计一个简单的磨损均衡策略。例如使用两个扇区A和B轮流存储。每次更新程序时写入到当前未使用的扇区然后更新一个“有效标志”指针。上电时根据指针读取有效扇区。 写入用户程序时必须先将整个扇区擦除STM32的Flash擦除以扇区为单位然后再按字32位或半字16位编程。关键点在擦除和写入关键数据如程序有效标志前最好先关闭总中断操作完成后再打开防止在此期间被中断打断导致Flash操作失败或数据错乱。5. 调试、测试与常见问题排查5.1 分阶段调试策略硬件基础测试PCB焊接完成后先不焊主芯片。上电用万用表测量各路电源电压24V, 5V, 3.3V是否正常纹波是否在可接受范围如50mV。然后焊接STM32最小系统部分通过ST-LINK连接测试能否识别芯片、下载一个简单的LED闪烁程序。外设驱动测试逐个测试外围电路。写测试程序让所有输出点循环动作用万用表或示波器测量输出端电压给所有输入点施加24V信号在程序中读取并打印状态。测试串口通讯用USB转TTL工具环回测试确保波特率、数据位、停止位、校验位设置正确。PLC内核功能测试先抛开通讯协议在代码中硬编码一段简单的梯形图逻辑如X0导通则Y0输出测试运行时内核的扫描和执行是否正常。使用调试器观察软元件区的变化。通讯协议联调这是最繁琐的一步。建议使用串口调试助手先手动拼接一个最简单的读命令帧例如读X0-X7发送给STM32看其返回的响应帧格式是否正确。然后逐步测试更复杂的命令。最后再尝试用三菱的编程软件连接。一个技巧在通讯代码中大量使用printf打印发送和接收到的原始字节十六进制格式这是定位协议问题最直接的方法。5.2 常见问题与解决方案速查表问题现象可能原因排查思路与解决方案上电后MCU不工作无任何反应1. 电源异常电压不对或电流不足2. 复位电路问题复位引脚被拉低3. 晶振未起振4. Boot引脚配置错误1. 测量3.3V、5V电压检查DC-DC芯片输入输出。2. 测量NRST引脚电压正常应为高电平~3.3V。3. 用示波器探头X10档测量OSC_IN/OSC_OUT引脚看是否有正弦波。注意探头电容可能影响起振可尝试更换匹配电容。4. 检查BOOT0和BOOT1引脚是否按要求接地或上拉。输入点信号不稳定偶尔误触发1. 光耦输入侧限流电阻过大工作电流处于临界值。2. 电源噪声干扰。3. GPIO输入未启用内部上拉处于浮空状态。4. 软件去抖动处理不当。1. 减小限流电阻增大光耦工作电流但不超过最大值。2. 检查电源滤波在光耦输出侧的VCC对地加104电容。3. 在初始化代码中将输入GPIO配置为上拉输入模式。4. 在软件中增加去抖动算法如连续采样N次状态一致才认为有效。输出点继电器动作但负载不工作1. 负载电源未接通或故障。2. 继电器触点接触不良或已损坏。3. 负载功率超过继电器触点容量。4. 输出回读电路故障误报输出已动作。1. 用万用表测量负载两端电压。2. 在继电器动作时测量其触点两端电阻应为导通状态接近0欧姆。3. 核对负载的额定电流/电压是否在继电器规格书范围内。4. 检查回读光耦电路。与编程软件连接失败提示“无法与PLC通信”1. 串口波特率、数据格式不匹配。2. PLC站号设置错误。3. 通讯协议帧解析错误响应不符合预期。4. 硬件流控RTS/CTS未处理。1. 确认软件和代码中的波特率如9600, 19200、数据位7/8、停止位1/2、校验位偶/奇/无完全一致。2. 检查代码中定义的本地站号与软件中设置的站号是否一致。3. 用串口监听工具抓取软件发出的请求帧和PLC返回的响应帧逐字节对比协议手册。4. 三菱常用协议通常不用硬件流控确保相关引脚已做适当处理如固定电平。PLC运行一段时间后死机或复位1. 看门狗未喂狗或溢出时间设置过短。2. 堆栈溢出。3. 中断服务程序执行时间过长或发生嵌套冲突。4. 电源波动或外部强干扰。1. 检查独立看门狗IWDG或窗口看门狗WWDG的配置和喂狗逻辑。2. 在启动文件startup_stm32f103xe.s中增大堆栈Stack_Size和堆Heap_Size的大小。3. 优化中断服务程序只做标志位设置等最小操作耗时任务放到主循环。检查中断优先级配置。4. 加强电源滤波和板级屏蔽检查接地是否良好。用户程序下载后无法运行1. Flash编程/擦除出错。2. 程序校验和错误。3. 程序块头信息如起始标志、长度损坏。4. 运行时内核加载程序的地址错误。1. 在Flash操作函数中加入返回值检查并打印错误信息。2. 在下载完成后增加一个回读校验的过程对比写入和读出的数据。3. 在程序块的开头和结尾增加固定的魔数Magic Number和CRC校验。4. 确认程序存储区的起始地址与内核中加载函数的地址一致。5.3 性能优化与稳定性提升当基本功能实现后可以从以下方面提升系统品质扫描周期优化使用STM32的定时器产生一个精确的中断如每1ms一次在中断服务程序中仅设置一个标志。主循环检测到这个标志后才执行一次完整的扫描。这样可以保证扫描周期的严格恒定不受程序逻辑复杂度的影响。通讯任务非阻塞化将通讯协议解析和响应放在主循环中避免在串口中断中处理复杂逻辑导致中断阻塞。中断只负责将数据存入环形缓冲区主循环从中取出处理。增加系统自诊断在上电初始化时和运行中定期检查关键电压、温度如有传感器、RAM/Flash的CRC等。发现异常时可以记录到非易失存储器中并通过一个专用的故障输出点报警。EMC设计与测试对于工业产品电磁兼容性至关重要。除了PCB设计时的布局布线规范后期可以进行简单的测试如群脉冲EFT和静电ESD测试。可以在输入输出端口、通讯端口增加更高级的防护电路如气体放电管、半导体放电管等。这个项目从硬件选型、电路设计、PCB绘制到软件内核、协议栈、驱动程序的编写是一个完整的嵌入式系统开发流程。它不仅仅是一个PLC的复制品更是一个深入理解实时系统、工业通讯协议和硬件软件协同设计的绝佳案例。即使最终不用于实际生产其过程中积累的关于稳定性、可靠性和抗干扰设计的经验对任何嵌入式开发者来说都是极为宝贵的财富。本文还有配套的精品资源点击获取
返回列表