基于状态机设计的抢答系统:倒计时、违规检测和时间查询
基于状态机设计的抢答系统:倒计时、违规检测和时间查询
本文介绍了一种基于状态机设计的抢答系统,该系统具备以下功能:
- 倒计时功能:显示倒计时时间,单位为0.1s,保留1位小数。
- 抢答者ID显示:显示第一位抢答者ID号(从1开始,0表示无抢答者)。
- 违规抢答检测:抢答开始后,如倒计时未结束前有违规抢答者,则记录第一违规抢答者ID号。
- 有效抢答记录:倒计时为0结束后有抢答者,则记录第一抢答者ID号,抢答有效。
- 启动/重启功能:设置 S2 为抢答启动/重启键,每次按下后即启动抢答前的5s倒计时。
- 按键对应关系:S0/S1/S3/S4 键由4位参赛者进行抢答操作,对应ID号为S0/S1/S3/S4。
- 违规抢答提醒:在启动抢答前的 5s 倒计时期间,如果检测到S0/S1/S3/S4键中有抢答动作,则立即停止倒计时,并显示第一违规抢答者ID号,同时通过指示灯闪烁3s进行提醒。
- 有效抢答指示:倒计时结束后,如果检测到 S0/S1/S3/S4键中有抢答动作,则立即显示第一抢答者ID号,同时点亮指示灯表示抢答有效,直到启动下一次抢答时熄灭。
- 时间查询功能:支持查阅功能,通过开关K0~K3显示某位参赛者从倒计时结束开始抢答到按下抢答键的时间,时间精度1ms。
1. 状态机设计
首先,我们需要设计一个状态机来实现抢答的功能。状态机的状态如下:
- 初始化状态:初始状态,等待按下抢答键。
- 倒计时状态:按下抢答启动键后,进入倒计时状态,开始倒计时。
- 等待抢答状态:倒计时结束后,进入等待抢答状态,等待参赛者按下抢答键。
- 抢答成功状态:检测到有参赛者按下抢答键后,进入抢答成功状态,记录抢答者ID号,并点亮指示灯。
- 违规抢答状态:在倒计时期间或等待抢答状态中,检测到有参赛者违规抢答,进入违规抢答状态,记录违规抢答者ID号,并闪烁指示灯3s。
- 查阅状态:在抢答结束后,可以通过开关K0~K3查阅某位参赛者的抢答时间。
根据以上状态机设计,我们可以得到如下的状态转移图:

2. 代码实现
根据以上状态机设计,我们可以实现如下的代码:
#include <reg52.h>
#include <intrins.h> // 延时函数需要用到
typedef unsigned char u8;
typedef unsigned int u16;
sbit LED = P2^0; // 定义指示灯(LED)输出口
sbit KEY_START = P3^2; // 定义抢答启动/重启键(S2)输入口
sbit KEY_S0 = P3^0; // 定义参赛者按键(S0)输入口
sbit KEY_S1 = P3^1; // 定义参赛者按键(S1)输入口
sbit KEY_S3 = P3^3; // 定义参赛者按键(S3)输入口
sbit KEY_S4 = P3^4; // 定义参赛者按键(S4)输入口
sbit SWITCH_K0 = P1^0; // 定义查阅开关(K0)输入口
sbit SWITCH_K1 = P1^1; // 定义查阅开关(K1)输入口
sbit SWITCH_K2 = P1^2; // 定义查阅开关(K2)输入口
sbit SWITCH_K3 = P1^3; // 定义查阅开关(K3)输入口
u8 STATE = 0; // 当前状态
u8 TIMER_01H = 0; // 定时器1计数器高8位
u8 TIMER_01L = 0; // 定时器1计数器低8位
u8 TIMER_0H = 0; // 定时器0计数器高8位
u8 TIMER_0L = 0; // 定时器0计数器低8位
u8 FIRST_INVALID_ID = 0; // 第一违规抢答者ID号
u8 FIRST_SUCCESS_ID = 0; // 第一抢答者ID号
u16 S0_TIME = 0; // S0参赛者抢答时间
u16 S1_TIME = 0; // S1参赛者抢答时间
u16 S3_TIME = 0; // S3参赛者抢答时间
u16 S4_TIME = 0; // S4参赛者抢答时间
u8 S0_FLAG = 0; // S0参赛者抢答标志
u8 S1_FLAG = 0; // S1参赛者抢答标志
u8 S3_FLAG = 0; // S3参赛者抢答标志
u8 S4_FLAG = 0; // S4参赛者抢答标志
u8 SWITCH_STATE = 0; // 查阅开关状态
// 定时器0中断服务函数
void timer0() interrupt 1 {
TH0 = TIMER_0H; // 重新赋值计数器高8位
TL0 = TIMER_0L; // 重新赋值计数器低8位
TIMER_01H++; // 定时器1计数器高8位加1
if (TIMER_01H == 0) {
TIMER_01L++; // 定时器1计数器低8位加1
if (TIMER_01L == 100) { // 1s
TIMER_01L = 0; // 重置定时器1计数器低8位
if (STATE == 1) { // 倒计时状态
if (FIRST_INVALID_ID == 0 && (S0_FLAG || S1_FLAG || S3_FLAG || S4_FLAG)) { // 检测违规抢答
FIRST_INVALID_ID = S0_FLAG ? 1 : S1_FLAG ? 2 : S3_FLAG ? 3 : 4; // 记录第一违规抢答者ID号
STATE = 4; // 进入违规抢答状态
} else { // 没有违规抢答
if (TIMER_0L == 0) { // 倒计时结束
STATE = 2; // 进入等待抢答状态
} else {
TIMER_0L--; // 计时器减1
}
}
if (FIRST_INVALID_ID != 0) { // 闪烁指示灯3s
LED = !LED;
_nop_();
_nop_();
_nop_();
}
}
}
}
}
// 定时器1中断服务函数
void timer1() interrupt 3 {
TH1 = TIMER_01H; // 重新赋值计数器高8位
TL1 = TIMER_01L; // 重新赋值计数器低8位
}
// 主函数
void main() {
TMOD = 0x11; // 定时器0、1工作方式1
TH0 = 0x3C; // 定时器0计数器初始值
TL0 = 0xAF; // 定时器0计数器初始值
TH1 = 0; // 定时器1计数器初始值
TL1 = 0; // 定时器1计数器初始值
ET0 = 1; // 允许定时器0中断
ET1 = 1; // 允许定时器1中断
EA = 1; // 允许总中断
TR0 = 1; // 启动定时器0
TR1 = 1; // 启动定时器1
while (1) {
switch (STATE) {
case 0: // 初始化状态
if (KEY_START == 0) { // 按下抢答启动/重启键
STATE = 1; // 进入倒计时状态
TIMER_0H = 0x3C; // 定时器0计数器初始值
TIMER_0L = 0xAF; // 定时器0计数器初始值
TIMER_01H = 0; // 定时器1计数器初始值
TIMER_01L = 0; // 定时器1计数器初始值
FIRST_INVALID_ID = 0; // 初始化第一违规抢答者ID号
FIRST_SUCCESS_ID = 0; // 初始化第一抢答者ID号
S0_TIME = 0; // 初始化S0参赛者抢答时间
S1_TIME = 0; // 初始化S1参赛者抢答时间
S3_TIME = 0; // 初始化S3参赛者抢答时间
S4_TIME = 0; // 初始化S4参赛者抢答时间
S0_FLAG = 0; // 初始化S0参赛者抢答标志
S1_FLAG = 0; // 初始化S1参赛者抢答标志
S3_FLAG = 0; // 初始化S3参赛者抢答标志
S4_FLAG = 0; // 初始化S4参赛者抢答标志
SWITCH_STATE = 0; // 初始化查阅开关状态
}
break;
case 1: // 倒计时状态
// 检测参赛者按键动作
if (KEY_S0 == 0) {
S0_TIME = TIMER_01L * 10 + TIMER_0L; // 记录S0参赛者抢答时间
S0_FLAG = 1; // 置S0参赛者抢答标志
}
if (KEY_S1 == 0) {
S1_TIME = TIMER_01L * 10 + TIMER_0L; // 记录S1参赛者抢答时间
S1_FLAG = 1; // 置S1参赛者抢答标志
}
if (KEY_S3 == 0) {
S3_TIME = TIMER_01L * 10 + TIMER_0L; // 记录S3参赛者抢答时间
S3_FLAG = 1; // 置S3参赛者抢答标志
}
if (KEY_S4 == 0) {
S4_TIME = TIMER_01L * 10 + TIMER_0L; // 记录S4参赛者抢答时间
S4_FLAG = 1; // 置S4参赛者抢答标志
}
break;
case 2: // 等待抢答状态
// 检测参赛者按键动作
if (KEY_S0 == 0) {
FIRST_SUCCESS_ID = 1; // 记录第一抢答者ID号
STATE = 3; // 进入抢答成功状态
}
if (KEY_S1 == 0) {
FIRST_SUCCESS_ID = 2; // 记录第一抢答者ID号
STATE = 3; // 进入抢答成功状态
}
if (KEY_S3 == 0) {
FIRST_SUCCESS_ID = 3; // 记录第一抢答者ID号
STATE = 3; // 进入抢答成功状态
}
if (KEY_S4 == 0) {
FIRST_SUCCESS_ID = 4; // 记录第一抢答者ID号
STATE = 3; // 进入抢答成功状态
}
break;
case 3: // 抢答成功状态
LED = 1; // 点亮指示灯
break;
case 4: // 违规抢答状态
if (SWITCH_STATE == 0) { // 查阅开关未打开
LED = !LED; // 闪烁指示灯
} else { // 查阅开关已打开
if (FIRST_INVALID_ID == 1) { // 查阅S0参赛者抢答时间
LED = S0_TIME % 2;
} else if (FIRST_INVALID_ID == 2) { // 查阅S1参赛者抢答时间
LED = S1_TIME % 2;
} else if (FIRST_INVALID_ID == 3) { // 查阅S3参赛者抢答时间
LED = S3_TIME % 2;
} else if (FIRST_INVALID_ID == 4) { // 查阅S4参赛者抢答时间
LED = S4_TIME % 2;
}
}
break;
}
// 检测查阅开关状态
SWITCH_STATE = 0;
if (SWITCH_K0 == 0) {
SWITCH_STATE |= 0x01; // 置查阅开关状态
}
if (SWITCH_K1 == 0) {
SWITCH_STATE |= 0x02; // 置查阅开关状态
}
if (SWITCH_K2 == 0) {
SWITCH_STATE |= 0x04; // 置查阅开关状态
}
if (SWITCH_K3 == 0) {
SWITCH_STATE |= 0x08; // 置查阅开关状态
}
}
}
3. 注意事项
- 定时器0的计数器初值需要根据实际情况进行调整,保证计时器在1s内溢出。
- 定时器1的计数器初值需要根据实际情况进行调整,保证计时器在倒计时期间正常计时。
- 在实际应用中,需要根据参赛者数量进行相应的修改,例如参赛者数量为3,则只需要检测S0、S1、S3三个参赛者的按键动作。
- 在违规抢答状态下,闪烁指示灯的时间需要根据实际情况进行调整。
- 在查阅状态下,查阅开关状态的读取需要进行去抖动处理。
注意: 以上代码仅供参考,实际应用中需要根据具体硬件和需求进行修改和完善。
希望本文能够帮助您理解基于状态机设计的抢答系统,并为您的项目提供一些参考。
原文地址: https://www.cveoy.top/t/topic/nCjm 著作权归作者所有。请勿转载和采集!