当前位置: 首页 > news >正文

零知开源——基于STM32F407VET6零知增强板的四路独立计时器

项目概述

本教程将指导你如何使用STM32F407VET6零知增强板实现一个功能强大的四路独立计时器。每个计时器可以独立控制,支持开始、暂停和重置功能,并具备定时报警功能(4小时或每小时触发)。项目结合了TFT显示屏、蜂鸣器按钮控制,提供了一个直观的用户界面。

目录

一、硬件准备 

二、软件环境配置

三、核心代码解析

 四、项目演示效果

 五、常见问题解答

六、完整源码获取 


核心功能

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

一、硬件准备 

1.1 硬件清单

主控板:        STM32F407VET6零知增强板<
显示屏:        1.54英寸TFT显示屏(ST7789驱动)<
蜂鸣器:        有源蜂鸣器模块<

LED:               LED灯珠<
按钮:            4个轻触开关<
连接线:        杜邦线若干<
电源:            5V电源适配器或USB供电<

 1.2 硬件连接

模块零知增强板引脚
TFT_CS53
TFT_DC2
TFT_MOSI51
TFT_SCLK52
TFT_RST4
蜂鸣器&LED3
按钮114
按钮215
按钮316
按钮417

 

 1.3 连接硬件图

主控零知增强板和ST7789显示屏:

蜂鸣器和按键电路: 

1.4 连接实物图

二、软件环境配置

1.零知开源开发工具(Lingzhi IDE)

2.安装必要的库:

  • Adafruit_GFX

  • Adafruit_ST7789

3.配置开发板类型:STM32F407VET6

三、核心代码解析

1. 引脚定义与初始化

#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. 项目视频演示

基于STM32F407VET6零知增强板的四路独立计时器

达到四小时计数后持续报警,按下任意键清除报警声,在四个小时基础上每过一个小时报警一次。

 五、常见问题解答

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

相关文章:

  • 快速掌握广告联盟APP开发全流程,短剧和游戏广告app
  • 全面拥抱vue3
  • kubectl get pod返回数据研究
  • InfluxDB 3 Core数据库管理指南:从概念到实操的完整流程
  • 机器学习开篇:算法分类与开发流程
  • JavaScript中Object()的解析与应用
  • Redis—主从复制
  • webpack+vite前端构建工具 -答疑
  • kubernetes部署3节点高可用elasticsearch v8.14.3
  • 【数学基础】复杂度理论
  • Python异步编程深度解析
  • 基于Spring Boot的网上购物平台设计与实现
  • AD22以上的基础操作
  • C++算法学习专题:双指针
  • 编译安装交叉工具链 riscv-gnu-toolchain
  • RGB+EVS视觉融合相机:事件相机的革命性突破​
  • 微机系统 第九章 人机接口 (包含了第四章的题)
  • MCPA2APPT:基于 A2A+MCP+ADK 的多智能体流式并发高质量 PPT 智能生成系统
  • 《自动控制原理 》- 第 1 章 自动控制的基本原理与方式
  • 分布式电源采集控制装置:江苏光伏电站的“智能调度中枢