基于Simulink模型的嵌入式代码生成与实际工程应用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在嵌入式开发中,利用Simulink的相关功能模块搭建逻辑流程以后,一个非常重要的步骤就是利用代码生成功能将我们搭建的功能转化为代码,并且和嵌入式平台的芯片软件工程融合,使我们开发的功能在单片机上跑起来,从而实现产品的功能要求。


一、工程创建

这次使用Simulink搭建的工程的目标产品是车载智能保险盒,前两篇文章中的CAN模块、雨刮模块等都是其中的功能,这些功能组装起来通过一个完整的simulink project进行管理。以功能为文件夹,把相关的功能文件进行统一归类。我的理解这样创建的工程类似于代码编辑时在编译器中创建的工程。在这里插入图片描述
就像KEIL或者IAR中可以进行编译,运行操作一样,simulink项目也可以进行model test、依存关系查看、运行检查等操作。版本管理也集成了SVN或git,只需要进行简单的设置也可以实现。我用的就是SVN进行版本管理,但是习惯上还是直接拷贝文件夹进行update和commit。

二、代码生成

1.搭建模块

生成代码每个模块的操作是一样的,设置参数和操作。本文采用近光灯模块进行解释,主要因为近光模块功能比较简单。信号量较少,且逻辑简单。主要信号如下:
输入信号:
1、点火开关IGN信号
2、大灯供电信号
3、近光灯开关信号
4、短路判断信号
5、开路判断信号
6、报文刷新标志
输出信号:
1、近光灯继电器的控制信号
2、CAN报文信号
3、报文发送标志位
其中报文刷新标志和报文发送标志是系统内部的判断信号,不作为对外的输出,相当于写代码时设置的标志位是一个局部变量。
工能逻辑结构如下:

一 整体结构
在这里插入图片描述
二 内部结构
结构
三 stateflow
在这里插入图片描述

功能搭建完成以后,点击RUN图标,若未报错证明语法上结构正确是符合了生成代码的前提条件,至于控制逻辑是否正确可以通过dashboard搭建指示灯进行仿真,仿真的方法可参考前文《Simulink模型实现汽车雨刮基本功能并仿真》。

2.设置参数

点击工具栏上面model setting的齿轮图标,进入设置参数界面
在这里插入图片描述
以本项目生成嵌入式代码为例,主要需要调节的参数设置如下:
选中
solver解算器下,Solever selection解算器选择,type选fixed-step(定步长),solver选discrete(离散型)
细节选项fixed-step size设置为0.01,采样步长为0.01s,这个参数关系到在嵌入式工程中多久调用一次功能循环,以本项目为例,设置为0.01s也就意味着,每10ms调用一次功能函数,要是不按照设定值进行定时中断,那么会影响模块中的计时操作。

在这里插入图片描述
code generation选项中主要需要注意的是两个参数,一个是System target file要选为ert.tlc,另一个就是Language要选C语言。

在这里插入图片描述
Report选项中选择生成代码报告,一般勾选前两项分别是生成报告和自动打开,第三个web view可选可不选。

在这里插入图片描述
根据所用单片机型号我们要在Hardware Implementation中选择相对应的单片机厂商和内核类型,本项目所用的是NXP,cortexM4内核S32k148芯片,选择相应选项。这里选择不像Keil这类编译器那么详细,主要作用就是确定单片机一些数据类型定义的位数,以及是否支持长整型等,点开detail可以看到详细。

3.自动生成

在完成以上步骤以后,就可以进行生成代码操作了。首选在APP选项中选择embedded coder功能卡,如下:
在这里插入图片描述
选择以后进入embedded coder APP
在这里插入图片描述
点击Build按钮,生成嵌入式C代码

在这里插入图片描述
代码生成以后会自动出现在右侧,方便我们查看。

在这里插入图片描述
因为我们勾选了生成Report选项,所以还会出现一个报告对话框,在报告里面可以查看本模块生成代码的相关内容,包括代码预览,模型信息,占用内存多少等信息。

在这里插入图片描述
我们在生成的代码中可以看到很多文件,但又用的主要就是功能相关的 LowBeam.c 和 LowBeam.h 这两个文件,其他的文件除了ert_main以外的可以不用管,直接拷贝放到我们自己的代码工程目录下。ert_main不用,我们用自己的main函数,不过可以参考ert_main中对功能函数的调用方法。

三、代码整合

按照上述的方法自动生成了代码以后,离成功使用还有最后一步操作,那就是将这些文件添加到工程并且与驱动代码相配合。针对这款芯片,其他同事已经进行了平台化的开发,提供了相关的接口,包括ADC、GPIO、CAN等资源都已经进行了封装,我们所需要做的就是接口对应。还是以本近光灯功能模块为例,LowBeam.c文件,几乎可以不用去管,那是Matlab软件根据我们搭建的Simulink逻辑自动生成的代码,可读性不是很强,但功能肯定是实现的。我们进行接口对应,关注LowBeam.h文件就好。
在.h文件中找到如下代码:

/* External inputs (root inport signals with default storage) */
typedef struct {
  IGN IG;                              /* '<Root>/IGN' */
  uint8_T Front_Power_Supply;          /* '<Root>/Front_Power_Supply' */
  uint8_T Work_Cmd;                    /* '<Root>/Work_Cmd' */
  uint8_T IS_Opencircuit;              /* '<Root>/DILAMP_Open' */
  uint8_T IS_Shortcircuit;             /* '<Root>/DILAMP_Short' */
  uint8_T New_Can_Flag;                /* '<Root>/New_Can_Flag' */
} ExtU_LowBeam_T;

/* External outputs (root outports fed by signals with default storage) */
typedef struct {
  uint8_T Low_Beam_On;                 /* '<Root>/Low_Beam_On' */
  uint8_T Can_Send_Flag;               /* '<Root>/Can_Send_Flag' */
  CAN_MESSAGE_BUS CAN_Msg_HighBeam;    /* '<Root>/CAN_Msg_HighBeam' */
} ExtY_LowBeam_T;

ExtU_LowBeam_T代表输入信号的结构体;ExtY_LowBeam_T代表输出信号的结构体。都是以全局变量的形式进行赋值。
我们自己写一个赋值函数:


/*近光灯模块*/
    LowBeam_U.New_Can_Flag = (uint8_T)can_status_store.bit.store_Newflag_LowBeam;
    LowBeam_U.Front_Power_Supply = (FrontLampTogether_Y.Left_Together_ON & FrontLampTogether_Y.Right_Together_ON);
    LowBeam_U.Work_Cmd = CAN_Receive_Model_Y.LowBeam_WorkCmd;
    LowBeam_U.IG = CAN_Receive_Model_Y.IGN_Sig;
    LowBeam_U.IS_Opencircuit =  (Output_Current_U.DILamp_FL_DRV < LOWBEAM_CURRENT_MIN) || 
                                (Output_Current_U.DILamp_FR_DRV < LOWBEAM_CURRENT_MIN) ? 1: 0;
    LowBeam_U.IS_Shortcircuit = (Output_Current_U.DILamp_FL_DRV > LOWBEAM_CURRENT_MAX) ||
                                (Output_Current_U.DILamp_FR_DRV > LOWBEAM_CURRENT_MAX) ? 1: 0;

其中涉及到其他模块的一些信号,但这些都是独立的全局变量,可以直接拿来进行逻辑判断。

输出信号:

if(LowBeam_Y.Low_Beam_On == 1)
    {
       HSD_SET_LEFT_LOWBEAM_ON;   //打开左近光灯
       HSD_SET_RIGHT_LOWBEAM_ON;  //打开又近光灯
    }  
    else
    {
       HSD_SET_LEFT_LOWBEAM_OFF;    //关闭左近光灯
       HSD_SET_RIGHT_LOWBEAM_OFF;   //关闭右近光灯
    }

对输出函数进行了封装,这里直接调用即可。

针对总线信号,写一个赋值函数:

void set_can_LowBeam_output(void) 
{
    if(LowBeam_Y.Can_Send_Flag)
    {
        if(can_send_output_set(CAN_INSTANCE_SET, &LowBeam_Y.CAN_Msg_HighBeam) == CAN_EOK)      //发送报文
        {
            LowBeam_Y.Can_Send_Flag = 0;
        }
    }
}

这里直接用了can_send_output_set()这个发送报文用的接口函数。

最后是在main函数中调用功能函数,我们在生成的LowBeam.h文件中可以看到三个函数申明:

/* Model entry point functions */
extern void LowBeam_initialize(void);
extern void LowBeam_step(void);
extern void LowBeam_terminate(void);

实际我们需要用到的只有LowBeam_step(void)这个函数,在main函数的主循环中每10ms调用一次该函数即可:

if(Get1MsTickInterval(modeldely) >= 10{ 
    modeldely = Get1MsTickVal();    
    LowBeam_step();                 //近光灯
}

其他的功能模块都可以按照这样的方法把_step函数添加在近光灯函数的下面。


总结

以上就是今天要讲的内容,本文简单介绍了通过Simulink搭建的逻辑框图如何生成嵌入式代码,并且将生成的代码添加到已有的单片机工程中实现近光灯模块的功能。

当然,自动代码生成在工程的调用需要多一层封装,占用了一定的资源。且对于一些业务熟练的工程师来说,一些简单的逻辑手写可以用很精简的C代码量实现同样的功能。不过,我们采用自动代码生成技术也有相应的好处,比如在功能逻辑较为复杂时可以更清楚的理清思路,而且simulink可方便的进行功能仿真,验证功能步骤可以放在生成代码之前,而手写代码则必须在编译通过以后烧录到样件中利用硬件设备进行功能测试。这样一来,自动利用simulink进行功能仿真和代码自动生成,就省去了反复的烧录过程。

当然,simulink的代码生成功能远不止本文所提到的部分,笔者还会在项目中探索,在以后的文章中和大家进一步探讨。

  • 18
    点赞
  • 135
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WeLikeStudy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值