拓冰建站拓冰建站
首页 / 资讯中心 / 正文

固件人机界面实战:基于STM32与OLED的PID调参菜单设计

做电控项目做到一定阶段你就会发现真正卡住进度的往往不是控制算法本身而是调试手段。这个系列写到第7期前几期我们把硬件底板、功率驱动、编码器采集、FOC电流环这些都铺开了速度环和位置环的PID框架也搭好了按理说可以开始整定了。可真到了整定那天我对着板子愣了半天参数调来调去只能靠改代码重烧看波形得抱着电脑蹲在台架旁边设备装到机架上之后更是没法随时连串口。缺的不是控制理论而是一个能让我在现场直接看数据、直接改参数的固件人机界面。所以这一期我不讲PID怎么整专门讲讲“整定之前先给固件长出人机界面”这件事。这个界面不一定要多好看但它必须能帮我把“改参数、看响应、调策略”的闭环从电脑搬到设备本体上。文章会从方案选型、菜单结构设计、刷屏与主循环的配合、参数掉电保存这些角度展开最后把我实机调试时踩过的几个坑完整梳理一遍。如果你也在做电机控制、运动控制或者任何需要PID整定的固件项目这篇应该能省下你不少弯路。1. 光有算法还不够整定最缺的是“现场看得到的手”先说结论给固件加人机界面不是锦上添花而是整定流程能不能顺畅跑起来的前提。很多人觉得整定就是拿个串口助手发几个命令看返回曲线然后把PID参数填进去就行。这个思路在实验室里确实成立可一旦设备装上机器、离开工位问题就来了。我之前做云台稳像项目时电机和驱动板装在云台内部整定需要在云台实际负载下进行。每次改参数都要拆开外壳、连上调试器、重新编译烧录再把云台装回去上电观察。改一个Ki整个流程走下来十分钟起步而且改完之后手感对不对全凭感觉。调了半天大部分时间浪费在“把参数送进固件”这个动作上而不是真正观察系统响应。还有一类做法是依赖上位机调参软件通过串口、CAN或者无线通道实时改参数、看曲线。这套方案功能强但有一个绕不开的问题通信链路本身就是个变量。波特率、协议版本、帧格式、固件与上位机的匹配任何一个环节出问题整定工作就变成调试通信了。我自己就遇到过固件升级后上位机还认旧协议结果发过去的参数被解析成乱码电机当场冲转的情况。人机界面解决问题的本质是把调试手段从外部工具收回到设备本身。OLED屏幕加一个旋转编码器成本不高但从此参数调整、状态监控、手动输出测试都能在设备跟前完成不需要任何外部依赖。而且当系统跑起来之后屏幕上实时刷新的速度环输出、电流、误差这些数据本身就是最好的整定反馈。整定这个事讲究的是“改一步看一步”界面能让你站在设备旁边手里握着旋钮眼睛盯着屏幕上的数字变化这种调试节奏比任何上位机都直接。这一期我是这么定位的以STM32F103C8T6主控、I2C接口OLED屏幕、旋转编码器按键为例说清楚固件里的人机界面从零到一怎么搭。整个过程不需要引入RTOS裸机主循环加中断就能搞定所以哪怕你的项目还在用最基础的单片机开发方式这套思路也完全适用。2. 三条路线对比屏显菜单、串口命令行、无线调试页决定做固件人机界面之后先别急着买屏幕第一步是选交互载体。交互载体决定了整个界面的开发模式和使用场景选错了后面返工成本很高。我把常见的方案分成三条路线实际对比过之后才做的决定。2.1 方案A本地屏显菜单推荐首选本地屏显是我最终采用的方案具体是0.96寸I2C OLED加一个带按键的旋转编码器。交互逻辑参考了老式数控机床的操作面板主界面显示运行状态单击进入参数菜单旋转编码器选择参数和调整数值长按返回。屏幕分辨率低不需要图形界面但胜在实时、直观、零依赖。这个方案的核心优势是调试效率。改P、I、D参数的时候编码器转一下就是0.1的步进按下确认键立刻写入RAM生效屏幕下一秒就显示新的输出响应。每次拉起整定工艺手都不用离开设备。设备装在机架上或者放上老化台连续跑测试我也能直接看屏幕上的状态不用一直连着一根串口线。成本上OLED屏和编码器加起来十几块钱对于一个已经在做的控制板来说基本可以忽略。开发量大约三到四个工作日。风险主要在OLED刷新和I2C通信对控制时序的影响这个我在后面第4节和第6节会专门展开。2.2 方案B串口命令行开发最快但留作备用串口命令行是很多固件项目默认的选择因为实现成本最低初始化一个USART写一个字符串解析函数把“P12.5”这样的命令解析成结构化数据再回显结果就行。我项目里也留了一个命令行入口作为调试后门专门处理那些屏幕上不方便展示的深层配置比如PID参数限幅值、编码器方向、电流环带宽标定系数。这个方案的麻烦在于它还是一条“线”。我拿它做主力调试的时候总觉得被串口线的长度束缚着而且串口工具动不动就丢帧、乱码处理起来很烦。后来我把命令行降级为辅助手段只有在线升级参数版本或者批量写入配置的时候才用。开发量一两天适合作为其他方案的补充。2.3 方案C蓝牙或WiFi无线调试页远程能力强但复杂度高如果用ESP8266或蓝牙模块做一个网页调试页手机扫码就能打开远程看着波形、拖拽滑杆调参确实很酷。这个方案的难点在于通信协议、帧校验、断线重连、安全鉴权这些链路问题一拥而上本来就还没整定完的参数又多了一堆网络变量。我在另一个项目里试过把ESP8266的AT固件接到STM32上通过TCP转发串口数据网页端用Canvas画曲线。花了大概两周把Demo跑通但稳定性一直不理想WiFi信道干扰、DHCP租约到期、连接掉线后固件端的状态机恢复这些都要处理。对本期“整定之前先有界面”的目标来说这条路线过重了我暂时没把它列为主方案。三条路线的对比如下路线开发量外部依赖适用场景主要风险本地屏显菜单3-4天无现场调参、设备装机的全场景OLED刷新占用主循环资源串口命令行1-2天串口线、电脑批量配置、参数导入导出通信异常、现场不便连线无线调试页两周以上WiFi/蓝牙、手机远程调试、批量设备管理链路不稳定、开发周期长所以我的最终选择是本地屏显为主、串口CLI为辅。屏幕负责日常整定的高频交互串口CLI负责那些低频但需要精确输入的批量操作两条通道互补哪个都不冲突。3. 界面长什么样菜单分三层按键逻辑一次讲清方案定了之后最核心的工作是设计菜单结构和交互逻辑。界面设计的目标用户不是消费者而是我自己——一个需要在现场快速操作、眼睛不用盯着屏幕太久就能读完信息的调试者。所以设计原则是信息密度优先交互路径越短越好。3.1 第一层主界面状态实时刷新一眼读完核心数据主界面显示的内容直接决定你整定时能不能“看得到”。我设计的主界面结构是一个固定的信息面板从上到下依次是当前运行模式速度环/位置环/手动、目标值、实际反馈值、误差绝对值、PID三项各自的实时贡献值、最终输出占空比。屏幕分辨率128x64一屏刚好能放下这些信息。关键数字用大号字体次要信息用小号字体压缩在角落。比如整定速度环时我最关心的是“实际转速跟不跟得上目标值”所以目标转速和实际转速用大字排在一起误差显示在后面P、I、D三个贡献值用小字排在底部用来判断当前到底是哪个环节在起作用。刷新频率设计在10Hz。为什么不是越快越好因为人眼对超过10Hz到15Hz的数字刷新已经不太敏感了而且OLED的I2C带宽有限刷新太快反而会把宝贵的总线时间占用掉。10Hz对读取数字、观察趋势来说刚好刷屏功耗也低。这个频率是在实机上试出来的一开始跑的20Hz刷得眼花改成10Hz反而更舒服。3.2 第二层参数菜单旋转编码器调速确认即时生效参数菜单是整个界面最核心的部分。这里需要明确一个设计决策参数修改是“即时生效”而不是“改完还要按保存才生效”。整定过程中我经常需要连续试几个Ki值观察系统是震荡还是收敛如果每次改完都要弹一个确认框调试节奏就被打断了。所以点进参数菜单后当前参数高亮显示旋转编码器左右转就调整数值数值在RAM里的PID结构体里立刻更新控制环路下一个周期就会使用新值。屏幕上的数字跟着变系统响应也跟得上这是整定体验提升最关键的一步。参数菜单本身做了两级划分第一级是参数组选择比如速度环PID组、位置环PID组、电流环限幅组第二级是组内的具体参数项比如P、I、D、积分限幅、输出限幅。用旋转编码器按下切换到下一级长按返回上一级。不再需要第四层了三层的深度对现场调试来说刚刚好太深了反而容易按迷失。3.3 手动输出模式整定之前的开环探路主界面和参数菜单解决“调参”的问题但整定之前还有一个环节需要人机界面支撑那就是手动开环测试。在PID参数还没有任何参考值时我习惯先用固定占空比驱动电机确认电机方向、编码器极性、电流反馈方向这些基础配置是否正确。这个测试以前靠改代码完成现在直接在手动模式下用旋钮调整输出电压的PWM占空比或者直接给一个目标转速让系统开环跑观察编码器反馈是否正确。这个模式还有一个用途就是整定过程中遇到异常比如电机尖叫、电流过大第一时间切到手动模式把占空比拉到0让系统安全停下来。屏幕上有专门的急停按键提示长按确认键三秒即切断所有输出比操作电脑快得多。3.4 菜单状态机的代码骨架菜单切换用状态机实现。这里给出一个简化版框架完整代码在项目仓库里typedef enum { UI_STATE_MAIN, UI_STATE_PARAM_GROUP, UI_STATE_PARAM_ITEM, UI_STATE_MANUAL_OUT } ui_state_t; static ui_state_t g_ui_state UI_STATE_MAIN; static int8_t g_menu_cursor 0; static int32_t g_encoder_acc 0; void ui_on_encoder_delta(int32_t delta) { g_encoder_acc delta; } void ui_on_button_event(button_event_t evt) { switch (g_ui_state) { case UI_STATE_MAIN: if (evt BUTTON_SHORT_PRESS) { g_ui_state UI_STATE_PARAM_GROUP; ui_draw_menu_group(g_menu_cursor); } else if (evt BUTTON_LONG_PRESS) { g_ui_state UI_STATE_MANUAL_OUT; ui_draw_manual_mode(); } break; case UI_STATE_PARAM_GROUP: if (evt BUTTON_SHORT_PRESS) { pid_group_t *grp g_pid_groups[g_menu_cursor]; g_ui_state UI_STATE_PARAM_ITEM; grp-item_cursor 0; ui_draw_param_item(grp); } else if (evt BUTTON_LONG_PRESS) { g_ui_state UI_STATE_MAIN; ui_draw_main(); } break; default: break; } } void ui_poll(void) { if (g_encoder_acc) { pid_param_adjust_by_cursor(g_encoder_acc); g_encoder_acc 0; ui_refresh_partial(); } }这段代码的核心思想是旋转编码器的增量先累加到一个变量里主循环轮询的时候一次性消费避免在中断里直接操作参数结构体造成数据竞态。按键事件类似中断或扫描函数只记录事件真正的状态切换放在ui_poll主循环里做。这个模式能保证控制环路的中断优先级始终高于UI逻辑后面第4节会详细解释为什么这个优先级必须守住。4. 让屏幕不拖后腿刷屏与主循环的协作方式OLED屏幕接在I2C总线上而I2C通信是典型的阻塞型操作。如果直接在控制ISR里调用OLED刷屏函数或者让刷屏逻辑占用了主循环的绝大部分时间控制环路的实时性就会被打穿。整定的时候最怕的就是系统响应数据里掺入“非控制因素”造成的毛刺你分不清到底是参数调得不对还是屏幕把时序搞乱了。4.1 控制中断与UI刷屏的优先级划分我的做法是把整个系统分成三个优先级层。第一层是定时器触发的控制ISR负责编码器采样、电流环计算、速度环计算、PWM输出更新这个中断固定1kHz频率不允许被任何UI操作阻塞。第二层是主循环负责处理UI事件、菜单切换、参数更新、Flash读写。第三层是低优先级的刷屏操作只在主循环的末尾或者空闲的时候执行。具体到实现上控制ISR只做一件事把当前时刻需要展示的“快照”拷贝到一个全局结构体里。主循环读这个快照去刷新UI而不去直接读控制环路的实时变量。这样即使屏幕刷新得很慢也不会影响控制ISR取最新数据的时序。快照结构体的访问在主循环端关中断保护一下避免读到一半数据被更新造成显示错乱。4.2 OLED刷屏的两种优化手段局部刷新和双缓冲标签从底层上诊断了“上传下载”环节而“下载”模式天然地引导用户在手机/PC网盘保存资源再者它回避了“资源上传”的法律风险使产品模式更稳妥。理论上可迁移至任何以简单“上传/分享”为价值主张的模块化工具。I2C OLED刷整屏需要发送1024字节的数据在400kHz的I2C频率下大约要25毫秒。如果每帧都整屏刷新主循环基本被吃掉了。所以我做了两个优化。第一是局部刷新。界面里真正变化的内容只有四个数字区域和一个状态图标我把这些区域标记为脏矩形只重新发送对应行列的显存数据。OLED驱动IC比如SSD1306支持页寻址模式你可以只更新某一页的某些列这个功能很好用。实测局部刷新一帧只发送几十字节耗时不到2毫秒主循环的负担几乎可以忽略。第二是双缓冲。显存里维护一个RAM副本所有绘图操作先在副本上进行完成后一次性提交到屏幕。这样做的好处是画面不会出现“先画了一半数字、再画另一半”的撕裂现象而且绘图逻辑和I2C发送逻辑可以彻底解耦。刷屏任务只在主循环里跑控制ISR永远不需要等待I2C总线。4.3 动态曲线的绘制方案主界面的数字刷新解决“看当前值”的问题但整定还需要“看趋势”——比如给一个阶跃目标后转速的响应曲线是超调还是爬升。OLED空间有限我用一个垂直滚动条代替完整波形屏幕右侧画一条32像素宽的纵向区域用最近N个周期的误差值映射为不同高度的柱状条像一个小型示波器。绘制时先把柱状条画进双缓冲显存然后整列提交。实际效果是在屏幕一角能看到误差的“尾巴”是长是短超调的时候柱子明显冲过高亮阈值。这个设计开发量不大但对整定的帮助非常大尤其是判断P值增大后系统是否接近临界振荡一眼就能看出来。4.4 实测验证画图不影响控制周期做完整套机制之后我用逻辑分析仪抓过控制ISR的实际执行间隔。抓取方式是在ISR入口翻转一个GPIO然后观察翻转信号的周期抖动。结果显示无论屏幕是否在刷新ISR周期始终稳定在1kHz抖动在微秒级别。这才是“人机界面不影响控制”的实证也是整定数据可信的前提。如果你发现刷屏后控制波形变脏先检查是不是把I2C调用直接塞进了ISR这个坑我见过太多次。5. 参数怎么存Flash掉电保存的实用做法整定过程中调好的PID参数如果只存在RAM里一掉电全没了下次开机又得从头调。所以人机界面上必须有一个“保存参数”的入口把参数固化到MCU的Flash里。这里有不少细节做不好会出现参数写坏、启动异常这类非常隐蔽的问题。5.1 参数块结构体不是简单把数据扔进Flash直接定义一个结构体然后memcpy到Flash是最朴素的做法但不推荐直接这么干。固件升级之后结构体字段可能变了旧参数被新固件读出来解释成完全不同的含义轻则参数异常重则上电就冲转。我的做法是在参数块前面加一个标志头和版本号尾部加CRC32校验#define PID_PARAM_MAGIC 0x50494431 // PID1 typedef struct { uint32_t magic; uint32_t version; float kp_speed; float ki_speed; float kd_speed; float kp_pos; float ki_pos; float kd_pos; uint16_t output_limit; uint16_t integral_limit; uint8_t reserved[8]; uint32_t crc32; } __packed pid_param_block_t;__packed防止编译器在结构体里插入填充字节保证Flash里存储的字节布局和程序里读出来的完全一致。CRC32的作用是在加载参数时校验数据完整性只要有任何一位不对立即放弃这组参数回到默认值。magic和version的作用是区分“这块Flash里到底存没存过参数”以及“存的参数是哪一版的”。这三个字段配合基本杜绝了参数错乱的情况。5.2 写入时机确认保存才写不能每次调整都写Flash的擦写寿命是有限的STM32F103的内部Flash标称擦除次数在1万次左右虽然实际上往往能扛住更多但整定过程可能连续调整几百次参数如果每次转动编码器都往Flash里写很快就把Flash磨坏了。所以保存参数必须是一个独立的、显式的动作在参数菜单里设置好所有参数后最后按“保存并退出”固件才执行一次Flash写入。我的用途上还加了“断电自动保存”的思路用一个大电容给MCU供电保持短时间检测到掉电事件后把RAM里的最新参数写入Flash。这个方案并非所有场景都适用但一些关键项目可以采用能避免忘记保存导致参数丢失。缺点是硬件成本增加而且需要在掉电检测ISR里处理Flash擦除逻辑更复杂。本期还是以“手动确认保存”为主更可控。5.3 内部Flash的写入流程与中断控制STM32内部Flash写入的关键在于写入之前必须先擦除所在扇区而且擦除过程中Flash模块会暂停CPU取指令但这个暂停时间在STM32F103上通常是毫秒级别对于1kHz控制中断来说还是可能造成“一次性卡顿”。我在实机上碰到过写参数时电机“咯噔”一声的情况原因就是擦除Flash期间控制中断被延迟太久导致PWM输出有一个短暂的空档。我的解决方式是把“保存参数”这个动作放到控制暂停状态下完成。具体流程是按下保存按键后先置位sys_suspend_flag控制ISR看到这个标志后暂停PID输出把PWM占空比强制置为0等待电机停稳。主循环执行Flash解锁、擦除指定扇区、写入参数块、上锁、读回校验。校验通过后清除suspend_flag控制ISR恢复PID计算和PWM输出。整个过程大约几十毫秒电机只是轻微停顿一下但不会出现电流冲击。这个短暂的“断电式保存”体验是值得的因为换来的是参数可靠性和Flash寿命。5.4 加载参数上电时的默认值与异常回退上电时固件读取Flash参数块先检查magic、version和CRC32全部通过才把这些参数加载到RAM。任何一个检查失败都说明Flash里的参数不可信这时加载默认PID参数同时在屏幕上显示一个“PRM ERR”警告提醒操作人员参数没有加载成功。另外还有一个细节升级固件版本后如果结构体版本号不变旧参数继续沿用如果结构体版本号变了比如新增了一个限幅字段旧参数块虽然CRC能对上但字段含义不再匹配所以版本检查要优先于CRC检查。这个顺序不能反因为结构体变化后CRC值本身就不一样了必须先靠版本区分新旧格式。6. 实测中踩过的四个坑及完整排查链路界面和存储逻辑写完后我在真实板子上调了一整天。这一天踩了好几个坑每一个都很有代表性单独拿出来讲清楚排查过程比直接给结论更有价值。6.1 坑一OLED刷新时电机声音发闷速度波形出现毛刺现象是屏幕每刷新一次电机运转的声音就跟着变一下轻微的“嗡嗡—哒—嗡嗡”节奏变化通过速度反馈波形能看到规律性的毛刺。我第一反应是I2C总线时序影响了编码器采样于是用逻辑分析仪同时抓了I2C SCL、控制ISR翻转的GPIO和电机电流反馈。抓到之后发现问题不在I2C总线本身而是我把一部分用来刷新屏幕的耗时操作放在了主循环里而主循环里还有其他任务比如编码器溢出处理、通信报文解析它们都抢CPU时间。刷新屏幕时主循环卡了几毫秒这期间如果有编码器更新事件或者通信中断到达响应就延迟了。速度环的积分项对更新延迟特别敏感一次几毫秒的延迟就会在输出上留下一个毛刺。解决方式是把所有UI操作彻底从主循环的时间关键路径上移出去同时把刷屏频率从20Hz降到了10Hz速度波形立刻干净了。这里的一个教训是控制周期能不能保持稳定不能靠“感觉”一定要用逻辑分析仪或者示波器看GPIO翻转信号数字才是唯一的依据。6.2 坑二旋转编码器快速转动时参数跳变严重现象是编码器往一个方向快速旋转参数数值本该线性增加或减少但有时候会突然跳好几个单位甚至出现回退。我一开始以为是编码器型号问题换了一个带定位槽的还是如此。用示波器同时抓编码器A相、B相信号能清楚看到在旋转过程中两路信号上叠加了很多毛刺尤其是在慢速旋转和刚启动的一瞬间机械触点抖动导致电平反复跳变。常规的“上升沿触发计数”在这里根本不够用。解决方式是三管齐下硬件上加RC低通滤波把毛刺削掉一部分软件上把编码器A、B相接在定时器的正交编码器模式引脚上让硬件定时器负责方向和计数MCU只读取定时器的计数值。正交解码模式天然会把机械抖动当成无效状态过滤掉配合定时器的硬件滤波功能计数就非常稳定了。关键是最终把编码器解析从软件中断移到硬件外设让最底层的精度问题不再消耗CPU精力。6.3 坑三保存参数时电机“咯噔”一下这个现象在第5.3节提到过完整的排查链路是这样的第一次出现时我怀疑是PWM模块在Flash操作期间异常复位于是查了PWM寄存器、看门狗配置都没发现问题。后来我把一个GPIO在保存参数期间拉高用示波器观察GPIO电平和PWM输出波形的关系才确认是Flash擦除期间控制ISR的响应时间被拖长了。为什么会被拖长STM32内部Flash擦除时Flash控制器会阻塞CPU对Flash的访问而控制ISR的代码本身也在Flash里执行虽然中断可以继续响应但取指会被卡住ISR的实际执行时间被拉长。解决方式不再重复核心就是“保存时先暂停控制输出”从机制上避免短时失控。这里想多说一句遇到控制异常时不要急着怀疑芯片坏或者编译器出问题先用示波器把“什么时候发生的”和“当时其他信号什么样”对应起来90%的嵌入式问题都能靠这个办法定位。6.4 坑四参数保存后重启读出来的P值和调好的不一致现象是参数明明保存成功了屏幕上显示“SAVE OK”但重新上电后屏幕显示的P值跟保存时差了一点点比如12.3变成了12.299999。排查到最后发现这是浮点数在Flash里以二进制存储、读出后显示时精度损失导致的“看起来不对”。12.3这个十进制小数在二进制浮点里本身就是无限循环小数存进去再读出来打印的时候做了四舍五入就会看到尾数抖动。这个其实不算功能故障但会让操作者怀疑参数没保存对。解决方式有两个层面显示时用%.1f格式只保留一位小数这是表面解决存储时用定点数代替浮点数比如把12.3存成123单位0.1彻底消除格式转换的歧义。我后来把PID参数全部改为定点存储内部计算时转成float算保存和显示都用int这套组合再没出现过尾数问题。7. 界面长出来后整定的工作流彻底变了人机界面上线之后最直观的感受是整定从“一次性集中干完”变成了“随时想调就调”。以前调速度环需要专门腾出半天时间打上调试器在电脑前守着串口工具现在把台架往面前一放上电屏幕亮起旋钮一转P从0开始往上加电机的响应手感就在手上。每一次调整的反馈时间从“重新烧录一次”缩短到“下个控制周期”整定的效率提升不是百分之几十而是数量级的变化。整个流程现在是这样跑的开机后屏幕显示主界面先切到手动模式开环给一个固定占空比确认电机旋转方向和编码器方向一致。然后切回速度环P值从0慢慢增加每加一档给一个阶跃目标观察屏幕上的转速跟踪曲线和右侧的误差柱。等P值接近临界振荡再逐步加I观察静差是否消除最后加D压超调。调完一组之后长按保存参数进Flash算完成一次整定。这套流程不再依赖电脑设备在什么环境就能在什么环境调。后来我把同样的交互套用到位置环和电流环上菜单结构不需要大改只换参数组和显示数据源就行。界面的框架已经沉淀为固件里的一个独立模块后续项目直接复用只需要改参数表的定义。也可以考虑一个更激进的方向让固件本身来辅助整定。屏幕上有实时误差数据MCU自己也存着最近一段时间的响应曲线那能不能在界面上增加一个“自动整定”入口让固件自动做阶跃激励、采集响应、估算出合适的PID初值我在这个方向上做过一些验证通过简单的临界比例度法可以估算出P的初值结果拿来当手动精调的起点很好用。这个内容已经超出本期的范围如果后面有空我打算单独开一期讲讲“固件里的自动整定怎么和这个界面配合”算是给第8期留个引子。每个项目都应该尽早长出人机界面不用等控制算法完美了再补。界面本身不复杂但它是整定流程的基石也是你在现场调试时唯一能信任的“眼睛”。把这块做好再动手调参你会回来感谢第7期的自己。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门