使用有限状态机在 Arduino 上的数字手表
组件和用品
| × | 1 | ||||
| × | 1 |
应用和在线服务
| ||||
|
关于这个项目
你好,
我将向您展示如何使用 YAKINDU Statechart Tools 创建数字手表并在使用 LCD Keypad Shield 的 Arduino 上运行。
数字手表的原始模型取自大卫·哈雷尔 (David Harel)。他发表了一篇关于“状态机和状态图的传统形式主义的广泛扩展”的论文。
在这篇论文中,他以数字手表为例进行了研究。我以它为灵感,用 YAKINDU 状态图工具(一种用于创建状态机图形模型并用它生成 C/C++ 代码的工具)重建了手表,并在 Arduino 上让它恢复生机。
数字手表的工作原理
让我们首先定义数字手表应该如何工作。你还记得这些......让我们说......每个人在 90 年代都拥有的“超酷”数字手表?一个集成的秒表、不同的闹钟和它每整整一小时发出的恼人的哔哔声。如果没有,看看:90年代的数字手表。
所以基本上它是一个具有不同模式的可配置手表。主要是显示当前时间,但还有一些其他功能。作为输入,您有一个开/关 , 模式 和一个集合 按钮。此外,您可以打开和关闭灯。
使用模式 按钮,您可以区分模式并激活/禁用时钟功能:
- 显示时间(时钟)
- 显示日期(Date)
- 设置闹钟(闹钟 1、闹钟 2)
- 启用/禁用提示音(设置提示音)
- 使用秒表(Stop Watch)
在菜单中,您可以使用开/关 按钮来配置模式。 集合 按钮允许您设置时间 - 例如用于时钟或闹钟。秒表可以控制 - 启动和停止 - 通过使用开灯和关灯按钮。您也可以使用集成的计圈器。
此外,还有一个钟声响起,并集成了可控背光。第一步我没有将它们连接到 Arduino。
状态机 图> 图> 图> 图> 图>
我不想详细解释这个例子。不是因为它太复杂,它只是有点太大了。我将尝试解释它如何工作的基本思想。通过查看模型或下载并模拟它,执行应该是不言自明的。状态机的某些部分在子区域中汇总,例如设置时间 地区。这样就可以保证状态机的可读性。
该模型分为两部分 - 图形和文本。在文本部分,将定义事件、变量等。在图形部分 - 状态图 - 指定了模型的逻辑执行。要创建满足指定行为的状态机,需要一些输入事件,可以在模型中使用:onoff , 设置 , 模式 , 轻 , 和 light_r. 在定义部分中使用了一个内部事件,它每 100 毫秒增加一次时间值:
每100毫秒/次+=1
基于 100 毫秒的步长,当前时间将在 HH:MM:SS 中计算 格式:
display.first =(time / 36000) % 24;
display.second =(time / 600) % 60;
display.third =(time / 10) % 60;
这些值将通过操作 updateLCD 连接到 LCD 显示器 每次调用状态机时:
display.updateLCD(display.first, display.second, display.third, display.text)
状态机的基本执行已在数字手表的工作原理部分定义 .在该工具中,我使用了一些“特殊”建模元素,例如 CompositeState , 历史 , 子图 , ExitNodes, 等。详细说明可以在用户指南中找到。
LCD键盘屏蔽
LCD Keypad Shield 对于简单的项目来说非常酷,这些项目需要一个用于可视化的屏幕和一些按钮作为输入 - 一个典型的简单 HMI(人机界面)。 LCD Keypad Shield 包含五个用户按钮和另一个用于重置。五个按钮一起连接到 Arduino 的 A0 引脚。它们中的每一个都连接到一个分压器,可以区分按钮。
您可以使用analogRead(0) 来查找特定值,这当然会因制造商而异。这个简单的项目在 LCD 上显示当前值:
#include
#include
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void setup() {
lcd.begin(16, 2);
lcd.setCursor(0,0);
lcd.write("测量值");
}
void loop() {
lcd.setCursor(0,1);
lcd.print(" ");
lcd. setCursor(0,1);
lcd.print(analogRead(0));
delay(200);
}
这些是我的测量结果:
- 无:1023
- 选择:640
- 左:411
- 向下:257
- 向上:100
- 右:0
有了这些阈值,就可以读取按钮了:
#define NONE 0
#define SELECT 1
#define LEFT 2
#define DOWN 3
#define UP 4
#define RIGHT 5
static int readButton() {
int result =0;
result =analogRead(0);
if (result <50) {
return RIGHT;
}
if (result <150) {
return UP;
}
if (result <300) {
return DOWN;
}
if (result <550) {
return LEFT;
}
if (result <850) {
return SELECT;
}
return NONE;
}
连接状态机
状态机生成的C++代码提供了接口,必须实现这些接口才能控制状态机。第一步是将 in 事件与 Keypad Shield 的键连接起来。我已经展示了如何读取按钮,但是为了将它们连接到状态机,需要对按钮进行去抖动 - 否则事件将被多次引发,从而导致不可预测的行为。软件去抖动的概念并不新鲜。你可以看看Arduino文档。
在我的实现中,我检测到下降沿(释放按钮)。我读取按钮的值,等待 80 毫秒(使用 80 而不是 50 获得更好的结果),保存结果并读取新值。如果 oldResult 不是NONE (未按下),新结果为 NONE ,我知道,该按钮之前已按下,现在已释放。然后,我引发状态机的相应输入事件。
int oldState =NONE;
static void raiseEvents() {
int buttonPressed =readButton();
delay(80);
oldState =buttonPressed;
if (oldState !=NONE &&readButton() ==NONE) {
switch (oldState) {
case SELECT:{
stateMachine->getSCI_Button()->raise_mode();
break;
}
case LEFT:{
stateMachine->getSCI_Button()->raise_set();
break;
}
case DOWN:{
stateMachine->getSCI_Button()->raise_light();
break;
}
case UP:{
stateMachine->getSCI_Button() ->raise_light_r();
break;
}
case RIGHT:{
stateMachine->getSCI_Button()->raise_onoff();
break;
}
默认:{
break;
}
}
}
}
将事物连接在一起
主程序使用三部分:
- 状态机
- 计时器
- 显示处理程序(典型的 lcd.print(...))
DigitalWatch* stateMachine =new DigitalWatch();
CPPTimerInterface* timer_sct =new CPPTimerInterface();
DisplayHandler* displayHandler =new DisplayHandler();
状态机使用一个显示处理程序并获得一个计时器,该计时器将被更新以控制定时事件。之后,状态机被初始化并进入。
void setup() {
stateMachine->setSCI_Display_OCB(displayHandler);
stateMachine->setTimer(timer_sct);
stateMachine->init();
stateMachine->enter();
}
循环做了三件事:
- 引发输入事件
- 计算经过的时间并更新计时器
- 调用状态机
long current_time =0;
long last_cycle_time =0;
void loop() {
raiseEvents();
last_cycle_time =current_time;
current_time =millis();
timer_sct->updateActiveTimer(stateMachine,
current_time - last_cycle_time);
stateMachine->runCycle();
}
获取示例
而已。可能我没有提到实现的每一个细节,但你可以看看例子或发表评论。
将示例添加到正在运行的 IDE 中:
文件 -> 新建 -> 示例 -> YAKINDU 状态图示例 -> 下一步 -> Arduino - 数字手表 (C++)
>> 您可以在此处下载 IDE <<
您可以先试用 30 天。之后,您必须获得许可证,该许可证非商业用途免费 !
制造工艺