- 打卡等级:以坛为家III
- 打卡总天数:841
- 最近打卡:2026-03-26 07:36:26
论坛元老
- 积分
- 7160
|
下面提供一个严格符合 **C89** 标准的实现,使用 **静态内存分配**、**无递归**、**非阻塞** 状态机,通过 **定时器中断** 扫描按键。代码已针对 **STC8G1K08A** 的引脚和寄存器进行配置,可直接在 Keil C51 或 SDCC 环境下编译使用。
```c
/*----------------------------------------------------------------------------
* 长按/短按按键检测 (STC8G1K08A)
* P5.5 按键: 短按 (<3s) 翻转 P3.2, 长按 (≥3s) 翻转 P3.3
* 定时器1中断 (10ms), 状态机消抖, 非阻塞
*----------------------------------------------------------------------------*/
#include <STC8G.h> /* STC8G 系列寄存器头文件 */
/*-------------------------- 端口定义 --------------------------------------*/
sbit KEY = P5^5; /* 按键输入 (低电平有效) */
sbit OUT_SHORT = P3^2; /* 短按输出 */
sbit OUT_LONG = P3^3; /* 长按输出 */
/*-------------------------- 时间常数 --------------------------------------*/
#define TICK_MS 10 /* 定时器中断周期 (ms) */
#define DEBOUNCE_TICKS 3 /* 消抖时间 = 3 × 10ms = 30ms */
#define LONG_PRESS_TICKS 300 /* 长按时间 = 300 × 10ms = 3.0s */
/*-------------------------- 按键状态机枚举 --------------------------------*/
enum KEY_STATE {
KEY_STATE_IDLE, /* 空闲状态 */
KEY_STATE_DEBOUNCE, /* 消抖状态 */
KEY_STATE_PRESSED, /* 按下计数状态 */
KEY_STATE_LONG_PRESSED /* 长按已触发状态 */
};
/*-------------------------- 静态全局变量 ----------------------------------*/
static enum KEY_STATE key_state = KEY_STATE_IDLE; /* 当前状态 */
static unsigned char debounce_cnt; /* 消抖计数器 */
static unsigned int press_duration; /* 按下持续时间 (10ms 单位) */
/*-------------------------- 定时器1初始化 ---------------------------------*/
/* 系统时钟假设为 24MHz, Timer1 12T模式, 16位自动重载
* 计数频率 = 24MHz / 12 = 2MHz
* 10ms 需要计数 20000 次
* 重载值 = 65536 - 20000 = 45536 (0xB1E0)
*/
static void Timer1_Init(void)
{
AUXR &= ~(1 << 6); /* Timer1 12T模式 (清除 T1x12 位) */
TMOD &= 0x0F; /* 清除 Timer1 模式位 */
TMOD |= 0x00; /* Timer1 模式0 (16位自动重载) */
TH1 = 0xB1; /* 重载值高字节 */
TL1 = 0xE0; /* 重载值低字节 */
ET1 = 1; /* 使能 Timer1 中断 */
TR1 = 1; /* 启动 Timer1 */
EA = 1; /* 开启总中断 */
}
/*-------------------------- 端口初始化 ------------------------------------*/
static void Port_Init(void)
{
/* P3.2, P3.3 配置为推挽输出 */
P3M1 &= ~0x0C; /* 清除对应位: 输入模式清除 */
P3M0 |= 0x0C; /* 设置推挽输出 */
OUT_SHORT = 0; /* 初始低电平 */
OUT_LONG = 0;
/* P5.5 配置为准双向口 (带弱上拉) */
P5M1 &= ~0x20; /* 清除 P5.5 对应位 */
P5M0 &= ~0x20; /* 准双向模式 */
}
/*-------------------------- 按键扫描 (在中断中调用) -----------------------*/
static void Key_Scan(void)
{
unsigned char key_down = (KEY == 0); /* 读取按键: 0=按下, 1=释放 */
switch (key_state)
{
case KEY_STATE_IDLE:
if (key_down)
{
key_state = KEY_STATE_DEBOUNCE;
debounce_cnt = 0;
}
break;
case KEY_STATE_DEBOUNCE:
if (key_down)
{
debounce_cnt++;
if (debounce_cnt >= DEBOUNCE_TICKS)
{
/* 按键稳定按下 */
key_state = KEY_STATE_PRESSED;
press_duration = 0;
}
}
else
{
/* 抖动干扰, 返回空闲 */
key_state = KEY_STATE_IDLE;
}
break;
case KEY_STATE_PRESSED:
if (key_down)
{
press_duration++;
if (press_duration >= LONG_PRESS_TICKS)
{
/* 长按阈值到达, 翻转长按输出 */
OUT_LONG = !OUT_LONG;
key_state = KEY_STATE_LONG_PRESSED;
}
}
else
{
/* 按键释放, 判断是否为短按 */
if (press_duration < LONG_PRESS_TICKS)
{
OUT_SHORT = !OUT_SHORT; /* 翻转短按输出 */
}
key_state = KEY_STATE_IDLE;
}
break;
case KEY_STATE_LONG_PRESSED:
if (!key_down) /* 等待按键释放 */
{
key_state = KEY_STATE_IDLE;
}
break;
default:
key_state = KEY_STATE_IDLE;
break;
}
}
/*-------------------------- Timer1 中断服务函数 ---------------------------*/
void Timer1_ISR(void) interrupt 3 /* Timer1 中断向量号为 3 */
{
TF1 = 0; /* 清除中断标志 (自动重载模式下仍需手动清除) */
Key_Scan(); /* 每10ms扫描一次按键 */
}
/*-------------------------- 主函数 ---------------------------------------*/
void main(void)
{
Port_Init(); /* 配置 I/O 口 */
Timer1_Init(); /* 启动定时器中断 */
while (1)
{
/* 主循环可添加其他任务, 按键处理完全由中断完成, 非阻塞 */
}
}
```
## 关键设计说明
1. **定时器中断周期**
使用 Timer1 的 16 位自动重载模式,每 10ms 产生一次中断。系统时钟为 24MHz 时,重载值计算正确;若实际时钟频率不同,需调整 `TH1`、`TL1` 的初值。
2. **按键消抖**
消抖时间设为 30ms(3 个中断周期)。只有当按键电平连续稳定 30ms 后,才确认为有效按下,避免机械抖动误判。
3. **长/短按判别**
- 在 `KEY_STATE_PRESSED` 状态中,每 10ms 累加 `press_duration`。
- 若累加值达到 `LONG_PRESS_TICKS` (300 次 = 3 秒),立即触发长按动作(翻转 P3.3),并转入 `KEY_STATE_LONG_PRESSED` 状态,等待按键释放。
- 若按键在达到 3 秒前释放,则视为短按,翻转 P3.2。
4. **非阻塞设计**
所有按键处理均在定时器中断服务程序中完成,主循环完全空闲,可任意添加其他实时任务而不会阻塞按键响应。
5. **符合 C89 规范**
- 使用 `/* ... */` 注释,避免 `//` 注释。
- 变量声明均位于函数开头(代码块起始处)。
- 静态内存分配(`static` 变量),无动态内存申请。
- 无递归调用。
## 引脚配置说明
| 引脚 | 功能 | 配置 |
|------|--------------------|------------------------|
| P5.5 | 按键输入 (低有效) | 准双向口 (弱上拉) |
| P3.2 | 短按输出 | 推挽输出 (初始低电平) |
| P3.3 | 长按输出 | 推挽输出 (初始低电平) |
## 编译与烧录注意事项
- 若使用 **Keil C51**,请将项目芯片型号设置为 `STC8G1K08A` 或 `STC8G` 系列,并确保包含 `STC8G.h` 头文件。
- 若使用 **SDCC**,需将 `sbit` 语法替换为 `__sbit`,并包含相应的头文件。
- 定时器重载值基于 **24MHz** 系统时钟计算,若实际时钟频率不同,需根据公式 `重载值 = 65536 - (系统时钟频率 / 12) × TICK_MS / 1000` 调整。
该设计简洁、可靠,可直接应用于实际产品中。
|
|