0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

零知实验室 来源:PCB56242069 作者:PCB56242069 2025-07-01 10:13 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

项目概述

本教程将指导你如何使用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显示屏:

wKgZO2hjPhKAVo0KAAQTV3O3XsU175.png

蜂鸣器和按键电路:

wKgZO2hjPhyAWmKeAAUo-vriXic137.png

1.4 连接实物图

wKgZO2hjPjKAOY3sACCnm6yIGvA709.png

二、软件环境配置

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

https://blog.csdn.net/lingzhilab/article/details/148974172?spm=1001.2014.3001.5502#t18

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

五、常见问题解答

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


审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 计时器
    +关注

    关注

    1

    文章

    434

    浏览量

    34863
  • STM32F407VET6
    +关注

    关注

    2

    文章

    6

    浏览量

    3396
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    STM32F407VET6STM32F407IET6有什么区别?

    STM32F407VET6STM32F407IET6,除了引脚上数量不同之外,还有什么区别吗? 我的CAN驱动,在STM32F407VET6可正常运行。在STM32F407IET6
    发表于 07-03 07:06

    开源——STM32F4驱动MAX31865实现PT100高精度测温

    材料增强STM32F407VET6) MAX31865模块(支持三线制PT100) 三线制PT100传感
    发表于 05-26 18:52

    开源——基于STM32F407VET6增强独立计时器

    本帖最后由 PCB56242069 于 2025-7-1 11:24 编辑 项目概述本教程将指导你如何使用STM32F407VET6增强板实现一个功能强大的
    发表于 07-01 10:31

    开源——STM32F407VET6驱动SHT41温湿度传感完整教程

    目基于STM32F407VET6增强,实现SHT41高精度温湿度传感的驱动和数据采集。S
    发表于 07-10 12:01

    基于STM32F407VET6增强独立计时器

    计时器
    PCB56242069
    发布于 :2025年07月01日 09:41:44

    STM32F407VET6数据手册

    STM32F407VET6数据手册
    发表于 05-04 08:09

    STM32F407VET6的片上资源描述

    前言在ST官网看到了STM32F407VET6的片上资源描述,记录一下。实验STM32F407VET6片上资源产品型号STM32F407VET6主频(MHz)168内核ARM
    发表于 08-04 06:18

    STM32F103VET6/STM32F407VET6原理图相关资料分享

    STM32F103VET6/STM32F407VET6 原理图看不清楚就到这来免费下载吧 不用积分点击下载pdf档 不用积分点击下载SCH档
    发表于 08-05 06:59

    STM32F407VET6数据手册

    STM32F407VET6数据手册。
    发表于 04-13 14:10 642次下载

    STM32F407VET6核心的电路原理图免费下载

    本文档的主要内容详细介绍的是STM32F407VET6核心的电路原理图免费下载。
    发表于 03-18 08:00 539次下载
    <b class='flag-5'>STM32F407VET6</b>核心<b class='flag-5'>板</b>的电路原理图免费下载

    基于STM32F407VET6的CS1237驱动程序

    基于STM32F407VET6的CS1237驱动程序,亲测可用,欢迎大家一起交流
    发表于 08-28 11:22 202次下载

    STM32F407VET6

    STM32F407VET6规格书
    发表于 02-07 15:59 103次下载

    stm32f407vet6原理介绍

    STM32F407VET6是一款基于ARM Cortex-M4内核的高性能微控制,由STMicroelectronics公司生产。它具有丰富的外设和功能,广泛应用于各种嵌入式系统开发。本文将介绍
    的头像 发表于 01-03 16:49 1.5w次阅读
    <b class='flag-5'>stm32f407vet6</b>原理介绍

    开源——STM32F4结合BMP581气压传感实现ST7789中文显示教程

    开源平台是一个专为国开发者设计的软硬件开源平台,提供比Arduino更易上手的开发体验。本教程展示了如何在STM32F407VET6
    的头像 发表于 07-09 09:21 1237次阅读
    <b class='flag-5'>零</b><b class='flag-5'>知</b><b class='flag-5'>开源</b>——<b class='flag-5'>STM32F</b>4结合BMP581气压传感<b class='flag-5'>器</b>实现ST7789中文显示教程

    开源——STM32F407VET6驱动SHT41温湿度传感完整教程

    摘要:本项目基于STM32F407VET6增强驱动SHT41高精度温湿度传感,实现±0.
    的头像 发表于 07-09 18:53 890次阅读
    <b class='flag-5'>零</b><b class='flag-5'>知</b><b class='flag-5'>开源</b>——<b class='flag-5'>STM32F407VET6</b>驱动SHT41温湿度传感<b class='flag-5'>器</b>完整教程