-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
使用 UML 状态机设计嵌入式系统 Embedded System Design using UML State Machines[进行中] #210
Comments
01 - 简介004 有限状态机简介课程内容:状态机基础在本节课中,我们将深入理解什么是状态机(State Machine),也称为有限状态机(Finite State Machine,FSM)。状态机是一种软件计算模型,用于解决复杂的应用问题。状态机包含有限的状态,因此称为“有限”状态机。每个状态代表应用程序的不同情况,通过事件(即输入)触发状态间的转换。 状态机的基本概念
状态机的类型状态机有多种类型,例如:
我们将在课程中进一步讨论不同类型的状态机。 使用状态机的优势
常见的状态机模型
UML 状态机的工具在市场上,有一些工具可以解析 UML 状态机图,并自动生成基础代码。这些工具包括:
在后续的课程中,我们将使用 Quantum Leaps 提供的 QP 框架和 QM 建模工具,来实现嵌套的层次状态机。 下节课内容在接下来的课程中,我们将深入了解 Mealy 机器的原理,以及它与 Moore 机器的差异,同时探索 UML 规范用于绘制状态机图的标准。 005 Mealy 和 Moore 机器课程内容:Mealy机与Moore机的区别在本节课中,我们将深入了解 Mealy 机器 和 Moore 机器 之间的区别。这两种状态机的区别在于 输出生成的方式。 Mealy 机器
Moore 机器
Mealy 机器与 Moore 机器的对比
实际应用
在下一节课中,我们将继续深入了解 UML 状态机图的绘制规范。 006 Mealy 和 Moore 状态转换表课程内容:状态转换表和 Harel 状态图状态转换表(以灯光控制应用为例)
Harel 状态图
在接下来的课程中,我们将继续深入探讨 UML 状态机的绘制规范与应用。 007 练习-0001 LED 控制 Mealy 机器示例实践示例:实现灯光控制 Mealy 状态机1. 实验概述在本次实验中,我们将实现一个基于 Mealy 状态机的灯光控制应用,使用 Arduino Uno 控制 LED 的亮度。 2. 所需硬件
3. 编程框架概述Arduino 编程框架提供了控制外设的 API。在本次实验中,我们将使用 4. LED 亮度控制原理通过 PWM(脉宽调制) 控制 LED 亮度:
5. Arduino Uno 数字和 PWM 引脚
6. 代码传输及串口通信Arduino Uno 支持 串口通信。通过 USB 连接至计算机时会创建虚拟 COM 端口,支持在代码运行时通过串口发送指令。 7.
|
02 - UML 扁平状态机及其实现001 练习-003 生产力计时器演示第一部分:介绍
第二部分:复杂应用的实现
第三部分:实现 Moore 机
第四部分:创建新项目
第五部分:状态机的实现
第六部分:设置函数
第七部分:状态转移与入口动作
第八部分:状态转移的处理
第九部分:检查状态变化
第十部分:总结与结束
第二部分:生产力计时器(ProTimer)
第二部分:应用程序的功能
第三部分:需求说明
第四部分:组件介绍
第五部分:操作示范
第六部分:中止操作
第七部分:状态机的实现
第八部分:状态的说明
第九部分:计时器操作
第十部分:总结与结束
003 UML 简单状态和复合状态笔记项目需求
状态机模型
示例状态
状态的类型
UML状态机图
使用复合状态的优点
示例
这个结构和内容可以帮助您更好地理解和构建状态机图,以满足项目需求。 004 UML 状态机的内部状态活动(entry、exit、do)欢迎回到讲座在之前的讲座中,我们探讨了简单状态和复合状态,并理解了如何绘制简单状态及其简单部分。 内部活动区的探索现在让我们来探索内部活动区。 在我们的状态机图中,如果你考虑这个状态,你可以在这里提到内部活动。这就是内部活动区。 什么是内部活动?内部活动定义了状态的内部行为。每个状态可以有自己独特的内部行为。内部活动区持有与状态相关的内部行为列表。 如何表示状态的内部活动?表示状态的内部活动非常简单。语法如下: 首先,你需要写上“行为类型标签”,即内部活动的标签,后面跟一个“/”字符,然后写上“行为表达式”。行为表达式实际上就是一个动作,可以是任何编程表达式、可执行的编程语句,或者是函数调用,诸如此类。 简单来说,就是标签/action。 标签介绍这些标签包括‘entry’、‘exit’和‘do’,它们是UML规范中定义的内部活动标签。这些标签应该只出现在内部活动区,而不应在内部活动区之外用于表示特定应用事件。 了解‘entry’、‘exit’和‘do’标签这些标签标识了在什么情况下,‘行为表达式’指定的行为被执行。
内部活动的必要性是否需要这些内部活动,取决于你的项目设计和状态机设计。所有这些都是可选的。一个状态可能没有‘entry’或‘exit’动作,也可能只具有‘do’动作,或者可能只有‘entry’动作。这些都是可以在状态内部进行的可选操作。 内部活动区示例这里是内部活动区的一个示例。我们将在状态机图中做这个示例。 当对象在STAT状态时,这就是一个进入动作。这两个进入动作由逗号分隔。它们都用‘entry’标签标识。 当对象进入这个状态时,它会在LCD上显示一条消息,同时显示一个变量的值。而当对象离开该状态时,它会清除显示屏,这就是该状态的‘exit’动作。 同样,当对象进入IDLE状态时,它会将一些变量初始化为0。这些都是对象的属性。正如我在之前的视频中提到的,对象实际上是属性和方法(或行为)的集合。 这个主要应用对象‘mobj’是主要应用对象,这些是它的变量并被初始化,这就是该状态的‘entry’动作,同时它还显示时间为0,并显示消息‘set_time’。这些都是用逗号分隔的‘entry’动作。 当离开该状态时,这就是‘exit’动作,‘exit’动作是‘display clear’。这与其他不同的是,这里是内部转换。这不是内部活动,关于这一点我会在之后讲解。 总结内部活动始终由标签识别。下一节课,我们将了解内部转换。下次见! 005 UML 状态机的转换类型内部转换在上一节课中,我们探讨了状态的内部活动,包括“进入”、“退出”和“执行”等。这些都是状态的内部活动。现在,在本节课中,让我们了解内部转换。 内部转换是执行某些操作的一种方式,这些操作由行为表达式标识。当系统中发生某个“触发”(trigger)事件,并且“守卫”(guard)条件评估为真时,如果有守卫条件的定义,内部转换便会触发。这里的“触发”实际上是一个事件或事故的原因。 内部转换的语法如下:我们可以简单地写成 在内部转换中,对象不会离开其当前状态,因此没有退出状态的概念。 当“触发”事件发生时,如果守卫条件为真,则将在不退出或重新进入状态的情况下执行该动作。 示例假设我们有一个状态叫做“空闲”(IDLE)。在该状态下,当触发事件发生且守卫条件为真时,将执行特定动作,而不退出或重新进入该状态。 例如,当“时间滴答”(TIME_TICK)事件发生且某变量值为5时,执行一个“滴声”(beep)动作。 转换类型根据UML规范,转换可以分为外部转换、局部转换和内部转换。我们刚刚探讨的是内部转换。 外部转换在外部转换中,由于触发事件的发生,源状态被退出,接着执行与转换相关的可选动作,并执行目标状态的操作(如果有的话)。外部转换标志着对象生命周期中的状态或情境的变化。 转换的执行顺序在进行转换时,首先会执行当前状态的退出动作,然后执行转换动作,最后执行新进入状态的入口动作。这是根据UML规范定义的执行顺序。 示例假设对象当前在“倒计时”(COUNTDOWN)状态,当接收到“启动/暂停”(START_PAUSE)事件时,发生外部转换到“暂停”(PAUSE)状态。 在这个例子中,当“倒计时”状态接收到“启动/暂停”事件时,状态变量将更新为“暂停”。 总结在讨论转换时,内部转换不涉及状态的退出和重新进入,而外部转换则标志着状态的改变,并需遵循特定的执行顺序。通过这些概念,我们可以更好地理解状态机的行为和状态之间的关系。 006 事件和信号事件概述现在,在本讲中,让我们了解什么是事件,或者你也可以称之为触发器。 事件就是可以触发状态机的事件或刺激。 基本上,它们是应用程序中的异步事件或同步事件。 它们可以抽象为事件。在状态机中,事件可能导致转换,而转换可以是外部的或内部的。 微波炉示例让我们以微波炉为例。 你打开门,这个动作就是一个事件;当这个事件发生时,会生成一个事件,并被传送到微波炉内部的固件。固件根据其当前状态可能采取一些行动,例如关闭加热器、打开灯光等。关闭门也是一个事件,可能会有其相关的动作。 例如,设置定时器。你使用烤箱控制面板上的按钮设置定时器,开始烤箱操作等。这些都是事件,事件可能导致状态机图的转换,并可能有其相关的动作。 事件的组成部分事件通常有两个组成部分:
第二个组件实际上是可选的。 示例考虑一个应用程序,我们有三个按钮:“加”、“减”和“开始/暂停”。按下这些按钮会生成事件。 当你按下加号按钮时,它生成一个事件。这个事件有两个组成部分:
这没关系,因为那是可选的。 类似地,如果你按下减号按钮,它生成另一个事件,其信号名为“减少时间”。在这种情况下,这个事件也不需要任何参数。 你也可以这样写事件:无论用户按下哪个按钮,加号按钮还是减号按钮,生成事件“时间变化”(TIME_CHANGE)。然后使用参数“方向”来指示用户实际按下了哪个按钮。 你可以创建一个枚举,使用这些值UP或DOWN进行区分。这些将成为信号值。 这个事件通过其信号属性表明用户按下了一个改变时间的按钮。信号有一个相关的参数,编码了用户按下的按钮是增加时间还是减少时间。 计算器示例再举一个计算器的例子。 计算器有数字键盘,上面有10个数字(0到9)和几个操作符,以及结果按钮等。 那么,如何编码按下任意数字呢?你可以生成一个事件,其信号名为数字(0到9),参数则表示用户按下了哪个数字。 这样,你就可以只生成一个事件,而不是为每个数字创建一个事件,利用事件的参数组件区分用户按下了哪个数字。 类似地,你可以保持一个事件来表示按下操作符按钮。 通过这样的方式,在编程中,你可以使用结构体或枚举来建模应用程序的事件。 007 练习-003 状态和初始伪状态生产力计时器应用概述好吧,我们现在继续我们的练习二,即生产力计时器应用。我们已经理解了项目要求,我也已经演示过这些要求。 项目要求与状态这些是我们要转换为状态的不同情况,我们已经在Astah软件上绘制了这些状态。现在,让我们看看将用于该应用程序的各种事件。 用户活动与事件
‘SS’的值范围从1到10,其中1表示100毫秒,10表示1秒。 扩展状态变量我们将在该应用程序中使用一些扩展状态变量,这些变量对于捕捉数据和在状态机中做出决策至关重要:
这些变量将组织在一个名为 状态机图我们为该应用程序对象绘制的状态机跟踪应用程序的生命周期。
我们将从这个状态开始绘制外部转换、内部转换和活动。 伪状态初始伪状态至关重要,因为它代表状态机的起点。它只能有一个输出转换,并且不支持触发器或守卫。初始动作将涉及初始化扩展状态变量: mobj->curr_time = 0;
mobj->elapsed_time = 0;
mobj->productive_time = 0; 这些操作将把主对象的属性设置为零,作为我们启动应用程序的基础。 现在,让我们回到Astah软件中完成我们的状态机图。 008 练习-003 定义状态的 Entry 和 Exit 动作概述为了减少图表上的杂乱,我将用简短的变量名表示,而不是完整的变量名。 c_time、e_time 和 p_time。 我将只使用简短的名称。 现在,让我们设计一个空闲状态。 根据演示,当你为应用程序供电时,LCD 显示器应该显示“设置时间”。时间组件显示为 0,对吧?0 秒,0 分钟。 这就是为什么,每当应用程序进入空闲状态时,它应该显示某些内容。 因此,我们将为这个空闲状态定义一个进入活动。 只需点击该状态,在左侧可以看到进入/执行/退出。 现在让我们定义进入动作。 一个进入动作可以是: 在 LCD 上显示时间,我将称其为“显示时间”。显示时间是一个辅助动作,它是一个函数,用于显示时间。 但最初,当应用程序处于空闲状态时,它应该显示 0。 所以,传入的参数是 0。 显示时间函数根据传递的参数显示时间。传递的参数是 0(disp_time)。同时,如你所见,它还向用户显示一条消息,对吧?“设置时间”;显示消息也是另一个辅助动作函数。 在这里,我将发送消息“设置时间”。 所以,这就是两个进入动作。 接下来的进入动作是什么? 我们还应该做一个 因为它处于空闲状态。所以,我认为这些就是进入动作。
这些就是进入动作。既然我们已经在这里做了 我将在这里将 这就是我们的进入动作。 太好了。 现在我们完成了空闲状态的进入动作。 接下来让我们进入时间设置状态。 每当应用程序进入时间设置状态时,它应该显示当前时间。 因此,让我们为此定义一个进入动作。 我将选择这个状态,进入并将进入动作定义为“显示时间”。 你必须显示当前时间(c_time)。 因此,当应用程序进入这个状态时,它会显示时间。就这样。 现在,让我们为暂停状态编写进入动作。 每当你按下暂停按钮时,你可以看到这里倒计时停止,并显示消息“已暂停”。 现在,让我们在暂停状态下执行这一操作。 那么,这里的进入动作将是 它并不影响倒计时。所以稍后我们将看看这是如何发生的。 但是每当应用程序进入这个状态时,它应该发送消息“已暂停”。 现在,让我们进入倒计时状态。对于倒计时,它应该每秒倒计时一次。 这意味着,每当这个状态接收到 TICK 事件时,将会有一个倒计时过程。 所以,我不确定这是否需要进入动作, 如果你没有任何想法,那么你就可以不定义。 但是我不想为这个定义任何进入动作。 现在,让我们进入统计状态(STAT 状态)。如你在演示中所见, 每当你按下开始或暂停按钮时,它会显示生产时间,并且还会显示消息“生产时间”。 这表明你必须在这个状态的进入动作中做一些事情。 因此,选择这个状态,进入动作, 我们该做什么? 如你在演示中所见,它应该在显示器的第一行显示生产时间,并且在第二行发送一条消息。 这意味着我们有两个动作。
所以,每当你有两个或更多的动作要做时,你可以做一件事,结束每个动作用分号。 这样看起来不错。 在这一行之后,这是一个动作,你给一个分号,这是第二个动作。 我将给一个分号。 这是第三和第四个动作。 所以在这里你也只需用分号结束。 现在,让我们定义一些退出动作。 从空闲状态开始。 空闲状态在进入时显示了一些内容。当应用程序接收到某些事件时,它会转到其他状态,并可能显示其他内容。 因此,我认为在显示上显示了内容的应该清除它。所以,当退出这个状态时,这个状态必须清除它在显示上显示的内容。 所以,我将定义这个状态的退出动作为: 好的,我将去退出部分。退出动作将是“清除显示”。 稍后我们将看看是否需要更多的动作,但目前我只能想到在离开这个状态时清除显示。 对于 STAT 状态,退出动作也将是“清除显示”。 那么时间设置状态呢? 可能是需要的。所以,对于时间设置状态,我暂时不会定义任何退出动作。稍后再看看。对于暂停状态 它也显示了内容“已暂停”,对吧? 所以,它应该清除它。 因此,我将为此定义一个退出动作为“disp_clr”。 对于倒计时,我也不确定是否需要定义任何退出动作,所以稍后再看看。 现在我们已经部分实现了进入和退出动作。 让我们进行一些转换。 009 练习-003 绘制状态转换欢迎回来在之前的讲座中,我们为各种状态编写了一些入口和出口操作。 现在让我们实现状态转换。每个状态绘制的转换数量取决于该状态处理的事件数量。我们在这个应用中有多少事件呢? 我已经向你展示过这个表格。我们有5个事件,所以每个状态最多可以有5个转换。 IDLE状态现在,首先让我们从IDLE状态开始。你需要检查该状态是否真的处理某个特定事件。如果它不处理或不尊重该事件,那么你可以忽略它。 现在首先让我们从增量时间(INC_TIME)开始,针对IDLE状态。 当应用处于IDLE状态时,如果接收到增量时间事件,它应该转到TIME_SET状态,因为用户想要设置时间。 因此,我们在这里进行一个转换。你可以从任何地方绘制它。 这是一个转换,触发器是事件名称,即增量时间(INC_TIME)。 是否需要任何Guard?目前我没有给出任何Guard。每当按下“+”按钮时,它应该进行转换。 转换的操作这个转换的操作是什么? 你应该选择这条线,属性窗口会弹出。操作是用户按下“+”按钮,因此我们需要增量时间细节。 操作可以是 这就是操作。你可以考虑一些Guard,但我目前不放任何Guard以保持简单。 如果 TIME_TICK事件现在让我们处理下一个事件,即TIME_TICK事件。 该应用程序必须在返回到IDLE模式时发出20次哔声。假设每500毫秒应用程序需要发出一次哔声,持续10毫秒。 所以,它必须发出20次哔声,然后进入静音模式。 每当接收到TIME_TICK事件时,动作必须被执行,但没有转换。 这意味着这实际上是一个内部转换。 IDLE状态中的内部转换现在,让我们回去,选择IDLE模式,然后转到内部。将触发器设置为TIME_TICK。 此事件有一个参数。当 或者它也可以写成 结论现在我们已经覆盖了IDLE状态的所有事件。接下来让我们转到TIME_SET状态,思考一下你希望如何处理该状态中的所有事件。 下次见! 010 练习-003 实现 TIME_SET 状态处理事件现在让我们处理这个状态的所有事件。考虑这个状态。 现在应用程序处于设置时间状态,并假设接收到增量时间事件。 增量时间意味着 所以,现在的增量时间应该在这个状态内发生。这是一个负责设置时间的状态。因此,显然它不能过渡,它应该在内部处理。 因此,增量时间和减量时间将是此状态的内部转换。它不能请求其他人来进行增量时间或减量时间。这是这个状态的责任。 选择这个并转到内部,触发器是增量时间( 还有一个动作,那就是应该在显示器上显示增加后的时间。所以, 每当接收到增量时间时,它就会对 非常简单,对吧?现在,让我们处理减量时间。 减量时间也是一个内部转换。现在让我添加一个内部转换,减量时间( 动作是什么?你必须在这里做“−”。 mobj->c_time -= 60; 你修改了时间,所以要显示这个时间。 mobj->c_time; 就这样。 那么,这里的保护条件可能是什么? mobj->c_time 应该大于或等于 60; 这就是保护条件。 好的?所以这是一个布尔表达式,如果 请注意,使用增量时间和减量时间,你只能增减分钟,而不是秒。所以,这对秒没有影响。希望你能理解这一点。 现在我们处理了两个事件,让我们移动到下一个事件,即中止。 中止事件 所以,简单地中止。当接收到中止事件时,它进入空闲状态,但它显示了某些内容,对吧?这需要清除。 这就是为什么你可以在这里给一个退出动作。退出动作是 disp_clr(); 当它进入空闲模式时,它会将一切设置为0并显示0,同时发送消息“设置时间”。 开始/暂停事件 让我在这里写一个转换。让我在这里实现这个。 我将把这个触发器称为开始/暂停。那么保护条件是什么? 如果分钟为0,倒计时无法开始。这就是为什么保护条件是 mobj->c_time 应该大于(>)或等于 60; 所以,这就是这个转换发生的保护条件。如果这个条件不为真,那么这个转换就无法发生。这就是我们的开始/暂停事件。 时间滴答事件 我的意思是,没有与时间同步的动作。因为,设置时间几乎是根据用户按下的“+”或“−”按钮来修改时间变量。这就是为什么,它没有定义基于时间的动作。 所以,让我们在这个状态中忽略那个事件。现在让我们移动到下一个状态,即暂停。 你想想你在这里做了什么,尝试绘制你的步骤,我会在下节课中讲解这个。 011 练习-003 实现 PAUSE 状态实现暂停状态让我们来实现 PAUSE 状态。 在项目需求中提到,当倒计时暂停时,可以修改时间。这意味着,当应用处于暂停状态时,时间可以被修改。 这意味着它会尊重增量时间和减量时间事件。 所以,当我们处于 PAUSE 状态时,假设我们接收到增量时间事件,那么我们应该处理它,对吗?我们应该进入 TIME_SET 状态。我将其称为增量时间(INC_TIME)。 那么,行动是什么? 明白吗?这就是转移过程中的一个动作。 还有一个转移。让我们再画一个从这里到这里的转移,用于减量时间。 这个触发器是 DEC_TIME。保护条件是你已经知道了, 而动作是 好的,这就是 PAUSE 状态处理增量时间和减量时间事件的方式。接下来是 START_PAUSE。 那么,什么是 PAUSE?PAUSE 就是倒计时已暂停。当倒计时已暂停时,应用处于 PAUSE 状态。 如果它再次接收到 START_PAUSE 事件,那么它应该恢复倒计时,对吧?所以,它应该回到 COUNTDOWN 状态。 所以,触发器就是 START_PAUSE。就这样。 那么 Abort 事件呢?所有事情都必须被中止。它应该回到 IDLE 模式。 所以,让我们从这里到这里再画一个转移,可以画成这样。 或者这样。好的,这就是 Abort。 看起来不错。所以,当应用处于暂停状态时,它不处理 TIME_TICK 事件。这就是为什么它会忽略 TIME_TICK 事件。 现在,让我们进入下一个状态 COUNTDOWN。 所以,当时间倒计时时,你不能修改时间。这就是为什么在这个状态下不处理增量时间和减量时间事件。我们可以忽略它们。 下一个是 START_PAUSE 事件。这个事件实际上是会被处理的。每当它接收到 START_PAUSE 事件时,它必须进入 PAUSE 状态。 所以,这就是为什么必须有一个转移从这里到这里。这就是 START_PAUSE。 那么 Abort 呢?你可以中止它。所以,画一个转移在这里。这就是 Abort。 那么,TIME_TICK 事件在 COUNTDOWN 状态下呢?每当发生 TIME_TICK 事件时,当子秒字段为 10(这意味着已经过 1000 毫秒),那么我们应该将当前时间减少 1 秒。 所以,COUNTDOWN 每 1 秒发生一次。现在,让我们这样做。 那么首先,你会将其实现为外部转移还是内部转移?实际上我们会选择内部转移,好吗?我会在这里选择内部转移。 让我添加一个内部转移 TIME_TICK。 所以,保护条件是,当事件参数子秒=10。那么,什么是动作? 我们必须减少 抱歉,其实不太清晰,所以 012 练习-003 实现 STAT 状态实现 STAT 状态现在,让我们来实现 STAT 状态。 首先,我们来看一下项目需求。 要求是,当应用处于 IDLE 模式时,按下 START/PAUSE 按钮应显示 STAT 1 秒,并自动返回到 IDLE 模式。 现在,让我们回到软件。当应用处于 IDLE 模式时,按下 START_PAUSE 按钮应该显示 STAT。 所以,我们需要在这里定义一个从 IDLE 到 STAT 的转移。 这就是 START_PAUSE。 没有保护条件,也没有动作。 进入这个状态时,它会显示生产时间,并发送一条消息,然后在 1 秒后自动返回到 IDLE 模式。 首先,假设我们的应用在 STAT 状态。 在 STAT 状态下,它不处理增量时间事件、减量时间事件,也不处理 START_PAUSE 事件和 Abort 事件。它会显示某些内容,然后在 1 秒后自动返回。 让我们在这里再画一个转移,从 STAT 到 IDLE。 我们称之为 TIME_TICK。 TIME_TICK 事件的保护条件是,当“ss”参数等于 10 时。当此事件发生且 (ss=10) 时,它将自动返回到 IDLE 模式。 所以,我们成功完成了应用的状态机图。 请回顾一下,我已经解释了所有内容,包括状态、转移、事件、选择伪状态、初始伪状态,以及其他许多细节。 但这看起来有点乱,不是吗? 因为它是一个平面状态机,而不是一个层次结构的状态机。稍后我们会看到如何将其转换为层次结构状态机,或者我会介绍其他示例。我们还将做一个层次状态机的项目。 这只是一个开始,我的目标是通过示例介绍各种规范术语。 这就是我在这个应用中的目标。 使用层次状态机实际上可以使你的图表变得不那么混乱;你确实可以减少杂乱,我们稍后会看到这一点。 从下节课开始,我们将把这个图转化为代码,下一节课见。 013 安装 Microsoft VS Code 和 PlatformIO 扩展实现状态机的项目设置在之前的讲座中,我们已经完成了我们应用的状态机图。 现在,让我们创建一个新项目,并开始使用 C 编程语言实现这个状态机。 为了这次练习和未来的练习,我将不使用 Arduino IDE 来创建基于 Arduino 的项目。因为在这个应用中,我们使用了很多结构和其他内容,我们需要像代码提示、自动补全等功能,这些功能显然会加快代码编写,但 Arduino IDE 缺乏这些特性。 所以,这次练习以及未来的练习,我将介绍一个新的设置,使用 Microsoft Visual Studio Code IDE,并配合 PlatformIO 扩展。 Microsoft 的 IDE 是一个很棒的 IDE,支持所有操作系统平台,如 Windows、Linux 和 Mac,因此在安装这些软件时不会遇到任何问题。 现在,我将解释如何在 Microsoft Visual Studio Code 中安装 PlatformIO 扩展。然后我们将在其上创建一个 Microsoft 项目,并实现这个应用。 好的,现在我将向你展示如何安装 Microsoft Visual Studio Code。 非常简单。 只需访问这个网站 下载适合你的机器的版本并安装。安装后,打开 Visual Studio Code 应用。 好的,这里是我的一些之前的工作区,没有问题。 一旦打开,转到扩展,那里有搜索扩展的框。 在这里输入 PlatformIO。 点击这个 PlatformIO IDE 并进行安装。 就这样。对我来说,已经安装好了,所以显示的是卸载选项。 但对你来说,应该看到安装按钮,只需安装即可。 安装完成后,关闭 IDE,然后重新打开或重启 IDE。 此时,你会看到一个“激活扩展”的消息。 这将激活你刚刚安装的 PlatformIO 扩展。 这就是如何安装 Visual Studio Code 和 PlatformIO 扩展的步骤。 希望你能做到,我们下节课见。 |
03 - 扁平状态机练习实现001 练习-003 创建新项目课程讲义:使用 Visual Studio Code 和 PlatformIO 进行 Arduino 开发介绍在上一节课中,我们完成了应用程序的状态机图。现在,我们将创建一个新项目,并开始使用 C 编程语言实现该状态机。 创建新项目
编写代码
配置串行监视器的波特率
小结到此,您已经成功创建并上传了一个基本的 Arduino 项目。接下来,我们将进行更复杂的实现。感谢您的参与,下次课再见! 002 练习-003 数据结构说明嗨,欢迎回到讲座。在上一节课中,我们设置了这个IDE,而在这一节课中,让我们开始实现。 第一步是为我们的项目创建更多的头文件和源文件。我们有 现在,让我们再创建一个文件——一个头文件。右键选择“新建文件”,并命名为 接下来,我们将状态机的实现分隔到一个单独的文件中,我将把它命名为 现在我们有了 在 #ifndef MAIN_H
#define MAIN_H
#endif 这将防止头文件被多次包含。 接下来,我们还需要包含 #ifndef LCD_H
#define LCD_H
#endif 看起来不错。现在,让我们继续为我们的应用程序添加几个数据结构。我们将使用枚举定义各种信号状态,然后创建主应用程序结构,包含主应用程序的属性。 我们需要结构来表示事件,因为结构有两个组成部分:信号组件和相关参数。让我们使用结构来表示事件。 在 之后,我将创建一个主应用程序的结构,包含属性,如当前时间、经过时间和状态变量,这些变量表示应用程序的当前活动状态。 对于事件,我们有两种类型:用户生成的事件(仅具有信号组件)和滴答事件(系统生成的,具有信号和相关参数)。 为了更有效地处理事件,我们可以创建一个通用事件结构 这种方法可以被视为结构嵌入,或者用OOP术语来说是继承,其中一个结构从另一个结构派生属性。这种方法的优势在于,您可以发送成员元素的指针,并向下转型以获取父结构的地址,从而简化状态机中的事件处理。 因此,请将所有这些结构定义保存在 003 练习-003 定义初始转换函数现在在
|
04 - 嵌套 switch 技术实现状态机001 练习-003 嵌套 switch 实现 FSM 第 1 部分代码实现状态机现在,让我们继续编写代码。接下来,我们需要编写一个函数来实现状态机。 首先,打开文件 这个 这里我们将使用嵌套的 switch 语句,使用 switch case 来切换不同的状态。 switch (mobj->active_state) {
// ...
} 我们将实现不同的 case。例如,如果当前的 case IDLE:
state_handler_protimer_state_handler_INIT(mobj, e);
break; 需要为每个状态实现不同的 case,并在这个函数内部实现另一个 switch case,切换不同的事件以采取不同的动作。 我们将使用返回值来返回事件处理的状态,状态可能是事件是否被处理、被忽略,或事件是否导致了状态转移。 因此,我们将返回类型设置为 以下是为不同状态实现的示例: case TIME_SET:
state_handler_protimer_state_handler_TIME_SET(mobj, e);
break; 实现状态处理程序现在,让我们实现这些单独的状态处理程序。 这些处理程序接收指向主应用程序对象的指针和指向事件的指针,并返回 event_status idle_state_handler(protimer_t *mobj, event_t *e) {
// 实现 IDLE 状态的处理逻辑
} 处理信号IDLE 状态处理不同的信号,例如时间递增、TIME_TICK 和 START_PAUSE。它还处理内部活动的入口和出口,我们将它们视为信号。 switch(e->sig) {
case ENTRY:
// 处理入口信号
break;
case EXIT:
// 处理退出信号
break;
// 其他信号
} 在实现 定义
|
05 - 'C' 中的函数指针001 'C' 中的函数指针C语言中的函数指针讲座大家好,欢迎回到讲座。 在上一节课中,我们探讨了嵌套switch方法来实现状态机。在这一节课中,让我们理解状态处理器方法。之后,我们可以探索状态表方法。状态表和状态处理器方法都利用了函数指针的概念。 C语言中的函数指针首先,让我们探讨C语言中的函数指针。函数指针允许我们将函数的地址存储在一个变量中。 什么是函数指针?例如,考虑一个变量定义: int p; 在这里, 函数定义现在,假设我有一个叫做 你可以通过使用函数的名称在程序中表示函数的地址。例如: q = &p; 这将 q = &bar; // 这会因为指针类型不兼容而发出警告 理解警告警告的发生是因为 创建一个函数指针变量要创建一个函数指针,可以这样定义: void (*f)(); // f是一个指向没有参数并返回void的函数的函数指针。 这个声明指定 f = &bar; // 或简单地 f = bar; 通过函数指针调用函数要通过指针调用函数,可以对其解引用: (*f)(); // 调用指向f的函数 你也可以更简洁地写成: f(); // 调用指向f的函数 带参数的函数指针如果函数接受参数,例如: void bar(int i); 你需要修改函数指针的声明: void (*f)(int); // f现在指向一个接受int并返回void的函数。 结论请记住,在定义函数指针时,需要使用括号来指明这是一个指向函数的指针。同时,正确指定返回类型和参数类型,以避免指针类型不兼容的错误。 在C语言中,函数指针的概念非常强大,允许灵活的程序设计,尤其是在实现状态机和处理回调时。 002 作为函数参数传递函数指针在本讲中,我们将看看如何将函数指针作为参数传递给另一个函数。接下来,我们将进行一个小练习。 假设你有一个命令变量。 根据这个命令值,你需要采取某些行动或执行一些代码。 假设,如果命令值为0,你需要执行 如果命令值为1,那么执行 如果命令值为2,那么执行 假设你有两个变量 假设 你该怎么办? 你可以使用switch语句在这个变量上实现各种操作, 或者可以使用if-else-if结构。 如果我告诉你,你不应该使用任何比较语句,比如switch或if, 那么你该如何实现?根据命令的值,你必须采取这些行动,而不使用 if或switch语句。这里, 这个问题可以使用函数指针来解决。 让我来给你展示如何做。让我们将所有这些操作, 不同的操作或代码转换为单独的函数。 我这里提供3个函数。
现在,你可以这样做,创建一个通用函数
我创建一个typedef版本的函数指针变量定义。 我将使用 因此,这个函数
它还接受 它只是跳转到这个指针指向的函数,并使用参数 简单地这样写,也没有问题。 这两者是一样的。 请注意, 现在,在主函数中,让我们创建一个函数指针数组来存储这些函数地址。 所以,我将再次使用 一个字符指针,你该怎么做? 这是一个字符指针。 现在,如果你想将其转换为字符指针数组,你该怎么做?所以,字符指针数组。
同样,这里,你只需使用这个 typedef名称,创建一个变量,称之为 并初始化为 函数指针 你可以使用类似这样的语句。 这是一个函数指针数组,它初始化为这些函数地址。 现在你可以 做类似这样的事情。 调用这个通用函数 任何由这个命令值指向的地址。我们可以在这里使用命令值和 这里发生了什么? 这个通用函数被调用,带有3个参数。 这些是整数值,而这个是一个函数指针。那个函数 指针在这里接收到 这就是你如何将 函数指针从一个地址传递到另一个地址。 抱歉,这里是 这里只需返回结果。 因此,函数指针在我们的状态机实现中可能会非常有用。因为在我们的状态 机实现中,我们有不同的处理器,状态处理器。状态处理器函数中充满了 switch case语句。 它表示一组独特的指令。 所有这些不同的案例可以被分离为不同的函数。 你可以把这个看作是一个事件。根据接收到的事件,你可以传递与之关联的事件处理 函数 到通用分派代码中。你可以把这个看作是一个分派代码, 它在这里接收事件处理器地址并执行事件处理函数。 你可以把这个看作是一个包含各种函数指针的表, 这些指针就是不同事件处理函数的地址。这种方法 我们实际上在状态机实现中使用,称为状态表方法。在状态表方法中, 有一个表,里面充满了函数指针,每个表中的地址就是事件处理函数的地址。 我们将在覆盖状态表方法时看到这一点。无论你使用状态表 方法还是状态处理器方法,函数指针的 知识都是必需的。 这就是为什么我会简单介绍C语言中函数指针的基础知识。在下一节课中, 让我们探索状态处理器方法。 |
06 - 状态处理器技术实现状态机001 练习-004 使用状态处理器方法实现讲座欢迎词大家好,欢迎回到讲座。在这一讲中,我们将理解状态处理器的方法。 状态处理器的介绍这个方法与之前的类似,但重要的区别是,如果你还记得,我们在主应用程序结构中使用了 方法的优点在状态处理器中,实际上使用 在状态处理器的方法中,你不需要这样做,因为它包含指向状态处理器函数本身的指针。因此,你可以直接解引用这个变量并调用当前的状态处理器函数。 新项目的创建接下来,我们将创建一个新项目,因为我们不想在已有项目中做修改。让我们在 PlatformIO 中创建一个新项目,命名为 复制代码项目创建后,打开项目文件夹,将之前项目的所有代码复制到新项目中。确保新项目的文件能够正常显示。 编译项目在编译之前,我们需要添加 LCD 库,因为在之前的项目中我们已经添加过,但这是特定于项目的。找到并添加适合的液晶库。 项目中的更改进入 修改状态处理器初始化在 处理程序的修改我们需要修改调度代码,尽管在循环函数中的逻辑保持不变。首先,获取 编译检查错误在尝试编译时,如果遇到错误,比如 总结状态处理器的方法通过使用函数指针简化了状态管理,使得代码更易于维护和扩展。 |
07 - 状态表技术实现状态机001 练习-004 状态表实现 FSM 第 1 部分状态表方法介绍在本讲中,我们将学习状态表方法。在之前的讲座中,我们讨论了状态处理器方法和嵌套开关方法。 状态表方法在状态表方法中,我们将使用一个状态表,因此得名状态表方法。让我用一个例子来解释。 在前两种方法中,无论是状态处理器方法还是嵌套开关方法,我们都使用了许多比较。这些比较是通过使用开关语句来实现的。这里有多个开关语句和一些情况,进行了大量的比较。 在这种方法中将不会有比较。当事件发生时,相关的函数会被执行,我们可以通过函数指针来实现这一点。状态表实际上就是一个包含各种处理程序的表,或者可以称之为事件处理程序的表。 例如,假设在您的应用程序中,当应用程序处于倒计时状态时,如果发生 START_PAUSE 事件,则会调用这个处理程序。这个处理程序负责处理当状态为倒计时时的事件。因此,您需要创建一个表,对于每个事件,您必须根据状态编写自己的事件处理程序。因此,对于我们的应用程序,将会有一个包含五行的表,行表示状态的数量。我们有五个状态,所以五行;事件则表示列。在我们的例子中,表将有七列,因为我们有七个事件。 之后,您将把这个表转换成程序中的某种数据结构。您可以使用二维数组来保存这些信息。然后,您可以使用这个二维数组来执行不同的事件处理程序。稍后我会给您演示。 创建项目现在,我们将创建一个新项目,删除所有的开关语句,并将每个情况转换为不同的事件处理函数。让我们回到项目中。 现在,让我们实现状态表方法。创建一个新项目。我刚刚创建了一个新项目 005Protimer_ST。之后,进入项目文件夹,进入 003 项目,重新使用这个项目的文件。复制所有这些内容,然后去到 ST 目录下粘贴,替换目标中的文件。 让我们去 IDE。现在我们进入 protimer_state_machine.cpp。这是我们的嵌套开关实现,现在将不再有开关语句。 首先,您需要做的是删除这个函数,因为它不再需要。现在我们只有状态处理程序。您需要为每个情况创建一个函数,函数原型保持与之前相同。 让我创建一个函数来处理 IDLE 状态的 ENTRY 情况。我会写成 IDLE_Entry。这与之前的函数返回类型相同。然后,这里是两个输入参数。这是一个非静态函数,您不需要将其设置为静态,因为我们需要与 main.cpp 共享它,以便包含在状态表中。因此,这不能是一个静态函数。 接下来,您只需复制这个情况中的所有指令并粘贴到这里。就这样。再创建一个函数。下一个情况是 EXIT,所以创建一个 EXIT 函数,复制 EXIT 情况中的所有内容并粘贴到这里。 同样,您需要为所有状态和事件覆盖进行类似操作。之后,您去到 TIME_SET 处理程序并创建类似的函数。例如,您可能需要创建 TIME_SET_ENTRY,并将这些代码包含在其中。 我希望您能做到。之后,您只需删除这些状态处理程序。这些不再需要。因此,我也将删除这些函数原型。这些也是不需要的,因为我们将删除它们。 完成这些,我将在下节课见到您。 002 练习-004 状态表实现 FSM 第 2 部分讲座回顾欢迎回来。在这节课中,我为不同的状态创建了不同的事件处理程序。您可以看到,我们有很多函数。现在,这些处理程序的地址应该存储在状态表中。 状态表的创建因此,您需要与 main.cpp 共享这些函数的名称,我们将在其中实现状态表。您需要为所有这些函数创建原型,并将其包含在 现在我们进入 接下来,您需要创建这个表。在表中,您需要提到两个重要的细节:首先,您将调用哪个事件处理程序,其次,当事件发生时,下一状态是什么。 然后,我们需要将这个状态表转换为二维数组。 二维数组在下一节课中,我将介绍什么是二维数组,如何访问二维数组的不同元素,以及二维数组的内存组织是怎样的。如果您已经熟悉二维数组,则无需观看下一个视频。这只是为新接触 C 语言的初学者准备的。 在之前的课程中,我没有涵盖二维数组,因此我将在下一个视频中进行讲解。如果您对此已经了解,您可以直接跳过下一个视频。下节课见! 003 'C' 中的二维数组二维数组在C语言中的介绍一维数组的复习 你已经了解了一维数组的概念。这里有一个一维数组的例子,包含3个元素,类型为
索引一维数组 例如,索引0表示第一个元素,索引1表示第二个元素,索引2表示第三个元素。通过索引可以获取相应的值,例如:
二维数组的定义 二维数组是C语言中的一种数据结构,适用于表示表格形式的数据。表格由行和列组成。
int scores[2][3]; // 2行3列的二维数组 初始化二维数组 初始化时,第一行的值放在大括号内,用逗号分隔。例如: int scores[2][3] = {{10, 20, 30}, {22, 45, 33}}; 索引二维数组 访问二维数组中的元素,可以通过两个索引值来定位。例如:
使用二维数组的好处 考虑一下成绩单的情况,假设有4个学生和4门课程的成绩。可以使用二维数组存储这些信息: int marks[4][4]; // 4个学生,4门课程 在这种情况下,使用二维数组比使用一维数组更直观,避免了通过复杂的索引计算来获取特定元素。 内存存储方式 二维数组在内存中的存储方式与一维数组相似,都是连续分配内存。对于一个二维数组,数据按行存储,第一行的元素先存储,接着是第二行,依此类推。
这样,使用二维数组使得数据的组织和访问更为高效和直观。 004 练习-004 状态表实现 FSM 第 3 部分让我们回到项目中。进入 让我们在这里创建一个函数。让我们在事件分发器下实现这个函数。它接收指向主对象的指针。 静态函数并且返回空。现在,我们需要创建一个二维数组。我将其命名为 让我们去 main.h。这里有状态。只需在此处添加一个条目, 我们现在要做的是...去 TypeDef这是事件处理程序的函数指针类型。我们将使用此类型来创建数组:一个该类型的函数指针数组。之后,让我们初始化这个表。 现在,您需要用指针初始化这个二维数组。您必须准确地像这样初始化它。第一行是 IDLE 状态,第一个事件是这个,第二个事件是这个,依此类推。在进行二维数组初始化时,您还可以这样做。 假设...有两行,两列,您也可以这样做。首先,您写下行号。这表示您正在初始化第 0 行 = 然后使用大括号来初始化该行。您也可以这样做。这也是有效的初始化。 让我编译一下。这表示您正在初始化第 0 行。这表示您正在初始化第 1 行。所以,我会使用类似的方法,这样会更清晰。 让我们回到这里。首先,让我为 IDLE 状态初始化第 0 行 = 这是第一行初始化。接下来为 TIME_SET, 这就是 5 行。之后,每行应有 7 个条目。有些是 NULL,因为这表示该事件在该状态下没有处理。 首先,您必须提到...在 IDLE 状态下递增时间的事件处理程序。您必须提到它的地址。 MAX_SIGNALS 值为 7。这里应该有 7 个条目。类似地,您为 TIME_SET、COUNTDOWN、PAUSE 和 STAT 做这个初始化。一旦您完成这个初始化,接下来您要做的是,将这个状态表指针存储在我们的主应用对象中。因为我们在分发函数中需要它。 我们必须访问这个状态表...以获取适当的处理地址。这就是为什么,让我们去主应用结构,在这里创建一个指向状态表的指针。指针类型应该是什么?如果您对此有疑问,那就没问题。 只需输入 UPTR 并选择 uintptr_t,然后我们创建一个变量 它应该是一个静态局部变量,因为这个变量的地址必须是持久的。在主对象中,获取指针 让我们存储这个表的基地址...或者说数组的基地址。基地址就是第一个元素的地址,即 希望您能完成这些行。我已经附上了状态表文件,您可以下载,并根据该文件进行初始化。似乎有一个错误。这是 在此基础上,我想结束这节课,我们下节课再见。 005 练习-004 状态表实现 FSM 第 4 部分继续我们的编码。现在,让我们继续这个编码。 我们刚刚完成了这个函数。现在,让我们在 当控制第一次进入设置函数时,它会调用 现在,我们已经完成了这一步。现在我们的状态表在 让我们回到这里。现在,让我们先修复这个函数 让我们去那里。
首先,我们在这里做什么?我们设置一个事件, 如何获取这个?让我们创建一个变量。
|
08 - UML 分层状态机和 QP™ 框架001 分层状态机 (HSM)讲座回顾欢迎回来,今天我们将探讨HSMs(层次状态机)。 平面状态机回顾在之前的练习中,我们使用了平面状态机的方法。平面状态机意味着没有组合状态。状态图中只包含几个状态,彼此之间通过箭头相连,这些都是外部转移。 随着引入更多功能,状态数量增加,状态机图的复杂性也随之增加,维护和可视化变得繁琐。我们将探讨如何使用层次状态机(HSMs)来解决这一问题。 使用平面状态机的缺点随着状态数量的增加,转移的数量也随之增加,复杂性加大。平面状态机难以可视化、绘制和排查,遗漏转移的风险也随之增加,可能导致代码重复和错误的引入。 什么是层次状态机?层次一词在日常生活中随处可见,例如在企业中存在严格的层次结构。在这个结构下,所有员工的工作都是有层次的。在状态机中,我们也可以绘制类似的层次。 例如,有一个状态S1,下面有几个子状态。这种方式允许我们创建多个层级的状态。 HSM的示例考虑有3个状态S1、S2和S3,它们共享相同的行为——当触发器T1发生时,状态会转移到S0。这提示我们可以将这个状态机图转换为层次结构。 在这个例子中,SS1是一个复合状态,包含三个子状态S1、S2和S3。它继承了这些状态的共同行为。当T1发生时,状态机从SS1转移到S0。 HSM的优点在层次状态机中,当状态为S1时,T1的处理被传递到其父状态SS1,由其处理。这样的结构简化了状态机图的复杂性,并减少了代码重复。 在平面状态机中,可能需要在多个地方定义转移行为,而在HSM中,只需在一个地方定义。 未来的学习接下来的讲座中,我们将通过代码示例深入了解HSM。 002 逐步完成和 QP™ 框架在这个应用程序中,您可以看到生成事件的代码。这是事件生成器代码。事件生成器将事件发送到事件调度器,调度器函数,然后调度器函数将事件发布到状态机。 状态机执行该事件的行为操作。然后,状态机返回给事件调度器,事件调度器再返回给超级循环。 这是我们的Arduino循环函数。我们可以将整个步骤总结如下:
这实际上是完成运行模式(RTC模式)。什么是RTC?它代表运行到完成,当前事件的处理必须先完成,然后再处理下一个事件。 这就是我们所称的运行到完成的范例。请注意,如果您使用运行到完成的范例,则不应在这些步骤中放置任何阻塞代码,例如Arduino的delay()函数,这会违反RTC范例。 请注意,UML状态机的实现遵循运行到完成的范例。这是规范所说的。 运行到完成意味着,在没有异常或状态机的异步销毁执行的情况下,只有在完成先前发生的处理并达到稳定状态配置后,才会分派待处理事件的发生。 换句话说,当前事件的处理必须首先完成,然后才能处理下一个事件。 也就是说,当状态机执行正在处理先前事件时,不会调度事件的发生。选择这种行为范例是为了避免由于状态机试图响应多个并发或重叠事件而引起的并发冲突的复杂性。 因此,请记住,UML状态机应以运行到完成的方式实现。 事件驱动 + RTC范例提供了更好的能效。因为,当事件缺失时,您可以利用微控制器的低功耗模式将微控制器置于睡眠模式以节省一些电力。 在这种事件驱动架构中,微控制器只在执行RTC步骤期间处于活动状态。 这是一个RTC步骤。当RTC步骤完成后,微控制器可以休眠,直到另一个事件唤醒它。 现在,要解决分层状态机或实现分层状态机,我们需要一个事件处理器代码。 事件处理器实际上解决了嵌套分层状态机的遍历问题。也就是说,它遍历嵌套在分层状态机中的各种状态,并调用适当的状态处理程序。 它负责根据UML规范执行各种转换序列。为此,我们需要一个背景事件处理器。在之前的练习中,我们实际上使用了一个平面状态机,并且没有使用任何事件处理器。一个通用代码可以处理各种状态处理程序的调用,并根据规范解决各种状态转换。 但是,当您开始使用嵌套的分层状态机时,手动实现各种状态遍历和维护转换序列的执行将变得繁琐。 因此,您需要一个框架或使用一些通用事件处理器代码,这可以帮助您在项目中实现分层状态机。 因此,在本课程中,我将介绍由Quantum Leaps, LLC提供的QP实时嵌入式框架和QM工具。 这是一个著名的框架,它还带有一个称为QP Modeler的图形建模工具。这在现代嵌入式系统编程中使用事件驱动架构方面非常有名。 您可以通过这些链接探索更多关于此的内容。 最后,我们要特别感谢Quantum Leaps, LLC的创始人兼首席执行官Miro Samek,感谢他允许我们在本课程中使用QP框架及其相关组件。 您将使用QP框架、QP Modeler工具、QHSM事件处理器和QP Nano Arduino库来实现事件驱动的分层状态机项目。 QP框架的一些特性是,它提供运行到完成和事件驱动架构。并且它支持使用UML状态图实现嵌套的分层状态机。 使用称为QP Modeler的工具,您可以图形建模您的应用程序,这实际上是一个图形建模工具,我们将在本节中使用该工具。您可以自动生成代码,将UML状态图转换为可追踪的C和C++代码。 我们稍后将看到这一点。它还支持主动对象设计模式。 在本节中,我将不会覆盖主动对象,但该框架支持主动对象或演员设计模式。当在应用程序中使用主动对象以调度各种主动对象时,您可能需要一个内核。各种轻量级内核提供,QV、QK、QXK。 QV是一个简单的合作内核,框架本身就有,因此您无需拉取其他实时操作系统。QK是一个非阻塞的抢占式运行到完成内核。QXK是一个抢占式双重模式,您可以将其用作运行到完成或阻塞RTOS内核。主动对象还具有其他特性,例如它们带有自己的事件队列和各种数据封装技术。 因此,您可以通过Quantum Leaps提供的主动对象文档进行更多探索。 该框架还支持一个名为QSpy的跟踪工具,可以使用QSpy进行实时跟踪,还有一个名为QUTest的工具,用于进行单元测试。许可非常友好,采用双重许可; 提供开放和封闭许可。请通过此链接了解不同的许可方案。并且该框架符合MISRA-C和MISRA-C++编码标准。 有关更多信息,请访问此链接。 之后,QP框架有三种版本。 一种是QP/C,代表C语言的量子平台。该框架支持用C语言实现基于UML的FSM和嵌套HSM实现。QP C++代表C++语言的量子平台。 因此,如果您在项目中使用C++,那么可以使用QP C++,还有一种称为QP-nano的版本,代表量子平台Nano,这是一个轻量级的框架。 这实际上是为RAM和ROM大小非常有限的低8位或16位微控制器(例如AVR、MSP430或8051)设计的。 这是该框架的框图。这张图片我来自statemachine.com。 这是整个框架的图片。在本节中,我们将使用这个分层事件处理器。 请注意,QP框架也以Arduino库的形式提供。您可以将该库复制粘贴到Arduino库文件夹中,开始使用该框架。 对于基于AVR的Arduino,例如Uno和Nano,QP-nano Arduino库已可用。 您只需下载即可开始使用。对于基于ARM的Arduino,例如Arduino Duo,它基于ARM Cortex M架构,QP/C++ Arduino库已可用。因此,您可以在这些板上使用该库。 或者,您也可以在这些板上使用QP/C框架。 但是,对于Arduino Uno板或Nano板,我们必须使用QP-nano框架,这实际上是QP/C的轻量级版本。您可以在此链接中获取有关QP Arduino库的更多信息。 在本课程中,由于我们使用Arduino Uno板,因此我们将使用QP-nano Arduino库。 但不建议在新设计中使用,因为QP-nano框架已被停止使用,如该网页所述。对于较新的设计,您必须将硬件升级到更新的硬件,例如Arduino DUE或Arduino ZERO, 这些都是基于ARM架构的Arduino。在这种情况下,您可以使用QP/C++框架或QP/C框架。 让我们继续。现在我们需要下载几个东西。 首先,我们需要QP框架,您可以从这里下载,也要下载QM图形建模工具, 所有内容都可以通过一个下载获得。我会给您演示。 我们还需要下载QP Arduino库。 我们还需要参考QP-nano框架的一些API,以使用该框架。 所以,我稍后会给你展示。现在,让我们下载这两个东西。 003 下载 QP™ Nano Arduino 库安装QP-nano Arduino库现在,让我们下载QP-nano Arduino库。为此,前往资源部分并选择Arduino。 下载QP Arduino下载适合你机器的QP Arduino,我将选择Windows。 安装QP框架首先,让我们安装QP框架。只需双击它。 选择安装目录选择目录,默认情况下会安装在“C”目录下。这是可以的。 取消不需要的选项在这里,你可以取消不需要的选项。例如,我们在这里不使用ARM处理器,因此可以取消这些选项。我们也不使用QP/C++,所以也可以取消该选项。但我会保留所有选项,因为我有足够的磁盘空间。 完成安装我将点击下一步,然后下一步,最后安装。这需要一些时间。现在,让我们点击完成。 安装QP Arduino库安装完成后,现在让我们安装QP Arduino库。返回下载页面并解压缩它。 复制库到Arduino现在,你需要复制所有文件并将其粘贴到Arduino草图书位置。为此,打开Arduino IDE。 打开Arduino IDE在Arduino IDE中,点击文件,然后选择首选项。复制该位置。 打开文件资源管理器打开文件资源管理器并进入该位置。这是我的草图书位置。在这里,你需要粘贴刚刚复制的QP Arduino库。 粘贴库文件只需复制QP Arduino库,并在草图书位置中粘贴即可。 替换文件我已经完成了这一步,所以它询问是否替换某些文件。我将选择替换目标位置中的文件。 文件已复制现在,你可以看到文件已在此处复制。如果你查看库中,可以看到两个库:qpcpp(适用于ARM架构的Arduino板)和qpn(即QP-nano框架,适用于AVR架构的Arduino板)。 整合Arduino库成功这包含了所有框架相关的源文件。我们成功集成了Arduino库。 下一讲内容在下一讲中,我们将测试Arduino板上的转换执行序列和事件传播。这实际上是一个示例,用于理解各种转换执行序列和嵌套层次状态机图中的事件传播。 示例已在QP框架中这个示例已包含在你下载和安装的QP框架中。它位于特定位置,因此你可以直接在你的机器上测试该应用程序。 下次实验在下一讲中,我们将在Arduino板上进行测试,从而学习如何将这个工具与Visual Code和PlatformIO扩展结合使用。将会在下次讲解中详细介绍。 004 HSM 转换执行顺序测试课程回顾欢迎回到讲座。在上一节课中,我们安装了QP框架,这同时也安装了QM工具,一个图形建模工具。此外,我们还安装了QP-nano Arduino库。 理解状态机在本节课中,我们将理解嵌套层次状态机中的各种转换执行序列,并了解事件传播。当事件发送到嵌套层次状态机时,状态之间是如何转换的,以及各种行为操作的执行顺序是什么。 示例代码为了理解这一点,已经有一个示例代码可在指定位置获取。你可以直接在Visual Studio中启动该应用程序进行测试。 无需连接板子这不需要连接任何硬件。你只需前往QP安装文件夹。这个文件夹安装在“C”驱动器下的“qp”目录中,然后进入“qpc”,接着进入“examples”和“workstation”,在这里打开“qhsmtst”。 启动项目打开这个项目,并启动这个Visual Studio解决方案。为此,你需要先安装Visual Studio,我已经安装好了。让我们打开它。 打开QM模型文件项目已打开,接下来我们回到QM模型文件。这需要使用你在上一节中安装的QM工具来打开。 启动QM工具在上一节中安装的QP框架中也包括了QM工具。现在让我们启动它。 打开模型文件使用QM工具打开这个模型文件。我会复制这个路径,接着打开文件,选择“打开模型”。 扩展包打开后,展开这个包,双击以打开该类的状态机。我们稍后会理解如何从头开始创建所有这些内容,例如创建类、添加属性、添加操作和状态机等。 嵌套层次状态机这是该项目的嵌套层次状态机。可以看到,它接收各种事件,事件被命名为A、B、C,一直到I。这些事件发送到该项目以观察各种转换。 测试项目现在,在机器上测试这个项目。你已经在Visual Studio中打开它,只需编译即可。 编译与运行编译并运行,它将开始运行。现在,你可以发送各种事件。 发送事件例如,代码当前处于状态s211。让我们发送一个事件,比如说发送G事件。 观察状态变化发送事件G后,状态机的状态从s211转换为s11。这意味着状态机成功地完成了从s211到s11的转换。 执行顺序这就是事件G的转换执行序列。在状态机状态为s211时接收到事件G,执行了各种动作的顺序。 集成Arduino项目在此之前,我们将了解如何将该项目与Arduino集成,并学习如何创建文件,以及使用QP框架的代码生成指令生成各种函数声明和定义。如果你不想尝试Arduino,也可以按照我刚才解释的方式直接在计算机上进行尝试。 结束语这节课到此为止,你可以向该程序发送各种事件,以观察转换执行序列。 |
09 - UML HSM 转换执行顺序001 练习-006 在 Arduino 上测试 HSM 转换执行顺序创建Arduino项目欢迎回到讲座。在本节课中,我们将创建一个新的Arduino项目来测试已经提供的模型 打开Visual Code IDE首先,让我们关闭Visual Studio,并打开Visual Code IDE。 创建新项目要创建新项目,进入PlatformIO首页,点击“打开”,然后选择“新项目”。 设置项目名称项目名称设为 创建新文件夹新项目 下载模型文件现在非常重要的是,下载与本节课相关的 粘贴模型文件我将把模型文件粘贴到该目录的 完成步骤完成上述步骤后,模型文件就会成功放置在 002 在 QM 工具中添加文件生成QM模型文件代码的步骤
003 使用 QM 工具向文件添加代码欢迎回来现在,让我们为 Test.h 编码我将其标记为外部,但暂时让我去掉这个限制,将其设为内部。 首先,添加包含保护这里,我们创建一个枚举,包含状态机的所有事件。如果你点击那个状态机,可以看到它实际上接受许多事件,事件从 'A' 到 'I',还有一个叫做终止事件的事件。 现在,在这里编码A_SIG,你必须在事件后面加上 _SIG。因为当它生成状态机模型的代码时,你可以看到,这个框架使用了 _SIG。因此,每个事件都要使用 _SIG 后缀。 事件范围事件从 'A' 到 'I'。在提到所有事件后,最后你必须提到一个宏 MAX_SIGNAL。这个枚举常量实际上表示应用程序支持的事件总数,所以要把它放在最后。这个框架可能会使用这个宏,我不知道,但这是必要的。 用户事件的开始这是第一个用户事件。在文档中,你可以查看 QP-nano 文档文件,这里在 'qpn.h' 中有一些框架保留信号,比如 Q_ENTRY_SIG、EXIT_SIG、INIT_SIG,以及与超时相关的信号等。Q_USER_SIG 是一个宏,表示用户信号或用户事件的开始。因此,你必须将这个事件等于 Q_USER_SIG。它的值实际上是 8,8 被分配给这个,它标记应用程序信号或用户信号的开始。 保留信号的冲突这是为了区分保留信号,因为第一个保留信号从 1 开始,这就是 Q_ENTRY_SIG。如果你不这样做,那么值将与这些保留信号冲突。 生成代码现在,让我们生成代码。我刚刚生成了代码。让我们去 .h 文件,你可以看到,它已经在这里了。 解决一些错误现在,让我们解决一些错误。首先,去 .cpp 文件,这里你需要包括一些头文件。第一个需要安装的头文件是 生成代码现在,让我们生成代码。它出现了,你可以看到,我们解决了大部分错误。现在还有一个错误。我不确定为什么它会显示这个错误提示,因为我已经在这里包含了库。我们将在构建时查看,它可能会消失。 下一步现在,在模型中,如果你检查模型,状态机模型,你可以看到,它为不同的转换使用不同的动作。例如,如果你考虑这个状态,那么模型定义了 ENTRY 动作和 EXIT 动作。现在我们需要给这些函数的实现 'BSP_display'。 创建新文件让我们这样做。为此,让我在这里创建一个文件。我将其称为 'bsp.cpp'。另一个文件,我可以将其命名为 'bsp.h',在这里你可以保留任何与外设相关或 BSP 相关的代码。QM 工具没有生成这些代码。这是我们的 BSP 代码,用于驱动各种外设。 创建函数在 'bsp.h' 中,添加包含保护。我们来创建这两个函数 BSP_display() 和 BSP_exit()。还有一个函数你需要给出,BSP_exit()。只需写这个字符串,它不会返回任何东西。只需在这里给出声明,让我们在 cpp 中实现它。 包含头文件所以,只需包含 Arduino 头文件。然后我们将使用 serial.print()。因此,我们将在串行终端上打印一些文本。 包含 bsp.h 头文件现在,让我们包括这个头文件 'bsp.h'。并生成代码。它在这里。 转到 main.cpp让我们去 main.cpp。main.cpp 产生事件。它接收事件或生成事件,并将其发送到状态机。因此,它必须知道支持哪些事件。这就是为什么让我们包含 QHSM_Test.h。 编译现在让我们编译。我们将查看它是否可以正常构建。 选择项目我实际上正在编译旧项目。所以,我必须选择项目,活动项目是 006 QHsmTest。你可以看到错误消失了。 继续编译现在,让我们编译。有一个错误。让我们检查一下。这个函数未使用,这没关系。我们稍后会用到。它说这个宏在这个作用域中未声明。我们实际上在这个头文件中使用了它。你要做的是,在 main.cpp 中,在包含这个头文件之前,先包含 'qpn.h'。 编译成功让我们编译。现在,它构建成功。 总结到目前为止,我们完成了所有这些步骤。在下节课中,我们将探索 QP-Nano 的一些 API,以将事件发送到状态机。我会在下节课见到你。 004 添加类属性事件发布到状态机在接下来的工作中,我们需要将事件发布到状态机。为此,我们需要探索 QP Nano 的 API。可以访问 state-machine.com/qpn 查看 API 参考。以下是我们需要使用的 API。 状态机的初始化我们首先调用分层状态机的构造函数 调用顺序
框架基于超类 嵌入超类到应用结构体我们已经在应用结构中嵌入了超类结构,这意味着现在我们的结构继承了这个超类的属性。该结构体包含一个重要字段——状态处理器,用于保存当前活动状态处理器的指针,这是一个状态变量。 如何初始化状态变量要初始化状态变量,可以使用构造函数
在 IDE 中调用构造函数可以在 属性类型在工具中添加属性时,可以选择两种类型的属性:
类属性的静态与非静态类属性有两种类型:
实验工具中的静态与非静态可以在工具中实验选择静态和非静态属性,以了解其行为。例如,可以创建一个非静态类属性 自由属性自由属性可以在文件范围内定义,既可以是静态变量,也可以是全局变量。可以通过在工具中点击包并选择“添加属性”来创建自由属性。 005 添加类操作构造函数的创建和操作的添加这是我们类的构造函数,作为一个操作添加到模型中。在右侧可以看到它被称为自由操作。在之前的课程中,我们已经了解了如何添加类属性和自由属性。现在,让我们学习如何添加类操作。 类操作类操作是添加到类中的方法或函数。通常有两种方法可以在类中创建:静态方法和非静态方法。这些术语来源于 C++。在 C 中,也可以理解为添加到结构体的操作,但不能在结构体中放置任何方法。而在 C++ 中,可以实现。例如,有一个类 静态与非静态操作在静态操作中, 使用工具创建操作可以使用工具添加静态类操作、非静态类操作,以及类构造函数。
创建静态与非静态操作在工具中,可以通过选择“添加操作”选项来添加类操作。要创建静态操作,需要勾选“静态”选项。例如,添加一个名为 然后,可以创建一个非静态操作 自由操作可以通过点击包并选择“添加操作”来添加自由操作。自由操作的返回类型为 构造函数的实现构造函数作为自由操作添加,返回类型必须为 006 添加断言失败回调处理 Q_onAssert 错误我们遇到了 在框架的任何 API 中,如果由于无效参数或过多的分层状态机嵌套导致断言失败,就会发生断言错误。断言失败的原因可能有很多种。如果发生断言失败,将通过此回调函数通知您的应用程序。需要在 在 main.cpp 中实现 Q_onAssert在
我们可以使用
编译并验证完成上述步骤后,编译代码。构建已成功。 后续内容在下一节中,我们将探索其他 API,包括 007 QHSM_INIT() 和 QHSM_DISPATCH() API处理 Q_onAssert 错误我们遇到了 在框架的任何 API 中,如果由于无效参数或过多的分层状态机嵌套导致断言失败,就会发生断言错误。断言失败的原因可能有很多种。如果发生断言失败,将通过此回调函数通知您的应用程序。需要在 在 main.cpp 中实现 Q_onAssert在
我们可以使用
接下来,编译代码。构建成功。 后续内容在下一节中,我们将探索其他 API,包括 008 练习-006 测试状态机测试和事件处理分析欢迎回到课程。在之前的课程中,我们完成了该练习,现在让我们测试这个状态机。在 分析初始转换可以看到应用程序已启动,并且执行了与初始转换相关的操作。让我们分析这些步骤。
事件
|
10 - 使用 QM 工具的 UML HSM 练习001 练习-007 闹钟简介练习 007:ClockAlarm在这个练习中,我们将使用软件实现一个实时时钟(RTC),不使用任何 RTC 芯片。本项目的需求如下:
电路图该项目的电路图与上一个项目非常相似,但有以下两个变化:
项目演示接下来,我们将展示该应用程序的演示,以便更清晰地了解应用程序的界面和项目的其他需求。 002 练习-007 闹钟演示练习 007:ClockAlarm 应用演示接下来,我们将演示该应用的功能。通过演示,您将更清晰地了解该应用的需求。我们将使用分层状态机的方法来实现此应用。 在该项目中,我重新使用了之前的电路和组件。不同的是,当前应用只需使用 2 个按钮,以及 LCD 显示屏。LCD 与 Arduino 板之间的连接与之前相同,此外还可以选择性地加入一个蜂鸣器以及用于调节 LCD 对比度的电位器。 按钮功能
我已将应用程序下载到 Arduino 板并运行。现在重置 Arduino 板,应用程序将显示当前时间,格式为时、分、秒和亚秒。同时,显示时间格式(24 小时制或 12 小时制)以及闹钟符号,表示闹钟当前已开启。这被称为“计时模式”,即应用程序实时显示当前时间。 时钟设置模式当应用处于计时模式时,按下第一个按钮(SET/CLOCK_SET)即可进入时钟设置模式。在时钟设置模式下,LCD 光标会闪烁,指示当前正在修改的字段。可以按位修改小时、分钟等信息,每次按下“SET”按钮即切换数值。如果数值正确,可以按“OK”确认,光标会移动到下一个字段。如果希望取消整个操作,可以同时按下两个按钮回到计时模式,而时钟将继续在后台运行。 设置时间格式进入时钟设置模式后,您可以设置为 24 小时制或 12 小时制。如果选择 12 小时制,还可以设置为 AM 或 PM。当设置不匹配(如 24 小时制设置为 AM/PM)时,会提示“错误”。在此情况下,OK 按钮无效,必须通过 SET 按钮重新调整设置。 闹钟设置模式当应用处于计时模式时,按下“OK/ALARM_SET”按钮进入闹钟设置模式。第一行显示当前的闹钟设置,第二行显示当前时间。在闹钟设置模式中,按“SET”按钮可逐步设置闹钟时间和 AM/PM,并设置闹钟开启或关闭。当时间接近设定的闹钟时间时,闹钟会响起,同时屏幕显示闹钟提醒。按下 OK 后返回计时模式。 中途退出和恢复在进行设置过程中,如果需要中途退出并恢复到先前的状态(如闹钟触发时),应用会记录当前状态,待闹钟提醒结束后自动回到设置状态。无论是在时钟设置还是闹钟设置模式下,都可支持这种“历史状态”功能。 快照功能进入时钟设置模式时,系统会捕捉当前时间,用户可以从该时间点开始逐位调整时间。 003 练习-007 使用的状态、信号和数据结构应用状态机的设计和实现您刚刚看到了应用的演示,通过演示您可以想象出应用中的一些状态。例如,应用有一个展示当前时间的状态,我们称之为计时模式(或计时状态)。用户可以进入时钟设置模式,因此可以定义一个时钟设置状态。此外,用户也可以进入闹钟设置模式,因此还需要一个闹钟设置状态。在开始建模之前,可以将这些状态写在纸上。 状态机的初始状态
状态转换
状态的超级状态设计可以创建一个包含所有状态的超级状态,称为Clock超级状态。在这个超级状态中定义一个“Alarm”事件,该事件将应用程序切换到闹钟通知状态。无论应用处于哪个状态,只要闹钟响起,都会进入闹钟通知状态。当用户按下“OK”按钮后,闹钟通知结束,应用将返回到之前的历史状态(Clock超级状态中的上一次状态),即用户可能正在进行的时钟设置或闹钟设置。 子状态设计在时钟设置模式中,需要定义更多的子状态,以配置小时、分钟、秒等信息。这部分将在后续课程中详细讨论。 主应用结构的设计需要创建一个主应用结构来绘制状态机图。此主应用结构将继承自
将根据状态机的需求逐步添加其他变量。 信号和事件在状态机中使用以下信号和事件:
本节课的任务
下一节课程我们将开始实现此应用。 004 练习-007 绘制 HSM创建 ClockAlarm 状态机模型欢迎回到本节课程。我们继续进行练习 007。首先,创建一个新文件夹,命名为 'qm'。在该文件夹中存放即将使用 qm 工具创建的新模型文件。 创建新模型
创建包在创建状态机模型之前,首先需要创建一个包。右键点击选择“添加包”。包用于将不同元素分组并提供命名空间。项目可以包含多个包,若需要在不同包中复用变量名或函数名,可以指定不同的命名空间。选择“组件”作为类型,为包命名为 创建类在包中创建类,右键点击选择“添加类”,将类命名为 添加属性在
可以继续添加其他属性。 添加目录和文件在包中添加一个目录,路径为
确保头文件中包含 include guards。保存并生成代码,接受 GPL 许可,生成的文件将显示在指定目录中。 创建状态机在类中添加状态机,右键点击选择“添加状态机”。双击打开状态机画布,在画布中绘制状态和转换。
绘制转换
设置历史状态添加 保存项目并完成当前步骤。我们将在下节课程中继续完善。 005 练习-007 添加主应用对象和构造函数生成代码并定义信号
解决错误并设置库路径
定义状态处理程序和创建主对象
添加构造函数
生成代码后,您将在 006 Atmega328p 计时器外设说明ATmega328P 微控制器的 Timer 外设在本节课中,我们将深入了解 ATmega328P 微控制器的 Timer 外设,因为我们将在应用中使用 Timer ISR 来跟踪时间。 时间变量与计时机制在我们的主结构中,
请注意, 其他时间变量
Timer 外设概览ATmega328P 具有三个 Timer/Counter 外设:
使用 Timer1我们将使用 ATmega328P 的系统时钟与 Timer 分频在 Arduino Uno 板上,ATmega328P 使用 16 MHz 的外部晶振作为主系统时钟。我们可以使用定时器的分频器减慢时钟以控制计数速率。TCCR1B 寄存器中的分频设置如下:
输出比较匹配值计算假设我们希望生成 100 毫秒的时间基准。按以下方式计算输出比较匹配值:
因此,输出比较匹配值应设置为 Timer1 的 CTC 模式我们将以“CTC 模式”运行定时器,即在比较匹配时自动清零计数器并触发中断:
定义 Timer1 ISR在 ISR(TIMER1_COMPA_vect) {
// ISR 代码
} 这将实现定时器的中断服务例程,用于在比较匹配时触发。 007 Atmega328p 计时器寄存器和设置代码在 main.cpp 配置 Timer1 外设在 配置 CTC 模式要将定时器配置为 CTC 模式,首先需要查看寄存器描述。以下是配置步骤:
定义 ISR 中断服务程序在
该 ISR 将在每次比较匹配时触发,从而实现定时中断。 008 练习-007 添加类操作将
|
11 - 活动对象001 活动对象探索 Active Object 设计范式在本讲中,我们将探讨如何在应用程序中使用 Active Object(活动对象)设计范式,并逐步了解其应用。首先,我们来讨论在当前应用程序中可能存在的一些设计问题,并探讨如何通过 Active Object 范式进行优化。 1. 应用程序中的问题
2. 引入事件队列为了避免漏掉事件并保持 RTC 语义,我们可以引入一个事件队列。
3. 多状态机场景如果应用程序包含多个状态机,每个状态机有自己的事件队列,允许各个对象在独立的线程中并行处理事件。
4. Active Object 的定义在 QP 框架中,Active Object 是一个拥有自己线程控制的对象。各对象的状态机行为完全由其当前状态决定,不受其他对象的影响。Active Object 设计范式中的特征包括:
5. 事件发布与订阅QP 框架提供了两种事件传递方法:
6. 使用
|
使用 UML 状态机设计嵌入式系统 Embedded System Design using UML State Machines
01 - 简介
004 有限状态机简介
005 Mealy 和 Moore 机器
006 Mealy 和 Moore 状态转换表
007 练习-0001 LED 控制 Mealy 机器示例
008 练习-001 LED 控制 Mealy 机器实现部分 1
009 练习-001 LED 控制 Mealy 机器实现部分 2
010 练习-002 LED 控制 Moore 机器实现
02 - UML 扁平状态机及其实现
001 练习-003 生产力计时器演示
003 UML 简单状态和复合状态
004 UML 状态机的内部状态活动(entry、exit、do)
005 UML 状态机的转换类型
006 事件和信号
007 练习-003 状态和初始伪状态
008 练习-003 定义状态的 Entry 和 Exit 动作
009 练习-003 绘制状态转换
010 练习-003 实现 TIME_SET 状态
011 练习-003 实现 PAUSE 状态
012 练习-003 实现 STAT 状态
013 安装 Microsoft VS Code 和 PlatformIO 扩展
03 - 扁平状态机练习实现
001 练习-003 创建新项目
002 练习-003 数据结构说明
003 练习-003 定义初始转换函数
004 实现状态机的不同方法
04 - 嵌套 switch 技术实现状态机
001 练习-003 嵌套 switch 实现 FSM 第 1 部分
002 练习-003 嵌套 switch 实现 FSM 第 2 部分
003 练习-003 硬件连接
004 练习-003 实现事件生成代码
005 练习-003 分发时间滴答事件
006 按钮抖动解释
007 练习-003 按钮软件防抖实现
008 在 PlatformIO 项目中添加 Arduino 库
009 练习-003 实现 LCD 功能 第 1 部分
010 练习-003 实现 LCD 功能 第 2 部分
011 练习-003 辅助函数实现
012 练习-003 实现初始转换动作
013 练习-003 硬件测试
05 - 'C' 中的函数指针
001 'C' 中的函数指针
002 作为函数参数传递函数指针
06 - 状态处理器技术实现状态机
001 练习-004 使用状态处理器方法实现
07 - 状态表技术实现状态机
001 练习-004 状态表实现 FSM 第 1 部分
002 练习-004 状态表实现 FSM 第 2 部分
003 'C' 中的二维数组
004 练习-004 状态表实现 FSM 第 3 部分
005 练习-004 状态表实现 FSM 第 4 部分
08 - UML 分层状态机和 QP™ 框架
001 分层状态机 (HSM)
002 逐步完成和 QP™ 框架
003 下载 QP™ Nano Arduino 库
004 HSM 转换执行顺序测试
09 - UML HSM 转换执行顺序
001 练习-006 在 Arduino 上测试 HSM 转换执行顺序
002 在 QM 工具中添加文件
003 使用 QM 工具向文件添加代码
004 添加类属性
005 添加类操作
006 添加断言失败回调
007 QHSM_INIT() 和 QHSM_DISPATCH() API
008 练习-006 测试
009 练习-006 测试历史状态
10 - 使用 QM 工具的 UML HSM 练习
001 练习-007 闹钟简介
002 练习-007 闹钟演示
003 练习-007 使用的状态、信号和数据结构
004 练习-007 绘制 HSM
005 练习-007 添加主应用对象和构造函数
006 Atmega328p 计时器外设说明
007 Atmega328p 计时器寄存器和设置代码
008 练习-007 添加类操作
009 练习-007 定义初始转换动作
010 练习-007 为 TICKING 状态编写代码
011 练习-007 添加自由操作
012 练习-007 通过类操作读取 curr_time
013 练习-007 在 TICKING 状态处理 TICK 事件并测试
014 练习-007 绘制 CLOCK_SETTING 状态
015 练习-007 实现 CLOCK_SETTING 状态 第 1 部分
016 练习-007 实现 CLOCK_SETTING 状态 第 2 部分
017 练习-007 实现 CLOCK_SETTING 状态 第 3 部分
018 练习-007 实现 CLOCK_SETTING 状态 第 4 部分
020 练习-007 更新实时
021 练习-007 ALARM_SETTING 状态
022 练习-007 实现 ALARM_SETTING 状态
023 练习-007 实现 ALARM_NOTIFY 状态
11 - 活动对象
001 活动对象
002 正交状态模式
003 练习-008 实现 第 1 部分
004 练习-008 实现 第 2 部分
005 练习-008 实现 第 3 部分
006 练习-008 实现 第 4 部分
007 练习-008 实现 第 5 部分
009 练习-008 实现 第 6 部分
010 练习-008 实现 第 7 部分
011 练习-008 实现 第 8 部分
012 练习-008 实现 第 9 部分
01 - Introduction
004 Introduction to Finite State Machine
005 Mealy and Moore machine
006 Mealy and Moore State Transition Table
007 Exercise-0001 LED control Mealy machine example
008 Exercise-001 LED control Mealy machine implementation part 1
009 Exercise-001 LED control Mealy machine implementation part 2
010 Exercise-002 LED control Moore machine implementation
02 - UML Flat state machine and Implementation
001 Exercise-003 Productivity Timer demo
003 UML Simple and Composite states
004 UML state machine internal state activities(entryexitdo)
005 UML state machine types of Transitions
006 Events and Signals
007 Exercise-003 States and Initial Psuedostates
008 Exercise-003 Defining states Entry and Exit actions
009 Exercise-003 Drawing state transitions
010 Exercise-003 Implementing TIME_SET state
011 Exercise-003 Implementing PAUSE state
012 Exercise-003 Implementing STAT state
013 Installing Microsoft VS Code and PlatformIO extension
03 - Flat state machine exercise implementation
001 Exercise-003 Create new project
002 Exercise-003 Data structure explanation
003 Exercise-003 Defining initial transition function
004 Different approach to implement state machine
04 - Nested switch technique to implement State Machine
001 Exercise-003 Nested switch implementation of an FSM part 1
002 Exercise-003 Nested switch implementation of an FSM part 2
003 Exercise-003 Hardware connections
004 Exercise-003 Implementing event producer code
005 Exercise-003 Dispatching time tick event
006 Button bouncing explanation
007 Exercise-003 Button software de-bouncing implementation
008 Adding arduino Library to project in platformIO
009 Exercise-003 Implementing LCD functions Part 1
010 Exercise-003 Implementing LCD functions Part 2
011 Exercise-003 Helper function implementation
012 Exercise-003 Implementing initial transition actions
013 Exercise-003 Testing on hardware
05 - Function pointers in 'C'
001 Function pointers in C
002 Passing function pointers as function arguments
06 - State handler technique to implement State Machine
001 Exercise-004 Implementation using state handler approach
07 - State table technique to implement State Machine
001 Exercise-004 State table approach for implementation of an FSM part-1
002 Exercise-004 State table approach for implementation of an FSM part-2
003 2D arrays in C
004 Exercise-004 State table approach for implementation of an FSM part-3
005 Exercise-004 State table approach for implementation of an FSM part-4
08 - UML Hierarchical State Machines and QP™ framework
001 Hierarchical State Machines(HSMs)
002 Run-to-completion and QP™ framework
003 Download QP™ Nano Arduino library
004 HSM transition execution sequence testing
09 - UML HSM transition execution sequences
001 Exercise-006 Test HSM transition execution sequence on Arduino
002 Adding files in QM tool
003 Adding codes to files using QM tool
004 Adding a class attribute
005 Adding class operation
006 Adding assertion failure callback
007 QHSM_INIT() and QHSM_DISPATCH() APIs
008 Exercise-006 Testing
009 Exercise-006 Testing History state
10 - UML HSM exercise using QM tool
001 Exercise-007 Clock Alarm Introduction
002 Exercise-007 Clock Alarm demo
003 Exercise-007 States, Signals and Data structure used
004 Exercise-007 Drawing an HSM
005 Exercise-007 Adding main application object and constructor
006 Atmega328p Timer peripheral explanation
007 Atmega328p Timer registers and setup code
008 Exercise-007 Adding class operations
009 Exercise-007 Defining initial transition actions
010 Exercise-007 Coding for the TICKING state
011 Exercise-007 Adding free operations
012 Exercise-007 Reading curr_time through class operation
013 Exercise-007 Handling TICK event in TICKING state and testing
014 Exercise-007 Drawing CLOCK_SETTING state
015 Exercise-007 Implementing CLOCK_SETTING state part-1
016 Exercise-007 Implementing CLOCK_SETTING state part-2
017 Exercise-007 Implementing CLOCK_SETTING state part-3
018 Exercise-007 Implementing CLOCK_SETTING state part-4
020 Exercise-007 Updating real time
021 Exercise-007 ALARM_SETTING state
022 Exercise-007 Implementing ALARM_SETTING state
023 Exercise-007 Implementing ALARM_NOTIFY state
11 - Active Objects
001 Active Objects
002 Orthogonal state pattern
003 Exercise-008Implementation part 1
004 Exercise-008Implementation part 2
005 Exercise-008Implementation part 3
006 Exercise-008Implementation part 4
007 Exercise-008Implementation part 5
009 Exercise-008Implementation part 6
010 Exercise-008Implementation part 7
011 Exercise-008Implementation part 8
012 Exercise-008Implementation part 9
The text was updated successfully, but these errors were encountered: