【Matlab学习日记】① Sinmulink自动代码生成教程

文摘   科技   2024-11-19 07:30   山东  

关注+星标公众,不错过精彩内容


作者 | 量子君

微信公众号 | 极客工作室

【Matlab学习日记】专栏介绍

在这个专栏中,你可以找到大量与Matlab有关的知识和技能,包括基础语法、常用函数、绘图、数据分析和建模等方面的内容。此外,该专栏还包括一些与Matlab应用相关的内容,如simulink、图像处理、信号处理、机器学习等。

本章目录

  • 前言
  • 一、Sinmulink代码生成教程

    • 1.1、根据需求建立系统框图

    • 1.2、代码生成

      • 1.2.1 solver页面的设置

      • 1.2.2 Hardware Implenmatation页面的设置

      • 1.2.3 启动编译

  • 二、Sinmulink代码生成进阶教程

    • 2.1 代码生成工具

    • 2.2 代码生成基础操作

    • 2.3 函数的封装

    • 2.4 生成代码的设置

  • 三、Simulink代码生成应用教程

    • 3.1 控制器基础介绍

    • 3.2 例子介绍

    • 3.3 Simulink模型的修改

  • 总结



 前言

本章介绍了Sinmulink代码生成的详细教程。

一、Sinmulink代码生成教程

Simulink自带了种类繁多、功能强大的模块库,在基于模型设计的开发流程下,Simulink不仅通过仿真可以进行早期设计的验证,还可以生成C/C++、PLC等代码直接应用于PC、MCU、DSP等平台。在嵌入式软件开发中发挥着重要的作用,本文以Simulink模型生成嵌入式C代码为例分析代码生成的原理及应用。

1.1、根据需求建立系统框图

低通滤波:又叫一阶惯性滤波,或一阶低通滤波,是使用软件编程实现普通硬件RC低通滤波器的功能。其适用于单个信号,有高频干扰信号的情形。

 一阶低通滤波的算法公式为:

式中:α是滤波系数;X(n)是本次采样值;Y(n-1)是上次滤波输出值;Y(n)是本次滤波输出值。

根据以上计算公式可以建立如下图所示模型:


1.2、代码生成

 Simulink的Simulink Coder工具箱提供了将模型转换为可优化的嵌入式C代码的功能。 
Configuration Parameter中集中管理着模型的代码生成方法、格式等约束条件。为了生成嵌入式代码,至少需要配置三部分:模型的解算器solver,模型的系统目标文件(如ert. tlc或其他自定义的嵌入式系统目标文件),硬件实现规定(Hardware Implenmatation)。
 按下Ctrl+E打开模型的Configuration Parameter对话框,如下图所示:

  • 1.2.1 solver页面的设置 

  solver页面如下图所示:

解算器类型必须选择固定点解算器。固定点solver中提供了多种算法,此模型由于没有连续状态,可以选择discrete方法。步长默认auto,在简单的通用嵌入式代码生成过程中此参数没有实际作用,可以采用默认或设置0.01s。而在针对目标芯片定制的代码生成过程中,硬件驱动工具箱往往会将步长step size作为其外设或内核中定时器的中断周期,使得生成的算法代码在硬件芯片中以同样的时间间隔执行。并且由于解算器步长为整个模型提供了一个基础采样频率,故被称为基采样率(base-rate)。
  • 1.2.2 Hardware Implenmatation页面的设置

Hardware Implenmatation选项是规定目标硬件规格的选项。在这个选项卡中可以配置芯片的厂商和类型,设置芯片的字长、字节顺序等。学习使用通用嵌入式芯片为目标的代码生成流程及原理,选择32位嵌入式处理器作为芯片类型,如下图所示框中部分:
 另外一个关键的设置选项是控制整个代码生成过程的系统目标文件System Target File,ert. tlc文件是Embedded Coder提供的能够生成专门用于嵌入式系统C代码的系统目标文件。在Code Generation页面中,单击下图所示右上角Browse按钮可以弹出对话框以选择系统目标文件:
在选择框中选择ert. tlc之后,Code Generation标签页下面的子标签也会发生变化,提供更多的功能选项标签,如下图所示,方框内为新增子标签:
  1. Optimization子标签当模型中使用参数变量,如Gain模块的增益值,在生成代码时,如果希望使用该参数的值直接展开到代码中,就需要设置参数内联选项,如下图所示框中选项:


    Default parameter behavior选项表示默认的参数行为类型,可以选"Inline"和"Tunable"。"Inline"表示生成代码时直接用这个参数的值,"Tunable"表示会有这个参数的变量定义,以保证代码实际运行时,可以修改这个参数的可能。我们来看一下区别。


  2. Report子标签能够打开设置关于生成代码报告的页面,可以选择是否创建HTML格式的代码生成报告,并通过勾选框选择是否在模型编译结束后自动打开。其对话框页面如下图7所示:


    Metrics 组的 Static code metrics 选择框,勾选时将会在代码生成报告中包含静态代码的参数指标。
    推荐勾选 Create Code Generation Report 及Open report automatically 两个选项,模型生成代码完毕后会自动弹出报告列表,而不需要到文件夹中逐一将源文件手动查找并打开。 


  3. Comments子标签中包含对生成代码中注释内容的配置,其对话框如下图所示:
    Include comments选项的勾选决定是否在生成代码中添加Simulink自带的注释。
    启动此选项后,Auto Generated comments组及Custom comments组的选项便被使能,我们可以根据需要选择希望生成的注释内容。
    推荐启动 Include comments 选项并勾选 Simulink block comments 和Stateflow object comments选项以生成注释,注释中带有可以从代码跳转到对应模型的超链接,方便追溯模块与代码的对应关系。


  4. Identifiers子标签页面用于设置ert.tlc—族系统目标文件控制下的代码生成不变定义规则,如下图所示:

    这些符号包括数据变量和数据类型定义、常量宏、子系统方法、模块的输出变量、局部临时变量及命名的最长字符数等。
    Identifier format control 参数组里默认使用标示符 $R$N$M$T,是Embedded Coder 内部使用的标示符,如下图所示:


    这些标示符的具体意义如下表所列:


    通过上表各种标示符的不同组合,即可规定生成代码中各部分(变量、常量、函数名、结构 体及对象)的名称的生成规则。
    Simulink提供的这些标示符生成的变量名虽然可读性不强,但是不会引起代码编译错误。推荐使用默认设置,不要为了提高生成代码可读性轻易进行修改,以免造成不必要的错误。以后会学习更好更安全的提高代码可读性的优化方法。


  5. ustom Code子标签页面主要用于添加用户自定义的或者编译模型时必需的源文件、头文件、文件夹或者库文件等,其页面如下图所示:

  6. Interface 子标签页面中包含4组参数:Software Environment、Code Interface、Data Exchange Interface和Deep Learning,其对话框如下图所示:
    Software Envirionment 组的参数中提供 CPL(Code Placement Library)的选择,CPL 中 定义一个表,根据表格将Simulink模块与所对应目标语言的数学函数及操作函数库挂接,以便从模型生成代码。Embedded Coder提供默认的CPL。
    Support参数组由7个选择框构成,如下:

    每个选择框代表一种嵌人式编码器对代码生成的支持功能,其中一些功能是需要Simulink提供的头文件来支持才能编译为目标文件的,这些头文件一部分存储在路径为MATLABroot\ simulink\include的文件夹中,一部分是在模型生成代码过程中自动生成的(rt开头的头文件)。具体参考下表:
    Code Interface、Data Exchange和Deep Learning参数组分别用来配置生成代码的接口、数据记录的方式和深度学习,如无特殊要求建议使用默认配置。


  7. Code Style子标签页面提供了一些关于生成代码风格的选择框选项,如if else分支的完整性确保,if else与switch case语句的选用,生成括号的频度,是否保留函数声明中extern关键字等,如下所示:


  8. Template子标签页面内嵌入式编码器提供了一组默认的代码生成模版,如下图所示:


    ert_code_template.cgt 中使用TLC变量方式规定了文件生成的顺序及添加模型信息注释的位置。模型生成的源文件、头文件及全局数据存储和外部方法声明文件的生成可以使用统一模板。
    ert_code_template.cgt中主要规定了代码段的顺序,section包含了源文件从注释到变量再到函数体各种分 段,如下图所示:


    顺序从上到下,依次为:文件说明的注释(File Banner)、头文件包含(Includes)、宏定义 (Defines)、数据结构类型的定义(Types)和枚举类型的定义(Enum)、各种变量的定义(Definitions),以及函数体的声明(Declarations)和闲数体定义(Functions)。我们可以在相邻的段中插人自定义内容,但是不要打乱既存段的相对顺序。


  9. Code Placetnem子标签提供的选项将影响生成代码的文件组织方式和数据存储方式及头文件包含的分隔符选择等,其页面如下图所示:


    常用的选项是File packaging format,表示生成文件的组织方式,对应的生成文件个数不同,内容紧凑程度也不同。具体如下表所列:


    省去的只是文件个数,其内容被合并到了其他文件中,内容的转移如下表所列:


    我们可以使用默认设置,如果希望减少生成代码列表中文件的个数,可以考虑使用Compact 的组织方式。


  10. Data Type Replacement子标签默认情况下仅提供一个选择框选项:


    勾选之后则弹出3列数据类型类表,分别是Simulink Name,Code Generation Name 和 Replacement。勾选 Replace data选项之后界面如下图所示:


    前两列按照数据类型的对应关系给出了每种数据类型在Simulink和嵌入式编码器生成代码中的类型名,第3列则供用户设置,填人自定义的类型名之后,生成代码时将使用自定义的类型名替换Code Generation Name。
    用户填人的自定义类型名不仅是一个别名字符串,还必须在Base Workspace中定义其作为Simulink .AliasType类型对象才可以。
    如定义U16数据别名对象来替换uint16_T这个内部类型。第3列的edit框不必全部填入自定义类型名,可以根据应用场合选择部分或全部来使用。并且可以使用同一个数据类型名替代多个内建数据类型,如使用U8同时替换uint8_T和Boolean_T类型。
    Code Generation标签页下提供的子标签页功能说明均已完成。
  • 1.2.3 启动编译

 当这些配置好以后,我们就可以启动模型编译:
或者按下ctrl+B,模型左下角从ready显示为building:

 

片刻后弹出Code Generation Report界面,如下图所示:

与模型名相同的.c若模型配置无误,则文件中包含model_step()函数,这里的代码表示模型所搭建的逻辑:

Ccoder_test_Y.Out1 += (Ccoder_test_U.In1 - Ccoder_test_Y.Out1) * Ccoder_test_U.In2;
未经优化的代码可读性较差,但是从四则运算关系中及结构体的成员名上可以看出每一个变量所代表的意义。
除此之外,生成的代码还提供了 Code to Model追踪功能,单击下图方框中的超链接,可以直接跳转到模型中对应的模块,该模块或子系统将会以蓝色显示。如下所示:
我们单击<Root/In2>来演示这个超链接,这个追踪功能提示用户模型与代码的对应关系,即直接跳转到模型中对应的模块,该模块或子系统将会以蓝色显示。如下所示:


二、Sinmulink代码生成进阶教程

一个完整的控制器(xCU)嵌入式程序在逻辑架构均可分为两层:一个是应用逻辑层(Application Software),用来表达整个控制逻辑的实现算法;还有一个是基础软件层(Basic Software)也就是常说的底层驱动,负责芯片功能进行初始化,例如:定时器、锁相环、CAN、SPI等。逻辑层与基础软件层之间需要有一个接口层(Interface Layer)实现数据的交互。
Simulink代码生成技术的出现很好的解决的控制器程序开发的分工问题,控制策略由各专业工程师开发,而底层驱动由嵌入式工程师完成。控制策略工程师可利用Simulink强大的建模与测试功能开发控制策略模型,最后将生成嵌入式代码与底层驱动一起编译成二进制文件。另外,如果硬件变更或者硬件升级,只需要修改驱动成代码不需要更改测试好的逻辑层策略。

2.1 代码生成工具

早期Simulink代码生成主要依托dSpace公司的TargetLink实现。安装了TargetLink后,在Simulink中就会出现TargetLink自带的元件库,如果你原先仿真时用的是Simulink的标准原件,就需要将模型转换成TargetLink模型。近几年由于Simulink EmbeddedCoder在技术上日趋成熟,并且推广力度也比较大。所以越来越多厂家开始使用EmbeddedCoder生成嵌入式代码。接下来篇幅就来演示如何使用Simulink的EmbeddedCoder功能快速生成C语言代码。

2.2 代码生成基础操作

这部分内容为了让各位对嵌入式代码生成有个直观的感受。
以一个简单的模型为例,有两个输入分别为x、y,一个输出z。运算过程为z=(x+y)*k;
第一个重要的设置就是解算法,这里**一定要设置为离散的定步长算法,并设置步长**。因为所有的控制器内部都会有一个模数递减器,用来实现一个步长的精确定时。大部分控制一个步长都设置为10ms。

在模型设置中的代码生成选项,选取代码生成的模板。本次使用的时EmbeddedCoder,所以选择与其对应的ert.tlc。特别注意的是由于生成的代码不会使用Simulink的编译工具进行编译,所以需要勾选Generate Code Only,这样就只会生成C文件与h文件。

点击Build Model按钮或者使用快捷键Ctrl+B进行生成代码,生成的代码会有一个报告,包含模型的配置信息。在左边的框中提示了本次生成代码的文件,点击可以对代码进行查看。生成ert_main.c文件包含了一些模型的初始化并调用模型的主函数,正常开发过程中这个文件是不会被用到。Main函数正常都在芯片开发环境中编写。接口与调度这部分内容会在后面的文章中详细叙述。GenerateCode.c文件则就是存放Simulink模型生成代码的文件,还有若干个h文件和Simulink自带的引用文件。
GenerateCode_Step函数中,就是Simulink模型生成C语言代码,可以看到这里的输入与输出都是以结构体的形式进行表达的,k值则是在WorkPlace中直接赋值。这样的代码可读性比较低,接下来就对模型进行更多的优化设置。

在Simulink数据管理器中使用mpt.Signal添加4个变量,分别命名为x、y、z、k设置数据类型、存储方式等,并且对k赋予初始值。
选中信号线并右键进入properites【属性】对话框,在SignalName中输入刚刚创建的变量名,并勾选Signal name must resolveto Simulink signal object。
设置后的模型效果,可以看到这时候信号线上多了个关联图标并再次对模型生成代码。

生成后的代码就可以看到与第一次生成的相比,那几个晦涩难懂的结构体已经被具体的变量名取代了。以上这个部分很重要,这是后续接口层开发的要点。

2.3 函数的封装

手工写过代码的朋友一定有这样的经验,尽可能的要把相同逻辑的代码用一个函数封装起来。这样既可以很方便地调用代码,也能够减少代码冗余提高执行效率。同样的使用Simulink建模也需要考虑这个问题。
对刚刚的模型进行简单的封装,并复制一个出来。两个子系统中的逻辑都是z=(x+y)*k,区别仅仅是输入与输出不同。
右键对Subsystem模块属性进行设置,勾选【Treat as atomic unit】设置为原子子系统,并自定义一个函数名。

生成代码后可以看到,左边两个Subsystem分别调用了不同函数。

2.4 生成代码的设置

生成代码的设置除了上面的选择编译模板一定要配置外,下面这几个选项也是需要注意的。如果比较细心的朋友会发现前面生成报告的硬件信息为Intel->x86-64 (Windows64),在模型设置中可以对设备进行指派,设置芯片的厂家、型号、各数据类型的数据长度、甚至字节序。

另外这部分是很容易被忽略的,这里可以根据不同的C语言标准来生成代码,支持C99和C89标准。做嵌入式开发的朋友一定要根据自己的开发环境来选择,如使用code warrior 5.2的朋友一定要选择C89,选择C99就会出现编译错误的问题。

三、Simulink代码生成应用教程

3.1 控制器基础介绍
首先要搞清楚控制器底层驱动(BWS)的大致工作流程。本期不是专门讲底层如何设计,所以抛开Bootloader/标定等功能及其调度过程。由于每家公司的设计思路、芯片选择都不一样,所以只对通用部分的思路进行阐述。
控制器或控制单元本质就是一个简易的计算机,一样具有输入与输出功能。如图:输入信号类型有CAN/Lin/开关信号/ATD,输出信号类型有CAN/Lin/开关信号。每个零部件的控制器(xCU)都会有一个工作步长,简单的理解就是有个定时器。定时器控制每一次【输入-计算-输出】所间隔的时间。步长功能可通过单片机内部的定时器(模数递减器/实时定时器等)实现,在嵌入式开发环境中的【定时器中断函数】中实现。

3.2 例子介绍

模型有两个输入x,y,一个输出z。为了配合本期内容,单片机选用MC9S12XEP100并假设x、y通过控制器的CAN模块接收,计算得到的结果z通过控制器的CAN模块发送,k为标定量。
在开发环境中对申明In_X、In_Y、Celib_K、Out_Z对应x、y、z、k,所谓接口变量名称
在MC9S12XEP100的开发环境CodeWarrior5.2中,根据前一小节介绍的中断函数。其中ModelInput()对应【模型输入函数】作为模型变量的输入接口,ModelOuput()对应【模型输出函数】作为模型计算结果的输出接口,VcuApp_setp()对应的就是【Sinmulink模型代码】。

3.3 Simulink模型的修改

上面介绍完了底层驱动的接口部分,接着我们就需要对Simulink模型进行调整。

在Simulink的数据管理器中,创建与接口变量一致的信号名。这里需要特别注意是,信号类型一定要为Simulink.Signal,如果是一个map数组,这里要改为Simulink.Param。另外,数据存储类型从Auto切换到ImportedExtren,意为Simulink生成的代码不会重新申明变量,而是使用extren标志为外部引用变量。效果等等在生成后的代码中进行展示。当然也可以使用其他的数据存储类型,有兴趣的话大家可以都尝试下,看看哪个更加符合自身项目的需求。
完成信号创建之后会在Matlab的Workplace提示,注意将其保存为mat文件。每次打开模型进行代码生成时候都要加载mat中的信号信息。
对上一期的模型结构进行调整,使用DataStoreRead取代In模块,DataStoreWrite取代Out模块,使用Product模块替换Gain模块。由于我们底层定时器中断中Simulink模型接口函数名为VcuApp_step(),所以模型的文件名也更改为VcuApp.slx。


总结

本章介绍了Sinmulink代码生成的详细教程。






若觉得文章对你有帮助,随手点『好看』、转发分享,也是对我的支持
关注我的微信公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。
点击“阅读原文”查看更多分享

极客工作室
一个专注于嵌入式系统、智能硬件、AIoT的极客自媒体
 最新文章