項目概述
本教程將指導你如何使用STM32F407VET6零知增強板實現(xiàn)一個功能強大的四路獨立計時器。每個計時器可以獨立控制,支持開始、暫停和重置功能,并具備定時報警功能(4小時或每小時觸發(fā))。項目結合了TFT顯示屏、蜂鳴器和按鈕控制,提供了一個直觀的用戶界面。
目錄
一、硬件準備
二、軟件環(huán)境配置
三、核心代碼解析
四、項目演示效果
五、常見問題解答
六、完整源碼獲取
核心功能
>四路獨立計時器:每個計時器獨立運行,互不影響
>多種控制模式: 開始、暫停、重置功能
>智能報警系統(tǒng): 4小時及以上每小時報警提示
>直觀的用戶界面:TFT顯示屏顯示計時器狀態(tài)
>聲音提示: 蜂鳴器提供報警音效
>長/短按操作:按鈕支持不同時長的操作
一、硬件準備
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顯示屏:

蜂鳴器和按鍵電路:

1.4 連接實物圖

二、軟件環(huán)境配置
1.零知開源開發(fā)工具(Lingzhi IDE)
2.安裝必要的庫:
Adafruit_GFX
Adafruit_ST7789
3.配置開發(fā)板類型:STM32F407VET6
三、核心代碼解析
1. 引腳定義與初始化
#include #include #include // 屏幕引腳配置 #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 // 按鈕引腳 - 高電平觸發(fā) #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; // 計時器報警狀態(tài) } Timer; Timer timers[4]; // 四個計時器
2. 按鈕狀態(tài)檢測
// 按鈕狀態(tài) 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; // 報警激活狀態(tài) bool alarmSilenced = false; // 報警被靜音 unsigned long lastBeepTime = 0; // 上次蜂鳴器響的時間 unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; // 消抖時間(毫秒) // 報警參數(shù) const unsigned long ALARM_INTERVAL = 800; // 蜂鳴器報警間隔(ms) const unsigned long HOUR_SECONDS = 3600; // 1小時的秒數(shù) const unsigned long ALARM_HOURS = 4; // 報警小時數(shù) // PWM參數(shù) const int TONE_FREQUENCY = 2500; // 蜂鳴器頻率 (Hz) const int TONE_DURATION = 300; // 蜂鳴器單次響聲持續(xù)時間 (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. 主循環(huán)控制
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. 報警系統(tǒng)實現(xiàn)
// 檢查報警條件
void checkAlarmConditions(int index) {
// 檢查是否達到4小時或每小時
if (timers[index].totalSeconds >= ALARM_HOURS * HOUR_SECONDS) {
// 檢查是否達到新的小時
if (timers[index].totalSeconds % HOUR_SECONDS == 0) {
// 避免連續(xù)觸發(fā)
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);
}
}
// 輪詢按鈕狀態(tài)(帶消抖)
void pollButtons() {
unsigned long currentMillis = millis();
for (int i = 0; i < 4; i++) {
// 讀取按鈕狀態(tài)(高電平表示按下)
ButtonState reading = (digitalRead(buttons[i].pin) == HIGH) ? BUTTON_PRESSED : BUTTON_RELEASED;
// 如果狀態(tài)改變,重置消抖計時器
if (reading != buttons[i].lastState) {
lastDebounceTime = currentMillis;
}
// 如果狀態(tài)穩(wěn)定時間超過消抖延遲
if ((currentMillis - lastDebounceTime) > debounceDelay) {
// 更新按鈕狀態(tài)
if (reading != buttons[i].state) {
buttons[i].state = reading;
// 記錄按下開始時間
if (buttons[i].state == BUTTON_PRESSED) {
buttons[i].pressStartTime = currentMillis;
}
}
}
// 保存當前狀態(tài)用于下次比較
buttons[i].lastState = reading;
}
}
// 靜音報警并清除報警狀態(tài)
void silenceAlarm() {
alarmActive = false;
alarmSilenced = true;
noTone(BUZZER_PIN);
// 清除所有計時器的報警狀態(tài)
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;
}
}
// 如果有按鈕事件,靜音報警并清除報警狀態(tài)
if (buttonEventOccurred) {
silenceAlarm();
}
}
7. 用戶界面設計
void drawTimers() {
tft.fillScreen(ST77XX_BLACK);
// 繪制四個計時器區(qū)域
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;
}
// 清除時間顯示區(qū)域(避免殘留字符)
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);
// 清除狀態(tài)顯示區(qū)域
tft.fillRect(x, y + 30, 60, 10, ST77XX_BLACK);
// 顯示狀態(tài)
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");
}
// 顯示報警狀態(tài)(僅在報警觸發(fā)且未靜音時顯示)
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");
}
//優(yōu)化選擇高亮繪圖
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);
//如果這是選定的計時器,則繪制新選區(qū)
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小時及以上時,每小時觸發(fā)一次報警
顯示屏顯示"ALARM"狀態(tài)
蜂鳴器發(fā)出提示音
四、項目演示效果
1. 四小時報警功能演示
當任意一個計時器達到4小時或以上時,系統(tǒng)會觸發(fā)報警功能:
計時器區(qū)域顯示"ALARM"文字、報警狀態(tài)會持續(xù)顯示直到用戶操作、蜂鳴器發(fā)出悅耳的2500Hz提示音
2. 報警靜音操作演示
報警觸發(fā)后,用戶可以通過以下方式關閉報警:
按下任意一個計時器按鈕(短按)、蜂鳴器立即停止發(fā)聲、屏幕上"ALARM"提示消失、系統(tǒng)進入靜音狀態(tài)
長按當前選中計時器的按鈕(>1秒)、除了停止報警,還會重置該計時器、計時器歸零并顯示"Reset"狀態(tài)
3. 多計時器獨立運行演示
四個計時器可完全獨立操作:
四個計時器可同時開始計時、每個計時器獨立記錄時間、顯示屏分區(qū)顯示各自狀態(tài)
>選擇計時器1:短按按鈕1
>開始/暫停:再次短按
>按鈕1重置:長按按鈕1(>1秒)
其他計時器操作類似
4. 項目視頻演示
https://blog.csdn.net/lingzhilab/article/details/148974172?spm=1001.2014.3001.5502#t18
達到四小時計數(shù)后持續(xù)報警,按下任意鍵清除報警聲,在四個小時基礎上每過一個小時報警一次。
五、常見問題解答
Q1: 報警聲音可以調整嗎?
A: 可以,在代碼中修改以下參數(shù):
const int TONE_FREQUENCY = 2500; // 頻率(Hz),范圍0-5000 const int TONE_DURATION = 300; // 單次響聲持續(xù)時間(ms) const unsigned long ALARM_INTERVAL = 800; // 報警間隔(ms)
Q2: 為什么我的報警沒有觸發(fā)?
A: 請檢查:
計時器是否達到4小時(顯示04:00:00)
ALARM_HOURS參數(shù)設置是否正確(默認為4)
蜂鳴器接線是否正確(正負極)
Q3: 如何改變報警的小時閾值?
A: 修改代碼中的常量定義:
const unsigned long ALARM_HOURS = 2; // 2小時觸發(fā)報警
Q4: 按鈕按下后沒有響應怎么辦?
A: 檢查:
按鈕是否正常連接(用萬用表測試通斷)
按鈕引腳配置是否正確
消抖參數(shù)是否合適(可調整debounceDelay)
六、完整源碼獲取
百度網(wǎng)盤獲取鏈接,通過網(wǎng)盤分享的文件:STM32-Multi-Timer.zip
https://pan.baidu.com/s/1v9NuKp690DUWvqAC73VV8Q?pwd=3wv2
壓縮包內容:
/STM32-Multi-Timer
├── TIM_NVIC.ino // 主程序
├── Adafruit-ST7735-Library-master/ // 所需庫文件
├── SPI/ // 電路圖
^_^本教程詳細展示了四路獨立計時器的報警功能和操作演示,并提供了完整的源碼獲取方式。這個項目不僅具有實際應用價值,還涵蓋了嵌入式開發(fā)的多個關鍵技術點:
>多任務處理(四個獨立計時器)>用戶界面設計(TFT顯示)
>中斷處理(按鈕響應) >報警系統(tǒng)設計(聲光提示)
>狀態(tài)機實現(xiàn)(計時器狀態(tài)管理)
?零知開源是一個真正屬于國人自己的開源軟硬件平臺,在開發(fā)效率上超越了Arduino平臺并且更加容易上手,大大降低了開發(fā)難度。
?零知開源在軟件方面提供了完整的學習教程和豐富示例代碼,讓不懂程序的工程師也能非常輕而易舉的搭建電路來創(chuàng)作產(chǎn)品,測試產(chǎn)品。快來動手試試吧!
?訪問零知開源平臺,獲取更多實戰(zhàn)項目和教程資源吧!
www.lingzhilab.com
審核編輯 黃宇
-
計時器
+關注
關注
1文章
433瀏覽量
34656 -
STM32F407VET6
+關注
關注
2文章
6瀏覽量
3381
發(fā)布評論請先 登錄
STM32F407VET6和STM32F407IET6有什么區(qū)別?
零知開源——STM32F4驅動MAX31865實現(xiàn)PT100高精度測溫
零知開源——基于STM32F407VET6零知增強板的四路獨立計時器
零知開源——STM32F407VET6驅動SHT41溫濕度傳感器完整教程
STM32F407VET6的片上資源描述
STM32F103VET6/STM32F407VET6原理圖相關資料分享
stm32f407vet6原理介紹
零知開源——STM32F4結合BMP581氣壓傳感器實現(xiàn)ST7789中文顯示教程

零知開源——基于STM32F407VET6零知增強板的四路獨立計時器
評論