[Arduino资料] 零知开源——基于STM32F407VET6零知增强板的四路独立计时器

[复制链接]
 楼主| lingzhiLab 发表于 2025-7-1 09:14 | 显示全部楼层 |阅读模式
项目概述
本教程将指导你如何使用STM32F407VET6零知增强板实现一个功能强大的四路独立计时器。每个计时器可以独立控制,支持开始、暂停和重置功能,并具备定时报警功能(4小时或每小时触发)。项目结合了TFT显示屏、蜂鸣器按钮控制,提供了一个直观的用户界面。

核心功能
>四路独立计时器:        每个计时器独立运行,互不影响
>多种控制模式:           开始、暂停、重置功能
>智能报警系统:           4小时及以上每小时报警提示
>直观的用户界面:        TFT显示屏显示计时器状态
>声音提示:                  蜂鸣器提供报警音效
>长/短按操作:             按钮支持不同时长的操作

一、硬件准备 1.1 硬件清单
主控板
STM32F407VET6零知增强板
显示屏
1.54英寸TFT显示屏(ST7789驱动)
蜂鸣器
有源蜂鸣器模块
LED
LED灯珠
按钮
4个轻触开关
连接线
杜邦线若干
电源
5V电源适配器或USB供电


1.2 硬件连接
模块
零知增强板引脚
TFT_CS
53
TFT_DC
2
TFT_MOSI
51
TFT_SCLK
52
TFT_RST
4
蜂鸣器&LED
3
按钮1
14
按钮2
15
按钮3
16
按钮4
17

1.3 连接硬件图主控零知增强板和ST7789显示屏:
屏幕截图 2025-06-28 160303.png

蜂鸣器和按键电路:
图片1.png

1.4 连接实物图
6c1c365c-e4bb-40e0-8f8f-69f9461ca90f.jpg

二、软件环境配置1.零知开源开发工具(Lingzhi IDE)
2.安装必要的库:
Adafruit_GFX
Adafruit_ST7789
3.配置开发板类型:STM32F407VET6

三、核心代码解析1. 引脚定义与初始化
  1. #include <Adafruit_GFX.h>
  2. #include <Adafruit_ST7789.h>
  3. #include <SPI.h>

  4. // 屏幕引脚配置
  5. #define TFT_CS         53
  6. #define TFT_RST        4
  7. #define TFT_DC         2
  8. #define TFT_MOSI       51
  9. #define TFT_SCLK       52

  10. // 使用硬件SPI
  11. Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

  12. // 蜂鸣器引脚
  13. #define BUZZER_PIN 3

  14. // 按钮引脚 - 高电平触发
  15. #define BUTTON_PIN1 14
  16. #define BUTTON_PIN2 15
  17. #define BUTTON_PIN3 16
  18. #define BUTTON_PIN4 17

  19. // 计时器结构
  20. typedef struct {
  21.   unsigned long totalSeconds;
  22.   bool isRunning;
  23.   bool isReset;
  24.   unsigned long lastUpdateTime; // 每个计时器独立的更新时间戳
  25.   unsigned long lastHourAlarm;  // 上次小时报警时间戳
  26.   bool alarmTriggered;          // 计时器报警状态
  27. } Timer;

  28. Timer timers[4]; // 四个计时器

2. 按钮状态检测
  1. // 按钮状态
  2. enum ButtonState {
  3.   BUTTON_RELEASED,
  4.   BUTTON_PRESSED
  5. };

  6. // 按钮结构
  7. typedef struct {
  8.   uint8_t pin;
  9.   ButtonState state;
  10.   ButtonState lastState;
  11.   unsigned long pressStartTime;
  12. } Button;

  13. Button buttons[4];

  14. // 当前选中的计时器
  15. int selectedTimer = 0;
  16. bool alarmActive = false;       // 报警激活状态
  17. bool alarmSilenced = false;     // 报警被静音
  18. unsigned long lastBeepTime = 0; // 上次蜂鸣器响的时间
  19. unsigned long lastDebounceTime = 0;
  20. const unsigned long debounceDelay = 50; // 消抖时间(毫秒)

  21. // 报警参数
  22. const unsigned long ALARM_INTERVAL = 800; // 蜂鸣器报警间隔(ms)
  23. const unsigned long HOUR_SECONDS = 3600;   // 1小时的秒数
  24. const unsigned long ALARM_HOURS = 4;       // 报警小时数

  25. // PWM参数
  26. const int TONE_FREQUENCY = 2500; // 蜂鸣器频率 (Hz)
  27. const int TONE_DURATION = 300;   // 蜂鸣器单次响声持续时间 (ms)

3. 初始化设置
  1. void setup() {
  2.   Serial.begin(9600);
  3.   
  4.   // 初始化蜂鸣器
  5.   pinMode(BUZZER_PIN, OUTPUT);
  6.   digitalWrite(BUZZER_PIN, LOW);
  7.   
  8.   // 初始化按钮
  9.   buttons[0] = {BUTTON_PIN1, BUTTON_RELEASED, BUTTON_RELEASED, 0};
  10.   buttons[1] = {BUTTON_PIN2, BUTTON_RELEASED, BUTTON_RELEASED, 0};
  11.   buttons[2] = {BUTTON_PIN3, BUTTON_RELEASED, BUTTON_RELEASED, 0};
  12.   buttons[3] = {BUTTON_PIN4, BUTTON_RELEASED, BUTTON_RELEASED, 0};
  13.   
  14.   for (int i = 0; i < 4; i++) {
  15.     pinMode(buttons[i].pin, INPUT);
  16.   }
  17.   
  18.   // 初始化屏幕
  19.   tft.init(240, 320);
  20.   tft.setRotation(1);
  21.   tft.fillScreen(ST77XX_BLACK);
  22.   
  23.   // 初始化计时器
  24.   for (int i = 0; i < 4; i++) {
  25.     timers[i].totalSeconds = 0;
  26.     timers[i].isRunning = false;
  27.     timers[i].isReset = true;
  28.     timers[i].lastUpdateTime = 0;
  29.     timers[i].lastHourAlarm = 0;
  30.     timers[i].alarmTriggered = false;
  31.   }
  32.   
  33.   // 绘制初始界面
  34.   drawTimers();
  35. }

4. 主循环控制
  1. void loop() {
  2.   unsigned long currentMillis = millis();
  3.   
  4.   // 更新所有正在运行的计时器
  5.   for (int i = 0; i < 4; i++) {
  6.     if (timers[i].isRunning) {
  7.       // 每个计时器独立更新
  8.       if (currentMillis - timers[i].lastUpdateTime >= 1000) {
  9.         timers[i].totalSeconds++;
  10.         timers[i].lastUpdateTime = currentMillis;
  11.         
  12.         // 检查报警条件
  13.         checkAlarmConditions(i);
  14.         
  15.         // 只更新这个计时器的显示
  16.         drawTimer(i);
  17.       }
  18.     }
  19.   }
  20.   
  21.   // 处理按钮事件
  22.   pollButtons();
  23.   handleButtonEvents();
  24.   
  25.   // 处理报警声音
  26.   updateAlarmSound();
  27.   
  28.   delay(10);
  29. }

5. 报警系统实现
  1. // 检查报警条件
  2. void checkAlarmConditions(int index) {
  3.   // 检查是否达到4小时或每小时
  4.   if (timers[index].totalSeconds >=  ALARM_HOURS * HOUR_SECONDS) {
  5.     // 检查是否达到新的小时
  6.     if (timers[index].totalSeconds % HOUR_SECONDS == 0) {
  7.       // 避免连续触发
  8.       if (timers[index].totalSeconds != timers[index].lastHourAlarm) {
  9.         timers[index].alarmTriggered = true;
  10.         alarmActive = true;
  11.         alarmSilenced = false;
  12.         timers[index].lastHourAlarm = timers[index].totalSeconds;
  13.       }
  14.     }
  15.   }
  16. }

  17. // 更新报警声音
  18. void updateAlarmSound() {
  19.   if (alarmActive && !alarmSilenced) {
  20.     unsigned long currentMillis = millis();
  21.    
  22.     // 每秒响一次(300ms开,800ms关)
  23.     if (currentMillis - lastBeepTime >= ALARM_INTERVAL) {
  24.       lastBeepTime = currentMillis;
  25.       
  26.       // 播放悦耳音调
  27.       tone(BUZZER_PIN, TONE_FREQUENCY, TONE_DURATION);
  28.     }
  29.   } else {
  30.     noTone(BUZZER_PIN);
  31.   }
  32. }

  33. // 轮询按钮状态(带消抖)
  34. void pollButtons() {
  35.   unsigned long currentMillis = millis();
  36.   
  37.   for (int i = 0; i < 4; i++) {
  38.     // 读取按钮状态(高电平表示按下)
  39.     ButtonState reading = (digitalRead(buttons[i].pin) == HIGH) ? BUTTON_PRESSED : BUTTON_RELEASED;
  40.    
  41.     // 如果状态改变,重置消抖计时器
  42.     if (reading != buttons[i].lastState) {
  43.       lastDebounceTime = currentMillis;
  44.     }
  45.    
  46.     // 如果状态稳定时间超过消抖延迟
  47.     if ((currentMillis - lastDebounceTime) > debounceDelay) {
  48.       // 更新按钮状态
  49.       if (reading != buttons[i].state) {
  50.         buttons[i].state = reading;
  51.         
  52.         // 记录按下开始时间
  53.         if (buttons[i].state == BUTTON_PRESSED) {
  54.           buttons[i].pressStartTime = currentMillis;
  55.         }
  56.       }
  57.     }
  58.    
  59.     // 保存当前状态用于下次比较
  60.     buttons[i].lastState = reading;
  61.   }
  62. }

  63. // 静音报警并清除报警状态
  64. void silenceAlarm() {
  65.   alarmActive = false;
  66.   alarmSilenced = true;
  67.   noTone(BUZZER_PIN);
  68.   
  69.   // 清除所有计时器的报警状态
  70.   for (int i = 0; i < 4; i++) {
  71.     timers[i].alarmTriggered = false;
  72.     // 重绘计时器以清除"ALARM"显示
  73.     drawTimer(i);
  74.   }
  75. }

6. 按钮事件处理
  1. void handleButtonEvents() {
  2.   unsigned long currentMillis = millis();
  3.   bool buttonEventOccurred = false;
  4.   int previousSelectedTimer = selectedTimer; //跟踪之前的选择  
  5.   
  6.   for (int i = 0; i < 4; i++) {
  7.     if (buttons[i].state == BUTTON_PRESSED) {
  8.       // 长按检测(超过1秒)
  9.       if (currentMillis - buttons[i].pressStartTime > 1000) {
  10.         // 长按 - 只复位当前选中的计时器
  11.         if (i == selectedTimer) {
  12.           timers[i].totalSeconds = 0;
  13.           timers[i].isRunning = false;
  14.           timers[i].isReset = true;
  15.           timers[i].lastUpdateTime = 0;
  16.           timers[i].lastHourAlarm = 0;
  17.           timers[i].alarmTriggered = false;
  18.           drawTimer(i);
  19.           silenceAlarm();
  20.         }
  21.         buttonEventOccurred = true;
  22.       }
  23.     } else if (buttons[i].state == BUTTON_RELEASED) {
  24.       // 按钮释放时检测短按
  25.       if (buttons[i].pressStartTime > 0 &&
  26.           currentMillis - buttons[i].pressStartTime > debounceDelay &&
  27.           currentMillis - buttons[i].pressStartTime <= 1000) {
  28.         
  29.         buttonEventOccurred = true;
  30.         
  31.         // 短按 - 开始/暂停计时器或切换计时器
  32.         if (i == selectedTimer) {
  33.           timers[i].isRunning = !timers[i].isRunning;
  34.           timers[i].isReset = false;
  35.           timers[i].lastUpdateTime = millis();
  36.         } else {
  37.           previousSelectedTimer = selectedTimer;
  38.           selectedTimer = i;
  39.           // 重绘所有计时器以更新选中框
  40.           drawTimer(previousSelectedTimer);
  41.           drawTimer(selectedTimer);
  42.         }
  43.         
  44.         // 只更新当前计时器的显示
  45.         drawTimer(i);
  46.       }
  47.       
  48.       // 重置按下开始时间
  49.       buttons[i].pressStartTime = 0;
  50.     }
  51.   }
  52.   
  53.   // 如果有按钮事件,静音报警并清除报警状态
  54.   if (buttonEventOccurred) {
  55.     silenceAlarm();
  56.   }
  57. }

7. 用户界面设计
  1. void drawTimers() {
  2.   tft.fillScreen(ST77XX_BLACK);
  3.   
  4.   // 绘制四个计时器区域
  5.   tft.drawRect(0, 0, 160, 120, ST77XX_WHITE);    // 左上
  6.   tft.drawRect(160, 0, 160, 120, ST77XX_WHITE);  // 右上
  7.   tft.drawRect(0, 120, 160, 120, ST77XX_WHITE);   // 左下
  8.   tft.drawRect(160, 120, 160, 120, ST77XX_WHITE); // 右下
  9.   
  10.   // 绘制所有计时器
  11.   for (int i = 0; i < 4; i++) {
  12.     drawTimer(i);
  13.   }
  14. }

  15. void drawTimer(int index) {
  16.   int x, y;
  17.   
  18.   // 确定位置
  19.   switch (index) {
  20.     case 0: x = 30; y = 50; break; // 左上
  21.     case 1: x = 190; y = 50; break; // 右上
  22.     case 2: x = 30; y = 170; break; // 左下
  23.     case 3: x = 190; y = 170; break; // 右下
  24.     default: return;
  25.   }
  26.   
  27.   // 清除时间显示区域(避免残留字符)
  28.   tft.fillRect(x, y, 100, 20, ST77XX_BLACK);
  29.   
  30.   // 设置文本颜色和大小
  31.   tft.setTextSize(2);
  32.   tft.setTextColor(index == selectedTimer ? ST77XX_YELLOW : ST77XX_WHITE);
  33.   
  34.   // 计算时间
  35.   int hours = timers[index].totalSeconds / 3600;
  36.   int minutes = (timers[index].totalSeconds % 3600) / 60;
  37.   int seconds = timers[index].totalSeconds % 60;
  38.   
  39.   // 格式化时间
  40.   char timeStr[12];
  41.   sprintf(timeStr, "%02d:%02d:%02d", hours, minutes, seconds);
  42.   
  43.   // 显示时间
  44.   tft.setCursor(x, y);
  45.   tft.print(timeStr);
  46.   
  47.   // 清除状态显示区域
  48.   tft.fillRect(x, y + 30, 60, 10, ST77XX_BLACK);
  49.   
  50.   // 显示状态
  51.   tft.setTextSize(1);
  52.   tft.setCursor(x, y + 30);
  53.   if (timers[index].isReset) {
  54.     tft.print("Reset");
  55.   } else if (timers[index].isRunning) {
  56.     tft.setTextColor(ST77XX_GREEN);
  57.     tft.print("Running");
  58.   } else {
  59.     tft.setTextColor(ST77XX_ORANGE);
  60.     tft.print("Paused");
  61.   }
  62.   
  63.   // 显示报警状态(仅在报警触发且未静音时显示)
  64.   tft.fillRect(x + 60, y + 30, 40, 10, ST77XX_BLACK);
  65.   if (timers[index].alarmTriggered && !alarmSilenced) {
  66.     tft.setCursor(x + 60, y + 30);
  67.     tft.setTextColor(ST77XX_MAGENTA);
  68.     tft.print("ALARM");
  69.   }

  70.   //优化选择高亮绘图
  71.   static int lastSelected = -1;
  72.   
  73.   //显示选中框
  74.   if (index == selectedTimer || index == lastSelected) {
  75.     int rectX, rectY;
  76.     switch (index) {
  77.       case 0: rectX = 2; rectY = 2; break;
  78.       case 1: rectX = 162; rectY = 2; break;
  79.       case 2: rectX = 2; rectY = 122; break;
  80.       case 3: rectX = 162; rectY = 122; break;
  81.     }

  82.   //通过绘制黑色清除先前的选择
  83.     tft.drawRect(rectX, rectY, 156, 116, ST77XX_BLACK);
  84.     tft.drawRect(rectX+1, rectY+1, 154, 114, ST77XX_BLACK);
  85.    
  86.     //如果这是选定的计时器,则绘制新选区
  87.     if (index == selectedTimer) {
  88.       tft.drawRect(rectX, rectY, 156, 116, ST77XX_YELLOW);
  89.       tft.drawRect(rectX+1, rectY+1, 154, 114, ST77XX_YELLOW);
  90.     }

  91.   }
  92.   lastSelected = selectedTimer;
  93. }

使用说明
>选择计时器:短按对应按钮选择要操作的计时器(黄色边框表示选中)
>开始/暂停:短按当前选中计时器的按钮
>重置计时器:长按(>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: 可以,在代码中修改以下参数:
  1. const int TONE_FREQUENCY = 2500; // 频率(Hz),范围0-5000
  2. const int TONE_DURATION = 300;   // 单次响声持续时间(ms)
  3. const unsigned long ALARM_INTERVAL = 800; // 报警间隔(ms)
Q2: 为什么我的报警没有触发?
A: 请检查:
计时器是否达到4小时(显示04:00:00)
ALARM_HOURS参数设置是否正确(默认为4)
蜂鸣器接线是否正确(正负极)
Q3: 如何改变报警的小时阈值?
A: 修改代码中的常量定义:
  1. 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

您需要登录后才可以回帖 登录 | 注册

本版积分规则

22

主题

33

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部