在ARM微控制器上部署MATLAB/Simulink仿真模型

苏勇,suyong_yq@126.com,2022年12月

文章目录

  • 在ARM微控制器上部署MATLAB/Simulink仿真模型
    • Introduction
      • Overview
      • MATLAB、Simulink、StateFlow、Real-Time Workshop之间的关系
      • MATLAB Coder、Simulink Coder、Embedded Coder之间的关系
    • 从MATLAB算法模型到C语言
      • 在Simulink中配置模型的开发环境
      • 在MATLAB中创建符号和框图描述的算法模型
      • 使用TLC目标语言编译器生成描述算法的C源码
      • 将模型C源码集成到嵌入式平台工程
        • 集成模型C源码码到Keil工程中
        • 在嵌入式工程中适配模型
          • 使用硬件定时器调用求解器程序
          • 将模型的控制量同步到电路系统中
          • 在main()函数中调用模型
        • 编译嵌入式工程并运行
    • 参考文献

Introduction

本文在于记录使用Simulink建模仿真,到最终部署到微控制器上的过程。从Simulink算法模型到目标硬件平台可以支持的可执行文件,大体经过Matlab的各种模型描述语言,编译成通用C语言,再在目标硬件平台的编译环境中编译生成最终的可执行文件。市面上的相关的学术研究和实践经验,大多是使用了MATLAB已经官方认证集成的BSP,例如:Arduino、TI DSP、STM32、树莓派等,在这些工具包的加持下,MATLAB配合具体的目标平台的集成开发环境,可以一通到底。但实际上,MATLAB只要在将Simulink工具描述的算法模型转化成C语言,完全可以由开发者自行进一步适配的具体平台,而不需要依赖于具体的官方认证的设备支持包。更进一步,适用于Arm兼容的微控制器,Matlab已经集成了一个通用的模板,在将Matlab算法语言转化成C语言的过程中特别考虑在嵌入式平台上执行的代码优化。

本文详细演绎了从Simulink创建模型,仿真验证,之后再生成C源码部署到plus-f5270开发板的全过程。 其中描述的方法和操作步骤,为后续部署更多仿真模型奠定了基础。

Overview

从Simulink中以符号图形和算法框图表示的仿真模型,转化成可编译的C源码,再到可以运行在嵌入式平台的可执行程序,经历了多个阶段的处理,在每个阶段需要对应的工具,将上一阶段的输出文件作为本阶段的输入,经过处理后再输出给后续的环节。

图x 从Matlab模型到目标平台可执行文件

图x完整展示了MATLAB从算法模型到可执行程序的各个环节:

  • 使用MATLAB、Simulink和Stateflow工具可以在MATLAB中创建模型,此时的模型可能是以逻辑图符号的方式表示,保存成model.mdl文件

  • RealTime Workshop(RTW)是MATLAB中的一个工具,是用来做仿真的软件开发环境,可以使用描述模型符号及其逻辑关系的model.mdl文件作为输入,经过本地编译,形成描述模型的model.rtw文件。RTW文件是以ASCII码的形式存放,开发者可以直接读其中的内容。

    • TLC目标语言编译器(Target Language Compiler)是生成C源码的关键环节。TLC目标语言编译器也是RTW的一部分,可以读取model.rtw文件
    • 中的信息,将模型转化成源代码的描述。TLC目标语言编译器可以输出成多种语言,例如M语言、VHDL语言等,C语言是其中的一种选项。生成C语言也可以使用不同的模板,以约束生成代码的组织方式和风格,例如,一般实时目标使用grt.tlc模板,嵌入式实时目标使用ert.tlc模板。
    • 使用RTW生成源码,还有更多灵活的操作,例如,使用配置宏观代码框架的系统TLC文件和配置单个模块的模块TLC文件,直接用函数源码定义算法模块的S-Function等。RTW不能转换S-Function的代码,需要开发者手写代码嵌入到生成代码中。
    • RTW还能根据makefile的模板文件,生成可适用于目标硬件平台编译环境的makefile文件model.mk。但这个不是必须的,如果某些集成开发环境没有适配到MATLAB中,就需要手动添加生成的源文件到对应的集成开发环境当中。
  • 通过TLC目标语言编译器生成的C源码文件model.c文件,可以直接加入到嵌入式系统的集成开发环境当中(例如Keil)参与编译。但实际上,这里生成的C源码文件主要是描述算法的实现,是应用程序的框架,仍需要开发者在具体嵌入式硬件平台上,结合实际应用,手动向其中嵌入具体的输入输出函数。

  • MATLAB的工具生成算法执行的源码框架,配合目标嵌入式硬件平台的适配移植,最终在嵌入式硬件平台的编译环境中完成编译链接,最终生成可以在目标嵌入式硬件平台上运行的可执行文件。后续可以使用目标嵌入式硬件平台的一般操作过程完成下载和调试。

MATLAB、Simulink、StateFlow、Real-Time Workshop之间的关系

MathWorks ®

  • MATLAB 是一个集成开发环境,包含M语言的解析器,同时还提供多种特定应用的工具箱。
  • Simulink 是集成在 MATLAB 中的众多工具箱的一个,作为建模、分析和仿真的交互环境。
  • Stateflow 是Simulink中的一个功能组件,通过状态图执行选项拓展了 Simulink 的功能,使得可编程的状态图也能作为Simulink仿真系统中的一个组件。
  • Real-Time Workshop 在早期版本的MATLAB中曾经作为一个独立的组件,但后来并入Simulink工具箱中,可用于从Simulink模型生成优化的、可移植的和可定制的ANSIC代码。利用它可以针对某种目标平台(例如嵌入式平台)或是部分子系统可下载执行的C代码,以展开硬件在回路仿真。
  • Simulink Coder即是之前的 Real-Time Workshop 和 StateflowCoder,可以从 Simulink 框图和 Stateflow 系统以及 TargetLink 模型中自动生成 C 代码。

MATLAB Coder、Simulink Coder、Embedded Coder之间的关系

Mathworks公司的MATLAB软件环境中有3个代码生成工具:Matlab Coder、Simulink Coder和Embedded Coder。MATLAB可以将M语言转化成目标代码,Simulink Coder可以将Simulink中的仿真模型转化成目标代码,而Embedded Coder 依赖于MATLAB Coder以及Simulink Coder的,通过进一步优化,主要生成用于部署在嵌入式平台上的源码。

图x MATLAB中的3中Coder生成器的关系

考虑最终将生成的C代码部署到嵌入式平台上,从MATLAB程序和从Simulink模型生成源码的工具流有一些区别,如图x所示。

图x MATLAB中多种Coder生成器的工作流

从MATLAB算法模型到C语言

此处以实现一个流水灯的模型为例,说明基于模型设计的全过程。

在Simulink中配置模型的开发环境

在MATLAB中启动Simulink后,可通过工具栏的“建模”->“模型设置”,激活“配置参数”对话框。如图x所示。

图x 在Simulink中打开配置参数对话框

在“配置参数”对话框中,首先配置“求解器”。求解器是Simulink用数值方法结算模型切片状态的引擎,对应于Simulink的仿真过程的行为。这里考虑将来是在嵌入式系统中作为周期任务更新求解过程,设定在Simulink的仿真行为同嵌入式系统类似,为“离散”的“固定步长”,计算步长的时间为10ms,对应于配置对话框中的0.01s。如图x所示。在实际的嵌入式系统平台运行求解器时,将使用一个硬件定时器,以10ms的周期触发中断,执行求解器的程序。

图x 在配置参数对话框中配置求解器

在“配置参数”对话框的“硬件实现”标签中,选择即将生成代码的目标平台。考虑到未来将会在ARM微控制器的平台上部署(但不在Simulink的支持设备清单中),这里将“设备供应商”选择为“ARM Compatible”,选择“设备”类型为“ARM Cortex-M”。特别注意将“Code Generation system target file”选择成为“ert.tlc”,这是最终生成C语言的配置文件。

图x 在配置参数对话框中配置硬件实现

实际上,在配置TLC时,配置参数对话框会自动切换到“代码生成”页面,在这里选择使用etc.tlc文件。如图x所示。

图x 在配置参数对话框中配置代码生成

从图x中可以看到,Simulink提供了多种TLC配置文件可选。这里选择的ert.tlc,是专门适用于Embedded Coder生成嵌入式系统C代码的。

配置好项目之后,保存模型文件到文件系统中。从图x中可以看到,Simulink保存的模型文件可以有两种后缀名,一种是“.slx”,另一种是“.mdl”,这里,我们使用默认的“.slx”文件后缀名,保存成“leds_model.slx”文件。

图x 在Simulink中保存模型文件

在MATLAB中创建符号和框图描述的算法模型

在plus-f5270开发板上设计了4个可编程的LED灯,有原理图如图x所示。

图x plus-f5270开发板上的可编程LED灯

这里要设计一个逻辑,用一个计数器驱动小灯的亮暗状态,当计数器输出0时,控制仅LED1亮,当计数器输出1时,控制仅LED2亮,以此类推。计数器从0到3周期计数,小灯也依次闪烁。

在“库浏览器”中选择需要的符号元件,拖放到绘图工作区中,例如,常量“Constant”、等于号“Equal”、有限计数器“Counter Limited”、输出端点“Out”等,并可用示波器“Scope”和显示器“Display”查看仿真输出结果。使用连线,将符号的输入输出信号连接在一起,形成一个有输入输出的完整系统的功能框图。如图x所示。

图x leds_model模型的功能框图

从图x中可以看出,计数器的输出值驱动4个LED灯的亮暗状态。但默认情况下,计数器的计数节奏是同求解器的计算步长保持一致的,如之前设定求解器的计算步长是10ms,这就意味着这些小灯会闪烁得非常快,难以被人眼观察到。这里希望调整计数器的计数节奏为1m,需要调整计数器的计数节奏。双击计数器的图标,可以激活计数器模块的参数配置对话框,配置其中的“采样时间”值为1。如图x所示

图x 配置计数器的计时步长

此时,使用计数器控制小灯闪烁的流水灯模型就已经搭建完成。

在仿真的工具栏中,点击“步进”即可逐步运行,点击“运行”可以全速仿真运行。如图x所示。

图x Simulink的仿真工具栏

启动仿真后,可以通过系统框图中的示波器模块查看仿真结果,如图x所示。

图x leds_model模型中的示波器模块显示仿真结果

从图x中显示的示波器输出波形可以看到,4个输出通道以1s为控制节奏,依次输出高电平。验证仿真结果达到预期设计。

使用TLC目标语言编译器生成描述算法的C源码

经过仿真验证的系统设计框图,可以直接转化成嵌入式系统可以可以执行的C程序源码。在调用RTW编译模型生成C源码之前,必须再次确认几个配置要点:

  • 配置模型使用ert.tlc,这是专用于嵌入式系统平台的目标语言编译器配置选项。
  • 配置求解器的计算步长,0.001s或者0.01s均可,可根据实际需要的计算时间精度设定,这个时间长度对应于嵌入式系统中部署模型时,周期调用求解器程序的中断服务程序的周期。
  • 在MATLAB的主窗口切换当前工作目录到指定的项目录下。如图x所示。即将生成的C代码工程将位于这里指定的目录下。

图x 在MATLAB中设定当前工作目录

然后,试着生成C源码。在Simulink的界面中,在工具栏“APP”页面中选择“Embedded Coder”激活“C代码”标签页,然后在“C代码”标签页中其中选择“生成待代码”。如图x所示。


图x 在Simulink中使用Embedded Coder生成C代码

此时,Simulink的目标语言编译器TLC将启动编译,并生成C源码程序文件。如图x所示。

图x Embedded Coder生成的C源码

至此,在Simulink中调用Embedded Coder,已经将创建的leds_model模型转化成了C源码,保存在一系列源码文件中。此时,可以在预先设定的当前工作目录中,找到新建保存生成源码文件的文件夹“leds_model_ert_rtw”,顾名思义:leds_model是模型的名字;ert是Embedded Real-Time Target,对应源码生成器的配置选项;rtw是Real-Time Workshop,这是源码生成器的工具包,在MATLAB的早期版本中独立作为一个工具包,现在已经被集成在源码生成器内部。

将模型C源码集成到嵌入式平台工程

集成模型C源码码到Keil工程中

这里准备了一个plus-f5270开发板上可以运行的工程,plus-f5270_hello_world_mdk,作为部署模型的基础。

首先,将工程的目录名改为“plus-f5270_leds_model_mdk” ,便于标识该工程专用于部署leds_model模型。在该工程根目录下创建“model”目录,将由Embedded Coder生成的模型C源码文件全部复制到其中。此时,工程目录的源码结构如图x所示。

图x 将生成的模型源码文件复制到Keil工程目录下

然后,在Keil工程中添加模型源码,先试着编译一下,验证模型文件本身源码的完整性。如图x所示。

图x 在Keil中编译包含模型源码的工程

经编译验证可知,模型源码本身是闭包无误的,不会为基础工程引入额外的错误,也不需要专门针对嵌入式编译环境调整任何源码。在Keil中编译模型文件需要注意:

  • 需要在Keil工程中为新加入的模型源码添加源文件搜索路径。
  • 不要添加模型的ert_main.c文件。Embedded Coder为模型生成的ert_main.c文件中,包含了在嵌入式环境中调用模型的参考用例,其中包含两个重要的函数:一个是顶级调用main()函数,另一个是需要基于硬件定时器中实现的中断回调函数rt_OneStep()。在ert_main.c文件中,有注释对main()函数和rt_oneStep()函数的用法进行了详细的解释。见代码x所示。
代码x Embedded Coder为leds_model生成的ert_main.c文件
/** File: ert_main.c** Code generated for Simulink model 'leds'.** Model version                  : 1.4* Simulink Coder version         : 9.8 (R2022b) 13-May-2022* C/C++ source code generated on : Mon Dec 19 18:09:56 2022** Target selection: ert.tlc* Embedded hardware selection: ARM Compatible->ARM Cortex-M* Code generation objectives: Unspecified* Validation result: Not run*/#include <stddef.h>
#include <stdio.h>            /* This example main program uses printf/fflush */
#include "leds_model.h"                      /* Model header file *//** Associating rt_OneStep with a real-time clock or interrupt service routine* is what makes the generated code "real-time".  The function rt_OneStep is* always associated with the base rate of the model.  Subrates are managed* by the base rate from inside the generated code.  Enabling/disabling* interrupts and floating point context switches are target specific.  This* example code indicates where these should take place relative to executing* the generated code step function.  Overrun behavior should be tailored to* your application needs.  This example simply sets an error status in the* real-time model and returns from rt_OneStep.*/
void rt_OneStep(void);
void rt_OneStep(void)
{static boolean_T OverrunFlag = false;/* Disable interrupts here *//* Check for overrun */if (OverrunFlag) {rtmSetErrorStatus(leds_model_M, "Overrun");return;}OverrunFlag = true;/* Save FPU context here (if necessary) *//* Re-enable timer or interrupt here *//* Set model inputs here *//* Step the model */leds_model_step();/* Get model outputs here *//* Indicate task complete */OverrunFlag = false;/* Disable interrupts here *//* Restore FPU context here (if necessary) *//* Enable interrupts here */
}/** The example main function illustrates what is required by your* application code to initialize, execute, and terminate the generated code.* Attaching rt_OneStep to a real-time clock is target specific. This example* illustrates how you do this relative to initializing the model.*/
int_T main(int_T argc, const char *argv[])
{/* Unused arguments */(void)(argc);(void)(argv);/* Initialize model */leds_model_initialize();/* Attach rt_OneStep to a timer or interrupt service routine with* period 0.001 seconds (base rate of the model) here.* The call syntax for rt_OneStep is**  rt_OneStep();*/printf("Warning: The simulation will run forever. ""Generated ERT main won't simulate model step behavior. ""To change this behavior select the 'MAT-file logging' option.\n");fflush((NULL));while (rtmGetErrorStatus(leds_model_M) == (NULL)) {/*  Perform application tasks here */}/* Terminate model */leds_model_terminate();return 0;
}/** File trailer for generated code.** [EOF]*/

在嵌入式工程中适配模型

在plus-f5270工程的boards目录下创建model_port.c文件,集中存放基于plus-f5270开发板适配leds_model模型的配置源码。在model_port.c文件中创建model_init()函数,在顶级调用main()函数中将调用model_init()函数初始化并启动模型求解器。同时,在model_port.c文件中创建model_input()和model_output()函数,用于同步模型同硬件的绑定关系。

Note:在模型中使用的输入和输出,都是保存在内存中的数据。在微控制器芯片内部的电路系统中,通过访问地址映射空间中的寄存器,控制的状态和行为。在面向嵌入式平台设计的仿真系统中,生成的C源码,是不会直接控制具体嵌入式平台的电路系统的。这个比较好理解,因为仿真系统是同一份,但运行在不同的嵌入式平台上,对应的寄存器是不同的。这就需要嵌入式平台的开发者自行适配代码,将电路系统中状态量和控制量,与仿真模型的输入和输出进行同步。

使用硬件定时器调用求解器程序

在plus-f5270开发板上,选用硬件定时器TIM4产生周期为10ms的中断服务程序,在中断服务中周期调用模型生成的求解器处理函数,执行模型。

在model_init()函数中配置硬件定时器TIM4的代码如下:

/* model_port.c */
#include "board_init.h"#include <stddef.h>
#include <stdio.h>            /* This example main program uses printf/fflush */
#include "leds_model.h"                      /* Model header file */extern void leds_model_initialize(void);
extern void leds_model_step(void);void model_init(void)
{/* prepare the models. */leds_model_initialize();/* setup timer. */TIM_Init_Type tim_init;tim_init.ClockFreqHz = BOARD_MODEL_TIMER_CLKSRC_HZ;tim_init.StepFreqHz = 1000000; /* 1mhz per step. */tim_init.Period = 10000; /* 100hz for a round. */tim_init.EnablePreloadPeriod = true;tim_init.PeriodMode = TIM_PeriodMode_Continuous;tim_init.CountMode = TIM_CountMode_Increasing;TIM_Init(BOARD_MODEL_TIMER_PORT, &tim_init);/* enable interrupt. */NVIC_EnableIRQ(BOARD_MODEL_TIMER_NVIC_IRQn);TIM_EnableInterrupts(BOARD_MODEL_TIMER_PORT, TIM_INT_UPDATE_PERIOD, true);/* start timer. */TIM_Start(BOARD_MODEL_TIMER_PORT);
}/* bind the model input to hardware. */
void model_input(void)
{
}/* bind the model output to hardware. */
void model_output(void)
{
}/* hardware timer interrupt for model solver. */
void BOARD_MODEL_TIMER_NVIC_IRQHandler(void)
{uint32_t flags = TIM_GetInterruptStatus(BOARD_MODEL_TIMER_PORT);if (flags & TIM_STATUS_UPDATE_PERIOD){model_input();leds_model_step(); /* call the model solver step by step. */model_output();}TIM_ClearInterruptStatus(BOARD_MODEL_TIMER_PORT, flags);
}
/* EOF. */

当在model_init()函数中调用TIM_Start()函数之后,硬件定时器开始工作,自动周期触发定时器中断服务,由硬件中断服务程序自动周期调用模型生成的求解器程序leds_model_step()。在定时器中断服务程序中调用求解器之前,需要手动执行在适配过程中创建的model_input()函数,将嵌入式平台电子系统中的状态量同步到模型内部的输入变量中;在调用求解器之后,需要手动执行在适配过程中创建的model_output()函数,将模型求解器更新的输出变量的状态同步到嵌入式平台电子系统中,控制电路完成相关的操作。

将模型的控制量同步到电路系统中

具体在本例中使用定时器控制LED灯,仅需要在model_output()函数中实现根据模型输出状态控制LED灯的亮暗状态。但嵌入式平台进行编程时需要注意,使用GPIO控制LED灯之前,需要对将要使用到的GPIO硬件资源预先初始化,配置好对应的外设时钟、引脚复用功能,以及初始状态等。

在model_port.c文件中的model_init()函数中,添加配置GPIO引脚控制LED灯全部熄灭,在model_output()函数中,根据模型中表示LED灯状态的变量leds_model_Y.ledn(n=1, 2, 3, 4)配置到控制LED灯的GPIO引脚输出信号上。

void model_init(void)
{.../* setup leds. */GPIO_WriteBit(BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, 1u);GPIO_WriteBit(BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, 1u);GPIO_WriteBit(BOARD_LED3_GPIO_PORT, BOARD_LED3_GPIO_PIN, 1u);GPIO_WriteBit(BOARD_LED4_GPIO_PORT, BOARD_LED4_GPIO_PIN, 1u);...
}/* bind the model output to hardware. */
void model_output(void)
{GPIO_WriteBit(BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, (leds_model_Y.led1?0u:1u) );GPIO_WriteBit(BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, (leds_model_Y.led2?0u:1u) );GPIO_WriteBit(BOARD_LED3_GPIO_PORT, BOARD_LED3_GPIO_PIN, (leds_model_Y.led3?0u:1u) );GPIO_WriteBit(BOARD_LED4_GPIO_PORT, BOARD_LED4_GPIO_PIN, (leds_model_Y.led4?0u:1u) );
}

在board_init.h文件中定义LED和硬件定时器的具体资源,并添加对model_init()函数的声明。board_init.h文件也是main.c中包含硬件相关资源的统一入口,在适配模型的过程中所需要的其他软硬件相关的组件,也都可以在board_init.h文件的头部声明引用。

#ifndef __BOARD_INIT_H__
#define __BOARD_INIT_H__#include <stdio.h>
#include <stdint.h>#include "hal_common.h"
#include "hal_rcc.h"
#include "hal_uart.h"
#include "hal_tim.h"
#include "hal_gpio.h"#include "clock_init.h"
#include "pin_init.h"/* DEBUG UART. */
#define BOARD_DEBUG_UART_PORT        UART1
#define BOARD_DEBUG_UART_BAUDRATE    115200u
#define BOARD_DEBUG_UART_FREQ        CLOCK_APB2_FREQ/* leds. */
#define BOARD_LED1_GPIO_PORT         GPIOI
#define BOARD_LED1_GPIO_PIN          GPIO_PIN_0
#define BOARD_LED2_GPIO_PORT         GPIOD
#define BOARD_LED2_GPIO_PIN          GPIO_PIN_2
#define BOARD_LED3_GPIO_PORT         GPIOB
#define BOARD_LED3_GPIO_PIN          GPIO_PIN_14
#define BOARD_LED4_GPIO_PORT         GPIOC
#define BOARD_LED4_GPIO_PIN          GPIO_PIN_9/* model timer. */
#define BOARD_MODEL_TIMER_PORT       (TIM_Type *)TIM4
#define BOARD_MODEL_TIMER_CLKSRC_HZ  CLOCK_APB1_FREQ
#define BOARD_MODEL_TIMER_NVIC_IRQn  TIM4_IRQn
#define BOARD_MODEL_TIMER_NVIC_IRQHandler TIM4_IRQHandlervoid BOARD_Init(void);
void model_init(void);#endif /* __BOARD_INIT_H__ */

在clock_init.c文件中的BOARD_InitBoardClocks()函数中添加代码,启用GPIOB、GPIOC、GPIOD、GPIOI,以及TIM4外设模块的总线时钟。

void BOARD_InitBootClocks(void)
{CLOCK_ResetToDefault();CLOCK_BootToHSE120MHz();/* UART1. */RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_UART1, true);RCC_ResetAPB2Periphs(RCC_APB2_PERIPH_UART1);/* GPIOB. */RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true);RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOB);/* GPIOC. */RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOC, true);RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOC);/* GPIOD. */RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOD, true);RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOD);/* GPIOI. */RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOI, true);RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOI);/* TIM4. */RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM4, true);RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_TIM4);
}

在pin_init.c文件中的BOARD_InitPins()函数中添加代码,配置LED灯对应的PI0、PD2、PB14、PC9引脚的复用功能为GPIO输出。

void BOARD_InitPins(void)
{/* PB7 - UART1_TX. */GPIO_Init_Type gpio_init;gpio_init.Pins  = GPIO_PIN_6;gpio_init.PinMode  = GPIO_PinMode_AF_PushPull; //GPIO_PinMode_AF_PushPullgpio_init.Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio_init);GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_7);/* PB6 - UART1_RX. */gpio_init.Pins  = GPIO_PIN_7;gpio_init.PinMode  = GPIO_PinMode_In_Floating; //GPIO_PinMode_In_Floatinggpio_init.Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio_init);GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_7);/* PI0 - led1. */gpio_init.Pins  = GPIO_PIN_0;gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;gpio_init.Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOI, &gpio_init);GPIO_PinAFConf(GPIOI, gpio_init.Pins, GPIO_AF_15);/* PD2 - led2. */gpio_init.Pins  = GPIO_PIN_2;gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;gpio_init.Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOD, &gpio_init);GPIO_PinAFConf(GPIOD, gpio_init.Pins, GPIO_AF_15);/* PB14 - led3. */gpio_init.Pins  = GPIO_PIN_14;gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;gpio_init.Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &gpio_init);GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_15);/* PC9 - led4. */gpio_init.Pins  = GPIO_PIN_9;gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;gpio_init.Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &gpio_init);GPIO_PinAFConf(GPIOC, gpio_init.Pins, GPIO_AF_15);
}
在main()函数中调用模型

模型的求解器是由硬件定时器中断自动触发执行的,除了模型的初始化函数mode_init()之外,不需要嵌入式平台的开发者在main() 函数中人为调用任何模型生成的函数,并且也不影响原有程序的正常执行。实际上,本例工程中的main.c,在原有hello_world工程的main.c中,仅添加了一行model_init()函数的调用。

int main(void)
{uint8_t ch;BOARD_Init();printf("hello, world\r\n");/* init & start the model solver. */model_init();while (1){ch = getchar();putchar(ch);}
}

编译嵌入式工程并运行

在嵌入式平台上编译工程和下载的操作同一般嵌入式开发无异。在Keil中编译,下载可执行程序到plus-f5270开发板中。

图x 在Keil中编译包含模型源码的嵌入式工程

下载可执行程序到到开发板上,运行,可以观察到开发板上的4个LED小灯以1s为节奏演示流水灯的效果。如图x所示。

图x 在plus-f5270上部署leds_model模型

参考文献

  • 《基于模型的设计及其嵌入式实现》(第2版), 北京航空航天大学出版社, 刘杰, 2017.
  • MATLAB/Simulink&&STM32CubeMX&&Keil工具链完成基于模型的设计开发(一), https://blog.csdn.net/weixin_42650162/article/details/124760682, 2022-12-19.
  • simulink coder与embeded coder代码生成的区别, https://zhuanlan.zhihu.com/p/158618870, 2022-12-19.

在ARM微控制器上部署MATLAB/Simulink仿真模型相关推荐

  1. 60秒计时器的仿真电路_物联网应用基于Arm微控制器的低功耗定时关机计时器

    本文首发极术社区 作者:Philex Fan 翻译:Khorina 原文:物联网应用基于Arm微控制器的低功耗定时关机计时器 由于物联网传感器节点的尺寸形式非常小,他们的计算平台有严格的能源约束.为了 ...

  2. 嵌入式系统开发笔记91:认识ARM微控制器架构

    文章目录 前言 一.ARM 二.ARM微控制器的市场占有率 1.95%的智能手机 2.90%的平板电脑 3.96%的硬盘驱动器 4.85%的无人机 5.83%的数字电视 6.75%的工业机器人 三.A ...

  3. 基于VS Code搭建通用ARM微控制器开发平台

    基于VS Code搭建通用ARM微控制器开发平台 Data Author Version Note 2022.04.12 Dog Tao V1.0 Release as V1.0 - 使用基于STM3 ...

  4. 清华大学视频:ARM微控制器与嵌入式系统

    清华大学视频课件:ARM微控制器与嵌入式系统(2017) 本课程课程要求学生具备C语言编程基础,在课程中逐步讲解ARM微控制器(单片机)与嵌入式系统,面向实践安排教学,鼓励动手实践和自由创新,适合想参 ...

  5. 在MM32F5微控制器上使用外扩SRAM作为主内存

    在MM32F5微控制器上使用外扩SRAM作为主内存 苏勇,2022年8月 文章目录 在MM32F5微控制器上使用外扩SRAM作为主内存 引言 硬件电路 软件设计 使用bootloader初始化硬件环境 ...

  6. ARM微控制器-MCU基础及CPU运行过程(堆栈/中断/寄存器操作)

    目录 为什么计算机能读懂1和0? 一. CPU的基本结构和运行机制 1. 一个基本的MCU内部结构 2. MCU Structure 3. 分析其中的CPU: 一个完整的CPU: 4. 堆栈 5. 堆 ...

  7. 清华大学视频课件:ARM微控制器与嵌入式系统

    清华大学视频课件:ARM微控制器与嵌入式系统(2017春) 直接点击博客网页连接就可以进入下载页面,因为是免费分享,点击后可能会有广告,请量解,点击普通下载. 将网址复制粘贴到浏览器地址栏,因为是免费 ...

  8. 双馈风力发电机DFIG滑模控制SMC MATLAB/Simulink仿真模型 采用PI调节器为外环滑模控制器SMC作为内环控制,跟传统的双PI环相比,功率的很随性更好

    双馈风力发电机DFIG滑模控制SMC MATLAB/Simulink仿真模型(成品) 1.采用非线性控制滑模控制策略 2.采用PI调节器为外环滑模控制器SMC作为内环控制,跟传统的双PI环相比,功率的 ...

  9. 课程笔记 -- 《ARM微控制器与嵌入式系统》

    "人生是如此简单,而又是这般辉煌." 村上春树 --<如果我们的语言是威士忌> 推荐 最近看了清华大学曾鸣老师的嵌入式开发入门课程<ARM微控制器与嵌入式系统&g ...

最新文章

  1. R语言使用ggpubr包的ggbarplot函数可视化水平偏差条形图(计算数值的z-score、自定义填充色、自定义条形边缘色、自定义调色板、条形图全局排序从小到大、文本标签角度、添加图例标签、轴标签
  2. 【Mybatis 之应用篇】 3_Lombok、多对一处理和一对多处理
  3. Eclipse基金会
  4. java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.isAsyncStarted()Z 的解决
  5. python调用rest api_调用rest api使用python将数据推送到运动学
  6. ROS学习笔记01:安装ROS - 玩小海龟
  7. 电脑键盘下划线怎么打_电脑键盘失灵怎么办?你应该学会的四种方法
  8. 【C/C++】sizeof和strlen的一些比较
  9. Android Support Library 23.2更新啦
  10. flex弹性布局学习总结
  11. 基于jquery的全局ajax函数处理session过期后的ajax操作
  12. 昆仑mcgs 通讯控制台达B2伺服采用modbus rtu方式,昆仑屏直接控制台达b2伺服的正反转,停止及速度设定,简单好上手
  13. PyConChina 2019 深圳站之行
  14. php rn 返回,rn滑动返回页面监听 - osc_13a0punx的个人空间 - OSCHINA - 中文开源技术交流社区...
  15. 【文件上传漏洞11】中间件文件解析漏洞基础知识及实验——Nginx
  16. 互联网行业职位介绍——PM,RD,FE,UE,UI,QA,OP,DBA,BRD,MRD, PRD,FSD等
  17. Game Hacking Fundamentals 学习笔记1
  18. 怎么减少计算机内存占有,还在为电脑内存占用太高而烦恼吗?教你一招轻松解决...
  19. DHCP 实现动态 IP 上网简析
  20. http://www.dewen.net.cn/q/14879/搜索引擎结果自动跳转

热门文章

  1. (病理图像读写)病理图像(whole slide images,WSI)的读写(.svs, .tiff),使用openslide,和pyvips以及matlab
  2. Facebook公共主页内容品质和公共主页被封会有联系吗?
  3. 全球与中国导轨升降机市场现状及未来发展趋势2022-2028
  4. 君正X系列开发1---系统编译
  5. MySQL数据库引擎之INNODB和MYISAM小探知
  6. 每日辣评:瑞幸卖茶理由是什么?小米股价到底没有?
  7. 大数据--数据仓库1--电商数据仓库项目最全总结1
  8. 2021年安全员-C证证考试及安全员-C证实操考试视频
  9. linux查看磁带机端口,Linux系统下使用磁带机的常用命令Tar
  10. 视频监控数据价值 你还没有重视吗?