基于有限状态机的 Arduino 电子表 – YAKINDU Statechart Tools 实战
项目简介
本项目展示如何使用 YAKINDU Statechart Tools (SCT) 图形化建模有限状态机 (FSM),自动生成 C++ 代码并部署到 Arduino,实现功能完整的数字电子表。包含时钟、日期、双闹钟、整点报时、秒表、背光控制等经典功能,通过 LCD Keypad Shield 交互。适合学习状态机建模、代码生成与嵌入式系统设计。
核心工具链
| 工具 | 用途 | 备注 |
|---|---|---|
| YAKINDU Statechart Tools | 图形化状态机建模、仿真、C++ 代码生成 | 非商业免费,官网下载 |
| Eclipse C++ IDE for Arduino (Sloeber) | Arduino C++ 开发、编译、上传 | 集成 SCT 生成代码 |
| Arduino IDE | 备选上传工具 |
硬件平台
- Arduino UNO / Mega (兼容 LCD Keypad Shield 即可)
- LCD Keypad Shield (1602 字符屏 + 5 向按键 + 复位键)
状态机设计核心
功能规格 (参考 David Harel 经典数字表模型)
- 模式:时间显示 → 日期显示 → 闹钟1设置 → 闹钟2设置 → 报时开关 → 秒表
- 输入事件:
onoff、mode、set、light、light_r(对应 5 向按键) - 内部事件:
every 100ms / time += 1驱动时基 - 显示变量:
display.first/second/third分别对应 时/分/秒 或 年/月/日 等 - 输出操作:
display.updateLCD(...)刷新屏幕
状态机结构 (层次化设计)
- 顶层区域:主模式切换、背光控制、报时子状态机
- 子图:
SetTime(通用时间编辑)、StopWatch(秒表含圈数)、Chime(整点报时) - 高级元素:复合状态、历史状态 (浅/深)、退出节点、条件转移、内部转移
完整状态机图见项目图片(主图、子图拆解)。
LCD Keypad Shield 按键映射
5 个按键共用 A0 模拟口,通过分压电阻产生不同电压:
| 按键 | ADC 典型值 | 阈值区间 | 映射事件 |
|---|---|---|---|
| Right | 0 | < 50 | onoff |
| Up | ~100 | < 150 | light_r (秒表启动/圈数) |
| Down | ~257 | < 300 | light (秒表停止) |
| Left | ~411 | < 550 | set |
| Select | ~640 | < 850 | mode |
| None | 1023 | >= 850 | — |
按键消抖与事件上报
#define NONE 0
#define SELECT 1
#define LEFT 2
#define DOWN 3
#define UP 4
#define RIGHT 5
int oldState = NONE;
static int readButton() {
int 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;
}
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;
}
}
}
主程序框架
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, last_cycle_time = 0;
void loop() {
raiseEvents(); // 1. 处理按键事件
last_cycle_time = current_time;
current_time = millis();
timer_sct->updateActiveTimer(stateMachine, // 2. 更新定时器 (驱动 every 100ms)
current_time - last_cycle_time);
stateMachine->runCycle(); // 3. 运行状态机周期
}
DisplayHandler 关键实现
class DisplayHandler : public sc::io::Display_OCB {
LiquidCrystal lcd;
public:
DisplayHandler() : lcd(8,9,4,5,6,7) {} // LCD Keypad Shield 引脚
void updateLCD(int first, int second, int third, const char* text) override {
lcd.setCursor(0,0);
// 格式化显示 HH:MM:SS 或 文本模式
if (text) { lcd.print(text); }
else {
char buf[17];
sprintf(buf, "%02d:%02d:%02d", first, second, third);
lcd.print(buf);
}
}
};
获取与运行示例
- 安装 YAKINDU SCT (非商业免费,30 天试用后需申请免费许可)
- File → New → Example → YAKINDU Statechart Examples → Arduino - Digital Watch (C++)
- 连接硬件,使用 Sloeber/Eclipse 或 Arduino IDE 编译上传
扩展与学习价值
- 掌握模型驱动开发 (MDD) 流程:建模 → 仿真 → 代码生成 → 集成
- 理解分层状态机、历史机制、正交区域 等高级建模概念
- 可移植到其他单片机 (STM32, ESP32 等) 仅需替换定时器/显示/按键驱动层
- 扩展功能:添加闹钟铃声 (蜂鸣器)、RTC 模块 (DS3231) 断电走时、蓝牙同步手机时间
资源链接
- YAKINDU SCT 下载:yakindu.org
- Sloeber Eclipse for Arduino:sloeber.io
- 项目源码/模型文件:见 Code 部分
制造工艺
- 用 Arduino 打造绚丽显示器 Ambilight 系统
- Arduino 数字骰子:LCD 显示随机点数的电子骰子
- 用 Arduino 制作超声波悬浮装置:分步指南
- Arduino 与智能手机打造智能电压表——简单易做的 DIY 项目
- 基于 Arduino 与 MAX30100 的实时物联网心率监测系统
- Arduino Uno WiFi 网页服务器:通过浏览器控制 LED 开关
- 自制 Arduino 电子宠物「Tamaguino」:复刻经典 Tamagotchi 全攻略
- DIY Arduino 智能手表:打造属于你的 Apple Watch 风格腕表
- Arduino 自动化 Chrome 恐龙游戏 - LDR 光敏电阻 + Python 实现全自动跳跃
- 使用 Arduino Nano 和 RDA8057M 制作紧凑型 FM 收音机
- 无线Arduino编程盾:HC-05蓝牙模块实现免USB线烧录
- 设计有限状态机:从概念到实现