模拟任务书
实现一套三相交通指示控制器。红灯 5 秒、绿灯 5 秒、黄灯 1.5 秒循环。按键或 Touch 产生行人请求:绿灯至少运行 1 秒后提前转黄灯。长按按键进入红灯闪烁的紧急模式,再次长按恢复。所有计时不得使用阻塞式长延时。
- 串口每秒输出当前相位和剩余秒数。
- 串口可分别修改红、绿、黄相位时间。
- 紧急模式以 300 ms 周期闪红灯并短鸣。
- 普通 LED 在绿灯阶段点亮,其他阶段关闭。
建议评分点
| 评分项 | 分值 |
|---|---|
| 红绿黄三相自动循环正确 | 20 |
| 硬件定时 tick 与非阻塞状态机 | 15 |
| 按键中断、消抖和行人请求 | 15 |
| Touch 行人请求 | 10 |
| 紧急模式及恢复 | 15 |
| 串口改时间、查询倒计时 | 15 |
| 错误参数处理与状态日志 | 10 |
总分:100
接线与工程
| 功能 | 接法 |
|---|---|
| RGB | 板载 GPIO38,若不亮改 GPIO48 |
| 按键 | GPIO15 与 GND |
| Touch | GPIO4 接导电片 |
| 普通 LED | GPIO21 经 220 Ω 接 LED |
| 蜂鸣器 | GPIO16 |
工程:arduino_pio/mock_04_traffic_timer
串口验收
STATUS
REQUEST
PHASE EMERGENCY
PHASE RED
TIME RED 3000
TIME GREEN 4000
TIME YELLOW 1000
SAVE主要修改:src/task_logic.cpp中的 enterPhase()、shortPress() 和 tick()。
高概率变题
- 交通灯改为流水灯、工序控制或电梯状态。
- 行人请求改为只在红灯结束后生效。
- 倒计时结束前 3 秒增加蜂鸣提示。
- 要求断电保存各相位时间。
现场修改指南:先画顺序再改
当前状态表是 0 红、1 绿、2 黄、3 紧急,普通顺序是红 → 绿 → 黄 → 红。如果现场只是修改每段持续时间,直接使用 TIME命令;只有顺序、阶段数量或请求规则变化才改源码。
题目一句话对应哪里
| 题目关键词 | 函数/变量 | 作用 |
|---|---|---|
| 阶段名称 | phaseName() | 把 mode 0..3 翻译成 RED/GREEN/YELLOW/EMERGENCY |
| 每段持续时间 | phaseDuration() | 从 user1/user2/user3取红绿黄时间 |
| 进入新阶段 | enterPhase() | 统一改变 mode 并重新开始计时 |
| 短按/Touch 请求 | shortPress() | 只记录请求,何时执行由 tick()决定 |
| 长按紧急 | longPress() | 进入紧急或恢复之前阶段 |
| 顺序和倒计时 | tick() | 本题最重要的非阻塞状态机 |
| 串口阶段/时间 | command() | 解析 REQUEST、PHASE、TIME |
变量翻译
| 代码 | 含义 |
|---|---|
phase_started_ms | 当前阶段开始时刻 |
pedestrian_request | 是否已有行人/外部请求等待处理 |
resume_mode | 进入紧急前的状态,用于恢复 |
user1/user2/user3 | 红、绿、黄持续毫秒数,可由 SAVE 保存 |
elapsed | 当前阶段已经运行了多久 |
remaining | 当前阶段剩余毫秒数 |
最安全:只改时间和提示
| 现场要求 | 做法 |
|---|---|
| 红 3 秒 | TIME RED 3000 |
| 绿 8 秒 | TIME GREEN 8000 |
| 黄 1 秒 | TIME YELLOW 1000 |
| 掉电保持 | 全部设置后输入 SAVE |
| 紧急闪烁翻转 500 ms | tick()紧急分支中的 now_ms / 300改为 now_ms / 500 |
| 紧急蜂鸣 2000 Hz/100 ms | startBeep(1800, 120)改为 startBeep(2000, 100) |
四种高概率变题:可直接照着改
示例 A:普通顺序改成红 → 黄 → 绿 → 红
替换范围:在 tick()中找到三个 enterPhase()组成的状态转换块,完整替换为:
if (app.config().mode == 0) {
enterPhase(app, 2, now_ms); // 红 -> 黄
} else if (app.config().mode == 2) {
enterPhase(app, 1, now_ms); // 黄 -> 绿
} else {
pedestrian_request = false;
enterPhase(app, 0, now_ms); // 绿 -> 红
}同时检查请求条件:原代码只在 mode == 1的绿灯阶段提前处理请求。如果你的新故事中请求作用于其他阶段,也要修改转换块上方的条件。
示例 B:倒计时最后 3 秒,每秒鸣叫一次
替换范围:在 tick()末尾找到 if (seconds != last_countdown_second),完整替换为:
if (seconds != last_countdown_second) {
last_countdown_second = seconds;
app.emit("COUNTDOWN PHASE=%s SEC=%lu",
phaseName(app.config().mode),
static_cast<unsigned long>(seconds));
if (seconds > 0 && seconds <= 3) {
app.startBeep(2000, 100);
}
}利用“秒数变化”判断,蜂鸣器每秒只触发一次;不要直接把鸣叫放在 tick()外层,否则 10 ms 会调用一次。
示例 C:电位器控制绿灯时间 1~10 秒
替换范围:完整替换当前空的 adcSample()。
void adcSample(contest::ContestApp &app, uint32_t millivolts) {
const uint32_t limited = min<uint32_t>(millivolts, 3300);
app.config().user2 = 1000 + (limited * 9000UL / 3300);
}0 mV 对应 1000 ms,3300 mV 对应 10000 ms。若要求串口实时显示,可在函数末尾加入 app.emit(),但采样过快时日志会很多。
示例 D:行人请求只记录,等本轮绿灯自然结束后再进入黄灯
原代码会在绿灯运行至少 1 秒后提前切换。要取消提前切换,只需修改一个条件。
找到:
if ((app.config().mode == 1 && pedestrian_request && elapsed >= 1000)
|| elapsed >= duration) {改为:
if (elapsed >= duration) {请求标志仍会保留,并在完成一轮后清除。如果题目要求请求决定下一阶段,需要在状态转换块中读取 pedestrian_request。
改完后的最短验收
- 先把三个时间临时设为 1000 ms 左右,加快完整循环测试。
- 输入
STATUS,核对相位名称、请求标志和三个时间。 - 分别在红、绿、黄阶段短按,确认请求只在规定阶段生效。
- 长按进入紧急,再次长按,确认恢复到进入前的阶段。
- 运行过程中连续发送串口命令,倒计时和灯光不能停顿。
不要使用长 delay():阶段等待必须通过 now_ms - phase_started_ms判断。增加阶段时还要同步名称表、时间表、输出、命令范围和 createTaskHooks()最大模式。