Simulink代码自动生成(二)

前面一篇介绍了Simulink代码自动生成的基本步骤,虽然生成了符合模型逻辑的代码,但有些部分还需要进行进一步优化才能更便于我们使用和代码的集成编译。这一篇将从实用的目的作为出发点,将模型生成代码植到51单片机上,周期的点亮板子上的LED发光二极管。在移植的过程中,我们在根据需求对之前模型进行优化

Matlab版本:R2018B


一、软件的设计思路

上一篇中实现的计数器的模型,当计数使能时模型每运行一次计数器的值加1,当计数器达到设定目标值时,计数到达标志位置位。如果我们将该计数模块放在10ms周期任务中运行,设置计数目标值为100,当计数到达标志位置位时刚好1s时间到。我们可以以此为基础,使得LED发光二极管的状态每隔1s时间进行翻转一次。

计数模块输入使用了TarVal、InitVal、B_Init、B_Calc等变量,我们将这些变量在bsp_led.c模块中定义,bsp_led.c与生成代码TimerCnt.c之前的变量关系如下图所示:

bsp_led.c是与底层有关,通过手写实现的。其中实现了有两个函数,一个是bsp_led_initialize用于初始化时相关变量,另一个是bsp_led_10msTrg每10ms周期调用一次。两个函数中的内容如下:

void bsp_led_initialize(void)
{
    TarVal = 100;
    InitVal = 0;
    B_Init = 0;
    B_Calc = 1;
}

void bsp_led_10msTrg(void)            
{
    if(B_En == 1)
    {
        led = ~led;
        B_Init = 1;
        B_Calc = 0;
    }
    else
    {
        B_Init = 0;
        B_Calc = 1;
    }
}

bsp_led_initialize:设置计数器目标值、计数初始化设定值、使能计数

bsp_led_10msTrg:检测计时时间到让LED状态翻转,初始化计数模块。其它情况下使能计数模块计数

还需要初始化一个定时器,为xxx_10msTrg任务调用提供时基

二、模型的优化

上一篇模型生成代码的核心功能在一个cnt_timer_step函数中,我们可以直接将cnt_timer_step函数放入中断服务函数中直接调用,这种操作简单粗暴,实现起来容易,但很显然这样的命名很不具有规范性。我们想让生成的10ms周期调用函数都以这种方式命名xxx_10msTrg,其中xxx代表模型名。现在通过进一步的配置来进行实现我们的需求

第一步,选择模型,按ctrl+g将它封装成子系统

第二步,进入生成子系统中,再次重复步骤一操作,在重新生成的子系统中放入Trigger模块,并将Trigger模块的触发类型修改成function-call,由于是10ms周期调用,修改Trigger模块名称为10ms

第三步,从子系统返回上一层,设置该层子系统名称为r10ms,连接新的port端口到10ms函数触发位置。鼠标右击子系统模块,选择弹出项目中的Block Parameters选项,弹出下图中右边显示的对话框,设置code generation一栏。选择生成非继承函数,函数名使用子系统名称

第四步,再返回上一层,在上一层放置function-call generator模块,并连接到port5位置,修改子系统名称为TimerCnt。这样生成的函数最终会被function-call generator处命名的函数调用

这里并没有直接将function-call generator函数调用模块连接到TimerCnt的port5端口,而是使用了simulink库中的goto和from模块。其实这两种方法作用效果相同,名称相同的goto和from模块代表两个是互相连接在一起的

第五步,创建mpt.signal对象,用于设置模型中输入输出信号的数据类型

这里我们不再使用上一篇中介绍的使用Model explorer方式创建对象,这种创建方式有很大的弊端,我们每次重新打开matlab时都需要手动的进行重新创建,而且在输入输出变量比较多的时候容易出错误。我们使用一种新的方法,在.m文件中直接调用mpt.signal类创建对象,调用对象中的方法设置自己的属性。这样只需一次在.m文件中按照这种方式对输入输出信号进行定标,每次运行模型生成代码前运行这个.m文件即可

Storage class可以设置为 “ExportedGlobal”、“ImportedExtern”、“ImportedExternPointer” 或 “Model default”,常用的是“ExportedGlobal”、“ImportedExtern”

ImportedExtern:代表生成代码中的TarVal变量是在别处定义的,在这个文件中只是去使用

ExportedGlobal:代表生成代码中的该变量是在本文件中定义的

第六步,右键单击鼠标,选择C/C++ Code选项中的Excport Functions生成代码

观察生成代码,里面已经根据Function-Call Generator模块设置的名称TimerCnt_10msTrg创建了一个函数,这个函数调用计数模块的算法实现函数TimerCnt_r10ms。可以看出通过进一步对生成代码步骤进行优化,达到了我们想要的生成统一接口函数名xxx_10msTrg的目的

三、代码集成编译

① 编写main函数,在main函数中初始化一个10ms的定时器,将bsp_led.c和TimerCnt.c文件中的10ms周期调用的函数放到中断服务函数中

② 创建includes.h文件,包含各头文件,并将生成代码共享文件夹中rtwtypes.h文件中的数据类型定义拷贝到该文件中

③ 删除TimerCnt.c中原来包含的头文件,使其包含includes.h头文件

④ 编译代码,编译结果如下所示

将生成的hex文件下载到单片机中,运行效果如下动图所示。我们已经成功的将simulink生成代码移植到硬件上,完成了周期的点亮LED发光二极管的需求