|
1 傳統單片機程序開發的不足 在傳統的單片機程序中,通常是以“過程”和“操作”為中心的結構,程序按規定的過程順序地執行,與外設的連接一般采用中斷方式,在中斷服務程序中完成外設的全部處理工作,主程序一般是初始化系統并等待中斷的發生。這種結構成熟、易于理解,但有如下不足: (1)受單片機性能的限制,容易造成系統對其它中斷的響應變得遲緩,特別是對于中斷源較多、中斷處理耗時較多的系統(如:LED顯示、鍵盤掃描等); (2)中斷服務程序過長,在中斷服務期間系統無法響應同級的中斷; (3)可能導致代碼重入,增大堆棧開銷,造成難以預料的結果; (4)程序調試時,花在各模塊定時協調方面的時間、精力隨系統的復雜程序大幅增加。 如果在編寫單片機程序時,引入Windows程序中的事件驅動機制,把中斷響應與事件處理程序分離,中斷服務程序的任務只是產生一個中斷發生的標志,而事件處理則由處理程序來完成,主程序則負責判斷標志和調度處理程序。這樣,可大幅縮短中斷服務程序的長度,減少斷服務程序的耗時,提高系統對多中斷的響應能力,從而較好地解決上述矛盾。 2 Windows的事件驅動機制 在Windosw系統中,程序的設計圍繞事件驅動來進行。當對象有相關的事件發生時(如按下鼠標鍵),對象產生一條特定的標識事件發生的消息,消息被送入消息隊列,或不進入隊列而直接發送給處理對象,主程序負責組織消息隊列,將消息發送給相應的處理程序,使相應的處理程序執行相應的動作,做完相應的處理后將控制權交還給主程序。 在這種機制中,對象的請求僅僅是向隊列中添加相應的消息,耗時的處理則被分離給處理函數。這種結構的程序中各功能模塊界限分明,便于擴充,能充分利用CPU的處理能力,使系統對外界響應準確而及時。 3 事件驅動的單片機程序設計 與Windows系統相比,單片機的資源非常有限,因此,單片機程序中的事件驅動機制只能采取一種簡化的方式。當某個中斷發生時,中斷服務程序設置相應的標志,不同的標導代表不同的中斷發生的消息,而主程序不斷地判別這些標志,以決定啟動哪一個處理函數。相應的處理函數被啟動處理完相關的任務后,清除此標志,然后把控制權交還給主程序。采用這種機制,可合理地利用有限資源,使程序調試的工作量大幅下降。對于延時、定時處理(如LED顯示、鍵盤掃描等),更可方便地使用一定時器來完成延時、定時的任務,從而把CPU從這種耗時的任務中解放出來,確保系統對多中斷有足夠的響應能力。 本文以一IC卡讀寫機為例,說明事件驅動機制在單片機程序設計中的具體應用。 3.1 硬件結構 本系統以ATMEL公司的89C51為核心(如圖1)。89C51價格低廉,性能較好,片內有4KB的可擦寫程序存儲器,可滿足本系統的要求。為簡化硬件結構及系統能耗,鍵盤采用軟件掃描的矩陣鍵盤。LED顯示采用段位動態掃描,在任一時刻LED中最多只有一段被點亮。具體是在位選信號送某位LED的公共極時,每隔一個時間片依次輸出該位LED的段碼(含小數點),輸出完成一位后,再逐閃輸出下一位。從第一位至第N位LED依次分成8×N個時間片循環掃描顯示。串口UART作為系統與外部數據通信的通道,IC卡的讀寫由MCU模擬I2C協議來實現。 3.2 事件驅動機制的單片機程序設計 中斷申請標志 在系統中定義一個可位尋址的單元,在此把它命名為Message_Flag,用來記錄描述中斷事件發生的情況。各位的定義如下: *Message_Flag中某位為1表示當前有相應的事件發生,為0則當有沒有相應的事件發生。 LED顯示的實現 顯示模塊結構見圖2。以定時器T0作為LED的動態掃描的定時基準,T0的定時時間最大值Tseg=20ms/(8×N)(其中N為LED位數),改變Tseg的值可改變顯示的亮度。T0每隔Tseg時間向MCU申請中斷,在T0的中斷服務程序中置位相應的標志位(Message_Flag中的D0位)。主程序檢測到此標志位被置位后,啟動顯示模塊實現位段的顯示輸出。 鍵盤輸入的實現 鍵盤模塊結構見圖3。在LED動態掃描期間,只有被點亮的LED相應的位選線維持大約3ms的低電平,而在系統工作的絕大部分時間內LED的位選線(即鍵盤的列線)維持高電平。當有鍵被按下時,將把鍵盤的行線中某一根拉成高電平,經或非門后,向MCU申請INT1中斷,在INT1的中斷服務程序中啟動定時時間為20ms的定時器T1。T1的定時時間到后向MCU申請T1中斷,在T1的中斷服務器程序中置位相應的中斷申請標志(Message_Flag中的D1位)。 主程序檢測到此標志位被置位后,啟動鍵盤掃描模塊實現鍵盤輸入。鍵盤輸入完成(用戶按“確認”鍵),置位鍵盤輸入確認標志(Message_Flag中的D7位)。 IC卡的讀寫 IC卡的SDA、SCL經卡座分別通過P1.0、P1.1與MCU相連。當IC卡插入卡座時,座上的微動開關使INT0變為低電平,向MCU申請INT0中斷。在INT0中斷服務程序中置位相應的中斷申請標志(Message_Flag中的D2位),主程序檢測到此標志位被置位后,啟動IC卡的讀模塊,以軟件模塊I2C協議來實現讀卡操作。在數據處理完成后,同樣通過軟件模塊I2C協議來完成寫卡的操作。 串口通訊 實際應用中可把UART轉換成RS232C與PC相連或轉換成RS485等其它協議組成單片機網。MCU與外部的通訊采用中斷方式,在串口的中斷服務程序中置位相應的中斷申請標志(Message_Flag中的D4位)。主程序檢測到此標志位被置位后,啟動串口通訊模塊,實現與外部的數據通訊。 主程序的設計 綜上所述,主程序首先完成系統的初始化,然后循環檢測各中斷的中斷申請標志,如有某標志被置位,則啟動相應的處理模塊完成相應的任務。程序結構如下(用C51編寫): vnsigned bdata message_flag; sbit t0_int=message_flag^0; sbit t1_int=message_flag^1; sbit int0_int=message_flag^2; sbit uart_int=message_flag^4; sbit kb_enter=message_flag^7; unsigned char kb_buf[8]; unsigned char led_buf[8]; unsigned char ic_buf[8]; unsigned char num_buf[8]; void uum_proc(void); /*數據處理模塊*/ void ledbuf_write(unsigned,unsigned int); /*數據處理*/ void system_init(void); /*系統初始化*/ void uart_commune(void); /*串口通訊模塊*/ void led_display(void); /*LED顯示*/ void kb_scan(void); /*鍵盤掃描*/ void ic_reader(void); /*讀IC卡*/ void ic_writer(void); /*寫IC卡*/ void set_timer(unsigned int time_len,unsigned char type,unsigned char id); /*設置定時器*/ void t0_int_sever(void); /*定時器T0中斷服務*/ void t1_int_sever(void); /*定時器T1中斷服務*/ void int0_int_sever(void); /*INT0中斷服務*/ void int1_int_sever(void); /*INT1中斷服務*/ void uart_int_sever(void); /*串口中斷服務*/ void main(void) { system_init(); while(1) { if (t0_int) led_display(); if (int0_int) ic_reader(); if (t1_int) kb_scan(); if (uart_int) uart_commune(); if (kb_enter){ num_proc(); ic_writer(); ledbuf_write(num_buf,8); } } } 事件驅動的單片機程序設計是通過在中斷服務程序中置位相位標志,把耗時的中斷服務中的處理部分分離出來,中斷返回后,再由主程序根據標志啟動相應的處理模塊。在任務處理完成后,清除相應的標志。由于中斷服務程序短小,所以一般能實時地響應各種中斷;而處理程序之間不會被相互調用,所以不會產生代碼重入;各模塊界限分明,給程序中各模塊的統調帶來很大的方便。 實踐證明,運用事件驅動機制來紡織單片機程序,即使對于要求定時準,耗時多的多中斷、多模塊系統,也可輕松地完成。 |