本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页
最热资讯


亲,“电路城”已合并升级到更全、更大、更强的「新与非网」。点击查看「新与非网」
该项目解释了有限状态机如何用于制作带有字母数字显示器和旋转编码器的用户友好界面。作为示例,我们将实现一个简单的计时器,你将能够轻松创建自己的机器。
历史
我想把几个设备放在同一个盒子里。在这个盒子里,我连接了一个 Arduino Nano Every 和一个 2 x 16 字符的 LCD(液晶显示器)、一个旋转编码器、一个 RTC(实时时钟)、一个蜂鸣器和一个温度和压力测量模块。现在,通过转动旋转编码器,我可以访问时钟和日历、温度计和气压计、潮汐时钟、计时器等等……通过推动旋转编码器,我可以调整许多参数:温度单位( °C、°F 或 °K)、压力单位(mmHg 或 hPa)、用于计算海平面压力的高度、LCD 的亮度……
Arduino 板位于盒子的孔后面,因此我可以将它连接到我的 PC 并在不打开盒子的情况下对其进行编程。所以我创建并添加了新设备,但我从不打开盒子。今天我特意打开了它,只是为了给它拍照并展示里面的东西。
定时器使用说明
要使用此计时器,您可以通过按下旋钮 3 秒以上来设置延迟。您可以通过旋转并按下旋钮来选择小时数、分钟数和秒数。然后计时器启动,您可以在 LCD 上看到延迟减少。当延迟达到 0 时,计时器会发出哔哔声。您可以通过按下旋钮来停止蜂鸣器。
这很简单,所以我们可以添加一些功能,只是为了练习。首先,如果您转动旋钮,您可以看到并调整 LCD 的亮度。如果您一直转动旋钮,您可以边听边看和调整蜂鸣器的音调。最后一个功能是计时器倒计时的暂停:如果您在计时器运行时按下旋钮,计数将暂停。当您再次按下旋钮时,它将重新启动。
硬件
我在我的盒子里编程了这个计时器,它包含所有必要的硬件,但你可以使用面包板来制作自己的计时器。您将需要一个 Arduino 板(我有一个 Nano Every,但可以使用任何板)、一个带背光的标准 2 x 16 字母数字 LCD、一个用于调整 LCD 对比度的 10 kΩ 电位器、一个旋转编码器、一个蜂鸣器、一个 N 沟道 FET(2N7000 或等效产品)和一个驱动 LCD 背光的电阻器。
LCD 将以与 LiquidCrystal 库示例相同的方式连接到 Arduino 板。10 kΩ 电位计为 V0 输入提供可调电压。
旋转编码器的引脚 CLK、DT 和 SW 将分别连接到 Arduino 的引脚 6、7 和 8。在我的旋转编码器中,引脚 CLK 和 DT 有一个集成的上拉电阻,而 SW 引脚没有。因此,引脚 6 和 7 的 pinMode 为 INPUT,引脚 8 为 INPUT_PULLUP。
作为蜂鸣器,任何类型的扬声器都适用。我正在使用由 N 通道 FET 驱动的 50 Ω 微型扬声器。如果您的扬声器具有较小的电阻器,您可能需要添加一个与其串联的电阻器。
软件
该项目旨在展示我们如何使用有限状态机 (fsm) 构建带有旋转编码器和字母数字 LCD 的用户友好界面。所以我们将从一些关于 fsm 的词开始。
在一个 fsm 中,有多种状态,并且在任何时候,系统都处于这些状态之一,称为当前状态。事件可能发生,并改变当前状态。这些变化称为过渡。您可以通过状态图来描述系统,其中表示所有状态和所有转换。如果您想了解更多关于 fsm 的信息,建议您查看 Gustavo 的非常好的项目“Using Finite State Machines”。
当我们正在创建用户界面时,可以想象当用户操作旋转编码器时系统的当前状态会改变是合乎逻辑的。因此,使当前状态发生变化的事件是从旋转编码器发出的,它们可以是:
在我们的计时器中,有 8 个状态。它们在以下状态图中表示,并带有它们之间的转换:
状态图的渐进式构建
首先,让我们只考虑显示参数的三种状态:
这些状态中的每一个都显示不同的参数:计时器的剩余时间 (timerDisplay)、LCD 的亮度 (dimmingDisplay) 和蜂鸣器的频率 (toneDisplay)。这些状态之间的转换由事件 cwRot 和 ccwRot 激活,即用户通过转动旋转编码器来更改显示的参数。
timerDisplay 状态以红色表示,表示它是上电时的初始状态。
你可以想象这些状态在一个圆圈上,当你在一个方向或另一个方向转动旋转编码器时,你在这个圆圈上移动:
在我们的计时器中,圆圈上只有三个状态,但我们可以有任意数量的状态。这非常重要,因为它是扩展系统的方式。对于要添加到系统中的每个功能或设备,您在圆圈上添加一个新状态。并且如果主圈子上状态太多,可以添加其他圈子,放上状态,实现圈子之间的交流。(例如,主圆上的状态“系统”可以访问第二个圆,其状态用于调整系统参数)。
现在,从显示参数的每个状态,我们将添加一个过渡到允许用户调整此参数的状态。这些转换将通过长按旋转编码器激活:
一旦用户调整了参数,他通过短按旋转编码器返回到相应的显示状态。有一个例外:在设置计时器延迟的小时数后,他必须调整分钟然后调整秒数。所以我们将添加两个新状态,它们之间的转换也将通过短按旋转编码器来激活:
特殊的过渡
现在,我们将添加一个特殊的过渡。当此转换被激活时,系统从 timerDisplay 状态转到 timerDisplay 状态。不,当前状态不会改变:
当系统处于 timerDisplay 状态并且用户短暂按下旋转编码器时,会激活此转换。它的作用是调用一个定位布尔值的函数。这个布尔值改变了一些其他函数的行为。这样,用户可以在计时器激活时暂停或重新开始计数,或者在计时器达到 0 时停止蜂鸣器。简单但非常强大。
为了终止,我们将添加四个不是强制性的过渡,这只是最后的接触。想象一下,您正在调整蜂鸣器的频率(此操作过程中蜂鸣器会发出哔哔声),并且您的手机响了。当您回来时,蜂鸣器仍在发出哔哔声。这不是一个好主意。所以我们会在 30 秒后自动停止。为此,我们添加了一个由 timeOut 事件激活的转换,它从设置状态返回到蜂鸣器频率的显示状态。我们将对除 timerSecondSet 状态以外的所有设置状态执行此操作,因为如果我们自动停止此状态,则计时器将启动。当用户设置秒数时,计时器会根据需要等待他按下旋转编码器。
现在状态图就完成了:
软件实现
为了将这个 fsm 实现到计时器的软件中,我使用了 arduino-fsm 库。在这个库中,每个转换都可能与一个函数相关联(我用它来暂停计数或停止蜂鸣器),每个状态可能与三个函数相关联:第一个在系统进入状态时被激活一次,只要系统处于这种状态,第二个就会被重复激活,当系统离开这个状态时,第三个就会被激活一次。
例如,在 dimmingSet 状态下,第一个函数 (dimmingSetEnter) 使显示器的正确字符闪烁。第二个函数(dimmingSet)根据旋转编码器的旋转来调整显示器的亮度并显示其值。第三个函数 (dimmingSetExit) 将亮度值存储到 EEPROM 中。这对于在断电后检索值很有用。要对这些函数进行编码,您无需知道它们被调用的 fsm 状态以及它通过的转换。为了使您的系统发展,您可以独立修改功能或 fsm。
结论
关于这个软件还有其他几件事可以说,例如 EEPROM 的使用,但我认为评论足以解释它们,并且主要主题是使用有限状态机。
示意图:
如果您对此项目有任何想法、意见或问题,请在下方留言。
基于 Arduino UNO 的倒数计时器
2021-10-28
基于 Arduino Nano 的脉冲金属探测器—FoxyPI
2021-11-27
基于8位STM8S103F3P6微控制器和编码器的电子厨房计时器
2021-04-03
基于ESP8266的Tobers时间开关
2021-05-09
延时计时器,为电力系统提供故障保护
2021-01-21
讨论