引言
自动代码生成技术在航空航天、汽车、工业控制及自动化等领域有着广泛的应用。20世纪90年代,随着计算机技术和软件技术的发展,许多工业领域广泛采用基于模型的设计方法,极大地提高了系统的开发效率。基于模型的开发方法中一项核心的技术就是自动代码生成技术,在快速原型及系统实现等方面做出了巨大贡献[1]。
RealTime Workshop Embedded Coder(RTWEC)能够将Simulink中图形化设计的模块生成用户目标所需的C语言代码,该代码可以应用于许多实时或者非实时的场合,比如快速原型和硬件在环测试[2]。用户可以修改生成的代码,在独立于Matlab之外的环境下执行。代码转换是RTWEC的应用基础,它能生成与ANSI/ISO标准相兼容的C语言代码,这样的代码可运行于目标微控制器和实时操作系统(RTOS)。
本文以基于飞思卡尔公司的8位微控制器MC9S08QE128的电动智能车转向控制系统系统为例,阐述利用RTWEC生成高质量、高效率的C代码,并移植到该芯片内的过程。由于RTWEC对本课题所使用的MC9S08QE128并没有专门的支持,因此用户需要自己定制所需要的模块。
1RTWEC代码转换过程
RTWEC自动程序创建过程,能在不同主机环境下生成用于实时应用的程序。程序创建过程使用高级语言编译器中的联编实用程序,来控制所生成源代码的编译和链接过程。如图1所示,代码生成的整个流程可以分为3个步骤[3]。
图1RTWEC代码生成流程
① 通过RTWEC将建立起来的模型文件model.mdl文件编译成一个中间描述文件,即model.rtw。它包括模块参数、信号宽度、采样时间以及各模块的执行次序。该文件可以被用户修改,用来传递重要的参数信息。
② 读取中间描述文件mode1.rtw,然后进行编译和执行目标文件中的命令,包括系统目标文件和每一个模块目标文件。在这一阶段中,目标语言编译器TLC将中间描述文件model.rtw和相关的模块、S函数等转换为目标源代码。
③ RTWEC根据系统模板联编文件生成自定义联编文件model.mk,然后调用make实用程序,它根据生成的联编文件model.mk的规则,把生成的源代码编译、链接,从而生成可执行代码。比较特殊的一点是,CodeWarrior IDE使用的是project.mcp工程文件来进行编译、链接,而不是make实用程序,因此需要另外设计接口。
2目标驱动模块设计
目标驱动模块主要是用来生成微处理器所需的底层驱动代码,即对微处理器的寄存器进行设置。在CodeWarrior IDE的指令集中,大量寄存器的设置因所选单片机型号各异,对不熟悉其复杂指令的设计人员来说,调试过程是很漫长的。而目标驱动模块如同CodeWarrior IDE的Processor Expert模块,更为方便地提供给用户进行设计。如图2所示,用户只需要在PORT下拉列表框中选择相应的端口,而不需要了解繁琐的寄存器设置。
图2数字I/O输入模块
根据上述RTWEC代码生成的流程,可以得到目标驱动模块的设计过程。目标驱动模块主要是由S函数和TLC文件定制的。S函数(需要被封装)用于定制自定义simulink模块,该模块可以传递用户设置的参数信息,并保存在model.rtw文件中。TLC文件可以取出model.rtw文件中保存的参数信息,通过内联和一定的代码生成规则,用于控制代码生成过程与格式。
下面以数字I/O输入模块为例来说明目标驱动模块的设计。
(1) 编写C MEX S函数
S函数有很多种设计方式,比如m文件和C语言等。其中,只有用可被编译C语言文件(C MEX文件)或者S函数生成器建立起来的S函数,才能够内联TLC文件,从而生成所需代码。在编写S函数中,最重要的是参数传递,即将用户所选择的参数传递到TLC文件中去。这里RTWEC提供了一个model.rtw文件,该文件可以用作S函数和TLC文件的桥梁。其实现方法是,通过mdlRTW函数将用户参数传递到rtw中的SFcnParamSettings对象中去。代码如下:
#if defined(MATLAB_MEX_FILE)
#define MDL_RTW
static void mdlRTW(SimStruct *S) {
real_T port_name = mxGetPr(ssGetSFcnParam(S, 0))[0];
if (!ssWriteRTWParamSettings(S, 1, SSWRITE_VALUE_NUM, "port_name", port_name)) {
return;
}
}
#endif
这样,在TLC文件中通过SFcnParamSettings.port_name便可以调用该参数。完成后用命令mex进行编译,即可生成所需的C MEX文件。
(2) 封装S函数
该步骤主要为方便用户进行参数设置,注意参数名与S函数对话框中的参数名要一致,而且必须是DOUBLE类型的。封装好的模块便可以放到自定义的Simulink模型库中去。
(3) 编写TLC文件
这一文件的编写非常重要,它直接决定了生成代码的形式,即代码内容和它安放在生成的C语言文件中的位置。其参数的获得正是通过rtw中的SfcnParamSettings对象所得到的。注意,TLC文件名与S函数一致才能够被内联。下列TLC语句段显示了如何根据用户的选择将寄存器的设置嵌入到生成的代码中去:
%function Outputs(block, system) Output
%assign py0 = LibBlockOutputSignalAddr(0, "", "", 0)
%assign port_name=SFcnParamSettings.port_name
%if port_name==1
%<py0>=PORTA;
%elseif port_name==2
%<py0>=PORTB;
%else
%<py0>=PORTC;
%endif
%endfunction
3CodeWarrior IDE接口设计
为了在RTWEC生成代码的同时建立起CodeWarrior IDE的工程文件,并自动对其进行操作。RTWEC提供了两种截然不同的方法[4]。
(1) XML工程导入
CodeWarrior IDE的大多数版本都支持可扩展标记语言(XML)形式的工程导入或者导出。通过修改TLC文件,RTWEC可自动生成XML文件,作为CodeWarrior IDE的工程导入。但该方法有个缺陷,每当用户在CodeWarrior IDE中修改工程时,必须重新修改TLC文件,给使用带来了很大不便。其设计过程如图3所示。
图3以XML工程导入为接口
(2) COM组件控制工程建立
在CodeWarrior IDE有许多的接口函数,而这些函数是可以通过用户建立COM组件来进行调用的。COM组件技术可以自动执行一些重复性的任务,从而进行外部操作。比如通过命令actxserver('CodeWarrior.CodeWarriorApp')来打开CodeWarrior IDE,从而对其进行控制。
本文尝试了一种手工建立工程的方法,即在CodeWarrior IDE中建立工程,修改源文件main.c,然后将生成的代码文件和函数分别添加到工程和main.c中去,从而形成一种文件路径的映射关系。这样,每次在RTWEC中生成代码后,直接用CodeWarrior IDE编译即可。该方法使用简单,但缺点是自动化程度差,用户需要自行建立mcp工程,添加和修改文件。
4智能车随动算法的C代码生成与测试
下面以一个实例来说明RTWEC代码的生成及其测试。本文所设计的系统将前瞻传感器安装在智能车的伺服电机(随动舵机)上,前置并悬伸出车体前端,将前瞻距离延长到前排传感器所能达到的最大距离。然后,控制随动舵机使其跟踪黑线,并始终将黑线置于随动光电组的中间位置。在控制算法中通过随动比调节转向舵机的转角,使其配合随动舵机进行转向,如此便能达到智能车系统的超前调节和路径优化等目的[5]。
图4 随动控制算法的有限状态机模型
图4为利用Matlab/StateFlow搭建起来的控制器模型。该模型比较复杂,手写C代码易出错,调试周期长;而使用RTWEC自动代码生成技术,调试过程中很容易检查出逻辑冲突、数据溢出等错误,大大缩短了开发周期。
在生成的代码中,最重要的是初始化函数model_initialize()和运行主函数model_step()。设计人员只要将这两个函数包含在主函数里,就可以运行了。
为了测试RTWEC生成代码的正确性和运行效率,将同样的控制算法的生成代码和手写C代码放在VC++中运行。运行前将一传感器返回值设为1,设置不同的运行时间(运行次数),比较计算结果和CPU消耗周期(cpucounter)。其中计算结果和随动转角(angle)成正比,CPU消耗周期用库函数QueryPerformanceCounter()计算得到。
图5是手写代码与生成代码消耗CPU周期对比,图6为手写代码与生成代码计算结果对比。可见两者极为接近,自动生成的代码运行正确,且效率没有明显降低。
图5手写代码与生成代码消耗CPU周期对比
图6手写代码与生成代码计算结果对比
5结论
生成代码在CodeWarrior编译后效率会降低,但随着程序长度的增加,代码运行的主要执行时间将更多地消耗在逻辑判断上,RTWEC生成代码的执行速度会相对得到提高。编程人员除了要提高代码的执行效率之外,还需要考虑设计的快速性、调试的方便性,以及可读性等多方面因素。