|
汽车零部件采购、销售通信录 填写你的培训需求,我们帮你找 招募汽车专业培训老师
全文约4864字,你将看到以下内容:
集成式MBD开发方式
把FOC算法模型嵌入代码
写在最后
姗姗来迟的第五期(天太冷了,搭模型、写文章也需要毅力
)。
本期将是2021年的收官,也将是MBD实战之电机控制系列的最后一期。这一期将介绍如何将模型生成的代码集成到已有的代码中去——把算法模型嵌入你的代码之中去。
1 集成式的MBD开发方式
这个话题这前面的文章中提过了很多次,之所以频繁的提及这种集成的方法,是因为这是目前MBD(Model-Based Design,基于模型的设计)的主流开发方式。
具体而言,集成式MBD的开发方法是:软件代码手写一部分,模型生成一部分,然后再将两部分集成在一起,从而实现完整的软件开发。
手写代码和模型生成有着不同的分工:
手写代码:适用于底层驱动、外设初始化、内存管理、中断管理、OS、Bootloader、协议栈等,还包括片外扩展IC的初始化,以及其他数学特征不明显的功能;
模型生成:适用于数学算法、状态机、数据处理等。
Tips:为了便于区分,本文将手写的代码称为底层代码,而模型生成的代码称为模型代码。底层代码负责的模块并不一定全部都是必须的,例如OS(嵌入式中一般使用实时操作系统,比如FreeRTOS),很多控制可能不需要OS,只需要中断管理即可。
可以看出,底层代码负责的功能,并不适合使用模型来实现。上一期给出的完全由模型实现的电机控制框架,虽然实现了电机控制,但实际上忽略了很多实际项目中必须的模块,例如OS、Bootloader等,还有很多安全机制也没有考虑。
底层代码功能理论上也能通过模型来实现,但没必要、也不需要用模型的方式来实现。MathWorks官方也一直推荐仅使用Simulink/MATLAB搭建算法模型,像底层驱动、内存管理、OS、Bootloader、协议栈等等,还是通过手写代码的方式更为高效。
这一点我有直接咨询过MathWorks的工程师,他告诉我底层代码要通过模型来实现,其难点在于TLC语言的复杂性和硬件芯片的多样性(市面上的芯片太多,每一个都需要适配),这两点没有专业的团队,很难实现模型的高效性。
Tips:我有了解到,有的公司利用StateFlow实现了类似于OS的Task Manager,用于管理模型中的不同任务。其构成也是相当复杂,没有专门的团队是没办法开发和维护的。但也仅限于任务调度——我感觉像复杂的状态机,OS的很多功能是没办法用StateFlow实现的。
对于有复杂算法的软件,MBD代码生成就可以大显身手了,算法越复杂,优势就越显著。举一些例子,如:电机控制、电池管理系统(BMS)、路径规划、紧急制动(AEB)、自动泊车(APS)等算法,其算法实现都可以通过MBD的方式来开发。
用MBD来开发算法,简单总结有如下优势:
便于算法实现和调整:这里指的并不是找Bug,算法的数学表达可能是固定的,但在特定的应用场景下,算法的局部需要特定调整,例如添加状态机实现不同状态的切换。借助模型的图形化,以及MATLAB/Simulink上的各种工具箱,这种调整是很直观,并且很容易实现。
专注算法且算法独立:使用模型搭建算法,我们只需要专注算法实现的本身,不需要考虑底层驱动等因素;由于不考虑底层驱动,算法模型可以方便地实现移植。有人说算法的核心是数学,写代码往往并不太容易兼顾数学,但搭建模型可以。
便利的仿真测试:算法的测试对于优化算法是非常强有力的途径,通过MBD,可以方便的实现MIL、SIL、PIL和HIL,根据测试结果,反馈到模型中,再对模型进行调整,这其中的效率是手写代码无法比拟的。
我自己是有完全手写代码实现电机控制软件所有环节的经验的,对比搭建MBD电机控制框架的开发过程,上述优点是非常明显的,我深有体会。这些优点对新手入门也是非常友好,特别是对非电子、计算机专业的人来说。
在使用集成式MBD开发项目时,软件实现一般需要两个角色:
底层工程师:底层工程师需要对MCU和硬件电路设计很熟悉,他负责实现底层驱动、MCU初始化、OS、Bootloader、协议栈等。他使用手写代码的方式进行开发,在底层代码中给算法预留好接口,以供算法集成。
算法模型工程师:算法模型工程师则负责搭建模型、测试算法,测试通过后就可以生成代码,然后集成到底层代码中去。
Tips:软件实现还可以有一个统筹底层代码和模型代码的架构工程师,给底层和模型设计需求;非软件的部分,一般有一位硬件工程师。
集成好了之后可能还需要进行测试,因为仿真毕竟和实际情况有差异。这时候再调整模型,或者调整底层代码,就可以做到彼此独立、互不干扰了。这一点在传统的开发中往往是一大痛点,而MBD可以很好的解决这个问题。
2 把FOC算法模型嵌入代码
依然以PMSM无感电机控制软件开发为例来介绍如何实现模型代码的集成,硬件平台是NXP的电机开发套件MCSPTE1AK144。
电机开发套件MCSPTE1AK144 - From NXP
后文中提到的最新的模型和代码已经更新到了我的GitHub仓库中,下载模型可以访问下面这个链接:
GitHub仓库:autoMBD电机控制项目
https://github.com/TkungAI/autoMBD_Motor_Control
也可以在对话框中发送关键词“MBD电机控制”,即可收到链接信息,公众号里还有其他资源,发送关键词“资源”可以收到下载链接。
回到正题,如何开展集成式MBD开发,有以下三个工作环节:
1. 实现算法模型
首先要明确算法模型的需求,可以简单地概括为:
算法的输入和输出是什么?
算法的内部变量有哪些?
算法要处理什么任务(状态机)?
这三个问题的解答也需要和底层工程师和硬件工程师一起协商、相互约束、相互补充,共同明确需求。下图是本次实现的电机控制算法模型(文件名FOC_Ctrl_CodeModel.slx):
PMSM无感电机控制算法模型 - From autoMBD
这里给出我在本次的PMSM无感电机控制软件开发中,对上述三个问题的回答:
输入和输出
输入为5个ADC采样信号和2个电机控制信号,ADC采用数据用于FOC算法、目标转速的输入,电机控制信号用于控制电机的启停;输出为三相占空比Duty Cycle和LED信号,分别用来执行控制和显示控制状态(参见上图)。
中间变量
中间量不会输出,是在算法处理数据过程中产生的,可以体系算法的特征,比如处理后的电压信号、电流信号、无感算法估计的速度和位置、控制信号等。
这部分通过Bus数据对象实现对数据结构体的定义。下图中的varFOC即包含了所有的中间量。
中间变量和StateFlow Chart - From autoMBD
状态机设计
这部分也就是明确电机控制软件具有的功能、任务。
在不同的阶段,算法执行的内容是不一样的,例如关闭电机的状态下,不需要跑FOC算法。本次项目中,只实现了简单启动、关闭和故障清楚的功能,状态相对简单,如下图所示:
算法模型状态机 - From autoMBD
Tips:如果有看之前提出的电机控制框架模型,应该就能发现这里的状态机与之非常相似。实际上这里的状态机是对原版的更新和优化,把函数调用方式从Function Call换成了Simulink Function,在Chart内部即可调用函数,不需要把调用的函数模型建立在Chart外面了。具体状态机的讲解可以参考电机实战的03期和04期的内容。
要注意的是,在Run状态下,调用的FOC核心算法模型,其包含了独属于FOC算法的子状态机和中间变量FocInternalPara。这里将FOC算法部分和与非FOC算法部分进行了分别设计,增加算法的灵活性和移植性。
FOC核心数学算法 - From autoMBD
FOC核心算法模型子状态机 - From autoMBD
Tips:该状态机只负责FOC算法的状态控制,包括对齐、开环、追踪和无感闭环四个状态,具体内容可以参考电机实战的04期的内容。
算法模型的内容就介绍到这里,其他的细节还请下载模型后打开查看。模型创建好了就可以生成代码了:
算法模型生成代码 - From autoMBD
这里生成的代码即集成时需要用到的代码。生成代码文件很多,其实是可以优化减少的,后续再开新的文章来讲解如何优化生成的代码。
2. 实现底层代码
Tips:底层代码的实现本质上就是手写代码的开发方式,只不过不做算法部分而已,所以需要有一定的编程基础。网络上有很多嵌入式开发的文章和案例,autoMBD的目标也不是介绍手写代码的开发,所以这里不会很细致的讲如何实现底层代码,仅从方法论的角度简单介绍一下。
可以发现,上述的算法模型中已经没有任何的硬件支持包的模块了,即已经不包含任何的底层驱动、外设初始化等等的功能,这部分由底层代码来实现。
要开发底层代码,需要用到集成开发环境IDE。对于S32K1系列芯片,NXP公司提供了一套免费的、专用于NXP汽车芯片的IDE工具——面向Arm?的S32 Design Studio(简称S32DS)。
面向Arm?的S32 Design Studio - From NXP
打开S32DS后,窗口如下图所示:
S32DS窗口 - From NXP
要配置底层驱动,需要用到S32K1专用图形配置工具——Processor Expert(上图中红色方框中的部分)。该工具能够实现对S32K1系列芯片的灵活配置。而底层驱动则需要使用S32K1 SDK,该SDK具有丰富的API供开发者使用,功能完善且强大。
关于Processor Expert和S32K1 SDK的使用,更多请参考NXP的技术文档。
本次项目中我也实现了一个基本的PMSM无感电机控制的底层代码,包括外设配置、MCU初始化和中断管理的内容,该DS工程位于仓库的这个位置:
S32DS_Prjct -> FOC_Ctrl_MBD_Integration。
该底层代码实现的功能与上一期中的电机控制框架基本一致。
3. 模型代码的集成
到这里,模型代码和底层代码都已经实现,如何集成呢?实际上生成的代码中有一个ert_main.c的文件就给出了集成的实例,读者可以参考该文件。
模型代码集成的过程是比较简单的,主要分为以下几步:
模型初始化:FOC_Ctrl_CodeModel_initialize()
周期性运算Step函数:FOC_Ctrl_CodeModel_step()或rt_OneStep()
终止(如果需要):????FOC_Ctrl_CodeModel_terminate()
Tips:调用并运行模型算法的是Step函数,即计算模型的一个步长。两个Step函数功能是一样的,rt_OneStep()在”模型Step“的基础上封装了一层错误检查,但该错误检查基本上起不了什么作用。
特别要注意的是周期性调用Step函数的周期,需要和算法的运算周期保持一致,不然可能会出现错误结果。
本次项目中,模型初始化在MCU初始化完成后调用;Step函数的调用在Motor_ISR中;模型算法不会终止,所以不需要Terminate函数。具体的代码如下:
/* Low level driver initialization */ MCU_Init(); GD3000_Init(); FMSTR_Init();
/* Initialize model */ FOC_Ctrl_CodeModel_initialize();
MCU_Start();
/* Forever loop */while(1) { FMSTR_Poll(); mainCnt++; }
voidMotor_ISR(void){/* Read ADC*/ ADC_DRV_GetChanResult(INST_ADCONV1, 0, &adcResult[0][0]); /* V_REFSH */ ADC_DRV_GetChanResult(INST_ADCONV1, 1, &adcResult[0][1]); /* Pot */ ADC_DRV_GetChanResult(INST_ADCONV1, 2, &adcResult[0][2]); /* iA */ ADC_DRV_GetChanResult(INST_ADCONV1, 3, &adcResult[0][3]); /* Temp */ ADC_DRV_GetChanResult(INST_ADCONV2, 0, &adcResult[1][0]); /* V_REFSL */ ADC_DRV_GetChanResult(INST_ADCONV2, 1, &adcResult[1][1]); /* uDC */ ADC_DRV_GetChanResult(INST_ADCONV2, 2, &adcResult[1][2]); /* iB */ ADC_DRV_GetChanResult(INST_ADCONV2, 3, &adcResult[1][3]); /* iDC */
/* Set model inputs here */ FOC_Ctrl_CodeModel_U.ADCinput[0] = adcResult[0][2]; FOC_Ctrl_CodeModel_U.ADCinput[1] = adcResult[1][2]; FOC_Ctrl_CodeModel_U.ADCinput[2] = adcResult[1][3]; FOC_Ctrl_CodeModel_U.ADCinput[3] = adcResult[1][1]; FOC_Ctrl_CodeModel_U.ADCinput[4] = adcResult[0][1]; FOC_Ctrl_CodeModel_U.FaultSwitch = faultSwitch; FOC_Ctrl_CodeModel_U.MotorSwitch = motorSwitch;/* Calculate one-step for the modle */ rt_OneStep();/* Get model output here */ pwmDuty[0] = (uint16_t)(FULL_DUTY * FOC_Ctrl_CodeModel_Y.DUTY[0]); pwmDuty[1] = (uint16_t)(FULL_DUTY * FOC_Ctrl_CodeModel_Y.DUTY[1]); pwmDuty[2] = (uint16_t)(FULL_DUTY * FOC_Ctrl_CodeModel_Y.DUTY[2]);
/* Set PWM duty */ FTM_DRV_FastUpdatePwmChannels(INST_FLEXTIMER_PWM1, 3, pwmChannels, pwmDuty, true);
/* Clear FTM flag */ FTM_DRV_ClearStatusFlags(3, FTM_TIME_OVER_FLOW_FLAG|FTM_RELOAD_FLAG);}
可以看到模型的传参和返回结果是通过全局变量的方式实现的:
输入全局变量:FOC_Ctrl_CodeModel_U
输出全局变量:FOC_Ctrl_CodeModel_Y
这里是可以通过对算法模型进行设置的,可以改成传参的方式,本次项目使用的是默认方式。
前文中的算法模型的中间变量也是一个全局变量:
模型中间变量:FOC_Ctrl_CodeModel_DW.varFOC
到这里模型代码的集成就算完成了,可以像手写代码的方式调试和下载代码了。
其实还有很多关于代码集成的细节和技巧没有提到,本次项目实现的模型代码完全是在默认设置下生成的。
Simulink是可以对生成的代码进行很细致的控制的,例如控制代码生成的位置、函数传参的方式、函数的复用等,还有代码如何优化,如何创建数据字典来管理模型数据。关于这些技巧和实践,我会在后续的文章中进行介绍,欢迎持续关注作者
。
3 写在最后
先说一点题外话,我最近才了解到NXP新发布的S32K3系列芯片,其最新的硬件支持包MBDT有了一个较大的升级,它的底层模块已经实现了完全等同于S32K3专用底层配置工具的功能。这一升级使得底层驱动这一功能也可以在Simulink模型中实现。
现在的S32K1的MBDT底层模块由于功能很有限,很多情况下底层驱动的配置必须使用专用配置工具Processor Expert来配置才能满足需求。而S32K3的更新有望改变这一现状,有兴趣的可以了解一下S32K3芯片和它最新的硬件支持包MBDT。
S32K3X4EVB-Q172 - From NXP
到本篇文章为止,我认为关于MBD的基础部分,应该已经写得差不多了,如果看完我的全部文章,应该能有所收获。
下一期我将对所有发布的文章,和GitHub仓库进行一次清理和总结。
明年autoMBD将会进入到下一个阶段,会尝试更有挑战的内容,例如TLC语言、MBD实现通信、MBD与OS的结合等。所以请持续关注autoMBD,更多技术分享不容错过。
2021年还剩下最后一周,在这里提前祝大家2022年新年快乐,希望各位读者能在2022年收获更多! |
|