如何在 8051 單片機上編程一個阻塞延遲函數?
大家可能熟悉Arduino IDE 中的 delay() 函數。這是一個簡單的函數,它提供了一個適用于Arduino 微控制器系列中所有成員的阻塞延遲。當你過渡到裸機微控制器編程時,你可能會發現自己在尋找類似的代碼。不幸的是,你不太可能 在8051 “標準庫”中找到這樣一個延遲函數。
本文引用地址:http://www.ex-cimer.com/article/202403/456719.htm在本文中,我們將簡要探討硬件延遲方法,然后使用一組嚴格定義的假設提出 8051 Busy Bee 解決方案。
這種明顯的 delay() 遺漏的最大原因可能是靈活性。了解 Arduino 代碼控制了底層微控制器的很大一部分。時鐘速度和專用計時器都是預定義的,隱藏在后臺。這種隱藏的一致性允許簡單的編程體驗,delay() 函數在所有平臺上執行相同。否則需要 Arduino 程序員對外設的體系結構和操作有大量的了解。這樣的行為違背了 Arduino 為初學者和業余愛好者提供可訪問性的目的。
在典型的 8051 環境中,情況就大不相同了。沒有什么是隱藏的,你通常需要配置所有必要的外圍設備。你還可以負責時鐘,并可以自由選擇高速外部,內部,32.678 kHz 甚至深度睡眠,只有看門狗計時器定期喚醒微控制器。這些選項中的每一個都會損壞甚至停止一個函數,如 delay() 按預期運行。
找到一份 Busy Bee 參考手冊,讓我們開始吧。
技術貼士:術語阻塞意味著微控制器的主代碼在整個延遲期間被阻塞(什么都不做)。對于小的延遲和簡單的問題,這通常是可以接受的,但可能導致不可接受的操作。例如,當阻塞延遲正在進行時,微控制器將對按鈕按下無響應。這個問題的替代方案包括中斷和非阻塞延遲。
許多延遲選項是可用的
讓我們首先認識到,在微控制器中構造延遲函數的方法有很多種。一個簡短的列表可能包括:
■ 以 NOP (什么都不做指令)為特征的精心構造的匯編代碼。在這里,程序員將根據每個匯編命令的特征計算微控制器的時鐘周期。
■ 帶有矢量中斷的硬件定時器。與延遲相關的操作可以嵌入到中斷服務例程( ISR )中,或者中斷可以維持類似于 Arduino millis() 函數的系統時間。
■ 免費運行硬件定時器而不中斷。這里硬件定時器是不斷運行的。這類似于看一個帶模數60運算的掛鐘。假設當前時間是50秒,而你想要一個20秒的延遲。然后你會一直等到秒針到達10秒。在 8051 中,這樣的解決方案將根據選擇的計時器類型對 256 或 25536 取模。操作的“速度”取決于系統時鐘和定時器的預量程配置。
■ 不使用中斷的受控硬件定時器。使用這種方法,用戶程序將停止定時器,預加載定時器到一個已知值,啟用定時器,并等待溢出發生。
每種方法都有優點和缺點。選擇在很大程度上取決于單個項目的需求、程序員的技能、未來的可維護性以及可用的硬件資源。例如,如果項目需要一致的心跳,帶有矢量中斷的硬件計時器就是一個非常好的解決方案。這種精確的周期性定時對于比例積分導數控制器或使用 DAC 生成波形來說是很好的。
阻塞延遲
在這篇文章中,我們將介紹一個使用無中斷硬件計時器的解決方案?;叵胍幌?,微控制器的硬件計時器通常就像時鐘的秒針一樣計數。在此代碼開發中使用的 Silicon Labs 的 EFM8BB1 8051 具有一個定時器集合,其中包括四個16位通用硬件定時器,向后兼容標準 8051。
16位定時器具有模數2^16運算。與從0到60計數的時鐘一樣,16位計時器在回滾到 0 之前也會從 0計數到 65535。這個溢出事件是特殊的,因為硬件會自動設置一個中斷標志。這個操作建議在高度簡化的圖1框圖中。
圖 1 : Busy Bee 定時器 #2 的簡化框圖。
技術貼士:外設的中斷標志不會自動啟動中斷。只有在中斷或擴展中斷使能電阻中設置了相關的中斷使能位時,它才會這樣做。
這個中斷標志是本文所附代碼中描述的阻塞延遲的關鍵。延時的操作描述如下:
■ 將合適預售和模式的定時器分別配置為 1:1 和16位自動重載。還要使用sbit操作符別名運行控制和中斷標志。這些操作在while(1)超級循環之前執行一次。
■ 關閉定時器并清除中斷標志。
■ 用期望的延遲值加載計時器。
■ 啟用計時器的運行位。
■ 在 while 循環中旋轉(什么都不做),直到中斷標志。此操作將阻塞所有其他 main() 代碼,直到設置該標志。
技術提示:最初的8051是一個獨特的架構,能夠在位級寄存器上操作。這導致了快速的代碼,而不需要使用掩碼來選擇寄存器內的特定位。這是使用Keil C51特殊功能寄存器(Sbit)匯編器語句完成的。
在EFM8BB1等衍生產品中保留了這種向后能力。然而,較新的產品比最初的8051包含了更多的外設和相關的SFRs。不幸的是,并非所有的寄存器都是位可尋址的。只有以0x0或0x8結尾的寄存器才能使用這種方便快捷的位操作。仔細查看參考手冊,可以發現地址為0xC8的TMR2CN0是位可尋址的。
調用和數字開銷
在結束本文之前,我們需要考慮與調用函數和計算重載值相關的開銷。例如,這一小段代碼帶來了一個嚴重的問題:
tmr_load = -((n_us * 49) >> 1);
它試圖聰明地解釋每微秒24.5個時鐘節拍。它沒有使用float類型來解釋半比特,而是先乘以49,然后使用右移操作除以2;一個為高位,一個為低位,帶進位。
最后一步是用216減去裝彈值。簡寫就是對結果求反。換一種說法,在模65536環境中,65536 - x提供的答案與0 - x相同,因為65536 = 0(模65536)。這和60 = 0(模60)的時鐘是一樣的。
在下一行代碼中,我們將100添加到tmr_load變量中。這是一個粗略的方法,用于計算函數調用開銷以及計算tmr_load所需的時間?;叵胍幌?,Busy Bee沒有硬件乘法器。因此,通過8位ALU執行8位乘16位乘法需要時間。
這個函數開銷的一個犧牲品是接受小延遲的能力。它的開銷大約為4us,因此不可能延遲更小的東西。如果需要這些小延遲,應該使用前面提到的帶有NOP操作的手工編碼匯編程序。
真實世界的結果如圖2所示,我們看到一個引腳為5 us高,5 us低,然后又高。對于相關的blk_ms_delay也獲得了類似的結果,其中blk_ms_delay(2000)的調用在0.01 ms內,由 Digilent 的 471-060 (Analog Discovery)測量。你是否同意阻塞函數提供了合理的性能?
圖 2 :示波器測量現實世界的信號,編程5路開和5路關。
結論
如前所述,所附代碼高度依賴于Busy Bee的時鐘、預分頻和定時器配置。你將需要修改代碼,以解釋任何偏離此代碼開發中所做假設的條件。
也許你會修改代碼,使用較慢的振蕩器來節省能量。還有一些技巧可以讓微控制器進入深度睡眠狀態。然后,你可能希望以盡可能高的速度喚醒微控制器,以便在返回睡眠之前快速執行操作。這種靈活性使得微控制器裸機編程比你過去可能使用的高級編程更加困難。這也是解鎖性能的關鍵。
評論