項目概述
本教程將指導你如何使用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顯示屏:
蜂鳴器和按鍵電路:
1.4 連接實物圖
二、軟件環境配置
1.零知開源開發工具(Lingzhi IDE)
2.安裝必要的庫:
Adafruit_GFX
Adafruit_ST7789
3.配置開發板類型: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 // 按鈕引腳 - 高電平觸發 #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文章
432瀏覽量
33636 -
STM32F407VET6
+關注
關注
2文章
6瀏覽量
3218
發布評論請先 登錄
STM32F407VET6和STM32F407IET6有什么區別?
零知開源——STM32F4驅動MAX31865實現PT100高精度測溫
零知開源——基于STM32F407VET6零知增強板的四路獨立計時器

STM32F407VET6的片上資源描述
STM32F103VET6/STM32F407VET6原理圖相關資料分享
可以使用ST Link對STM32F407VET6黑板進行編程嗎?
stm32f407vet6原理介紹

評論