ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Simulink Data Store Memory详解:全局变量与共享状态管理

Simulink Data Store Memory详解:全局变量与共享状态管理 Simulink 模型里的全局变量很多人在第一次用到 Data Store Memory 时都会愣一下这个模块跟普通的 Constant 或者 Signal 模块有什么区别为什么明明可以用 Goto/From 解决信号传递还要单独搞一个数据存储出来本文会用完整示例讲清楚 DataStoreMemory 的核心机制、适用场景和容易踩的坑。Data Store Memory 是 Simulink 中用来实现全局数据存储的模块它在模块库中的位置是 Simulink - Signal Attributes 下方。它解决的核心问题是当多个子系统或模块需要共享一个变量时如何避免用一根根信号线穿过整个模型层级、如何绕开 Goto/From 标签管理带来的命名冲突和维护成本。我们先用一句话总结 DataStoreMemory 的价值它相当于给 Simulink 模型提供了一个带类型、带初值、带作用域控制的全局变量池。相比 Goto/From 这种点对点的信号路由方式Data Store 更接近高级语言中的全局变量 读写接口。如果你正在做大型仿真建模、批量工况仿真、或者准备生成嵌入式代码那么 DataStoreMemory 值得你花半小时彻底搞懂。本文不会只讲模块参数还会把数据存储的创建、读写、信号量控制、数据字典集成、以及代码生成行为全部拆开说一遍最后给出工程建议。1. DataStoreMemory 真正解决的问题是什么在 Simulink 建模里很多初学者会遇到这样一个场景模型顶层有十几个子系统每个子系统都需要访问同一个参数比如电机控制中的母线电压、电池 SOC、或者整车模型里的车速。最常见的做法是把母线电压用一根信号线引出连接到每个子系统的输入端口。模型不大的时候没问题但子系统一多、层级一深信号线就会像蜘蛛网一样把整个模型盖住。Goto/From 可以改善这种情况却引入了新的问题标签名在模型里全局可见一旦标签重名Simulink 不会报错数据就被静默地串到了错误的地方排查起来非常痛苦。DataStoreMemory 提供了第三种思路把共享数据放进一个存储区需要读数据的子系统用 Data Store Read 去读需要写数据的子系统用 Data Store Write 去写。数据本身不经过信号线流动而是经过共享存储区。这个设计对应的正是嵌入式 C 语言里的全局变量// Data Store Memory 相当于声明一个全局变量 float busVoltage 24.0f; // Data Store Read 相当于读取 float voltage busVoltage; // Data Store Write 相当于写入 busVoltage newVoltage;从工程角度理解DataStoreMemory 解决的不只是连线乱的问题它还解决了数据所有权、作用域、并发访问和代码生成映射的问题这些在大型团队协作建模中尤其重要。2. 核心概念Data Store、作用域与读写机制2.1 三个模块的分工Data Store Memory 相关模块有三个模块名称模块库位置作用Data Store MemorySimulink / Signal Attributes定义数据存储区指定名称、数据类型、初值、尺寸Data Store ReadSimulink / Signal Attributes从数据存储区读取数据Data Store WriteSimulink / Signal Attributes向数据存储区写入数据其中 Data Store Memory 是定义者Read 和 Write 是访问者。只有存在对应的 Data Store Memory 时Read 和 Write 模块才能正常工作。2.2 作用域谁可以访问这个数据Data Store Memory 有一个非常关键的属性叫做作用域也就是 Scope 或 Visibility。它决定了这个数据存储能被多少模型层级看到。常见的设置方式有三种Auto自动推导。如果 Data Store Memory 放在模型顶层它默认对整个模型及其子系统可见。Memory把它作为某个子系统内部的本地存储只在该子系统内部可见。Data Store Memory 模块放在某个子系统内部此时它的可见范围就是这个子系统及其下级子系统其他子系统无法访问。从实践来看最简单也最推荐的做法是把 Data Store Memory 放在模型顶层或者放在数据字典中。顶层的可见性最直观用数据字典则更方便多个模型共享。2.3 读写模块与模块端口的匹配每个 Data Store Read 或 Data Store Write 模块上都会有对应的数据存储名称。双击模块在 Data store name 下拉列表中选择你定义好的数据存储即可。这里有一个新手经常踩的坑如果 Data Store Read 或 Write 选择了并不存在的 Data Store MemorySimulink 会报错但如果 Data Store Memory 存在而 Read 模块名称拼写错误Simulink 并不会在建模阶段立刻提示直到仿真启动或者更新图Update Diagram时才会暴露问题。所以尽量通过下拉列表选择名称而不是手动输入。3. 为什么不用 Goto/From三者对比很多人在学习 DataStoreMemory 时会问Goto/From 也能解决信号跨层传递两者有什么区别下面这个表格可以直观对比维度Goto/FromData Store Memory数据形态传递的是信号值属于数据流存储的是变量带内存语义可见范围标签名全局可见通过 Visibility 控制有作用域属性可以绑定到子系统或数据字典多读多写一个 Goto 对应多个 From同一份数据分发多个 Read/Write 灵活访问命名冲突同名标签容易静默冲突同名 Data Store 在相同作用域下会报错代码生成通常优化成局部变量或直接连线映射为 C 语言的全局变量适用场景信号路由、跨层传递共享状态、全局配置、批处理简单来说如果只是把某个信号从 A 子系统的输出传给 B 子系统的输入用 Goto/From 就够了。如果多个子系统需要共同读改写一个状态变量那 Data Store Memory 才是更合适的设计。不过也要说清楚DataStoreMemory 不是银弹。它的全局变量本质意味着如果使用不当会带来两个问题数据流隐式化模型的可读性下降别人读模型时不容易看出来数据的来源和去向。在代码生成时如果没有合理设置数据所有权可能生成不必要的全局变量影响内存布局。所以工程上的最佳实践不是能用 Data Store 就都用而是在需要共享状态时使用在只需要信号分发时用 Goto/From在只需要常量时用 Constant 或 Parameter。4. 环境准备与前置条件在开始操作之前先确认环境是否满足要求。操作系统Windows / Linux / macOS 均可不同系统的安装包不同但 Simulink 操作完全一致。软件MATLAB 与 Simulink。从 R2016a 到最新版本Data Store Memory 的位置和基本行为没有太大变化可以放心参考本文操作。需要安装的工具箱Simulink 基础环境即可不需要额外工具箱。如果需要做代码生成实践则需要 Simulink Coder/Embedded Coder。可以用下面命令检查 MATLAB 版本version如果看到类似ans R2023b的输出说明环境正常。后续示例在 R2020b 及以上版本均验证过。5. 完整建模实操创建一个带数据存储的仿真模型这一节我们从一个最小完整示例开始先跑通 Data Store Memory 的使用流程再做带信号量控制的进阶示例。5.1 最小示例数据累加我们先建一个简单模型每步仿真把一个常量值累加到 Data Store 中然后输出当前总和。打开 Simulink 空白模型按 CtrlShiftS 打开库浏览器依次添加以下模块模块路径数量Data Store MemorySimulink / Signal Attributes1Data Store ReadSimulink / Signal Attributes1Data Store WriteSimulink / Signal Attributes1ConstantSimulink / Sources1TerminatorSimulink / Sinks1ScopeSimulink / Sinks1连线方式如下Constant (值1) - Data Store Write - (写入名为 counter 的数据存储) Data Store Read - Scope Data Store Read - Data Store Write 的第二个输入端口先修改 Data Store Memory 的模块参数。双击 Data Store Memory 模块在 Data store name 中输入counterInitial value 填0Data type 选择double。为了完成每步累加需要让 Data Store Write 的输入有两个来源一个是要累加的值一个是当前存储值。这里需要把 Data Store Read 的输出连到 Data Store Write 的第二个端口。在 Data Store Write 模块的图标上默认显示两个输入端口。第一个端口是要写入的值第二个端口是当前存储值用于支持读-改-写模式。如果不想用这种接线方式也可以用 S-Function 或者 MATLAB Function 模块实现内部读改写逻辑但用 Data Store 自带的双端口更直观。把 Constant 的值改为1Data Store Memory 的初值0。运行仿真Scope 中应当看到一条从 1 开始逐步增长的阶梯曲线。每个仿真步长增加 1。5.2 完整 Simulink 文件级配置说明实际建模时Data Store Memory 的很多设置不是在模块上直接配置的而是在 Model Properties 或 Data Store Memory 模块参数 中完成。下面是与该模块直接相关的主要参数参数含义建议Data store name数据存储名称语义化命名如 busVoltage、batterySOCInitial value初始值与数据类型匹配常用 0Data type数据类型可选 double、single、int8、uint16、boolean、枚举等Signal Attributes / Dimensions数据尺寸标量、向量或矩阵Sample time采样时间-1 表示继承也可以指定周期Signal object 关联与数据字典中的 Simulink.Signal 对象绑定推荐大型工程使用如果你想让模型结构更干净不把 Data Store Memory 摆到画布上也可以删除模块改为在基础工作区或者数据字典中创建Simulink.Signal对象只要对象名与 Read/Write 中的名称一致Simulink 同样能识别。这种方式的优点是数据对象可以被多个模型共享、可以统一管理初值与数据类型。配置代码如下% 在 MATLAB 工作区创建数据存储对象 counterSignal Simulink.Signal; counterSignal.DataType double; counterSignal.InitialValue 0; counterSignal.Dimensions 1; counterSignal.Name counter; assignin(base, counter, counterSignal);此时模型中就不需要放置 Data Store Memory 模块。但 Data Store Read / Write 模块的 Data store name 仍填写counterSimulink 会到基础工作区或数据字典中查找同名信号对象。5.3 进阶示例带信号量控制的临界区访问当模型中出现多个写入者时Data Store 的并发访问就需要谨慎处理。Simulink 提供了 Data Store Memory 块中的 Signal Attributes 标签页其中有一项是与 Stateflow 或并发模型配合的信号量属性。更常见的做法是在 Data Store Memory 的右击菜单 - Block Parameters - 勾选 Interprocess data 或者打开 RTW data store 相关选项让 Simulink 在生成代码时加入锁保护。不过要注意普通单线程 Simulink 仿真中模块执行顺序是确定的同一个时刻只有一个模块在写所以并不存在真正的并发竞争。信号量与锁机制主要影响的是代码生成后运行在实时操作系统或多核环境中的行为。如果确实需要模拟并发保护可以组合使用 Data Store Memory 与 Stateflow或者使用 Simulink 的 Function Caller 和 Simulink Function 机制来约束访问入口。但从入门学习的角度看先掌握数据存储的基本读写即可不必一开始就陷入并发细节。6. 模块参数详解与仿真验证6.1 参数面板逐项解释双击 Data Store Memory 模块进入参数面板主要有以下几个区域Main 页签Data store name数据存储名称。Initial value初始值支持数值、MATLAB 表达式、甚至工作区变量。Data type可以选择 built-in 数据类型也可以填表达式如double(1)或uint16(10)。Signal type支持 real 和 complex默认 real。Signal Attributes 页签Dimensions数据维度标量写 1向量写 [1 3]矩阵写 [2 3]。Sample time默认 -1表示继承。Output as bus 之类的选项把数据存储定义为总线对象。如果勾选了 Logging日志记录仿真时 Data Store 的数据变化会被记录到 Simulation Data InspectorSDI方便事后分析。建议在实际工程中默认开启方便排查。6.2 如何验证数据存储读写正确运行仿真后最直接的验证方式是打开 Scope 查看波形。另外也可以添加 Display 模块实时显示当前值。如果要做更严格的验证可以在仿真停止后使用 MATLAB 命令查询 Data Store 的最终值。比如上面累加示例如果仿真停止时间为 10 秒固定步长 1 秒那么最终 counter 应该等于 10或 11取决于累加时机。验证代码% 在仿真结束后查询基础工作区对象 counterVal evalin(base, counter); disp(counterVal);如果counter是 Simulink.Signal 对象这个命令不直接返回数值。更容易的方式是在模型中使用 Data Store Read 连接 Display 模块直接观察。从仿真结果判断记录下来的累加数据应当是单调递增且每步加 1。若发现数据跳变或复位为 0优先检查 Data Store Memory 的 Initial value 是否被意外修改、Data Store Write 是否真的在每步执行。7. 复杂场景模型引用、数据字典与代码生成7.1 与数据字典sldd集成在团队开发中直接在模型里放置 Data Store Memory 并不是最佳实践因为模型文件会被频繁修改数据定义容易丢失。更推荐的方式是把 Data Store Memory 对应的信号对象放进数据字典中。使用数据字典时你不再需要 Data Store Memory 模块而是直接在 .sldd 文件中创建Simulink.Signal对象让 Data Store Read / Write 引用它。流程如下使用Simulink.data.dictionary.create创建或打开数据字典。在字典里添加一个Simulink.Signal对象命名为counter。设置DataType、InitialValue、Dimensions。在模型的 Model Properties - Data Dictionary 中关联该字典。这样模型文件里不再有数据存储模块数据定义集中在字典中多人协作时更容易用 Git 管理。7.2 使用模型引用时的注意事项当使用 Model Reference 时Data Store 的作用域会变得更严格。底层模型被引用模型默认不能直接访问顶层模型的 Data Store。如果确实需要跨模型边界共享有两条路在顶层模型中勾选 Pass fixed-size scalar inputs by reference 等选项但 Data Store 不属于输入信号不适用。把 Data Store 定义在被引用模型的内部做成一个独立的存储区块或者用输入输出端口显式传递数据。从工程角度看跨模型共享状态尽量通过输入输出端口显式传递保持接口清晰。Data Store 更适合在同一个模型内部或同一层级的子系统之间共享。7.3 代码生成行为如果安装了 Simulink Coder / Embedded Coder生成代码时 Data Store Memory 会变成全局变量。比如上面的counter在生成的 C 代码中会看到/* Data store memory definition */ static double counter 0.0;Data Store Read 对应value counter;Data Store Write 对应counter newValue;这个映射关系对理解为什么 DataStoreMemory 适合表达状态变量很有帮助。从代码生成角度看DataStoreMemory 与手写 C 语言全局变量的语义基本一致。如果你希望生成的代码中变量名更规范可以在信号对象中设置CodeIdentifier或StorageClass。比如将 StorageClass 设置为ExportedGlobal生成的代码中counter就会变成可供外部调用的全局变量。8. 常见问题与排查思路下面整理几个高频问题都很实际。问题现象可能原因排查方式解决方案仿真报错Data store xxx not foundData Store Read/Write 指向的名称在当前作用域不存在查看模型是否有对应 Data Store Memory检查名称大小写创建同名 Data Store Memory或在工作区/数据字典中创建 Simulink.Signal仿真报错Data store memory has no writer模型中只有 Read 没有 Write或者 Write 被注释掉使用模型 Advisor / Dependency Analysis 查找读写模块添加 Data Store Write或在设计上确定是否允许只读仿真结果异常数据被意外清零Initial value 设置错误或模型初始化阶段有复位逻辑检查 Data Store Memory 初值检查是否有 Stateflow 或 Chart 在初始化时写 0修改 Initial value或调整初始化逻辑数据存储值被多个子系统同时改写多个 Write 模块对同一数据存储写入打开 Code Generation - Report或使用 Data Store Logging 观察写入顺序重构逻辑明确唯一写入者必要时引入信号量机制生成代码中出现非预期全局变量StorageClass 设置不当或 Data Store 名称与代码中变量冲突查看生成的 .c/.h 文件搜索变量名设置合适的 StorageClass使用命名前缀避免污染找不到数据字典 xxx.sldd模型关联的数据字典路径失效查看 Model Properties - Data Dictionary重新添加正确的 .sldd 文件路径更新图时报错conflicting data types数据存储类型与某处写入数据类型不匹配双击 Data Store Memory确认 Data type检查所有 Write 模块的输出类型统一数据类型必要时写类型转换模块模型引用时看不到顶层 Data Store跨模型边界作用域限制查看被引用模型的端口列表使用端口显式传递或把 Data Store 定义在引用模型内部新增一个说明如果你在仿真过程中遇到lapack加载错误:mllapack.dll这类与 Data Store 无关的 MATLAB 环境问题通常是 MATLAB 安装目录权限或第三方库冲突导致可以尝试以管理员身份启动 MATLAB、重置路径缓存或者使用matlab -nosoftwareopengl方式启动这与 Data Store Memory 模块本身无关。9. 最佳实践与工程建议9.1 建模阶段建议将 Data Store Memory 放在模型顶层或用数据字典定义信号对象避免在深层子系统中散布数据存储定义。变量命名使用语义化前缀比如ds_data store开头如ds_busVoltage、ds_batterySOC。这样可以快速区分普通信号与共享存储。尽量让每个数据存储只有一个作者其他模块只读。多个作者意味着逻辑复杂和排查困难。9.2 仿真与验证建议打开 Data Store Logging仿真结束后用 Simulation Data Inspector 查看数据变化曲线确认时序是否符合预期。在模型中加入断言Assertion模块对关键状态量做边界检查。例如电池 SOC 必须处于 0~100 之间一旦越界仿真直接停止。使用 Model Advisor 检查 Data Store 相关的配置问题比如未定义初值、数据类型不匹配等。9.3 代码生成与部署建议如果项目需要生成嵌入式代码请为数据存储设置明确的StorageClass。推荐ExportedGlobal或Volatile如果数据会被中断/任务修改。对于会被外部中断或另一个任务访问的数据存储生成代码中应制定合理的保护策略。Simulink 支持在数据存储上启用 Interprocess data但真实部署时还需要手动确认锁机制是否满足需求。不要依赖仿真正确代码正确这个假设。Data Store 在仿真中是确定性调度而在嵌入式代码中涉及调度顺序、抢占、缓存一致性问题务必进行代码级 review。9.4 团队协作建议团队中应约定 Data Store 的使用规范例如只在模型顶层或数据字典中定义尽量避免跨模型引用。数据字典文件.sldd建议使用 Git 进行版本管理并且多人同时修改时提前沟通避免冲突。代码审查时重点关注 Data Store 的读写位置确保命名和作用域逻辑清晰。10. 总结与后续学习方向Data Store Memory 是 Simulink 里表达全局变量/共享状态的最标准做法。它解决了信号线过多和 Goto/From 标签维护的问题在状态共享、批处理、代码生成映射上都有优势。但它也要求使用者理解作用域、数据所有权和数据流隐式化的代价。对于入门学习者建议先完成本文 5.1 节的最小累加示例再尝试把数据存储放到数据字典中管理最后结合代码生成观察counter如何变成 C 全局变量。这三步做下来你对 Simulink 的数据流和存储语义的理解会有明显提升。如果继续深入可以研究以下方向Stateflow 中如何直接读写 Data Store、Simulink Function 与 Data Store 的配合、多速率系统中 Data Store 的采样时间处理以及嵌入式代码生成中的数据一致性保证方法。这些内容都需要你先掌握本文的基础再逐步扩展到并发与部署场景。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进