提示:文章撰写完成后,可以自动生成目录,如何生成请参考右侧帮助文档。
目录
前言
在嵌入式开发中,利用Simulink的相关功能模块搭建好逻辑流程之后,很重要的一个步骤就是利用代码生成功能将我们搭建好的函数转换成代码,并与嵌入式平台的芯片软件工程进行融合,使得我们开发出来的函数能够在单片机上运行,从而实现产品的功能需求。
1. 项目创建
本次用Simulink搭建的项目目标产品是车载智能保险丝盒,前两篇文章中的CAN模块、雨刮器模块都是该项目的函数,这些函数通过一个完整的Simulink项目进行组装和管理,函数之间以文件夹的形式,将相关的函数文件统一归类,我的理解是这样建立的工程和编辑代码时在编译器中建立的工程类似。
和在KEIL或者IAR中编译运行一样,simulink工程也可以进行模型测试、依赖检查、运行检查等。版本管理也集成了SVN或者git,简单设置即可实现。我使用SVN进行版本管理,不过一般都是直接复制文件夹进行更新、提交即可。
2. 代码生成 1. 构建模块
各个模块的操作都一样,设置参数和操作。本文以近光模块为例进行说明,主要是因为近光模块功能比较简单,信号量小,逻辑简单,主要信号如下:
输入信号:
1. 点火开关IGN信号
2. 大灯电源信号
3. 近光灯开关信号
4.短路判断信号
5.开路判断信号
6. 消息刷新标志
输出信号:
1.近光继电器控制信号
2. CAN 报文信号
3. 消息发送标志
消息刷新标志和消息发送标志是系统内部判断信号,不作为对外输出,相当于写代码时设置的标志位是局部变量。
功能逻辑结构如下:
总体结构
2. 内部结构
三状态流
函数搭建完成后点击RUN图标,若无报错则证明语法结构正确,满足生成代码的前提条件。至于控制逻辑是否正确,可以通过搭建仪表盘上的指示灯进行仿真,仿真方法请参考上一篇文章《Simulink模型实现汽车雨刮器基本功能并仿真》。
2.设置参数
点击工具栏模型设置的齿轮图标,进入参数设置界面
以本项目中嵌入代码的生成为例,主要需要调整的参数设置如下:
在求解器下,选择求解器选择,类型选择固定步长,求解器选择离散
细节选项fixed-step size设置为0.01,采样步长为0.01s。这个参数和嵌入式工程中函数循环调用的频率有关,以本工程为例,设置为0.01s表示每10ms调用一次该函数。如果不按照设置值进行定时中断,将会影响模块中的定时操作。
代码生成选项中主要有两个参数需要注意,一是System target file选择为ert.tlc,二是Language选择为C语言。
在Report选项中选择Generate Code Report,一般前两项分别是Generate Report和Automatically Open,第三项Web View是可选的。
根据使用的MCU型号,我们需要在Hardware Implementation中选择对应的MCU厂商和内核类型。本工程采用的是NXP,cortexM4核心的S32k148芯片,选择对应的选项即可。这里的选择不像Keil等编译器那么详细,主要就是确定MCU的一些数据类型定义的位数,是否支持长整型等,点击detail可以查看详细信息。
3.自动生成
完成以上步骤后,就可以生成代码了,首先在APP选项中选择内嵌的coder功能卡,如下图:
选择进入嵌入式编码器APP
单击“Build”按钮生成嵌入式 C 代码
代码生成之后会自动出现在右侧,方便我们查看。
因为我们勾选了生成报告选项,所以会出现一个报告对话框,在报告中可以查看此模块生成的代码的相关内容,包括代码预览,模型信息,内存使用情况等信息。
在生成的代码中我们可以看到很多文件,但是主要用到的是LowBeam.c和LowBeam.h这两个文件,除了ert_main之外的其他文件大家可以忽略,直接复制到我们自己的代码工程目录下即可。我们不需要ert_main,使用我们自己的main函数,但是大家可以参考ert_main中函数函数的调用方法。
3. 代码集成
按照上面的方法自动生成代码之后,还差最后一步就能成功使用了,就是把这些文件添加到工程中,并与驱动代码进行匹配。针对这个芯片,其他同事已经进行了平台开发,并提供了相关接口,包括ADC、GPIO、CAN等资源都已经封装好了,我们需要做的就是接口对应。以近光功能模块为例,LowBeam.c文件几乎可以忽略不计,它是Matlab软件根据我们搭建的Simulink逻辑自动生成的代码,可读性不是很强,但是功能肯定是实现了的,我们会对应好接口,只关注LowBeam.h文件就行。
在.h文件中找到以下代码:
/* External inputs (root inport signals with default storage) */typedef struct { IGN IG; /* '/IGN' */ uint8_T Front_Power_Supply; /* '/Front_Power_Supply' */ uint8_T Work_Cmd; /* '/Work_Cmd' */ uint8_T IS_Opencircuit; /* '/DILAMP_Open' */ uint8_T IS_Shortcircuit; /* '/DILAMP_Short' */ uint8_T New_Can_Flag; /* '/New_Can_Flag' */} ExtU_LowBeam_T;/* External outputs (root outports fed by signals with default storage) */typedef struct { uint8_T Low_Beam_On; /* '/Low_Beam_On' */ uint8_T Can_Send_Flag; /* '/Can_Send_Flag' */ CAN_MESSAGE_BUS CAN_Msg_HighBeam; /* '/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),它可以在主函数的主循环中每 10ms 调用一次:
if(Get1MsTickInterval(modeldely) >= 10){ modeldely = Get1MsTickVal(); LowBeam_step(); //近光灯}
其它功能模块可以按照同样的方法在近光灯函数下面添加_step函数。
总结
以上就是我们今天要讲的内容,本文简单介绍了如何通过Simulink搭建的逻辑框图生成嵌入式代码,并将生成的代码添加到现有的MCU项目中,实现近光灯模块的功能。
当然,自动代码生成需要在项目调用时多一层封装,占用一定的资源。而且对于一些精通业务的工程师来说,一些简单的手写逻辑,用很简洁的C代码量就可以实现同样的功能。但是我们使用自动代码生成技术也有相应的优势,比如当功能逻辑比较复杂时,我们可以更清晰地理清思路,使用simulink可以很方便地进行功能仿真。验证功能步骤可以放在代码生成之前,而手写代码必须在编译通过后,使用硬件设备进行功能测试后,才能烧录到sample中。这样,使用simulink进行功能仿真和自动代码生成,就省去了重复烧录的过程。
当然,Simulink 的代码生成功能远不止本文所提到的这些,作者会在项目中进行探索,并在后续的文章中与大家进一步探讨。