2837xd 代码生成——补充(2)

5 基于模型的代码生成基本流程(MDB)

首先搭建如下的Simulink模型:

在这里插入图片描述

因为代码生成一般只支持离散定步长,所以解析器需要进行相应的设置。其基本设置过程都与之前所述的设置相同。生成代码后,点击C CODE 可以快速定位生成的代码语句:

在这里插入图片描述

再就是在Report中也会有交互式探针,但是我的有问题无法显示,现在还找不到原因。

在这里插入图片描述

5.1 编译流程

  编译流程如下:首先是用户搭建Simulink模型,模型经过编译之后会生成**.rtw文件。.TLC**(Target Language Compiler)文件会调用**.rtw中的资源进而生成.c.h**文件。

.rtw文件中主要写入了资源的名称与大小和数值,类似于.h文件,进行一个仓库的声明。告诉程序这里面存在什么东西,能够使用哪些。

.TLC可以很好的解析**.rtw**中的内容,进而生成合适的代码。

代码生成类型:

在这里插入图片描述

5.1.1 TLC语言

1)基本语法与数据类型

%<>是提取某个变量的值

%assign str="today"            %%定义字符串
%assign tr =TLC_TRUE
%assign tr2 =TLC_FALSE
%warning Hello Word %<str> or %<tr>		%%输出语句
%error Hello Word %<str>		%%错误提示语句
%trace Hello Word %<str>		%%必须使用 tlc xxx.TLC -v运行(调试使用)

%if ISEQUAL(tr,1)                       %%if语句
	%warning Hello Word %<str>
%else
	%warning good %<str>
%endif

%assign data=[1,2,3,4]                  %%数组,数据必须用逗号分隔(不能用空格分隔)

%switch TYPE(data)						%%switch语句
%case "Number"
%warning data is Number
%break
%case "String"
%warning data is String
%break
%case "Vector"
%warning data is Vector
%break
%case "Matrix"
%warning data is Matrix
%break
%case "Real"
%warning data is Real
%break
%case "Real32"
%warning data is Real32                             %%Real为浮点类型,类型还有很多
%break
%endswitch


2)循环语句

%assign data=[1,2,3,4] 
%foreach idx=5								%%循环语句
    %if ISEQUAL(idx,3)                       %%if语句
		%continue
	%endif
        
    %warning data[%<idx>] = %<data[idx]>
%endforeach
   
        
%%左式中1处为循环判断位,若为1则执行一次所有循环(iden2=3也执行)
%%若为零则执行body之间的语句(执行次数为iden1),iden2=3不执行
%for iden1 = 5,1,iden2=3                       
%warning start
%body
%warning data[%<iden1>] = %<data[iden1]>    
%endbody
%warning over
%endfor
    

3)矩阵的操作

%assign mat=[["A","B"],["A","B"],[6U,"B"]]
%foreach idx=size(mat,0)                       %%求有多少个[],
    %foreach idx2=size(mat,1)					%%求每个[]中有多少元素
    	%warning mat[%<idx>][%<idx2>] = %<data[idx][idx2]>    
	%endforeach    
%endforeach

4)数据流控制(写入文件),这样就写入了一个.c文件。可以知道代码生成的时候基本上都是这样自动化脚本进行写入某些变量(框架已经固定)。

%assign str="today"  
%assign func_name="main"   
%assign return_name="void"    
%assign arg_name="void"  
    
%openfile buf ="mytest.c"
#include "stdio.h"
%<return_name> %<func_name>(%<arg_name>)
{
    printf("Hello World");
    printf("%<str>");
}
%closefile buf

5)record(类似于结构体)

%%create
%createrecord NEW_RECORD{Name "author";Age 25}
%warning %<NEW_RECORD.Name>
 
%%add  
%addrecord NEW_RECORD School CJ          %%追加数据
%warning %<NEW_RECORD.School>    
    
%%merge 
mergerecord NEW_RECORD1 NEW_RECORD1       %%将两个record合并到NEW_RECORD1中
    
%%copy
%createrecord NEW_RECORD3{ }
%copyrecord NEW_RECORD3 NEW_RECORD2        %%将NEW_RECORD2的值拷贝到NEW_RECORD3中
    
%%delete
%undef  NEW_RECORD1                        %%删除
    
%with NEW_RECORD1			 %%之后的语句都只限定在NEW_RECORD1中读写
%undef School                %%这样就能删除内部成员
%endwith				
    
%with NEW_RECORD1.SUB			 %%之后的语句都只限定在NEW_RECORD1中读写(若在NEW_RECORD1中有SUB子结构体)
%undef School                	 %%这样就能删除SUB内部成员
%endwith		

6)文件流数据追加

%openfile buf = "text.txt"
//This is a text
%openfile buf1 ="text.c"
 
%selectfile buf
 //add text.txt
    
%selectfile buf1
 //add text.c
    
%selectfile STDOUT
//This is std               %%类似于cout输出到Command Window中
    
%selectfile NULL_FILE       %%不显示方式(一般作为函数开头)
//NULL FILE
    
%closefile buf
%closefile buf1

7)TLC函数编写

%selectfile NULL_FILE 
    
%function hex2dec(hexnum) void           %%void 不返回 output 返回
    %assign hexnum = FEVAL("regexprep",hexnum,"0x","");  %%调用matlab函数regexprep,将字符串中的0x就进行删除
    %warning hexnum = %<hexnum>
    %assign decnum = FEVAL("hex2dec",hexnum)
    %assign decnum = CAST("Number",%<decnum>)
    %warning decnum = %<decnum>
    %return decnum      
%endfunction    

调用:

 %assign a = hex2dec("0x5f")
  1. 代码生成实例(写在文件func_gen.tlc中)
%selectfile NULL_FILE 
 
%function code_gen(filename,ret,funcname,body,argu_list) Output
    %openfile buf ="%<filename>"
    #include <math.h>
    %<ret> %<funcname>(%<argu_list>)
    {
		%<body>
    }
	%closefile buf
%endfunction

调用:

%include "func_gen.tlc"

%createrecord codegen_rec{
    ret "float";...
    funcname "my_calcu";...
    argument "int a";...
    filename "calculate.c";...
    body     "float a;\n b=sin(a);\n return b;"..
}
%<code_gen(codegen_rec.filename,codegen_rec.ret,codegen_rec.funcname,codegen_rec.body,codegen_rec.argument)>

TLC文件,官方是不推荐进行自行编写的,不熟练的书写会导致代码生成失败,但是我们可以学会去读懂它。但是要使用c—mex封装自己的模块时,必须要自己进行编写模块级TLC。

5.2 系统默认ert.tlc生成代码结构

生成模块结构体可以分为以下几部分:

1)外部输入:struct:model_U(model为simulink文件的名称,下面不再赘述)

2)模块I/O:struct:model_B

3)外部输出:struct:model_Y

4)模块状态变量:struct:model_X

5)模块参数:struct:model_P

6)模块中间变量(工作空间):struct:rtRWork、rtIWork、rtPWork、rtDWork

5.3 优化代码生成

还是之前的例子首先讲解第一种优化方案:

1)给需要优化的信号线赋予我们命名的值。这里记录一下优化之前的step表达式:

 coder_test_Y.Out1 = coder_test_P.Constant_Value * coder_test_U.in;

目标优化成:

Out = 10 * in;

在这里插入图片描述

2)选中信号线选中Properties。

在这里插入图片描述

3)进入选项框(Code Generation-Storage class为图中所示),此处改为全局变量,便不会再使用结构体进行包含。

在这里插入图片描述

4)这里需要清楚的认识一点,模型中的in与out属于变量,而10属于常数。对于常数的设置需要到Configuration Parameters中进行设置。

在这里插入图片描述

5)再次生成代码即可:

在这里插入图片描述

可以发现已经达到目标要求。下面介绍第二种方式进行添加,开始之前先为模块加入一个gain:

在这里插入图片描述

1)首先将所有常量替换成表达式:

在这里插入图片描述

2)进入Command Window进行相关配置:分别键入以下语句

k_constant=Simulink.Parameter;
k_gain=Simulink.Parameter;
in=Simulink.Signal;
out=Simulink.Signal;

这里是为常量和信号分别建立了Simulink的结构,其种Parameter就是定义的参数类型的类。

在这里插入图片描述

这里Parameter各举一个例子进行说明:

I、信号线配置

在这里插入图片描述

II、Parameter配置

在这里插入图片描述

然后进入Simulink中进行信号线的配置:勾选上图中所示box其余信号线一模一样的配置。

在这里插入图片描述

标记完毕之后会出现信号标识:

在这里插入图片描述

然后再次生成代码即可:

在这里插入图片描述

可以发现相对于之前易读性加强。

注:Storage Class 中还有许多数据类型,可以自行尝试数据类型。

5.4 不同的Storage Class 生成区别

Signal中的类型

1)Default(Custom):与Global效果相似,生成全局变量。

2)BitField(Custom):可以生成自定义的结构体(结构体名字可以自定义)。

3)Volatile(Custom):可以生成自定义的头文件和源文件(.c与.h文件),并在此文件内声明Volatile类型变量。

4)Const(Custom):可以生成自定义的头文件和源文件(.c与.h文件),并在此文件内声明Const类型变量。

5)ConstVolatile(Custom):可以生成自定义的头文件和源文件(.c与.h文件),并在此文件内声明ConstVolatile类型变量。

6)ExportToFile(Custom):可以生成自定义的头文件和源文件(.c与.h文件),并在此文件内声明变量。

7)ImportFromFile(Custom):可以选择变量来自于用户自定义的文件(需要给定自己的文件)

8)FileScope(Custom):静态变量,只能在当前文件中进行访问。

9)Struct(Custom):与BitField(Custom)效果相同,但是BitField(Custom)可以生成位bianl(布尔类型)。

10)Reusable(Custom):重用类型变量,都属于Reusable(Custom)类型的变量会只定义一个变量。

Parameter相较于Signal多的类型

11)Define(Custom):宏定义参数

12)CompilerFlag:生成一个预处理语句。

#ifndef k3
#error error
#endif

还剩余一种GetSet类型没有描述,GetSet类型需要给定一个GetFunction与SetFunction中。

在这里插入图片描述

这里可以给定一个使用场景,即读取和写入GPIO对应的值:那么需要给定GPIORead与GPIOWrite函数分别进行读取和写入GPIO的值。因为在DSP需要通过函数才能正确的读取到该函数,所以需要使用方法进行调用,这里有点类似C++中的私有属性需要提供接口进行访问一样的概念。

从新构建如下的框图:

在这里插入图片描述

此框图的var代表GPIO的值,需要调用语句和进行读取和写入,var的类型设置如下:

在这里插入图片描述

在模型目录下存放GPIO.h与GPIO.c文件:

在这里插入图片描述

然后这里需要注意的是必须在Configuration Parameter中加入声明,不加入会报错找不到两个函数。这个是我看了官方的demos才知道的:

在这里插入图片描述

然后DEBUG就好。可以看到生成的代码将GPIO的值读取后,又放大15倍写入回去。

在这里插入图片描述

5.5 数据类型别名

Simulink中已经有部分数据类型进行了宏定义(rtwtypes.h文件中):

typedef double real_T;
typedef double time_T;
typedef unsigned char boolean_T;
typedef int int_T;
typedef unsigned int uint_T;
typedef unsigned long ulong_T;
typedef char char_T;
typedef unsigned char uchar_T;
typedef char_T byte_T;

在使用当中,我们自己能够定义类型别名,增加代码可读性:

1)输入指令后进行配置

在这里插入图片描述

2)Data Scope中分为导入和导出,Base type可以选择要typedef的数据类型,比如这里就是利用a定义了double类型。

这是一个类型的替代,往往我们需要进行多个类型的替代:

在Configuration Parameter 的Code Generation下的 Data Type Replacement进行定义:

在这里插入图片描述

定义完毕之后还是需要将这些别名在Workspace中进行定义,就如第一中一样:

//模型回调函数

k_constant=Simulink.Parameter;
k_constant.Value=10;
k_constant.CoderInfo.StorageClass='ExportedGlobal';

k_gain=Simulink.Parameter;
k_gain.Value=1.5;
k_gain.CoderInfo.StorageClass='ExportedGlobal';

in=Simulink.Signal;
in.CoderInfo.StorageClass='ExportedGlobal';

out=Simulink.Signal;
out.CoderInfo.StorageClass='ExportedGlobal';

var=Simulink.Signal;
var.StorageClass='GetSet'
var.CoderInfo.CustomAttributes.HeaderFile='GPIO.h';
var.CoderInfo.CustomAttributes.GetFunction='GPIORead';
var.CoderInfo.CustomAttributes.SetFunction='GPIOWrite';

my_double=Simulink.AliasType;
my_double.BaseType='double';

my_single=Simulink.AliasType;
my_single.BaseType='single';

my_int32=Simulink.AliasType;
my_int32.BaseType='int32';

my_int16=Simulink.AliasType;
my_int16.BaseType='int16';

这些可以利用脚本快速的写在模型的回调函数中,在模型加载时刻自动写入。生成的代码如下:

在这里插入图片描述

5.6 实例(Clark变换)

首先搭建如下图所示的Clark变换模型:

在这里插入图片描述

输入50Hz的三相交流进行测试:

在这里插入图片描述

可以看到输出的时间差在0.005s,所以对应相位差在0.005/0.02*(2*pi)=90°。所以Clark算法满足要求,下面对其进行代码生成。

首先定义信号线的输入和输出,这里设置的类型为ExportedGlobal

在这里插入图片描述

然后对子模块进行设置,在设置页码勾选Treat as atomic unit,这里是让整个模块作为一个原子(拥有一个采样频率)。

在这里插入图片描述

在代码生成界面有如下三个选项:1)Inline 2)Nonreusable function(不可重复使用) 3)Reusable function(可以重复使用)

在这里插入图片描述

1)Inline模式生成代码

在这里插入图片描述

可以看到代码直接就生成在step函数内部,没有任何封装。

2)Nonreusable function模式生成代码,进行如下的参数设置:

在这里插入图片描述

Function interface是设置函数是否允许带参数。这里在生成的过程报错,需要将Compact模式改为Modular。

生成效果:

在这里插入图片描述

在这里插入图片描述

可以看到Clark模块进行了单独封装,但是由于是变量的原因没有生成相应的参数,将变量改为模型参数变量,即可生成带参数的函数。

3)Reusable function模式生成:

当一个模块需要多次在Simulink中进行使用时,就必须设置成Reusable function模式进行生成代码,否则就会报错。

不使用reusable function会生成多个代码,不利于代码重复利用:

在这里插入图片描述
在这里插入图片描述

使用了reusable function之后,就会重复利用一个函数文件。这个在代码生成的过程中需要灵活使用

在这里插入图片描述

5.7 带有预处理的C代码生成

#if
#endif

上述就为预处理语句。

Simulink中提供了一个可以生成预处理代码的模块:Variant Model。

在这里插入图片描述

如下图所示,在该模块的属性界面可以进行编辑,这里的var变量的值可以在CommandWindow中进行赋值。当给定var=1时,可以看到模块内部情况(灰色即是不满足条件无法执行,说明说的很清楚out与in不可连接给模块,已经为系统进行了默认连接):

在这里插入图片描述

再就是生成代码时,必须将所有子模块设置成原子子模块。生成代码效果如下图(具有很明显的条件编译结构):

在这里插入图片描述

5.8 枚举变量类型创建

Matlab不仅仅支持面向程序的语言,同时也支持面向对象的语言,枚举类型的创建就是利用面向对象的语言进行的开发:

classdef week <Simulink.IntEnumType
    enumeration
        Sun(0)
        Mon(1)
        Tue(2)
        Wed(3)
        Thu(4)
        Fri(5)
        Sat(6)
    end
end

编制m脚本内容如上,与模型放在统一目录下,便定义了枚举类型week。

构建Simulink模型如下图所示:

在这里插入图片描述

生成代码可以进行观测,创建了枚举类型。Matlab的面向对象的语言也比较丰富。

在这里插入图片描述

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Quikk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值