/* =====================================================================
* 作业2:STM32 Blue Pill(STM32F103C8)多路按键控制 LED 闪烁模式
* —— 结构体 + 结构体指针 版本
* 仿真平台:Wokwi(https://wokwi.com/) 框架:Arduino(STM32duino)
* 班级:智能机器人3班 姓名:邵柏川 学号:25320322
* ---------------------------------------------------------------------
* 硬件连接(与本工程 diagram.json 完全一致):
* LED1(红色,主指示灯):B0 → 330Ω 限流电阻 → LED1 正极,LED1 负极 → GND
* LED2(蓝色,拓展镜像灯):B1 → 330Ω 限流电阻 → LED2 正极,LED2 负极 → GND
* K1~K4 按键:B12 / B13 / B14 / B15 各接一端,另一端共同接 GND;
* 引脚配置为内部上拉输入(INPUT_PULLUP),按下 = 低电平
*
* 功能说明(四组按键分别对应 LED 四种闪烁模式):
* K1(B12) → 短闪:每 1s 周期点亮 250ms(短促闪烁)
* K2(B13) → 长闪:每 1s 周期点亮 750ms(长亮缓闪)
* K3(B14) → 快闪:100ms 亮 / 100ms 灭(高频快闪)
* K4(B15) → 熄灭:LED 常灭
* 拓展:LED2 与主灯反相镜像(主亮副灭、主灭副亮),切换按键即点即生效,
* 串口实时打印当前模式,便于观察验证。
*
* 核心考核点(均已实现,并加【】标注方便对照检查):
* 1.【结构体】自定义按键结构体 KeyDev_t,封装按键硬件属性与工作状态;
* 另有 LED 设备结构体 LedDev_t、闪烁参数结构体 BlinkParam_t;
* 2.【结构体指针】全程以结构体指针操作数据:遍历用移动指针、
* 传参用结构体指针、成员访问用 "->",无一处数组下标访问;
* 3.【函数嵌套调用】多层架构:loop → keyTask → keyUpdate → modeRequest
* → modeTick → ledWrite,最长调用链 6 层,底层汇聚于 ledWrite;
* 4. millis() 非阻塞定时 + 按键三态状态机(空闲→消抖→触发),
* 四键每圈均被扫描,按键触发响应灵敏、互不阻塞。
* ===================================================================== */
#include <stdint.h>
#define KEY_NUM 4 /* 按键数量 */
#define LED_NUM 2 /* LED 数量(主灯 + 拓展镜像灯) */
#define DEBOUNCE_MS 25 /* 按键消抖时间 */
/* LED 四种工作模式编号 */
enum {
MODE_OFF = 0, /* 模式1:熄灭 */
MODE_BLINK_SHORT, /* 模式2:短闪 */
MODE_BLINK_LONG, /* 模式3:长闪 */
MODE_BLINK_FAST, /* 模式4:快闪 */
MODE_COUNT /* 模式总数(哨兵) */
};
/* ================【结构体】按键设备:硬件属性 + 工作状态 ================ */
typedef struct {
/* —— 硬件属性 —— */
uint8_t pin; /* 所接 GPIO 引脚 */
uint8_t activeLevel; /* 按下时的电平(LOW = 低电平有效) */
uint8_t mode; /* 本按键对应的 LED 模式 */
/* —— 工作状态 —— */
uint8_t state; /* 状态机:0 空闲 / 1 消抖确认 / 2 按住 */
uint32_t tMark; /* 状态切换时刻(ms),用于消抖计时 */
uint8_t fired; /* 本次按住期间是否已触发过 */
} KeyDev_t;
/* ================【结构体】LED 设备 ================ */
typedef struct {
uint8_t pin; /* 硬件属性:GPIO 引脚 */
uint8_t on; /* 工作状态:当前亮/灭 */
} LedDev_t;
/* ================【结构体】闪烁模式参数 ================ */
typedef struct {
uint16_t onMs; /* 一个周期内的点亮时长 */
uint16_t offMs; /* 一个周期内的熄灭时长 */
} BlinkParam_t;
/* -------- 设备实例表(访问一律走结构体指针,不用数组下标) -------- */
static KeyDev_t keys[KEY_NUM] = {
/* pin activeLevel mode state tMark fired */
{ PB12, LOW, MODE_BLINK_SHORT, 0, 0, 0 }, /* K1 */
{ PB13, LOW, MODE_BLINK_LONG, 0, 0, 0 }, /* K2 */
{ PB14, LOW, MODE_BLINK_FAST, 0, 0, 0 }, /* K3 */
{ PB15, LOW, MODE_OFF, 0, 0, 0 }, /* K4 */
};
static LedDev_t leds[LED_NUM] = {
{ PB0, 0 }, /* LED1 主指示灯 */
{ PB1, 0 }, /* LED2 拓展镜像灯 */
};
static const BlinkParam_t blinkTab[MODE_COUNT] = {
{ 0, 0 }, /* MODE_OFF :常灭 */
{ 250, 750 }, /* MODE_BLINK_SHORT:短闪 250ms/750ms */
{ 750, 250 }, /* MODE_BLINK_LONG :长闪 750ms/250ms */
{ 100, 100 }, /* MODE_BLINK_FAST :快闪 100ms/100ms */
};
static uint8_t curMode = MODE_OFF; /* 当前 LED 模式 */
static uint32_t modeT0 = 0; /* 当前模式的起始时刻(切换即重新计时) */
/* ============ 第 1 层(底层):LED 驱动 ——【结构体指针】传参 ============ */
void ledWrite(LedDev_t *led, uint8_t on)
{
if (led == 0) return; /* 【if 分支】指针有效性保护 */
led->on = on; /* 【结构体指针】写入工作状态 */
digitalWrite(led->pin, on ? HIGH : LOW);
}
/* ============ 第 2 层:按当前模式输出一帧 LED 状态 ============ */
void modeTick(uint32_t now)
{
if (curMode == MODE_OFF) { /* 【if 分支】模式1:熄灭 */
for (LedDev_t *l = leds; l < leds + LED_NUM; l++) /* 【指针遍历】 */
ledWrite(l, 0); /* 嵌套调用第 1 层 */
return;
}
/*【结构体指针】以指针算术取当前模式的参数行,不用下标 */
const BlinkParam_t *p = blinkTab + curMode;
uint32_t period = (uint32_t)p->onMs + p->offMs;
uint8_t phaseOn = (now - modeT0) % period < p->onMs; /* 亮相位判断 */
for (LedDev_t *l = leds; l < leds + LED_NUM; l++) { /* 【指针遍历】 */
uint8_t bit = (l == leds) ? phaseOn : !phaseOn; /* 主灯 / 镜像灯反相 */
ledWrite(l, bit); /* 嵌套调用第 1 层 */
}
}
/* ============ 第 3 层:模式切换请求(按键事件 → 模式生效) ============ */
void modeRequest(uint8_t m)
{
if (m >= MODE_COUNT) return; /* 【if 分支】非法模式号保护 */
if (m == curMode) return; /* 重复按同一键:无需切换 */
curMode = m;
modeT0 = millis(); /* 从切换时刻重新起算亮相位 */
modeTick(millis()); /* 立即刷新一帧,切换零延迟 */
Serial.print("[Mode] ");
switch (curMode) { /* 【switch 分支】串口提示当前模式 */
case MODE_BLINK_SHORT: Serial.println("K1 -> 短闪 (250ms/750ms)"); break;
case MODE_BLINK_LONG: Serial.println("K2 -> 长闪 (750ms/250ms)"); break;
case MODE_BLINK_FAST: Serial.println("K3 -> 快闪 (100ms/100ms)"); break;
case MODE_OFF: Serial.println("K4 -> 熄灭"); break;
default: Serial.println("?"); break;
}
}
/* ============ 第 4 层:按键三态状态机 ——【结构体指针】操作状态 ============ */
uint8_t keyUpdate(KeyDev_t *k)
{
uint8_t pressed = (digitalRead(k->pin) == k->activeLevel); /* 读硬件属性 */
uint32_t now = millis();
switch (k->state) { /* 【switch 分支】状态机调度 */
case 0: /* 空闲:检测按下沿 */
if (pressed) { /* 【if 分支】 */
k->state = 1;
k->tMark = now;
}
break;
case 1: /* 消抖确认 */
if (!pressed) { /* 抖动/干扰,退回空闲 */
k->state = 0;
} else if (now - k->tMark >= DEBOUNCE_MS) {
k->state = 2; /* 稳定按下:确认成立 */
k->fired = 0;
}
break;
case 2: /* 按住中:确认即触发,响应最灵敏 */
if (!pressed) { /* 松手:复位状态机 */
k->state = 0;
} else if (!k->fired) { /* 【结构体指针】读改工作状态 */
k->fired = 1;
return 1; /* 上报一次按键事件 */
}
break;
default: /* 异常状态兜底 */
k->state = 0;
break;
}
return 0;
}
/* ============ 第 5 层:按键任务 ============ */
void keyTask(KeyDev_t *k)
{
if (keyUpdate(k)) { /* 【函数嵌套调用】调用第 4 层 */
modeRequest(k->mode); /* 【结构体指针】取成员切换模式 → 第 3 层 */
}
}
/* ============ Arduino 标准框架 ============ */
void setup(void)
{
for (KeyDev_t *k = keys; k < keys + KEY_NUM; k++) { /* 【指针遍历】 */
pinMode(k->pin, INPUT_PULLUP); /* 内部上拉,断线不误触 */
}
for (LedDev_t *l = leds; l < leds + LED_NUM; l++) {
pinMode(l->pin, OUTPUT);
ledWrite(l, 0); /* 上电初始:全部熄灭 */
}
Serial.begin(115200);
Serial.println("STM32 Key-LED Struct Demo Start (25320322 ShaoBoChuan)");
}
void loop(void)
{
uint32_t now = millis();
/*【结构体指针】移动指针依次扫描 4 个按键,不使用数组下标 */
for (KeyDev_t *k = keys; k < keys + KEY_NUM; k++) {
keyTask(k); /* 【函数嵌套调用】 */
}
modeTick(now); /* 按当前模式刷新 LED 输出 */
}
Loading
stm32-bluepill
stm32-bluepill