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

DIY数字手表

发布时间:2022-08-17
分享到:

DIY数字手表

发布时间:2022-08-17
分享到:

为 16x2 LCD Keypad Shield 使用 YAKINDU 状态图工具创建数字手表。

我将向您展示如何使用YAKINDU Statechart Tools创建数字手表并在使用 LCD Keypad Shield 的 Arduino 上运行。

数字手表的原始模型取自大卫哈雷尔。他之前发表过一篇关于“状态机和状态图的传统形式的广泛扩展”的论文。在论文中,他以数字手表为例进行了研究。我以此为灵感,使用YAKINDU Statechart Tools (一种用于创建状态机图形模型并使用它生成 C/C++ 代码的工具)重建了手表,并在 Arduino 上让它重新焕发生机。

数字手表的工作原理
让我们从定义数字手表应该如何工作开始。

基本上,它是一个具有不同模式的可配置手表。主要是显示当前时间,但还有一些其他功能。作为输入,您有一个开/关、一个模式和一个设置按钮。此外,还可以打开和关闭灯。

使用模式按钮,您可以区分模式并激活/禁用时钟功能:

  • 显示时间(时钟)
  • 显示日期(日期)
  • 设置闹钟(闹钟 1、闹钟 2)
  • 启用/禁用铃声(设置铃声)
  • 使用秒表(秒表)

在菜单中,您可以使用开/关按钮来配置模式。设置按钮允许您设置时间 - 例如时钟或闹钟。秒表可以通过使用开灯和关灯按钮来控制 - 启动和停止。您还可以使用集成的计圈器。

此外,还有一个钟声,每时每刻都在响起,并且集成了一个可控的背光。不过在第一步,我没有将它们连接到 Arduino。

状态机

我不想详细解释这个例子。这不是因为它太复杂,它只是有点太大了。不过我会尝试解释它具体如何工作的基本思想。通过查看模型或下载并模拟它。状态机的某些部分在子区域中汇总,例如设置的时间区域。这样就可以确保状态机的可读性。

该模型共分为两部分 - 图形和文本。

  • 在文本部分,将定义事件、变量等。
  • 在图形部分 - 状态图 - 指定了模型的逻辑执行。

要创建满足指定行为的状态机,需要一些输入事件,这些事件可以在模型中使用:onoff 、set 、mode 、light和light_r。在定义部分中使用了一个内部事件,它每 100 毫秒递增一次时间值:

every 100 ms /  time += 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 、History 、Sub-Diagrams 、ExitNodes等。

LCD 键盘屏蔽
LCD Keypad Shield 对于需要一个可视化屏幕和一些按钮作为输入的简单项目来说非常酷 - 一个典型的简单 HMI(人机界面)。LCD Keypad Shield 包含五个用户按钮和一个用于重置的按钮。五个按钮一起连接到 Arduino 的 A0 引脚。它们中的每一个都连接到一个分压器,可以区分按钮。

您可以使用analogRead(0) 来查找特定值,这当然可能因制造商而异。这个简单的项目在 LCD 上显示当前值:

#include <Arduino.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
    lcd.begin(16, 2);
    lcd.setCursor(0,0);
    lcd.write("Measured Value");
}

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 的键连接起来。我已经展示了如何读取按钮,但是为了将它们连接到状态机,需要对按钮进行去抖动。否则事件将被多次引发,从而导致不可预测的行为。软件去抖动的概念并不新鲜。

在我的实现中,我检测到下降沿(释放按钮)。我读取按钮的值,等待 80 毫秒,保存结果并读取新值。如果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;
            }
            default: {
                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++)

如果您对此项目有任何想法、意见或问题,请在下方留言。

以上内容翻译自网络,原作者:Robin Herrmann,如涉及侵权,可联系删除。

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论