- #include <Adafruit_GFX.h>
- #include <Adafruit_ST7789.h>
- #include <SPI.h>
- // 屏幕引脚配置
- #define TFT_CS 53
- #define TFT_RST 4
- #define TFT_DC 2
- #define TFT_MOSI 51
- #define TFT_SCLK 52
- // 使用硬件SPI
- Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
- // 蜂鸣器引脚
- #define BUZZER_PIN 3
- // 按钮引脚 - 高电平触发
- #define BUTTON_PIN1 14
- #define BUTTON_PIN2 15
- #define BUTTON_PIN3 16
- #define BUTTON_PIN4 17
- // 计时器结构
- typedef struct {
- unsigned long totalSeconds;
- bool isRunning;
- bool isReset;
- unsigned long lastUpdateTime; // 每个计时器独立的更新时间戳
- unsigned long lastHourAlarm; // 上次小时报警时间戳
- bool alarmTriggered; // 计时器报警状态
- } Timer;
- Timer timers[4]; // 四个计时器
2. 按钮状态检测
- // 按钮状态
- enum ButtonState {
- BUTTON_RELEASED,
- BUTTON_PRESSED
- };
- // 按钮结构
- typedef struct {
- uint8_t pin;
- ButtonState state;
- ButtonState lastState;
- unsigned long pressStartTime;
- } Button;
- Button buttons[4];
- // 当前选中的计时器
- int selectedTimer = 0;
- bool alarmActive = false; // 报警激活状态
- bool alarmSilenced = false; // 报警被静音
- unsigned long lastBeepTime = 0; // 上次蜂鸣器响的时间
- unsigned long lastDebounceTime = 0;
- const unsigned long debounceDelay = 50; // 消抖时间(毫秒)
- // 报警参数
- const unsigned long ALARM_INTERVAL = 800; // 蜂鸣器报警间隔(ms)
- const unsigned long HOUR_SECONDS = 3600; // 1小时的秒数
- const unsigned long ALARM_HOURS = 4; // 报警小时数
- // PWM参数
- const int TONE_FREQUENCY = 2500; // 蜂鸣器频率 (Hz)
- const int TONE_DURATION = 300; // 蜂鸣器单次响声持续时间 (ms)
3. 初始化设置
- void setup() {
- Serial.begin(9600);
-
- // 初始化蜂鸣器
- pinMode(BUZZER_PIN, OUTPUT);
- digitalWrite(BUZZER_PIN, LOW);
-
- // 初始化按钮
- buttons[0] = {BUTTON_PIN1, BUTTON_RELEASED, BUTTON_RELEASED, 0};
- buttons[1] = {BUTTON_PIN2, BUTTON_RELEASED, BUTTON_RELEASED, 0};
- buttons[2] = {BUTTON_PIN3, BUTTON_RELEASED, BUTTON_RELEASED, 0};
- buttons[3] = {BUTTON_PIN4, BUTTON_RELEASED, BUTTON_RELEASED, 0};
-
- for (int i = 0; i < 4; i++) {
- pinMode(buttons[i].pin, INPUT);
- }
-
- // 初始化屏幕
- tft.init(240, 320);
- tft.setRotation(1);
- tft.fillScreen(ST77XX_BLACK);
-
- // 初始化计时器
- for (int i = 0; i < 4; i++) {
- timers[i].totalSeconds = 0;
- timers[i].isRunning = false;
- timers[i].isReset = true;
- timers[i].lastUpdateTime = 0;
- timers[i].lastHourAlarm = 0;
- timers[i].alarmTriggered = false;
- }
-
- // 绘制初始界面
- drawTimers();
- }
4. 主循环控制
- void loop() {
- unsigned long currentMillis = millis();
-
- // 更新所有正在运行的计时器
- for (int i = 0; i < 4; i++) {
- if (timers[i].isRunning) {
- // 每个计时器独立更新
- if (currentMillis - timers[i].lastUpdateTime >= 1000) {
- timers[i].totalSeconds++;
- timers[i].lastUpdateTime = currentMillis;
-
- // 检查报警条件
- checkAlarmConditions(i);
-
- // 只更新这个计时器的显示
- drawTimer(i);
- }
- }
- }
-
- // 处理按钮事件
- pollButtons();
- handleButtonEvents();
-
- // 处理报警声音
- updateAlarmSound();
-
- delay(10);
- }
5. 报警系统实现
- // 检查报警条件
- void checkAlarmConditions(int index) {
- // 检查是否达到4小时或每小时
- if (timers[index].totalSeconds >= ALARM_HOURS * HOUR_SECONDS) {
- // 检查是否达到新的小时
- if (timers[index].totalSeconds % HOUR_SECONDS == 0) {
- // 避免连续触发
- if (timers[index].totalSeconds != timers[index].lastHourAlarm) {
- timers[index].alarmTriggered = true;
- alarmActive = true;
- alarmSilenced = false;
- timers[index].lastHourAlarm = timers[index].totalSeconds;
- }
- }
- }
- }
- // 更新报警声音
- void updateAlarmSound() {
- if (alarmActive && !alarmSilenced) {
- unsigned long currentMillis = millis();
-
- // 每秒响一次(300ms开,800ms关)
- if (currentMillis - lastBeepTime >= ALARM_INTERVAL) {
- lastBeepTime = currentMillis;
-
- // 播放悦耳音调
- tone(BUZZER_PIN, TONE_FREQUENCY, TONE_DURATION);
- }
- } else {
- noTone(BUZZER_PIN);
- }
- }
- // 轮询按钮状态(带消抖)
- void pollButtons() {
- unsigned long currentMillis = millis();
-
- for (int i = 0; i < 4; i++) {
- // 读取按钮状态(高电平表示按下)
- ButtonState reading = (digitalRead(buttons[i].pin) == HIGH) ? BUTTON_PRESSED : BUTTON_RELEASED;
-
- // 如果状态改变,重置消抖计时器
- if (reading != buttons[i].lastState) {
- lastDebounceTime = currentMillis;
- }
-
- // 如果状态稳定时间超过消抖延迟
- if ((currentMillis - lastDebounceTime) > debounceDelay) {
- // 更新按钮状态
- if (reading != buttons[i].state) {
- buttons[i].state = reading;
-
- // 记录按下开始时间
- if (buttons[i].state == BUTTON_PRESSED) {
- buttons[i].pressStartTime = currentMillis;
- }
- }
- }
-
- // 保存当前状态用于下次比较
- buttons[i].lastState = reading;
- }
- }
- // 静音报警并清除报警状态
- void silenceAlarm() {
- alarmActive = false;
- alarmSilenced = true;
- noTone(BUZZER_PIN);
-
- // 清除所有计时器的报警状态
- for (int i = 0; i < 4; i++) {
- timers[i].alarmTriggered = false;
- // 重绘计时器以清除"ALARM"显示
- drawTimer(i);
- }
- }
6. 按钮事件处理
- void handleButtonEvents() {
- unsigned long currentMillis = millis();
- bool buttonEventOccurred = false;
- int previousSelectedTimer = selectedTimer; //跟踪之前的选择
-
- for (int i = 0; i < 4; i++) {
- if (buttons[i].state == BUTTON_PRESSED) {
- // 长按检测(超过1秒)
- if (currentMillis - buttons[i].pressStartTime > 1000) {
- // 长按 - 只复位当前选中的计时器
- if (i == selectedTimer) {
- timers[i].totalSeconds = 0;
- timers[i].isRunning = false;
- timers[i].isReset = true;
- timers[i].lastUpdateTime = 0;
- timers[i].lastHourAlarm = 0;
- timers[i].alarmTriggered = false;
- drawTimer(i);
- silenceAlarm();
- }
- buttonEventOccurred = true;
- }
- } else if (buttons[i].state == BUTTON_RELEASED) {
- // 按钮释放时检测短按
- if (buttons[i].pressStartTime > 0 &&
- currentMillis - buttons[i].pressStartTime > debounceDelay &&
- currentMillis - buttons[i].pressStartTime <= 1000) {
-
- buttonEventOccurred = true;
-
- // 短按 - 开始/暂停计时器或切换计时器
- if (i == selectedTimer) {
- timers[i].isRunning = !timers[i].isRunning;
- timers[i].isReset = false;
- timers[i].lastUpdateTime = millis();
- } else {
- previousSelectedTimer = selectedTimer;
- selectedTimer = i;
- // 重绘所有计时器以更新选中框
- drawTimer(previousSelectedTimer);
- drawTimer(selectedTimer);
- }
-
- // 只更新当前计时器的显示
- drawTimer(i);
- }
-
- // 重置按下开始时间
- buttons[i].pressStartTime = 0;
- }
- }
-
- // 如果有按钮事件,静音报警并清除报警状态
- if (buttonEventOccurred) {
- silenceAlarm();
- }
- }
7. 用户界面设计
- void drawTimers() {
- tft.fillScreen(ST77XX_BLACK);
-
- // 绘制四个计时器区域
- tft.drawRect(0, 0, 160, 120, ST77XX_WHITE); // 左上
- tft.drawRect(160, 0, 160, 120, ST77XX_WHITE); // 右上
- tft.drawRect(0, 120, 160, 120, ST77XX_WHITE); // 左下
- tft.drawRect(160, 120, 160, 120, ST77XX_WHITE); // 右下
-
- // 绘制所有计时器
- for (int i = 0; i < 4; i++) {
- drawTimer(i);
- }
- }
- void drawTimer(int index) {
- int x, y;
-
- // 确定位置
- switch (index) {
- case 0: x = 30; y = 50; break; // 左上
- case 1: x = 190; y = 50; break; // 右上
- case 2: x = 30; y = 170; break; // 左下
- case 3: x = 190; y = 170; break; // 右下
- default: return;
- }
-
- // 清除时间显示区域(避免残留字符)
- tft.fillRect(x, y, 100, 20, ST77XX_BLACK);
-
- // 设置文本颜色和大小
- tft.setTextSize(2);
- tft.setTextColor(index == selectedTimer ? ST77XX_YELLOW : ST77XX_WHITE);
-
- // 计算时间
- int hours = timers[index].totalSeconds / 3600;
- int minutes = (timers[index].totalSeconds % 3600) / 60;
- int seconds = timers[index].totalSeconds % 60;
-
- // 格式化时间
- char timeStr[12];
- sprintf(timeStr, "%02d:%02d:%02d", hours, minutes, seconds);
-
- // 显示时间
- tft.setCursor(x, y);
- tft.print(timeStr);
-
- // 清除状态显示区域
- tft.fillRect(x, y + 30, 60, 10, ST77XX_BLACK);
-
- // 显示状态
- tft.setTextSize(1);
- tft.setCursor(x, y + 30);
- if (timers[index].isReset) {
- tft.print("Reset");
- } else if (timers[index].isRunning) {
- tft.setTextColor(ST77XX_GREEN);
- tft.print("Running");
- } else {
- tft.setTextColor(ST77XX_ORANGE);
- tft.print("Paused");
- }
-
- // 显示报警状态(仅在报警触发且未静音时显示)
- tft.fillRect(x + 60, y + 30, 40, 10, ST77XX_BLACK);
- if (timers[index].alarmTriggered && !alarmSilenced) {
- tft.setCursor(x + 60, y + 30);
- tft.setTextColor(ST77XX_MAGENTA);
- tft.print("ALARM");
- }
- //优化选择高亮绘图
- static int lastSelected = -1;
-
- //显示选中框
- if (index == selectedTimer || index == lastSelected) {
- int rectX, rectY;
- switch (index) {
- case 0: rectX = 2; rectY = 2; break;
- case 1: rectX = 162; rectY = 2; break;
- case 2: rectX = 2; rectY = 122; break;
- case 3: rectX = 162; rectY = 122; break;
- }
- //通过绘制黑色清除先前的选择
- tft.drawRect(rectX, rectY, 156, 116, ST77XX_BLACK);
- tft.drawRect(rectX+1, rectY+1, 154, 114, ST77XX_BLACK);
-
- //如果这是选定的计时器,则绘制新选区
- if (index == selectedTimer) {
- tft.drawRect(rectX, rectY, 156, 116, ST77XX_YELLOW);
- tft.drawRect(rectX+1, rectY+1, 154, 114, ST77XX_YELLOW);
- }
- }
- lastSelected = selectedTimer;
- }
使用说明
>选择计时器:短按对应按钮选择要操作的计时器(黄色边框表示选中)
>开始/暂停:短按当前选中计时器的按钮
>重置计时器:长按(>1秒)当前选中计时器的按钮
>报警静音:任意按钮操作可暂时静音报警
>报警条件:
计时达到4小时及以上时,每小时触发一次报警
显示屏显示"ALARM"状态
蜂鸣器发出提示音
四、项目演示效果1. 四小时报警功能演示当任意一个计时器达到4小时或以上时,系统会触发报警功能:
计时器区域显示"ALARM"文字、报警状态会持续显示直到用户操作、蜂鸣器发出悦耳的2500Hz提示音
2. 报警静音操作演示 报警触发后,用户可以通过以下方式关闭报警:
按下任意一个计时器按钮(短按)、蜂鸣器立即停止发声、屏幕上"ALARM"提示消失、系统进入静音状态
长按当前选中计时器的按钮(>1秒)、除了停止报警,还会重置该计时器、计时器归零并显示"Reset"状态
3. 多计时器独立运行演示四个计时器可完全独立操作:
四个计时器可同时开始计时、每个计时器独立记录时间、显示屏分区显示各自状态
>选择计时器1:短按按钮1
>开始/暂停:再次短按
>按钮1重置:长按按钮1(>1秒)
其他计时器操作类似
4. 项目视频演示
https://live.csdn.net/v/483152
达到四小时计数后持续报警,按下任意键清除报警声,在四个小时基础上每过一个小时报警一次。
五、常见问题解答Q1: 报警声音可以调整吗?
A: 可以,在代码中修改以下参数:
- const int TONE_FREQUENCY = 2500; // 频率(Hz),范围0-5000
- const int TONE_DURATION = 300; // 单次响声持续时间(ms)
- const unsigned long ALARM_INTERVAL = 800; // 报警间隔(ms)
Q2: 为什么我的报警没有触发?
A: 请检查:
计时器是否达到4小时(显示04:00:00)
ALARM_HOURS参数设置是否正确(默认为4)
蜂鸣器接线是否正确(正负极)
Q3: 如何改变报警的小时阈值?
A: 修改代码中的常量定义:
- const unsigned long ALARM_HOURS = 2; // 2小时触发报警
Q4: 按钮按下后没有响应怎么办?
A: 检查:
按钮是否正常连接(用万用表测试通断)
按钮引脚配置是否正确
消抖参数是否合适(可调整debounceDelay)
六、完整源码获取 百度网盘获取链接,通过网盘分享的文件:STM32-Multi-Timer.zip
https://pan.baidu.com/s/1v9NuKp690DUWvqAC73VV8Q?pwd=3wv2
压缩包内容:
/STM32-Multi-Timer
├── TIM_NVIC.ino // 主程序
├── Adafruit-ST7735-Library-master/ // 所需库文件
├── SPI/ // 电路图
本教程详细展示了四路独立计时器的报警功能和操作演示,并提供了完整的源码获取方式。这个项目不仅具有实际应用价值,还涵盖了嵌入式开发的多个关键技术点:
>多任务处理(四个独立计时器) >用户界面设计(TFT显示)
>中断处理(按钮响应) >报警系统设计(声光提示)
>状态机实现(计时器状态管理)
✔零知开源是一个真正属于国人自己的开源软硬件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。
✔零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知开源平台,获取更多实战项目和教程资源吧!
www.lingzhilab.com