做运动控制器这些年,我越来越觉得一个项目最后能不能稳定交付,往往不取决于总线跑得多快、算法调得多顺,而是取决于掉电之后那几百毫秒里,系统到底把什么东西留住了。
这篇继续聊经济型EtherCAT运动控制器系列里最容易被低估的一环:数据存储。前面几篇讲主站方案、周期同步、轴控和IO逻辑,这些决定了控制器“能不能动”,而数据存储决定了控制器“断了电还记不记得自己是谁”。尤其在做24轴这类大轴数系统时,几十个伺服的参数、几十组配方、位置记录全压在存储设计上,这块没做好,前面所有实时性都白搭。
这篇文章会从存储对象梳理、介质选型、掉电保护、实时任务协调、大轴数存储布局几个角度展开,最后把我实际踩过的几个坑挑出来说透。适合正在做EtherCAT主站固件、或者准备把原型机推向量产机的工程师参考。
1. 先理清楚:经济型控制器究竟要存哪些数据
很多新手做存储,第一反应是“找个Flash把参数存起来”,结果做着做着就乱了。因为运动控制器的数据不是一块铁板,不同数据对容量、寿命、掉电可靠性、写入频率的要求完全不一样。
1.1 数据分类:配置参数、工艺配方、掉电保持值和日志
我习惯把运动控制器要存的数据分成四大类,每一类单独设计存储策略:
- 系统配置参数:包含轴数量、轴类型(脉冲/总线)、PDO映射、加减速时间、行程限位、齿轮比、软件版本号等。这类数据的特点是数量固定、结构固定、低频修改,一般只在调试或在线修改参数时写入。按轴扩展,一轴算256字节,24轴就是6KB左右。
- 工艺配方数据:位置表格、速度曲线、IO联锁逻辑、不同产品的加工参数集合。这类数据是批量下发的,可能一组配方就1KB,一个系统存几十组不稀奇。配方切换是用户日常操作,写入频率比系统参数高,但依然属于“低频大批量”写入。
- 掉电保持数据:绝对位置、生产计数、当前运行模式、报警状态、回零完成标志。这类数据的特点是值本身小,但必须在掉电瞬间安全落盘,而且要频繁更新。比如位置值每走一段就要刷新一次,甚至关机前最后一次位置不能丢。
- 报警履历和运行日志:报警代码、时间戳、当时的轴位置、温度、IO状态。这类数据是持续追加的,写入频率高,每条几百字节,还要能循环覆盖老记录。
如果你正在做的控制器把这四类数据一股脑塞进同一个存储区,后面基本必出问题。因为系统参数要低出错率,配方要批量原子切换,掉电保持数据要快,日志要耐写,四者的需求是冲突的。
提示:设计第一步不是选芯片,而是给数据分类。哪怕用一张Excel表把数据项列全,标清楚写入频率和掉电要求,后续选型也会清晰很多。
1.2 不同数据的可靠性要求差很多
同样是“丢数据”,后果完全不一样。设计时必须分清楚哪些数据丢了会出安全事故,哪些丢了只是麻烦。
先说绝对不能丢的:行程限位、齿轮比、回零方向、软限位使能这一类参数错了,机器上电一跑就可能撞机;绝对位置丢了,多轴联动产线可能直接停在未知位置,批量报废。这类数据必须双备份+CRC校验+掉电保持。
再说丢了会麻烦但能补救的:生产计数、批次号、运转时长统计。丢了不影响安全,但影响产量报表和售后判断,能保住就保住,保不住也不至于停机事故。
最后是丢了无所谓的:报警历史、调试日志。丢了顶多不知道之前发生过什么,但控制器本身还能跑。对这类数据,我会刻意降低可靠性设计成本,比如用循环覆盖、允许部分丢失,换取Flash寿命。
明白这个分级之后,再回头看整个存储方案就简单了:用EEPROM或FRAM做掉电保持小数据,用NOR Flash做系统参数和配方的批量存储,用SD卡(如果经济性允许)做日志导出和配方导入,报警日志放Flash的循环区,耐写优先。
2. 存储介质怎么选:Flash、EEPROM、铁电和SD卡的账要算明白
经济型方案最怕“什么都想要”。存储芯片选型其实是容量、寿命、速度、成本四个维度砍一刀的结果。
2.1 介质横向对比
我把市面上做主控板常见的几种介质做个表,这里直接给结论:
| 介质 | 容量范围 | 擦写寿命 | 写入速度 | 典型成本位置 | 适合场景 |
|---|---|---|---|---|---|
| SPI NOR Flash(W25Q系列) | 1Mbit~256Mbit | 1万~10万次 | 按页写,按扇区擦(4KB约几十ms) | 低 | 系统参数、配方、程序存储 |
| EEPROM(AT24Cxx) | 1Kbit~1Mbit | 约100万次 | 按字节写,快 | 很低 | 小容量掉电保持参数、标志位 |
| FRAM(FM24CL64等) | 64Kbit~1Mbit | 100万亿次 | 按字节写,不损坏 | 高 | 绝对位置等高频掉电保持值 |
| SD卡(TF卡) | GB级 | 万次级别 | 快,但受文件系统影响 | 低 | 配方导入导出、日志导出 |
经济型EtherCAT控制器的主流组合是SPI NOR Flash + 一颗小容量EEPROM:EEPROM负责掉电瞬间抢救一些小参数(位置、标志、计数),Flash负责存大块配置和配方。FRAM在成本允许时用于关键绝对位置存储,能省掉很多掉电时序上的麻烦。
2.2 为什么NAND Flash在经济型运动控制里不常用
很多人看到“Flash”就默认选NAND,觉得便宜容量大。但做运动控制器我有意避开NAND,原因有三个:
- NAND有坏块管理,需要跑FTL或MLC管理算法,对主控CPU占用和代码复杂度要求高;
- NAND读改写是页级+块级,中途掉电更容易出现位翻转,得靠额外的ECC和硬件RAID来兜底;
- 运动控制器的参数容量根本用不了几百MB,NOR Flash绰绰有余,而NOR的随机读和XIP(原地执行)能力NAND给不了。
经济型方案本身就是“用最少的物料干最多的事”,NOR Flash一片搞定程序、参数、配方,省一颗NAND控制器,也省一堆驱动代码,实际BOM成本反而更低。
2.3 容量估算:24轴系统到底要多大Flash
以常见的24轴伺服系统为例,我列一个粗略的容量清单,你照着推就行:
- 系统参数区:按4KB规划,备份双份8KB
- 轴参数表:24轴 × 256B = 6KB,加扩展余量按8KB算,双备份16KB
- 配方区:32组 × 1KB = 32KB
- 报警日志区:环形200条 × 512B = 100KB(预留循环覆盖)
- 用户程序(梯形图/脚本/编译后固件):16~32KB
加起来约170KB~180KB。考虑到扇区磨损均衡和升级备份空间,1MB(8Mbit)是安全起步,16Mbit更从容。我在实际项目里给24轴经济型控制器选的是16Mbit SPI NOR Flash + 32Kbit EEPROM,既扛得住算法升级,也留够了日志空间。
容量算完还没完,一定要算写寿命。比如报警日志每10秒写一条512B,一天8640条。如果再叠加配方频繁切换,Flash的寿命消耗就得精细控制:日志区做成环形追加,写满才擦除;参数区只在变化时写入,而不是每个周期都写。
3. 掉电保护:数据存储最容易翻车的环节
掉电保存是运动控制器存储设计的“珠穆朗玛峰”。之前有个客户的项目,伺服断电后绝对位置经常丢,机器一上电就得重新回零,折腾了两个多月。后来我过去排查,发现是整个掉电时序设计出了问题,根本不是控制器主芯片的问题。
3.1 先算掉电窗口:电容多少钱,能撑多久
掉电保护的物理基础是断电瞬间控制器还能“喘口气”,这段时间称为掉电保持窗口。经济型方案最常用的是在电源输入端加检测电路和储能电容。
一个非常粗略的估算公式:假设5V系统工作电流100mA,允许从5V掉到4.5V,用2200μF电容,持续时间为:
Δt = C × ΔV / I = 2200μF × 0.5V / 0.1A ≈ 11ms
这11ms够干什么?够往EEPROM里写几十个字节,但不够完成一次NOR Flash的4KB扇区擦除(通常需要几十到几百ms)。所以经济型方案的经典做法是:掉电瞬间只往EEPROM/FRAM里写“最小关键集”(绝对位置、运行模式、标志位、计数),大块配置参数的批量保存放到正常运行时去做。
如果你希望掉电时还能把Flash里某个扇区擦掉再写入,那储能电容就要上几千μF甚至超级电容,成本可能直接吞掉“经济型”三个字。因此我通常建议客户:掉电只保最关键的几百字节,其余数据通过状态机在低功耗主循环里尽快写完,实在写不完就放弃,靠双备份恢复。
3.2 双分区+CRC的写入流程:别让半道断电毁了全部
掉电最怕的不是“没写进去”,而是“写了一半”。Flash写入或擦除途中断电,扇区数据可能处于不确定状态,既有旧数据又有新数据,还有可能带校验错误。所以成熟方案必须做双区镜像+完成标志+两轮校验。
我用的写入流程是这样的(以系统参数区为例):
- 先把完整参数块打包成结构体,加上版本号、数据长度、CRC32;
- 写入B区(备份区),写完立刻回读校验CRC;
- 校验通过后,在B区头部写入“B区有效”标志;
- 再擦除A区,把同样的参数块写入A区,回读校验;
- 校验通过后,在A区头部写入“A区有效”标志。
启动加载的时候反过来:先读A区,如果A区CRC有效,用A区;如果A区损坏,读B区;如果两区都坏,才有理由报“参数丢失,使用出厂默认值”。这个“A坏读B”的逻辑保证了任何单次掉电最多坏一区,另一区总能兜底。
3.3 启动恢复的完整路径
实际项目里,恢复路径不是简单“读一个区”,而是一整套决策:
- 上电后先读Flash头部的启动标志;
- 如果头部标志是“升级中”或“参数写入中”,说明上次掉电正好发生在批量写入阶段,不能信任Flash参数区;
- 此时进入“保守模式”:用EEPROM里的最小关键集恢复轴位置和运行状态,其余参数加载出厂默认值,同时向上位机报“参数配置未完成”;
- 让用户在触摸屏上确认后再触发一次完整参数写入。
这套流程看起来繁琐,但能避免很多“莫名其妙”的故障。比如现场电工突然拉闸,刚好在配方切换中途,主站掉电后重新启动,如果直接加载半截配方,可能某几个轴参数是新的、某几个轴参数是旧的,机器一开就出乱子。
注意:掉电保护设计时,别忘了给电压检测电路留个“阈值可调”的口子。24V工业现场在电机急停时电压跌落非常剧烈,检测阈值定得太低可能来不及触发保存,定得太高又会频繁误触发。
4. 存储与EtherCAT实时任务共存:别让写Flash拖垮总线周期
EtherCAT运动控制器的周期一般是1ms甚至500μs,PDO收发、轴规划、IO扫描全部压在这个周期里。而一块SPI NOR Flash擦除一个4KB扇区可能要几十ms。如果直接在主循环里调用Flash写入,总线周期必然抖动,轻则丢站,重则看门狗复位。
4.1 问题根源:Flash擦写是阻塞操作
很多人觉得“Flash操作不就几行代码嘛”,问题在于SPI NOR Flash的擦除和写入命令一旦发出,中途不能简单中止,否则数据损坏。过程中主控芯片要么轮询状态寄存器,要么等中断通知,这段时间CPU是“卡死”的。对普通MCU项目无所谓,但对EtherCAT周期任务就是灾难。
我见过有工程师把Flash写入放在定时器中断里执行,结果周期任务经常被拖到2ms、3ms,伺服跟着抖,现场调试怎么都调不平。后来把存储操作挪出周期任务,抖动立刻消失了。
4.2 解决办法:脏标记+异步落盘
实际工程里最稳的模式是分层解耦:
- 周期任务层(1ms):只负责把需要保存的数据复制到RAM双缓冲,并置一个“脏标记”。这个过程只有memset级别的开销,完全不影响周期。
- 后台任务层(低优先级循环):检测到脏标记后,把RAM缓冲里的数据打包、算CRC、再写入Flash。后台任务和周期任务共享一颗CPU,调度时只要保证后台任务每次只运行一小段(比如一次只拷贝4KB级别的块),就不会长时间阻塞周期任务。
我一般用RTOS邮箱或信号量实现:周期任务只发一个“保存”命令,后台任务收到后开始执行。后台任务中间如果来了周期任务,通过任务优先级抢占,写Flash的循环被切成小步,每步只做一小段,给实时任务让路。
4.3 落盘状态机:别让后台任务一口气干完
后台任务忌惮“一条路走到黑”。一个健壮的落盘任务应该是个状态机,每个循环只推进一个状态:
- IDLE:空闲,等待脏标记;
- PACK:把RAM双缓冲的数据打包成记录格式,计算CRC;
- ERASE:擦除目标扇区(一次触发,然后等待擦除完成,期间允许被周期任务抢占);
- WRITE:按页写入数据;
- VERIFY:回读校验,决定是否置完成标志。
每跑一次循环只做其中一步,即使EtherCAT周期突然占用CPU,后台任务也只是暂停在某个状态,不会留下半截写入的垃圾数据。
另外要特别提醒:写入Flash期间一定禁止芯片进入低功耗模式。有些MCU在掉电检测后进入了低功耗,结果Flash控制器状态残留,扇区直接锁定不可擦。我踩过这个坑后,会在掉电中断里先禁掉低功耗,等关键数据写完再睡。
5. 24轴场景的存储布局:从参数表到从站EEPROM的分工
到了24轴级别,存储设计就不能“随便找块空地写”了。大轴数系统最典型的问题是:参数多、配方多、从站多,主站和从站各自该存什么,一定要想清楚。
5.1 主站Flash分区设计示例
下面是一个可以参考的Flash分区表,基于16Mbit SPI NOR Flash(2MB容量)规划:
| 起始地址 | 大小 | 内容 |
|---|---|---|
| 0x000000 | 32KB | Bootloader |
| 0x008000 | 32KB | 用户程序区(固件升级用) |
| 0x010000 | 8KB | 系统参数A区(含CRC和版本号) |
| 0x012000 | 8KB | 系统参数B区(备份) |
| 0x014000 | 12KB | 轴参数表(24轴,每轴512B) |
| 0x017000 | 32KB | 配方区(32组×1KB) |
| 0x01F000 | 128KB | 报警日志区(环形覆盖) |
| 0x03F000 | 余量 | 预留扩展区 |
分区设计有两个原则:不同数据不同扇区粒度,升级程序不能误擦参数区。Bootloader和用户程序区独立分区,固件升级后参数照常;参数区每区占整数个扇区,可以单独擦写。
轴参数表的结构我建议每个轴固定一个结构体,包含参数版本号、轴配置、PID、限位、回零参数,尾部加CRC。保存时整表写入,加载时逐轴校验,某个轴校验失败就默认该轴用出厂值并向上位机报警。24个轴的参数一次写入大概12KB,通过后台状态机分步完成。
5.2 配方切换的原子性:不许中途掉电
24轴系统的配方面积很大,一组配方就可能覆盖24轴的全部位置数据。最怕的是用户正在切换配方时突然断电,主站重新上电后不知道当前到底是旧配方还是新配方,或者更糟,一半轴是旧配方、一半轴是新配方。
我采用的机制是“三段式配方切换”:
- 保存当前正在运行的配方编号到EEPROM,并写入“配方切换中”标志;
- 才开始逐轴加载新配方,每轴加载完成做一次校验;
- 全部轴校验通过后,清除“切换中”标志,把当前配方号更新。
如果掉电发生在第2步,上电后检测到“切换中”标志,就保持停机状态并提示“上次配方加载未完成,请重新确认”,而不是直接跑起来。别看这逻辑简单,真能挡住现场各种拉闸事故。
5.3 从站EEPROM与主站Flash的分工
EtherCAT系统里还有一层存储容易被忽略:每个从站自己的EEPROM(SII)。伺服驱动器的厂家参数、PDO映射、厂商识别码都存在从站EEPROM里。
主站千万不要频繁写从站EEPROM。从站EEPROM一般是工业EEPROM,寿命虽然高,但每写一次也要耗时,而且有些伺服固件在EEPROM写入时会短暂影响实时性。合理的分工是:
- 从站EEPROM:只保存从站出厂信息和PDO映射配置,主站在系统首次配置或PDO变更时写一次,之后以读为主;
- 主站Flash:保存所有轴的应用参数(PID、限位、回零、增益等),运行时只通过PDO/CoE下发到从站RAM。
还有个细节:绝对位置保持。多数经济型伺服自带绝对值编码器和EEPROM,断电后从站自己能记住位置;但主站侧最好也保存一份最终位置值到自家EEPROM,用于开机后比对从站回读值,防止某些从站在断电期间被外力移动造成的“位置漂移”没人发现。
6. EEPROM/Flash那些坑:几个真实磨出来的教训
存储设计的坑不在教科书里,全在现场。我把这几年踩过的比较典型的坑集中说说,每个都对应一个具体的故障现场。
6.1 坑一:固定地址反复写,Flash两个月就报废
最早做样机时,我图省事,把“当前模式”这个变量放在Flash固定地址上,每次切换模式就调用一次写Flash。结果第二个月,这块Flash就写不进去了。查了半天,发现这颗NOR Flash的寿命标称1万次擦写,而现场一天要切换几十次模式,加上日志叠加,很快就到了寿命极限。
教训就是前面反复强调的:固定地址频繁写,是Flash杀手。后来我把“当前模式”这类高频小数据挪到EEPROM,只有掉电保底才写Flash,寿命问题才消失。
6.2 坑二:掉电瞬间的“假成功”
有个项目,启动时参数校验总是偶发失败,但重新上电有时又好了。排查到最后发现是掉电瞬间的“假写入成功”:写Flash命令已经发出,掉电导致扇区数据损坏,但扇区头部的成功标志残留着旧值,启动加载时误判为写入成功,导致读出来的是坏数据。
解决手段就是之前讲的双区镜像+完成标志+启动双向校验三件套。只做CRC不够,CRC可能凑巧通过一半脏数据;只做备份不够,因为备份区可能好久没更新过。必须保证任何时刻至少有一个完整有效区。
6.3 坑三:擦除命令被低功耗模式打断
掉电检测触发后,我的代码想尽快保存数据,结果保存前芯片进入了低功耗模式。早先没注意,后来发现Flash某个扇区处于“半擦除”状态,怎么擦都报错。检查芯片手册才知道,Flash擦除期间进入低功耗会让控制器异常,扇区状态机错乱。
改进方法是:掉电中断里第一件事是禁止低功耗模式,等Flash操作完成或超时后再允许进入;如果Flash确实不可用,就只写EEPROM,确保最小关键集能保住。
6.4 坑四:日志频繁追加把Flash刷爆
设计报警日志时,我最初想着简单点:每产生一条日志就写一个扇区。结果一条报警日志512字节,一个4KB扇区写8条就要擦除一次。现场设备正常运行一天能产生上千条日志,Flash寿命根本撑不住一年。
后来改成环形页追加模式:日志按页写,一页写满再写下一页,整个环形区写满才擦最旧的一页。这样日志存储次数提升了一两个数量级。再后来,日志量更大的系统我直接改为可选SD卡存储,Flash里只保留最近100条紧急报警,这样既保住了重要记录,也不消耗主Flash寿命。
6.5 坑五:只备份不轮换,备用区也变成“老数据”
双区备份如果用得不当,也会有副作用。有个系统我配了A/B两个参数区,但A区永远优先,B区只有在A彻底坏了才被读取。跑了半年后,B区里的参数还是半年前的旧版本,A区某天掉电损坏后,系统加载B区老参数,机器出现一批次品。
正确的做法是A/B区定期轮换:每次正常保存参数时,轮流选A或B作为主写区,这样两个区都有较新的数据。同时启动时做“交叉校验”,如果A新B旧,就把B区更新一次;如果两区版本差距过大,直接报提醒。
6.6 坑六:主站频繁写从站EEPROM,伺服烧掉配置区
最后一个坑在EtherCAT项目里很典型:开发初期,我为了方便,每次启动都强制往从站EEPROM写PDO映射,结果反复重启几十次之后,某个从站的EEPROM内容开始出现随机错乱,伺服直接报配置错误。
后来改成“先读后写”:启动时先读从站EEPROM的PDO配置区,和主站期望值比对,一致就不写入,只有配置变更时才触发一次完整写入。这样既保证了配置精确,也把从站EEPROM的擦写次数降到了最低。这条经验在批量项目里尤其重要,因为产线上电次数非常频繁。
数据存储这块,真正要在量产机上跑,靠的是把“掉电时序、双区备份、磨损均衡、实时抢占、从站分工”这几件事都想透了。每次看到有人问“为什么我的控制器重启就丢参数”,我脑子里浮现的往往是某个具体环节没兜住。把上面这些细节一一落实,经济型控制器的存储也就稳了大半。