返回首页

Arduino 上的事件驱动编程:模式和示例

这篇文章通过 EVA Core 库演示了在 Arduino 项目中实现事件驱动架构。考虑了组件组合原理、传感器和外设的代码示例、方法的限制。面向中高级开发者的材料。

Arduino 事件模型:从理论到实践
Advertisement 728x90

Arduino 上的事件驱动编程:嵌入式企业级模式

Arduino 传统上与线性草图和循环的 loop() 相关联。但随着项目复杂度增加,这种方法会导致面条代码。我们介绍一种方法,将企业级应用的事件驱动架构引入嵌入式开发,使用 EVA Core 库。

微控制器中的结构问题

在桌面和服务器应用中,代码围绕事件处理器组织:点击、定时器、网络数据包。然而,Arduino 将一切强制塞入单一的 loop(),需要手动使用 millis() 和标志跟踪状态。这导致两个关键限制:

  • 无法隔离组件逻辑——时序会传播到整个对象层次结构
  • 模块间缺乏明确的时序要求契约

标准 Arduino 库仅零散解决此问题:有按钮处理器、独立定时器,但无统一事件系统。尝试构建“全合一”解决方案会与特定 loop() 实现紧密耦合。

Google AdInline article slot

EVA Core:统一系统中的三个组件

EVA Core 库(事件驱动架构)通过三个交互层解决问题:

  • 类方法的回调机制 — 无需全局函数即可订阅事件
  • Tickable 接口 — 通过 tick() 方法为对象提供时间循环访问
  • Survival Kit — 基于前两原则的现成组件集(按钮、定时器)

关键创新——将时序逻辑与业务规则分离。传感器管理自身时序,应用仅响应事件。这实现:

  • 在组件中隔离滤波和去抖算法
  • 保证嵌套对象的更新节奏
  • 无需通过层次结构传播调用

让我们通过温度传感器示例查看实现:

Google AdInline article slot
#include <evaTickable.h>
#include <evaHandler.h>

using namespace eva;

class TempSensor : public Tickable {
private:
  IHandler* listener = nullptr;
  unsigned long lastRead = 0;

public:
  void subscribe(IHandler* handler) {
    listener = handler;
  }

private:
  void tick() override {
    if (millis() - lastRead > 1000) { // Interval 1 sek
      int value = readTemperature();
      if (listener && value != lastValue) {
        CallbackInfo info;
        info.eventType = TEMP_UPDATE;
        info.eventArg = value;
        listener->invoke(this, info);
      }
      lastRead = millis();
    }
  }
};

class ClimateControl : public IHandler {
private:
  TempSensor sensor;

  void onTempUpdate(int value) {
    if (value > 25) activateCooling();
  }

public:
  ClimateControl() {
    sensor.subscribe(this);
  }

  void invoke(void* sender, CallbackInfo info) override {
    if (info.eventType == TEMP_UPDATE) {
      onTempUpdate(info.eventArg);
    }
  }
};

void setup() {
  static ClimateControl system;
}

void loop() {
  eva::tac(); // Edinaya point update
}

此方法的优势

  • TempSensor 封装了轮询和滤波逻辑
  • ClimateControl 只了解事件,不知时序细节
  • 添加新传感器无需修改 loop()
  • 可隔离测试业务逻辑与时序

外设的 LEGO 架构

Survival Kit 通过编译时模板实现组合原则。例如,模拟输入的多按钮处理构建为变换器链:

template <int PIN, int PIN_MODE, signed short... LEVELS>
using PinMultiButton = Button<QuantizeDecor<DebounceDecor<AnalogPinReader<PIN, PIN_MODE>>, LEVELS...>>;

信号处理架构:

Google AdInline article slot
  • 模拟信号 →
  • 去抖 →
  • 按电平量化 →
  • 按钮逻辑 →
  • 事件

此模式允许从现成组件组装自定义处理器。例如,由离散按钮组成键盘:

#include <evaSwitch.h>

class MyKeypad {
public:
  MyKeypad() {
    pinMode(2, INPUT_PULLUP);
    // ... initialization ostalnykh pinov
  }

  signed short getValue() {
    if (digitalRead(2) == LOW) return 'u';
    // ... processing drugikh knopok
    return 0;
  }
};

// Withborka chains processing
Switch<DebounceDecor<MyKeypad>> keypad;

// Podpiska on events
keypad.setListener(new Handler<App>(this, &App::onKeyPress), ON_PRESS);

每层处理自身职责。如需,可将去抖替换为更复杂算法,或添加按压持续时间滤波——无需更改核心应用逻辑。

限制与使用场景

EVA Core 定位为方法论工具,而非通用解决方案。适用于:

  • 构建复杂对象层次的项目
  • 需要清晰分离时序与业务逻辑
  • 在简单设备上教授事件驱动架构原则

关键限制:

  • 由于虚表,RAM 使用增加
  • 对初学者复杂(需了解模板和方法指针)
  • 简单项目过剩(如控制单个 LED)

需理解:库目标是演示架构模式,而非资源优化。对于资源紧张的生产项目,需分析开销。

关键要点

  • 事件模型将时序依赖隔离在组件中
  • Tickable 接口确保嵌套对象定期更新
  • 模板组合提供灵活性,无运行时开销
  • 无需传播调用,简化重构和测试
  • Arduino 成为企业模式的试验场

此方法证明,即使在资源受限微控制器上,也可应用专业设计方法。关键益处——代码变得可预测且可扩展,而非仅勉强完成任务。

— Editorial Team

Advertisement 728x90

继续阅读