Ampel:嵌入式交通信号轻量级状态机框架
1. Ampel 库概述面向交通信号控制的嵌入式轻量级状态机框架Ampel德语“红绿灯”是一个专为嵌入式交通信号控制系统设计的开源 C 库最初面向 Arduino 平台构建但其架构具备良好的可移植性可无缝迁移至 STM32、ESP32、Raspberry Pi Pico 等主流 MCU 平台。该库并非一个功能堆砌型的“全栈交通控制器”而是一个以状态机为核心、以时间驱动为骨架、以传感器反馈为闭环依据的轻量级控制框架。其设计哲学直指嵌入式交通控制中最本质的工程问题如何在资源受限Flash 32KB、RAM 4KB、实时性要求明确相位切换误差需 100ms、且需应对突发扰动行人请求、紧急车辆优先、故障降级的约束下实现信号逻辑的清晰表达、可靠执行与安全演进。项目关键词arduino, traffic, light, sensor准确勾勒出其技术边界它不处理视频识别或 V2X 通信等上层智能算法而是聚焦于将标准《GB 14886-2019 道路交通信号灯设置与安装规范》中定义的“定周期控制”、“感应控制”、“多时段控制”等基础模式转化为可验证、可调试、可复用的底层软件构件。其价值不在于提供开箱即用的“红绿灯成品”而在于为硬件工程师和固件开发者提供一套符合 IEC 61508 功能安全基本理念的控制内核——所有状态跃迁均受显式条件约束无隐式跳转所有定时操作均基于硬件滴答SysTick 或 Timer避免delay()引发的阻塞所有外部输入按钮、红外对射、地磁线圈均通过统一的事件接口注入解耦硬件抽象层与业务逻辑。在实际工程实践中Ampel 库常作为交通信号机固件的“控制中枢”被集成。例如在基于 STM32F407 的路口主控板上HAL 库负责初始化 GPIO驱动 LED 灯组、EXTI捕获行人请求按钮中断、TIM生成 1ms 基础滴答、UART与上位机通信而 Ampel 则在此之上构建信号配时模型定义kRed,kYellow,kGreen三个灯色状态配置phase1_duration 45s,phase2_duration 30s,yellow_duration 3s注册onPedestrianRequest()回调以触发“行人过街相位插入”并最终通过setLightState(Phase::kPhase1, Light::kGreen)等语义化 API 控制 HAL 层输出。这种分层设计使固件具备极强的可维护性——当需将单路口升级为协调式绿波带时仅需替换 Ampel 的调度器Scheduler实现而无需改动任何灯色驱动或传感器读取代码。2. 核心架构与设计原理2.1 分层状态机Hierarchical State Machine, HSM模型Ampel 的灵魂在于其采用的分层状态机架构这直接决定了其处理复杂交通逻辑的能力上限。传统平面状态机如enum { IDLE, RED, YELLOW, GREEN }在面对“主路直行左转分离”、“T 型路口三相位”或“夜间黄闪模式”时状态爆炸问题严重。Ampel 通过引入两级状态嵌套将问题解耦顶层状态System State描述系统宏观运行模式kNormalOperation标准多时段定周期控制kPedestrianMode检测到行人请求后进入的专用相位kEmergencyMode接收到消防车/救护车优先通行信号kFailureSafe检测到任一灯组开路/短路故障后的降级模式全红或黄闪底层状态Phase State在当前系统模式下各物理相位的具体灯色组合以标准十字路口为例定义两个正交相位kPhaseNS南北向、kPhaseEW东西向每个相位独立维护其状态Light::kRed,Light::kYellow,Light::kGreen状态跃迁由TransitionRule显式定义例如// 从南北绿灯切换至南北黄灯的规则 TransitionRule ns_green_to_yellow { .from_state Light::kGreen, .to_state Light::kYellow, .duration 3000, // 3秒 .guard []() { return true; } // 无条件触发 };此设计确保了模式切换的安全性当系统处于kEmergencyMode时kPhaseNS和kPhaseEW的状态机被强制重置为kRed且禁止任何非kRed的跃迁从而在 10ms 内完成全路口红灯封锁满足《GB/T 20606-2022 智能交通信号控制系统技术要求》中“紧急优先响应时间 ≤ 50ms”的硬性指标。2.2 时间驱动引擎Time-Driven EngineAmpel 放弃了 Arduinomillis()的粗粒度轮询转而依赖高精度硬件定时器构建时间驱动内核。其核心是TimerManager单例它在SysTick_HandlerARM Cortex-M或TIMER0_IRQHandlerAVR中每 1ms 更新一次全局滴答计数器g_tick_ms并遍历注册的定时任务队列// 定时任务结构体简化版 struct TimedTask { uint32_t next_fire_ms; // 下次触发绝对时间戳ms uint32_t period_ms; // 周期0 表示一次性任务 void (*callback)(); // 回调函数指针 bool is_active; // 使能标志 }; // 在 SysTick 中调用的核心调度函数 void TimerManager::tick() { const uint32_t now g_tick_ms; for (auto task : s_task_list) { if (task.is_active now task.next_fire_ms) { task.callback(); if (task.period_ms 0) { task.next_fire_ms now task.period_ms; } else { task.is_active false; // 一次性任务执行后禁用 } } } }该引擎支撑两大关键能力精确相位定时每个PhaseState关联一个TimedTask其period_ms设为相位持续时间如 45000mscallback执行状态跃迁。传感器去抖与超时行人按钮按下事件被封装为TimedTask首次触发后启动 50ms 去抖定时器若 50ms 内未检测到电平变化则确认有效若 30s 内无后续请求则自动退出kPedestrianMode。2.3 传感器抽象层Sensor Abstraction LayerAmpel 将所有外部输入统一建模为SensorEvent枚举并通过SensorHub进行集中管理彻底隔离硬件差异SensorEvent 类型典型硬件实现触发条件Ampel 处理逻辑kPedestrianRequest机械按钮 上拉电阻按下低电平注册kPedestrianMode跃迁规则kVehicleDetection地磁线圈TLE4935磁场强度 ΔB 15mT延长当前相位绿灯时间最大10skEmergencySignal专用 433MHz 接收模块解析到合法 CRC 数据包立即切换至kEmergencyModekFaultDetectedLED 驱动芯片TLC5940FAULT 引脚检测到开路/过流触发kFailureSafe并记录故障码SensorHub提供标准化接口class SensorHub { public: static void init(); // 初始化所有已注册传感器 static void poll(); // 在主循环中周期调用推荐 10ms 间隔 static bool hasEvent(SensorEvent event); // 查询事件是否发生 static void clearEvent(SensorEvent event); // 清除事件标志 private: static volatile uint8_t s_event_flags; // 位图每位代表一种事件 };此设计使硬件工程师可自由更换传感器型号——只需修改SensorHub::init()中的初始化代码并确保poll()函数正确读取新硬件的寄存器上层 Ampel 控制逻辑完全无需变更。3. 主要 API 接口详解3.1 核心控制类TrafficControllerTrafficController是 Ampel 的门面类封装了所有用户级操作。其实例通常声明为全局静态对象确保单例性和零成本抽象。API参数说明返回值典型用途begin()voidbooltrue初始化成功必须在setup()中首先调用初始化内部状态机、定时器及传感器setPhaseDuration(Phase phase, uint32_t ms)phase: 相位枚举ms: 毫秒数void设置某相位的标准绿灯时长如setPhaseDuration(kPhaseNS, 45000)setYellowDuration(uint32_t ms)ms: 黄灯持续毫秒数void统一设置所有相位黄灯时间通常为 3000~5000msstart()voidvoid启动主控制循环开始执行相位切换stop()voidvoid暂停所有相位切换保持当前灯色不变forcePhase(Phase phase, Light light)phase: 目标相位light: 目标灯色void强制设置某相位灯色调试/故障恢复用getPhaseState(Phase phase)phase: 查询相位Light当前灯色获取实时状态用于 UI 显示或日志记录关键设计点start()并非阻塞函数它仅设置内部运行标志m_is_running true真正的状态跃迁由TimerManager的后台定时任务驱动。这保证了主循环loop()的绝对自由可安全执行 UART 日志打印、WiFi 连接等耗时操作。3.2 状态机配置类StateMachineConfig该类用于在编译期或运行期定制状态机行为是 Ampel 灵活性的关键。配置项类型默认值说明enable_pedestrian_modebooltrue是否启用行人请求功能pedestrian_max_wait_msuint32_t30000行人请求后最长等待时间超时自动退出emergency_timeout_msuint32_t120000紧急模式持续时间超时后自动恢复常态fault_recovery_strategyenum { kAllRed, kYellowFlash }kAllRed故障模式下的默认策略log_levelenum { kError, kWarn, kInfo, kDebug }kWarn日志详细程度影响Serial.print()输出量配置通过TrafficController::configure()方法应用StateMachineConfig config; config.enable_pedestrian_mode true; config.pedestrian_max_wait_ms 45000; // 延长至45秒 controller.configure(config);3.3 传感器事件注册 APIAmpel 提供两种事件响应机制适配不同实时性需求回调注册Callback Registration适用于高优先级、需立即响应的事件如紧急车辆controller.onEvent(kEmergencySignal, []() { Serial.println(EMERGENCY: Activating priority mode!); // 执行自定义逻辑如触发声光报警 });轮询查询Polling Query适用于低频、可容忍微小延迟的事件如行人请求void loop() { if (SensorHub::hasEvent(kPedestrianRequest)) { controller.requestPedestrianPhase(); SensorHub::clearEvent(kPedestrianRequest); } }4. 典型应用场景与工程实践4.1 标准十字路口定周期控制最简部署这是 Ampel 的“Hello World”场景仅需 20 行核心代码即可实现符合国标的四相位控制#include Ampel.h TrafficController controller; void setup() { Serial.begin(115200); // 配置GPIO假设D2-D5控制南北红黄绿D6-D9控制东西红黄绿 pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT); controller.begin(); controller.setPhaseDuration(kPhaseNS, 45000); // 南北绿灯45秒 controller.setPhaseDuration(kPhaseEW, 30000); // 东西绿灯30秒 controller.setYellowDuration(3000); // 黄灯3秒 controller.start(); } void loop() { // 主循环空闲状态机由定时器驱动 }此时Ampel 自动构建状态跃迁序列kPhaseNS:kGreen → kPhaseNS:kYellow → kPhaseEW:kRed → kPhaseEW:kGreen → ...严格遵循“红-绿-黄-红”循环且黄灯总在绿灯结束后、下一相位红灯开始前精确亮起。4.2 感应式行人过街控制增强交互在标准路口基础上增加一个按钮接 D10实现“按即走”void setup() { // ... 前序初始化 pinMode(10, INPUT_PULLUP); // 按钮上拉按下为LOW controller.begin(); // ... 相位配置 controller.start(); // 注册按钮中断Arduino风格 attachInterrupt(digitalPinToInterrupt(10), onButtonPress, FALLING); } void onButtonPress() { // 中断服务程序中仅置位标志避免在ISR中调用复杂API static volatile bool request_pending false; request_pending true; } void loop() { if (request_pending) { controller.requestPedestrianPhase(); // Ampel内部处理相位插入逻辑 request_pending false; } }Ampel 的requestPedestrianPhase()会检查当前是否允许插入如非kEmergencyMode若当前为kPhaseNS:kGreen则缩短其剩余时间至 5s然后执行NS:Green→NS:Yellow→NS:Red→EW:Red→Pedestrian:Green行人绿灯持续 25s 后自动恢复主路正常周期4.3 故障安全模式工业级可靠性在真实部署中LED 灯组开路是最高发故障。Ampel 通过硬件电路在 LED 驱动回路中串联电流检测电阻与软件协同实现快速诊断// 在主循环中定期检测例如每500ms void checkLampHealth() { // 读取南北绿灯驱动芯片的电流检测引脚假设接A0 int adc_val analogRead(A0); if (adc_val 100) { // 电流低于阈值判定开路 controller.enterFailureMode(kAllRed); Serial.println(FAULT: NS Green lamp open-circuit!); } } void loop() { checkLampHealth(); // ... 其他逻辑 }一旦enterFailureMode(kAllRed)被调用Ampel 立即将kPhaseNS和kPhaseEW的状态强制设为kRed禁用所有相位定时器启动一个 1Hz 的黄闪定时器若配置为kYellowFlash通过Serial或 CAN 总线广播故障码0x0101NS 绿灯开路此机制确保即使在最恶劣的硬件失效场景下路口也能以可预测、可审计的方式进入安全状态满足功能安全 SIL2 等级要求。5. 移植指南从 Arduino 到 STM32 HALAmpel 的跨平台能力源于其严格的硬件抽象。将其移植至 STM32F407 平台仅需重写三个文件5.1platform/stm32f4xx_hal.cpp—— HAL 适配层#include stm32f4xx_hal.h #include Ampel.h // 实现 Ampel 要求的底层 GPIO 操作 void platform_set_light(Phase phase, Light light) { GPIO_TypeDef* port; uint16_t pin; // 根据 phase 和 light 查找对应 GPIO例如 NS-Green GPIOA, Pin 5 switch (phase) { case kPhaseNS: port GPIOA; pin GPIO_PIN_5; break; // ... 其他映射 } HAL_GPIO_WritePin(port, pin, (light kGreen) ? GPIO_PIN_SET : GPIO_PIN_RESET); } // 实现 1ms SysTick 定时器 void SysTick_Handler(void) { HAL_IncTick(); TimerManager::tick(); // 调用 Ampel 时间引擎 }5.2platform/stm32f4xx_sensor.cpp—— 传感器驱动#include stm32f4xx_hal.h extern C { // 重定向 Ampel 的 SensorHub::poll() void SensorHub::poll() { // 读取 EXTI 按钮状态 if (__HAL_GPIO_EXTI_GET_FLAG(GPIO_PIN_13) ! RESET) { __HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_13); s_event_flags | (1 kPedestrianRequest); } // 读取 ADC 通道获取地磁数据... } }5.3main.c—— 主程序整合#include stm32f4xx_hal.h #include Ampel.h TrafficController controller; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化所有灯控GPIO MX_EXTI_Init(); // 初始化按钮中断 MX_ADC_Init(); // 初始化地磁ADC controller.begin(); controller.setPhaseDuration(kPhaseNS, 45000); controller.setYellowDuration(3000); controller.start(); while (1) { // Ampel 状态机由 SysTick 驱动此处可处理其他任务 HAL_Delay(10); } }整个移植过程无需修改 Ampel 库的任何一行源码体现了其“硬件无关性”设计的工程价值。在 STM32 平台上还可利用 FreeRTOS 将TrafficController::start()封装为独立任务进一步提升系统并发能力void trafficTask(void *pvParameters) { controller.start(); for(;;) { vTaskDelay(1); // 释放CPU但状态机仍由SysTick驱动 } } // 创建任务 xTaskCreate(trafficTask, TrafficCtrl, 256, NULL, 2, NULL);6. 调试与诊断技巧Ampel 内置了面向工程师的深度诊断能力远超简单Serial.println()状态机快照State Snapshot调用controller.dumpState(Serial)可输出当前所有相位状态、剩余时间、活跃事件标志格式如下[STATE DUMP] 12456ms System Mode: kNormalOperation PhaseNS: kGreen (remaining: 23456ms) PhaseEW: kRed (remaining: 0ms) Events: kPedestrianRequest0, kEmergencySignal0, kFaultDetected0定时器审计Timer Audit启用log_level kDebug后每次定时器触发都会记录Timer fired: PhaseNS-Yellow at 12456ms便于排查相位漂移。硬件环回测试Hardware Loopback Test在setup()中调用controller.selfTest()它会依次点亮所有灯组并读取电流反馈生成一份包含PASS/FAIL的硬件健康报告是产线烧录后的必备测试项。这些工具使 Ampel 不再是“黑盒”而是成为嵌入式工程师手中一把可信赖的精密手术刀——在千行交通固件中精准定位到那一个因millis()溢出导致的 49.7 天后相位错乱的 bug。