戰(zhàn):人體反應(yīng)計(jì)時(shí)器設(shè)計(jì)與實(shí)現(xiàn))
1. 項(xiàng)目概述從“中斷”到“反應(yīng)計(jì)時(shí)器”最近在整理一些嵌入式教學(xué)案例時(shí)翻到了一個(gè)挺有意思的老項(xiàng)目——“Human Response Timer”直譯過來就是“人體反應(yīng)計(jì)時(shí)器”。這個(gè)項(xiàng)目的核心是利用STM32微控制器的外部中斷EXTI功能來測量一個(gè)人從看到視覺或聽覺信號(hào)到做出物理按鍵反應(yīng)之間的時(shí)間間隔。聽起來是不是有點(diǎn)像我們小時(shí)候玩的“打地鼠”游戲機(jī)或者一些心理實(shí)驗(yàn)儀器沒錯(cuò)其本質(zhì)就是量化人體的反應(yīng)速度。為什么這個(gè)項(xiàng)目值得拿出來聊聊因?yàn)樵谇度胧介_發(fā)尤其是STM32的學(xué)習(xí)路徑上“中斷”是一個(gè)至關(guān)重要的分水嶺。很多朋友在點(diǎn)燈、調(diào)串口之后一碰到中斷就感覺有點(diǎn)懵配置寄存器一堆優(yōu)先級(jí)、嵌套、服務(wù)函數(shù)這些概念攪在一起。而這個(gè)“反應(yīng)計(jì)時(shí)器”項(xiàng)目恰恰是一個(gè)絕佳的中斷應(yīng)用實(shí)戰(zhàn)案例。它場景具體、目標(biāo)明確硬件需求極簡一個(gè)LED、一個(gè)按鍵、一塊STM32核心板足矣但軟件上卻完整涵蓋了從EXTI配置、NVIC管理、定時(shí)器精準(zhǔn)計(jì)時(shí)到主程序狀態(tài)機(jī)協(xié)調(diào)的全流程。通過實(shí)現(xiàn)它你能把那些枯燥的中斷理論變成看得見、摸得著的“幾百毫秒”的反應(yīng)時(shí)間數(shù)據(jù)理解會(huì)深刻得多。接下來我將為你完整拆解這個(gè)項(xiàng)目的設(shè)計(jì)思路、關(guān)鍵實(shí)現(xiàn)細(xì)節(jié)以及那些調(diào)試過程中容易踩的“坑”。無論你是剛接觸STM32的新手還是想溫故知新的老鳥都能從中獲得可直接復(fù)現(xiàn)的代碼和接地氣的經(jīng)驗(yàn)。2. 核心硬件與系統(tǒng)設(shè)計(jì)思路2.1 需求分析與硬件選型這個(gè)項(xiàng)目的目標(biāo)很純粹測量人體反應(yīng)時(shí)間。因此我們需要一個(gè)“起始信號(hào)發(fā)生器”、一個(gè)“終止信號(hào)檢測器”和一個(gè)“高精度計(jì)時(shí)器”。起始信號(hào)發(fā)生器通常使用視覺或聽覺提示。最簡單且成本最低的是視覺提示所以我們選用一個(gè)LED。讓LED突然點(diǎn)亮作為測試開始的信號(hào)。終止信號(hào)檢測器需要測試者做出一個(gè)明確的動(dòng)作來響應(yīng)起始信號(hào)。最直接的方式是按鍵。當(dāng)測試者看到LED亮起立即按下指定的按鍵。高精度計(jì)時(shí)器測量從LED亮起到按鍵按下之間的時(shí)間差。STM32片上的定時(shí)器TIM精度遠(yuǎn)高于軟件延時(shí)循環(huán)是理想選擇。因此最低硬件配置為一片STM32最小系統(tǒng)板如STM32F103C8T6、一個(gè)LED接GPIO輸出、一個(gè)輕觸按鍵接GPIO輸入并配置為外部中斷源。如果需要聽覺提示可以增加一個(gè)蜂鳴器。2.2 軟件架構(gòu)與狀態(tài)機(jī)設(shè)計(jì)整個(gè)系統(tǒng)的邏輯不能是線性的“點(diǎn)亮LED-等待按鍵-計(jì)算時(shí)間”因?yàn)槲覀冃枰幚砀鞣N情況比如按鍵提前按下作弊、反應(yīng)超時(shí)、多次測試等。一個(gè)清晰的狀態(tài)機(jī)State Machine是讓代碼有條不紊的關(guān)鍵。我通常設(shè)計(jì)四個(gè)核心狀態(tài)IDLE空閑系統(tǒng)上電或一次測試結(jié)束后的狀態(tài)。等待啟動(dòng)命令如另一個(gè)“開始測試”按鍵。READY準(zhǔn)備按下“開始測試”鍵后系統(tǒng)進(jìn)入準(zhǔn)備狀態(tài)。此時(shí)可以加入一個(gè)隨機(jī)延時(shí)例如1-3秒以消除測試者的預(yù)判讓反應(yīng)時(shí)間測量更真實(shí)。SIGNAL發(fā)出信號(hào)隨機(jī)延時(shí)結(jié)束立即點(diǎn)亮LED同時(shí)啟動(dòng)定時(shí)器開始計(jì)時(shí)。系統(tǒng)進(jìn)入等待反應(yīng)的狀態(tài)。MEASURE測量結(jié)束有兩種子狀態(tài)有效反應(yīng)在預(yù)設(shè)的最大等待時(shí)間內(nèi)如2秒檢測到反應(yīng)按鍵按下。立即停止定時(shí)器讀取計(jì)時(shí)值計(jì)算并顯示反應(yīng)時(shí)間。無效/超時(shí)反應(yīng)按鍵在LED亮起前就按下作弊或超過最大等待時(shí)間仍未按下。此時(shí)判定本次測試無效給出提示不記錄時(shí)間。狀態(tài)之間的轉(zhuǎn)換由按鍵中斷和定時(shí)器中斷來觸發(fā)。主循環(huán)主要負(fù)責(zé)狀態(tài)顯示如通過串口打印到電腦或OLED屏幕顯示和狀態(tài)遷移的邏輯判斷。注意這里的“隨機(jī)延時(shí)”不建議使用標(biāo)準(zhǔn)C庫的rand()因?yàn)樵谇度胧江h(huán)境中其偽隨機(jī)序列可能不夠“隨機(jī)”。一個(gè)常見的技巧是讀取一個(gè)未初始化的RAM值或者連續(xù)運(yùn)行的定時(shí)器計(jì)數(shù)器如SysTick的低位作為種子這樣每次上電的序列差異更大。3. 關(guān)鍵技術(shù)實(shí)現(xiàn)細(xì)節(jié)拆解3.1 外部中斷EXTI的配置要點(diǎn)反應(yīng)按鍵是整個(gè)系統(tǒng)的核心輸入必須配置為外部中斷模式以確保按鍵事件能被CPU即時(shí)響應(yīng)而不是被主循環(huán)輪詢錯(cuò)過。以STM32CubeMX配置為例關(guān)鍵步驟如下GPIO模式將按鍵對應(yīng)的GPIO引腳如PA0配置為“外部中斷模式”上拉或下拉電阻根據(jù)硬件電路選擇通常按鍵接地則配置為上拉輸入。EXTI線選擇STM32的GPIO引腳與EXTI線是復(fù)用的需要正確映射。PA0、PB0、PC0...都共享EXTI0線。在CubeMX的NVIC設(shè)置中勾選對應(yīng)的EXTI線中斷如EXTI line0 interrupt。觸發(fā)邊沿這是最容易出錯(cuò)的地方。對于消除機(jī)械按鍵抖動(dòng)的反應(yīng)測試必須使用下降沿Falling edge或上升沿Rising edge觸發(fā)嚴(yán)禁使用雙邊沿Rising Falling觸發(fā)。因?yàn)榘存I抖動(dòng)會(huì)產(chǎn)生多個(gè)邊沿導(dǎo)致一次按鍵動(dòng)作誤觸發(fā)多次中斷。我們選擇下降沿觸發(fā)按鍵按下引腳從高電平變?yōu)榈碗娖?。NVIC優(yōu)先級(jí)為這個(gè)EXTI中斷設(shè)置一個(gè)合適的優(yōu)先級(jí)。反應(yīng)計(jì)時(shí)器對實(shí)時(shí)性要求高可以設(shè)置一個(gè)較高的優(yōu)先級(jí)數(shù)字小的優(yōu)先級(jí)高但不必是最高要避免不合理的中斷嵌套。對應(yīng)的代碼初始化片段HAL庫// GPIO和EXTI的初始化通常在CubeMX生成的代碼中完成 // 關(guān)鍵的中斷服務(wù)函數(shù)如下 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin KEY_Pin) { // 禁止在回調(diào)函數(shù)內(nèi)做復(fù)雜操作僅設(shè)置標(biāo)志位。 key_pressed_flag 1; } }實(shí)操心得中斷服務(wù)函數(shù)ISR要遵循“快進(jìn)快出”原則。絕對不要在EXTI_Callback里進(jìn)行復(fù)雜計(jì)算、延時(shí)或調(diào)用可能阻塞的函數(shù)如某些HAL_Delay。正確的做法是設(shè)置一個(gè)全局的“事件標(biāo)志”如key_pressed_flag由主循環(huán)中的狀態(tài)機(jī)來查詢和處理這個(gè)標(biāo)志。這是寫出穩(wěn)定、可靠中斷程序的第一鐵律。3.2 高精度定時(shí)器TIM的計(jì)時(shí)實(shí)現(xiàn)我們需要一個(gè)定時(shí)器來測量微秒或毫秒級(jí)的時(shí)間差。以STM32F103的通用定時(shí)器TIM2為例配置為向上計(jì)數(shù)模式預(yù)分頻器PSC和自動(dòng)重載值A(chǔ)RR的設(shè)置決定了計(jì)時(shí)精度和周期。假設(shè)系統(tǒng)時(shí)鐘為72MHz我們希望定時(shí)器每1微秒計(jì)數(shù)一次定時(shí)器時(shí)鐘 72MHz / (PSC 1)欲得1MHz計(jì)數(shù)頻率1us加1則需 PSC 71。設(shè)置ARR為最大值6553516位定時(shí)器則定時(shí)器溢出周期為 65536 us ≈ 65.5 ms。這對于測量通常100-500ms的人體反應(yīng)時(shí)間來說溢出風(fēng)險(xiǎn)較高。更穩(wěn)健的方案是測量毫秒級(jí)時(shí)間并處理溢出設(shè)置PSC 7199則定時(shí)器時(shí)鐘 72MHz / 7200 10KHz即每計(jì)數(shù)一次為0.1ms。設(shè)置ARR 9999則定時(shí)器每1000ms1秒產(chǎn)生一次更新中斷。在SIGNAL狀態(tài)我們不僅啟動(dòng)定時(shí)器還要記錄下啟動(dòng)時(shí)刻的計(jì)數(shù)器值start_counter __HAL_TIM_GET_COUNTER(htim2)。在按鍵中斷觸發(fā)后在MEASURE狀態(tài)讀取此時(shí)的計(jì)數(shù)器值end_counter。計(jì)算時(shí)間差時(shí)必須考慮定時(shí)器可能發(fā)生的溢出次數(shù)。我們可以利用定時(shí)器的更新中斷來維護(hù)一個(gè)全局的overflow_count變量。計(jì)算反應(yīng)時(shí)間的公式為reaction_time (overflow_count * (ARR 1) (end_counter - start_counter)) * (1 / timer_freq)其中timer_freq是定時(shí)器計(jì)數(shù)頻率本例為10KHz。// 定時(shí)器更新中斷溢出中斷服務(wù)函數(shù) void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { timer2_overflow_count; } } // 計(jì)算時(shí)間差的函數(shù) uint32_t get_elapsed_time_us(uint32_t start_cnt) { uint32_t current_cnt __HAL_TIM_GET_COUNTER(htim2); uint32_t current_overflow timer2_overflow_count; uint32_t elapsed_ticks; // 處理讀取期間可能發(fā)生的溢出 if (__HAL_TIM_GET_FLAG(htim2, TIM_FLAG_UPDATE) ! RESET) { // 如果讀取計(jì)數(shù)器后發(fā)現(xiàn)有溢出標(biāo)志說明在讀取過程中發(fā)生了溢出 current_overflow; __HAL_TIM_CLEAR_FLAG(htim2, TIM_FLAG_UPDATE); } if (current_cnt start_cnt) { elapsed_ticks (current_overflow - start_overflow) * (htim2.Init.Period 1) (current_cnt - start_cnt); } else { // 當(dāng)前計(jì)數(shù)器值小于開始值說明發(fā)生了溢出但中斷還未處理 elapsed_ticks (current_overflow - start_overflow - 1) * (htim2.Init.Period 1) (current_cnt (htim2.Init.Period 1) - start_cnt); } // 將 tick 數(shù)轉(zhuǎn)換為微秒 (假設(shè) 1 tick 0.1ms) return elapsed_ticks * 100; }注意事項(xiàng)上述代碼中處理溢出的邏輯是嵌入式計(jì)時(shí)中的經(jīng)典問題。在多任務(wù)或高優(yōu)先級(jí)中斷環(huán)境中讀取計(jì)數(shù)器current_cnt和溢出計(jì)數(shù)current_overflow的“原子性”可能被破壞。更嚴(yán)謹(jǐn)?shù)淖龇ㄊ窃谧x取前先關(guān)閉定時(shí)器更新中斷讀取后再打開但這會(huì)增加中斷延遲。對于反應(yīng)計(jì)時(shí)器這種精度要求毫秒級(jí)和事件頻率不高的場景上述方法在大多數(shù)情況下是可靠的。若追求極致穩(wěn)健可研究定時(shí)器的“捕獲/比較”功能直接測量脈沖寬度。3.3 主程序狀態(tài)機(jī)的代碼實(shí)現(xiàn)主循環(huán)中的狀態(tài)機(jī)是實(shí)現(xiàn)項(xiàng)目邏輯的骨架。下面是一個(gè)簡化的代碼框架typedef enum { STATE_IDLE, STATE_READY, STATE_SIGNAL, STATE_FINISH_VALID, STATE_FINISH_INVALID } SystemState_t; SystemState_t sys_state STATE_IDLE; uint32_t signal_start_time 0; uint32_t reaction_time_ms 0; int main(void) { // 硬件初始化HAL_Init 時(shí)鐘配置 GPIO EXTI TIM... // ... while (1) { switch (sys_state) { case STATE_IDLE: // 等待“開始測試”按鍵假設(shè)是另一個(gè)按鍵KEY_START if (start_key_pressed_flag) { start_key_pressed_flag 0; sys_state STATE_READY; // 初始化隨機(jī)種子生成隨機(jī)延時(shí) random_delay generate_random_delay(1000, 3000); // 1-3秒 delay_start_tick HAL_GetTick(); } break; case STATE_READY: // 檢查隨機(jī)延時(shí)是否結(jié)束 if ((HAL_GetTick() - delay_start_tick) random_delay) { sys_state STATE_SIGNAL; // 發(fā)出信號(hào)點(diǎn)亮LED HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // 啟動(dòng)高精度定時(shí)器并記錄起始時(shí)刻 HAL_TIM_Base_Start(htim2); signal_start_time get_current_precise_tick(); // 封裝好的函數(shù)獲取定時(shí)器tick timer2_overflow_count 0; // 重置溢出計(jì)數(shù) start_overflow timer2_overflow_count; start_counter __HAL_TIM_GET_COUNTER(htim2); // 設(shè)置一個(gè)反應(yīng)超時(shí)定時(shí)器可以用另一個(gè)定時(shí)器或SysTick start_reaction_timeout_timer(2000); // 2秒超時(shí) } break; case STATE_SIGNAL: // 等待反應(yīng)按鍵或超時(shí) if (key_pressed_flag) { key_pressed_flag 0; // 停止定時(shí)器計(jì)算時(shí)間 uint32_t end_counter __HAL_TIM_GET_COUNTER(htim2); reaction_time_ms calculate_time_ms(start_counter, end_counter); // 使用前述函數(shù) HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); sys_state STATE_FINISH_VALID; } else if (is_reaction_timeout()) { // 超時(shí)處理 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); sys_state STATE_FINISH_INVALID; } break; case STATE_FINISH_VALID: // 通過串口或屏幕顯示反應(yīng)時(shí)間 printf(Reaction Time: %lu ms\n, reaction_time_ms); // 延時(shí)一小段時(shí)間讓用戶看到結(jié)果 HAL_Delay(2000); sys_state STATE_IDLE; break; case STATE_FINISH_INVALID: printf(Invalid! (Too fast or timeout)\n); HAL_Delay(2000); sys_state STATE_IDLE; break; } // 其他后臺(tái)任務(wù)如屏幕刷新等 } }4. 調(diào)試技巧與常見問題排查即使邏輯清晰在實(shí)際調(diào)試中還是會(huì)遇到各種問題。下面是我在實(shí)現(xiàn)和教學(xué)過程中總結(jié)的幾個(gè)典型“坑”及其解決方案。4.1 按鍵抖動(dòng)與誤觸發(fā)問題現(xiàn)象一次按鍵動(dòng)作導(dǎo)致多次進(jìn)入中斷反應(yīng)時(shí)間記錄為0ms或極短的不合理值。根本原因機(jī)械按鍵的觸點(diǎn)閉合和斷開時(shí)會(huì)產(chǎn)生毫秒級(jí)的物理抖動(dòng)導(dǎo)致電壓產(chǎn)生多個(gè)邊沿。解決方案硬件消抖在按鍵兩端并聯(lián)一個(gè)0.1uF左右的電容可以吸收部分抖動(dòng)。但對于反應(yīng)計(jì)時(shí)電容會(huì)延遲電壓變化可能影響測量起點(diǎn)不推薦。軟件消抖推薦在中斷服務(wù)函數(shù)或主循環(huán)中處理。最佳實(shí)踐是在EXTI中斷中只標(biāo)記事件在主循環(huán)的狀態(tài)機(jī)里進(jìn)行消抖判斷。// 在主循環(huán)的狀態(tài)處理中 if (key_pressed_flag) { key_pressed_flag 0; uint32_t tick_now HAL_GetTick(); if ((tick_now - last_key_tick) 50) { // 50ms消抖延時(shí) last_key_tick tick_now; // 確認(rèn)為一次有效的按鍵按下執(zhí)行狀態(tài)轉(zhuǎn)移 // ... } }技巧消抖延時(shí)如50ms會(huì)被人為地加到反應(yīng)時(shí)間里。為了精確這個(gè)延時(shí)必須在計(jì)算反應(yīng)時(shí)間時(shí)被減去。更精確的做法是在EXTI中斷觸發(fā)時(shí)立即記錄一個(gè)高精度時(shí)間戳t1在軟件消抖確認(rèn)有效后再記錄一個(gè)時(shí)間戳t2。最終反應(yīng)時(shí)間 (t2-LED點(diǎn)亮?xí)r刻) - (t2-t1)。t2-t1就是消抖引入的固定延遲可以校準(zhǔn)。4.2 定時(shí)器計(jì)時(shí)不準(zhǔn)或溢出處理錯(cuò)誤問題現(xiàn)象測量的反應(yīng)時(shí)間忽大忽小或明顯錯(cuò)誤。排查步驟檢查時(shí)鐘樹確認(rèn)定時(shí)器的時(shí)鐘源APB1或APB2和頻率是否正確。在CubeMX的Clock Configuration界面仔細(xì)核對。驗(yàn)證分頻計(jì)算手動(dòng)計(jì)算一下定時(shí)器的實(shí)際計(jì)數(shù)頻率。用示波器或邏輯分析儀測量一個(gè)GPIO翻轉(zhuǎn)在定時(shí)器更新中斷里翻轉(zhuǎn)的周期看是否與理論值相符。溢出邏輯測試故意設(shè)置一個(gè)很小的ARR值讓定時(shí)器快速溢出測試你的get_elapsed_time_us函數(shù)在溢出邊界附近是否能正確計(jì)算長時(shí)間間隔。中斷優(yōu)先級(jí)沖突如果系統(tǒng)中有多個(gè)中斷如EXTI、TIM、串口確保它們的優(yōu)先級(jí)設(shè)置合理。反應(yīng)按鍵的EXTI中斷和計(jì)時(shí)定時(shí)器的更新中斷之間不應(yīng)有長時(shí)間的相互阻塞??梢詫XTI優(yōu)先級(jí)設(shè)為最高定時(shí)器更新中斷設(shè)為次高。4.3 系統(tǒng)響應(yīng)“卡頓”或無反應(yīng)問題現(xiàn)象按下開始鍵后LED不亮或者按下反應(yīng)鍵后沒任何輸出。排查步驟檢查中斷是否使能在調(diào)試器中查看NVIC的ISER寄存器或直接在代碼中檢查__HAL_TIM_ENABLE_IT(htim2, TIM_IT_UPDATE)和HAL_NVIC_EnableIRQ(EXTI0_IRQn)是否被正確調(diào)用。檢查回調(diào)函數(shù)確認(rèn)你重寫了正確的弱回調(diào)函數(shù)。對于EXTI是HAL_GPIO_EXTI_Callback對于定時(shí)器更新是HAL_TIM_PeriodElapsedCallback。函數(shù)名拼寫錯(cuò)誤或參數(shù)類型不對都不會(huì)進(jìn)入你的函數(shù)。主循環(huán)是否阻塞檢查主循環(huán)中是否有HAL_Delay或其他忙等待循環(huán)。確保狀態(tài)機(jī)能夠快速運(yùn)行。所有耗時(shí)操作如顯示刷新應(yīng)盡量拆分成非阻塞式。使用調(diào)試器設(shè)置斷點(diǎn)在EXTI和TIM的中斷服務(wù)函數(shù)入口設(shè)置斷點(diǎn)看是否能觸發(fā)。這是最直接的驗(yàn)證方式。5. 項(xiàng)目優(yōu)化與功能擴(kuò)展基礎(chǔ)版本實(shí)現(xiàn)后你可以從這個(gè)核心出發(fā)進(jìn)行很多有趣的擴(kuò)展讓項(xiàng)目更像一個(gè)完整的“產(chǎn)品”。5.1 增加多種測試模式簡單視覺當(dāng)前版本。簡單聽覺用蜂鳴器替代LED作為起始信號(hào)。注意EXTI中斷的觸發(fā)要改為蜂鳴器響起的時(shí)刻而非驅(qū)動(dòng)蜂鳴器的GPIO變化時(shí)刻可能需用另一個(gè)定時(shí)器控制鳴叫時(shí)長。選擇反應(yīng)時(shí)設(shè)置多個(gè)LED如紅、綠、藍(lán)隨機(jī)點(diǎn)亮其中一個(gè)測試者需要按下與之對應(yīng)的特定按鍵。這需要更復(fù)雜的狀態(tài)機(jī)和更多的GPIO中斷。5.2 改善人機(jī)交互與數(shù)據(jù)管理添加顯示模塊使用OLED或LCD屏幕實(shí)時(shí)顯示狀態(tài)“準(zhǔn)備”、“反應(yīng)”、“時(shí)間xxx ms”比串口打印更直觀。增加聲音反饋反應(yīng)結(jié)束后用不同頻率或節(jié)奏的蜂鳴聲表示成功、失敗或時(shí)間長短。數(shù)據(jù)統(tǒng)計(jì)在STM32的Flash中開辟一個(gè)小區(qū)域存儲(chǔ)多次測試的結(jié)果計(jì)算平均反應(yīng)時(shí)、最快/最慢反應(yīng)時(shí)等并通過屏幕顯示歷史數(shù)據(jù)曲線。5.3 提升計(jì)時(shí)精度與抗干擾能力使用定時(shí)器輸入捕獲功能這是更專業(yè)的做法。將反應(yīng)按鍵的GPIO連接到定時(shí)器的輸入捕獲通道。將LED點(diǎn)亮信號(hào)作為一個(gè)GPIO輸出同時(shí)用另一路定時(shí)器通道或另一個(gè)定時(shí)器輸出PWM來模擬這個(gè)信號(hào)并利用定時(shí)器的“主從模式”或“事件觸發(fā)”功能讓LED亮起事件自動(dòng)啟動(dòng)定時(shí)器計(jì)數(shù)按鍵按下事件自動(dòng)捕獲計(jì)數(shù)器值。這樣完全由硬件聯(lián)動(dòng)計(jì)時(shí)消除了軟件中斷響應(yīng)延遲帶來的誤差精度可以達(dá)到定時(shí)器計(jì)數(shù)周期的水平如0.1微秒。使用實(shí)時(shí)操作系統(tǒng)RTOS如FreeRTOS。將按鍵檢測、計(jì)時(shí)、顯示、邏輯控制分別放在不同的任務(wù)中通過隊(duì)列、信號(hào)量進(jìn)行通信。這會(huì)使系統(tǒng)架構(gòu)更清晰易于擴(kuò)展復(fù)雜功能但也會(huì)增加系統(tǒng)復(fù)雜性和開銷。實(shí)現(xiàn)一個(gè)“人體反應(yīng)計(jì)時(shí)器”遠(yuǎn)不止是讓一個(gè)燈亮、一個(gè)表跑那么簡單。它強(qiáng)迫你去深入思考中斷與主程序如何協(xié)作、狀態(tài)機(jī)如何設(shè)計(jì)、時(shí)間如何精確測量以及如何排除硬件和軟件的不可靠因素。當(dāng)你成功測出第一個(gè)穩(wěn)定的“250ms”反應(yīng)時(shí)間時(shí)你對STM32中斷系統(tǒng)和嵌入式系統(tǒng)時(shí)序的理解就已經(jīng)跨越了入門階段向著更扎實(shí)的方向前進(jìn)了。這個(gè)項(xiàng)目麻雀雖小五臟俱全是鞏固中斷知識(shí)、鍛煉嵌入式系統(tǒng)思維的一個(gè)絕佳練手項(xiàng)目。