文章目录
前言
Simulink广泛应用于工业建模与控制系统设计,对于在工业控制和仿真领域的专业人员使用方便,但是不进行科研仿真的实际使用客户使用则显的特别难以操作和使用,而一般的使用者经常要求具有良好的交互体验和图像可视化。利用共享内存能够使得不同应用软件之间交互数据,将Simulink仿真信号传递给第三方可视化软件,例如利用C#编写的具有三维动画效果,以及二维曲线等可视化良好的软件。同时,也可以通过在第三方软件中添加一些可视化良好的控件,利用共享内存传递给MATLAB/Simulink,从而达到控制MATLAB/Simulink的运行。
本篇博文给设计类似于Prescan软件的动画效果的工程人员给出了一些参考意义,首要条件时Prescan软件基于Simulink平台,而且Prescan在Simulink平台下用于车辆场景建模与辅助驾驶策略验证,该软件能够将Simulink的仿真信号传递给第三方软件,并利用三维模型引擎得到动画显示效果,极大的丰富了软件的可视化。
本博文中用到的所有程序和Simulink模型可在文末附件中下载。
Simulink如何调用C函数
Simulink使用共享内存的方式是调用能操作共享内存的C、C++函数;我们可以从Simulink如何调用C函数入手来实现这一过程,让 Simulink调用C函数主要有以下三种方式:
1.最简单的方法使用Simulink模块库中提供的 C Call 模块,该模块在MATLAB 的 2020a版本中可使用(估计以后的版本中均支持该模块),但是该种方式不能保护源代码,如果模型发布则源代码也必须附带。
2.使用stateflow调用C函数,该种方式非常适合状态机建模而且有需求调用C函数的情况下,在使用共享内存时,能够很好的解决共享内存调用时的写内存和读内存的执行顺序,而且可以利用stateflow,搭建出较好的调用顺序。非常推荐使用该种方式操作共享内存。
3.使用S函数,该种方式能够完全自定义共享内存函数的操作,但是该种方式需要写 C S MEX,一般不熟悉C 函数写 S 模块的使用者不太友好。
Simulink与应用程序交互实现
Simulink根据求解器设置的步长大小,每个步长执行一遍模型,因此如果利用Simulink调用C函数,在采样率为固定步长且调用C函数的模型采样时间为继承的条件下,Simulink每经过一个步长大小,遍执行一遍C函数。为表述方面,下文默认Simulink求解器为固定步长。
1、C函数操作共享内存
利用windows提供的开辟内存空间函数,调用如下函数便可开辟内存空间。该函数同样可以被MATLAB的m脚本调用。
/**************************************************************************
* Function open_shared_memory()
* Goal : Creation of a memory-mapped file to share data between 2 processes
* IN : -
* IN/OUT: -
* OUT : - Error status in integer coded as following:
* > 0 => No error occurred and the shared memory has been created
* > 1 => The process could not create the memory-mapped file object
* > 2 => The process could not return a pointer to the file view
**************************************************************************/
int open_shared_memory()
{
/* Definition of the handle to the memory-mapped file object */
HANDLE hMappingFile;
/* Create a new shared memory area if it does not exist yet */
if (sharedMemoryAddress == NULL)
{
/* Creation of the file mapping object */
hMappingFile = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BUFFER_SIZE, // maximum object size (low-order DWORD)
SHARED_MEMORY_NAME); // name of the mapping object
/* Exception handling */
if (hMappingFile == NULL)
{
/* The file mapping object could not have been created */
_tprintf(TEXT("Could not create the shared memory called: %s\n"), SHARED_MEMORY_NAME);
return 1;
}
/* Create a view of the file in the process address space */
sharedMemoryAddress = MapViewOfFile(hMappingFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);
/* Exception handling */
if (sharedMemoryAddress == NULL)
{
/* No pointer to the file view could have been returned */
_tprintf(TEXT("Could not map the shared memory called: %s\n"), SHARED_MEMORY_NAME);
/* The process no longer needs access to the file mapping object */
CloseHandle(hMappingFile);
return 2;
}
}
/* No error occurred */
return 0;
}
当开辟内存空间后,即可使用共享内存写函数将数值写入共享内存中:
void write_register_val(unsigned short address, double val1)
{
/* Access the shared memory area */
if (open_shared_memory() == 0)
{
/* Set the new value in the desired register */
double * temp = (sharedMemoryAddress + address);
*temp = val1;
}
else
{
getchar();
exit(EXIT_FAILURE);
}
}
2、Simulink与第三方应用程序数据交互框架
利用C共享内存函数(C或者C++类似),利用stateflow调用写共享内存函数。值得说明的是,由于Simulink每个步长需要执行一遍写共享内存函数,在下次运行共享内存写入函数之前,即Simulink运行下一个步长之前,需要查看是否第三方的软件应用读取到该地址上的数值。因此需要设定是否可写标志位,用于协调Simulink运行速度和第三方软件应用。Simulink的协调流程如下图所示:
首先清空前后台是否写入标志位,使得首个步长能够写入共享内存;当写入内存后,便可将写入标志位置位;然后进入下一次Simulink的调用,当第三方程序没有读取共享内存的数据时,写入标志位不变,则下次运行到该stateflow模块时会一直判断是否可写入数据,(为了避免长时间死循环等待,这里增加了粗略的超时计数器,即每检测可写状态一次,计数器自增,然后判断计数器大于某个数据便可退出死循环);当第三方应用程序读取到共享内存中的数据时,则会将写入标志位清零,这样Simulink就不会一直等待在死循环中了。
Simulink与第三方应用程序配合,便可以将每个步长下Simulink运行得到的数据写入共享内存中了。同时,第三方应用程序也会依次将共享内存中的数据读取出来了。
注:为了避免Simulink运行过快,而第三方应用程序读取速度慢从而导致拖慢Simulink的现象,因此需要第三方应用程序开辟新的线程,专门使用一个缓冲队列来接收Simulink发送过来的数据,这样第三方应用程序展示数据的速度和Simulink的仿真速度之间不协调的现象缓解,同时第三方应用程序也不太受到Simulink仿真速度的限制。
3、第三方应用程序控制Simulink
利用第三方应用程序控制Simulink主要有两种方式,分别为:
1、利用第三方应用程序调用MATLAB提供的COM接口,直接控制MATLAB执行控制命令,关于第三方应用程序调用COM接口函数的方法可参考我的另一篇博文,C#应用程序与MATLAB联合编程。
在MATLAB中控制Simulink的开始、暂停、终止、继续、步进运行,控制指令分别为:
model_name = gcs;
set_param( model_name,'SimulationCommand' ,'start'); % 启动Simulink
set_param( model_name,'SimulationCommand' ,'pause'); % 暂停 Simulink
set_param( model_name,'SimulationCommand' ,'stop'); % 停止 Simulink
set_param( model_name,'SimulationCommand' ,'continue'); % 继续 Simulink
set_param(model_name, 'SimulationCommand', 'step'); % 步进 Simulink
注:该种方式需要MATLAB不能正在运行其他M脚本,需要MATLAB处于正常状态
2、利用第三方应用程序共享内存写入运行标志控制MATLAB脚本运行,进而通过 MATLAB 脚本控制Simulink运行。该种方式能够对MATLAB最为全面的控制,推荐使用。
这里需要介绍一下MATLAB和Simulink之间的运行机制,当Simulink正在运行时,MATLAB可以在命令窗口中执行控制指令。因此我们才可以利用第一种方式对Simulink进行控制。当MATLAB执行脚本函数时,Simulink不能执行,处于等待状态,只有脚本暂停执行时,Simulink才能继续执行。
由于第三方应用程序是通过共享内存的方式对MATLAB进行控制,因此需要MATLAB一直监控共享内存的标志位状态。这就需要MATLAB一直运行M脚本函数来实现这一过程,这样就导致Simulink一直处于阻塞,不能进行仿真运行。因此,在需要执行Simulink模型时,需要M函数在监控共享内存标志位的同时,短暂的暂停一段时间,用于Simulink运行。下面采用该种方法对 MATLAB/Simulink 进行控制。
4、实现方法
前台控制后台Simulink与仿真程序的运行流程2所示。本流程为M脚本的执行流程。主要的详细部分为Simulink相互的控制部分。当运行完成前处理后,主函数将调用模型仿真。首次判断前台是否允许模型启动仿真,当前台未允许进行仿真时,后台m主程序将暂停并等待,直到前台允许仿真为止。当前台使能后台模型仿真后,后台将控制Simulink仿真运行,然后M文件将暂停,等待Simulink运行。M文件暂停结束后,再检测前台的控制命令,接收前台的控制指令。这样遍实现了M文件能够接受前台的控制指令的同时,也能让Simulink运行并接受前台的控制。
从图中可以看出,前台控制后台运行的控制指令是通过共享内存的标志位完成的。前台控制Simulink运行的指令包括:启动、步进、暂停、继续、和终止。
注意:当前台运行较快时,由于M文件在暂停的时候Simulink运行,而且Simulink运行完成后将模型运行完成标志位置位,这时M文件还在暂停状态,而且前台接收到运行完成状态后将模型运行完成标志位置位,这时M文件暂停结束,会导致M文件仍在运行的错误状态。因此需要前台在模型运行完成与指示下次模型运行的这段时间大于M文件暂停的时间间隔,避免这种情况的发生。