文章目录
Qt Creator 调试 Simulink S-Function
1. 应用场景
在Simulink中用S-Function编程搭建模块时,或者在Simulink测试一些Legacy的代码时,只能看到模块的输入输出,但看不到中间变量,或者简单地说不能Debug。
虽然很多时候可以用ssPrintf打印一些信息,但是如果编写的代码要放到其它平台运行时,或者这些代码本来就是从其它平台移植过来的时候,这些插入的代码就会很大程度上破坏原来代码的完整性和可移植性。
而且对周期性执行的代码来说,用打印的方式来Debug也不是一个好的选择,需要插入的代码也可能会很多。这时候如果能直接像C/C++一样Debug,那就很爽了!只可惜MATLAB本身并不支持Debug用C/C++编写的s-function.
在官方的Help中隐约有提到用VS来调试MEX代码,但是说得非常简单,而且是基于单个文件的,而不是在Simulink中。
后来在MATLAB论坛中,在某个人提问如何用VS调试MEX的时候,有个Answer提到可以用Qt Creator,比用VS方便得多。
相比于VS,Qt的优点很明显,一方面是跨平台的可移植性,另一方面是当然是开源。
但是搜遍全网,也没有一篇介绍了这种用Qt在Simulink上下文中调试s-function的文章。自己摸索了半天之后终于把这个链条打通了!
通过Qt+Simulink这一系列的工具链可以实现很多功能。于我而言,最大的便利就是在进行模块化测试与调试时可以非常方便地将输入输出以图形化方式显示出来,而不用自己写UI和显示部分的代码。而且还能调试!
而且这种方式可以很好地约束作者以模块化的方式来实现功能,尽量地去耦合,因为只有这样才能最方便地将代码在Simulink中集成。
下面就详细记录一下这个过程。
2. 实现过程
以下以简单的一阶低通滤波来演示整个过程
2.1 创建simulink模型
2.1.1 创建s-function模块
新建模型,并拖入s-function builder模块
双击模块,将S-Function Name 改成 “FirstLPF”并回车,使其生效。
将Number of PWorks置1,以使用动态内存分配
为了便于将代码移植到MCU,将输入输出数据类型都改成int32类型。
包含头文件#include <stdlib.h>
以使用内存分配函数
在以下不同函数内输入相应的代码:
void FirstLPF_Start_wrapper(void **pW)
:
//为记录$k-1$时刻的值分配空间:
int32_T* pPrevValue = malloc(sizeof(int32_T));
*pPrevValue = 0;
pW[0] = pPrevValue;
void FirstLPF_Outputs_wrapper(const int32_T *u0,int32_T *y0,void **pW)
:
int32_T* pPrevValue = pW[0];
*y0 = (*pPrevValue * 3 + *u0) >> 2;
*pPrevValue = *y0;
void FirstLPF_Terminate_wrapper(void **pW)
:
free(pW[0]);
整体文件内容如下:
/* Includes_BEGIN */
#include <math.h>
#include <stdlib.h>
/* Includes_END */
/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */
void FirstLPF_Start_wrapper(void **pW)
{
/* Start_BEGIN */
/*
* Custom Start code goes here.
*/
int32_T* pPrevValue = malloc(sizeof(int32_T));
*pPrevValue = 0;
pW[0] = pPrevValue;
/* Start_END */
}
void FirstLPF_Outputs_wrapper(const int32_T *u0,
int32_T *y0,
void **pW)
{
/* Output_BEGIN */
/* This sample sets the output equal to the input
y0[0] = u0[0];
For complex signals use: y0[0].re = u0[0].re;
y0[0].im = u0[0].im;
y1[0].re = u1[0].re;
y1[0].im = u1[0].im;
*/
int32_T* pPrevValue = pW[0];
*y0 = (*pPrevValue * 3 + *u0) >> 2;
*pPrevValue = *y0;
/* Output_END */
}
void FirstLPF_Terminate_wrapper(void **pW)
{
/* Terminate_BEGIN */
/*
* Custom Terminate code goes here.
*/
free(pW[0]);
/* Terminate_END */
}
如果模块需要配置的参数,可以在下部窗口中添加。
如果需要调用外部函数,只需要在窗口中设置好文件路径,头文件路径以及文件名,即可以此处代码中调用。路径可以用相对路径。
完成以上步骤后,点击右上角的“Build”下面的下拉箭头,勾选择“Create a debuggable MEX-file",然后Build。Build成功之后,即已生成相应的MEX文件。
2.1.2 完善模型
模块及模型对应的参数设置如下:
仿真结果:
2.2 联合调试
- 在Qt Creator中直接打开刚才S-Function Builder中生成的两个C文件,即不需要创建工程,也不需要编译,只需要打开这两个文件即可。然后在适当的地方设置断点:
- 确保包含这两C个文件的路径包含在MATLAB的搜索路径中
- 在Qt Creator中依次进入Debug/Start Debugging/Attach to Running Application
- 在进程列表中选择MATLAB主进程。确保Kit中选择正确的编译器和调试器配置。然后点击Attach to Process
在笔者的设备中,MATLAB的编译器被配置为与Qt同一个编译器MINGW,具体过程网上有很多说明,只要找到Qt所使用的编译器路径,然后设置对应的环境变量,最后在MATLAB中运行一个setenv命令即可。
- 成功后Qt会显示汇编窗口,指针停在某一行,这时MATLAB程序是被Hold住的,要点击运行才能回到MATLAB中继续操作
- 回到Simulink,运行模型,这时窗口会自动回到Qt Creator,并停在所设的有效断点处。这时即可对程序进行单步,继续运行,查看变量等调试操作:
至此即完成了Qt Creator与Simulink的联合调试。