當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 技術(shù)讓夢(mèng)想更偉大
[導(dǎo)讀]沒(méi)有y【開(kāi)篇就白給?】對(duì)大家熟悉的Cortex-M處理起來(lái)說(shuō),無(wú)論是強(qiáng)調(diào)極致資源和低功耗的Cortex-M0、還是頻率達(dá)到上GHz且能與某些應(yīng)用處理器掰一掰手腕的Cortex-M7,都不會(huì)缺席了SysTick的身影。正因?yàn)镾ysTick是官方欽定的“不可或缺”的“基礎(chǔ)設(shè)施”,無(wú)論...

沒(méi)有y


【開(kāi)篇就白給?】

對(duì)大家熟悉的Cortex-M處理起來(lái)說(shuō),無(wú)論是強(qiáng)調(diào)極致資源和低功耗的Cortex-M0、還是頻率達(dá)到上GHz且能與某些應(yīng)用處理器掰一掰手腕的Cortex-M7,都不會(huì)缺席了SysTick的身影。
正因?yàn)镾ysTick是官方欽定的“不可或缺”的“基礎(chǔ)設(shè)施”,無(wú)論是RTOS系統(tǒng)還是裸機(jī)應(yīng)用,幾乎所有的嵌入式固件都會(huì)用到它。在這一背景下,如果我告訴你,有一個(gè)基于C語(yǔ)言的模塊,提供以下功能:
  • 精確測(cè)量系統(tǒng)性能

  • 精確測(cè)量函數(shù)執(zhí)行時(shí)間

  • 精確測(cè)量中斷響應(yīng)延遲

  • 提供精確到us級(jí)的阻塞或非阻塞的延時(shí)服務(wù)

  • 改善偽隨機(jī)數(shù)的隨機(jī)數(shù)特性

  • 提供系統(tǒng)時(shí)間戳

  • ……


使用了SysTick卻不會(huì)占用SysTick;


或者說(shuō)提供以上功能的同時(shí),用戶(hù)的原有的SysTick應(yīng)用(比如RTOS調(diào)度器或是普通的應(yīng)用延時(shí))絲毫不會(huì)受到影響;


再直白點(diǎn)說(shuō):以上功能都是白送的,每個(gè)Cortex-M處理器都能立即享有,且不受到芯片型號(hào)的影響,


你是不是要直呼:“真就白給?”


是的!作為一個(gè)在Github上開(kāi)源的C語(yǔ)言模塊,它真就白給!



【請(qǐng)張嘴……啊~】

perf_counter版本一路進(jìn)化,從加入對(duì)GCC、IAR的支持、通過(guò)Library簡(jiǎn)化用戶(hù)部署以來(lái),從版本1.6.1開(kāi)始更是把模塊的部署做到了極致的簡(jiǎn)化:

這次,你只要從下面的鏈接“一次性”的下載 CMSIS-Pack,就可以在MDK環(huán)境中實(shí)現(xiàn)傻瓜式的部署:

https://raw.githubusercontent.com/GorgonMeducer/perf_counter/CMSIS-Pack/cmsis-pack/GorgonMeducer.perf_counter.1.6.3.pack
也可以單擊文章末尾的“閱讀原文”進(jìn)行下載。
下載后,雙擊pack文件進(jìn)行無(wú)腦安裝:


在確認(rèn)了開(kāi)原許可Apache 2.0的條款后,一路Next,直到單擊Finish,完成整個(gè)安裝過(guò)程:



一般來(lái)說(shuō),部署會(huì)非常順利,但如果出現(xiàn)了安裝錯(cuò)誤,比如下面這種:



則很可能是您所使用的MDK版本太低導(dǎo)致的——是時(shí)候更新下MDK啦。關(guān)注【裸機(jī)思維】公眾號(hào)后發(fā)送關(guān)鍵字"MDK",即可獲得最新的MDK網(wǎng)盤(pán)鏈接。


當(dāng)我們想在任何已有工程中部署perf_counter時(shí),只需要單擊MDK工具欄上如下圖所示的圖標(biāo):



打開(kāi)RTE配置窗口:

我們會(huì)注意到,在列表的末端出現(xiàn)了一個(gè)Utilities條目,依次展開(kāi)后勾選Performance下的perf_counter——默認(rèn)情況下系統(tǒng)會(huì)自動(dòng)選擇以庫(kù)的形式來(lái)實(shí)現(xiàn)模塊的部署——這也是我吐血推薦的方式,因?yàn)樗∪チ瞬槐匾木幾g麻煩。


單擊OK按鈕后,我們會(huì)發(fā)現(xiàn)Utilities已經(jīng)被加入到了工程管理器中,而perf_counter.lib也已經(jīng)成功的部署到了目標(biāo)工程中:


看過(guò)我往期文章《【教程】如何用GCC“零匯編”白嫖MDK》的小伙伴一定知道,MDK也可以使用GCC作為編譯器。perf_counter也將這種情況考慮在內(nèi)——當(dāng)用戶(hù)實(shí)際使用的是GCC時(shí),對(duì)應(yīng)的 libperf_counter_gcc.a(而不是arm compiler 5arm compielr 6下的perf_counter.lib)會(huì)被加入到工程中。



GCC環(huán)境下使用perf_counter略微有一些注意事項(xiàng),由于在文章《【教程】如何用GCC“零匯編”白嫖MDK》的末尾已經(jīng)有過(guò)非常詳盡的介紹,這里就不再贅述了。


當(dāng)我們?cè)贛DK環(huán)境下使用Arm Compiler 6作為編譯器時(shí),需要打開(kāi)對(duì)GNU擴(kuò)展和C99(極其以上)語(yǔ)言標(biāo)準(zhǔn)的支持,具體方法如下圖所示:在Language C標(biāo)準(zhǔn)下拉列表中選擇帶有g(shù)nu前綴的選項(xiàng)——如果沒(méi)有什么特別的顧慮,推薦直接拉滿(mǎn)——使用gnu11即可。


如果你使用的是較老的Arm Compiler 5,則應(yīng)該同時(shí)勾選C99 ModeGNU extensions兩個(gè)選項(xiàng)。找不到上述兩個(gè)選項(xiàng)的小伙伴,應(yīng)該認(rèn)真考慮升級(jí)你們的MDK版本了。



【一鍵終結(jié)甜咸之爭(zhēng)?】

雖然在RTE中,perf_counter推薦并默認(rèn)使用library的方式來(lái)部署,

但考慮到總有小伙伴對(duì)黑盒子有莫名的恐懼:
“……往往要親眼看著黃酒從罎子裏舀出,看過(guò)壺子底裏有水沒(méi)有,又親看將壺子放在熱水裏,然後放心……https://zh.m.wikisource.org/zh/孔乙己

(圖片來(lái)自網(wǎng)絡(luò),侵刪)

因此,perf_counter貼心的提供了以Source源代碼來(lái)進(jìn)行部署的方式:


此時(shí),相關(guān)的perf_counter.csystick_wrapper_ual.s會(huì)取代原本的庫(kù)文件加入到編譯中:



當(dāng)然,使用Source方式來(lái)編譯也是有代價(jià)的,即perf_counter的源代碼對(duì)CMSIS有依賴(lài)——當(dāng)你的工程中并未在RTE配置界面中勾選CMSIS-CORE,就會(huì)出現(xiàn)類(lèi)似下圖所示的黃色警告信息:“Additional software components required”。


此時(shí),你可以簡(jiǎn)單的單擊Resolve按鈕來(lái)解決問(wèn)題——你會(huì)發(fā)現(xiàn),所謂的解決方案就是RTE自動(dòng)把Source模式依賴(lài)的CMSIS-CORE幫你勾選上了而已:



如果你對(duì)此并無(wú)異議,則問(wèn)題圓滿(mǎn)解決;如果你的系統(tǒng)中存在別的版本的CMSIS比如很多正點(diǎn)原子、野火、以及CubeMX生成的工程都很可能會(huì)攜帶不經(jīng)由RTE配置界面來(lái)管理的CMSIS),則很有可能出現(xiàn)CMSIS版本沖突——而關(guān)于沖突的解決方案,則稍微復(fù)雜一些——你可以參考我的文章《CMSIS玩家的“陰間成就”指南》來(lái)實(shí)現(xiàn)某種取舍……又或者……


還是推薦你繼續(xù)使用Library模式吧,畢竟它對(duì)CMSIS沒(méi)有任何依賴(lài)。

【一鍵更新的……嵌入式軟件模塊?】

一旦你安裝了perf_counter任何一個(gè)版本的pack,都會(huì)在MDKpack-installer中留下痕跡:

此時(shí),只要通過(guò)菜單 Pcak->Check For Update,我們就能實(shí)時(shí)的查詢(xún)?perf_counter是否存在最新版本:


如果Pack-Installer真的從github上發(fā)現(xiàn)了更新,就會(huì)以黃色Update的圖標(biāo)來(lái)告知我們:



此時(shí),單擊Update按鈕,即可安裝最新版本:


那么,如何才能鼓勵(lì)博主多多更新、加入更多更好的功能呢?


當(dāng)然還是要靠有能力科學(xué)上網(wǎng)的小伙伴多多Star呀!


https://github.com/GorgonMeducer/perf_counter.git


【庫(kù)的初始化和注意事項(xiàng)】


關(guān)于頭文件在任何需要使用perf_counter的C語(yǔ)言源文件中,我們需要首先加入對(duì)頭文件的引用:
#include?"perf_counter.h"需要注意的是,通過(guò)RTE方式部署的perf_counter并不會(huì)在工程管理器中引入?perf_counter.h?以供用戶(hù)查看。想要查看perf_counter.h查找可用API的小伙伴,可以“簡(jiǎn)單的”在上述代碼行上單擊右鍵,從彈出菜單中選擇“Open document 'perf_counter'”?來(lái)實(shí)現(xiàn)對(duì)頭文件的訪問(wèn)。



關(guān)于庫(kù)的初始化一般來(lái)說(shuō),用戶(hù)會(huì)在某一個(gè)地方,比如?main()?函數(shù)內(nèi)完成對(duì)CPU工作頻率的配置,我們應(yīng)該在完成這一工作之后確保全局變量?SystemCoreClock?被正確的更新——保存當(dāng)前CPU的工作頻率,比如:
extern?uint32_t?SystemCoreClock;void?main(void){????system_clock_update();????//!?更新CPU工作頻率????SystemCoreClock?=?72000000ul?//!?假設(shè)更新后的系統(tǒng)頻率是 72MHz????...}一般來(lái)說(shuō),你的芯片工程如果本身都是基于較新的CMSIS框架而創(chuàng)建的,你的啟動(dòng)文件中已經(jīng)為你定義好了全局變量?SystemCoreClock——當(dāng)然,凡事都有例外,如果你在編譯的時(shí)候報(bào)告找不到變量?SystemCoreClock?或者說(shuō)“Undefined symbol __SystemCoreClock” 之類(lèi)的,你自己定義一下就好了,比如:

uint32_t?SystemCoreClock;void?main(void){????system_clock_update();????//!?更新CPU工作頻率????SystemCoreClock?=?72000000ul?//!?假設(shè)更新后的系統(tǒng)頻率是 72MHz????...}在這以后,我們需要對(duì)?perf_counter?庫(kù)進(jìn)行初始化。這里分兩種情況:


1、用戶(hù)自己的應(yīng)用里完全沒(méi)有使用SysTick。此時(shí),在編譯時(shí),我們多半會(huì)看到類(lèi)似如下的錯(cuò)誤提示:


Error: L6218E: Undefined symbol $Super$$SysTick_Handler (referred from systick_wrapper_ual.o).
對(duì)于這種情況,我們需要在任意的C文件中添加一個(gè)SysTick中斷處理程序:

#include "perf_counter.h"...
__attribute__((used))????//!void SysTick_Handler(void){
}
然后我們?cè)?main()?函數(shù)里初始化?perf_counter?服務(wù):

#include?...
void?main(void){????system_clock_update();???? //!?更新CPU工作頻率????SystemCoreClock?=?72000000ul?//!?假設(shè)更新后的系統(tǒng)頻率是 72MHz????init_cycle_counter(false);????...}需要特別注意的是:由于用戶(hù)并沒(méi)有自己初始化?SysTick,因此我們需要將這一情況告知?perf_counter?庫(kù)——由它來(lái)完成對(duì)?SysTick?的初始化——這里傳遞?false?給函數(shù)?init_cycle_counter()?就是這個(gè)功能。如果由perf_counter 庫(kù)自己來(lái)初始化SysTick,它會(huì)為了自己功能更可靠將 SysTick的溢出值(LOAD寄存器)設(shè)置為最大值(0x00FFFFFF)。


2、用戶(hù)自己的應(yīng)用里使用了SysTick,擁有自己的初始化過(guò)程。對(duì)于這種情況,我們需要確保一件事情:即,SysTick的CTRL寄存器的 BIT2(SysTick_CTRL_CLKSOURCE_Msk)是否被置位了——如果其值是1,說(shuō)明SysTick使用了跟CPU一樣的工作頻率,那么SysTick的測(cè)量結(jié)果就是CPU的周期數(shù);如果其值是0,說(shuō)明SysTick使用了來(lái)自于別處的時(shí)鐘源,這個(gè)時(shí)鐘源具體頻率是多少就只能看芯片手冊(cè)了(比如STM32就喜歡將系統(tǒng)頻率做 1/8 分頻后提供給SysTick作為時(shí)鐘源),此時(shí)SysTick測(cè)量出來(lái)的結(jié)果就不是CPU的周期數(shù)。

在確保了?CTRL?寄存器的?BIT2?被正確置位,并且SysTick中斷被使能(置位?BIT1,SysTick_CTRL_TICKINT_Msk?)后,我們可以簡(jiǎn)單的通過(guò)?init_cycle_counter()?函數(shù)告訴perf_counter模塊:SysTick?被用戶(hù)占用了——這里傳遞 true 就實(shí)現(xiàn)這一功能。

#include ...
void main(void){ system_clock_update(); //! 更新CPU工作頻率 SystemCoreClock = 72000000ul //! 假設(shè)更新后的系統(tǒng)頻率是 72MHz init_cycle_counter(true); ...}

關(guān)于Library的匹配問(wèn)題perf_counter.lib?庫(kù)在編譯的時(shí)候,開(kāi)啟了?Short enums/wchar(分別對(duì)應(yīng)命令行的?-fshort-enums -fshort-wchar)。這么做其實(shí)沒(méi)什么特別的原因,但如果你的工程使用了不同的配置,例如:

下圖的工程配置中,沒(méi)有勾選 "Short enums/wchar"


你一定會(huì)看到這樣的編譯錯(cuò)誤:

.\Out\example.axf: Error: L6242E: Cannot link object perf_counter.o as its attributes are incompatible with the image attributes. ... wchart-16 clashes with wchart-32. ... packed-enum clashes with enum_is_int.

既然知道了原因,解決方法就很簡(jiǎn)單,要么在工程配置中勾選上這一選項(xiàng);要么使用源代碼編譯的模式。


【時(shí)間類(lèi)服務(wù)】


微秒級(jí)阻塞延時(shí)perf_counter提供了一個(gè)us級(jí)阻塞延時(shí)函數(shù)?delay_us(),它的函數(shù)原型如下:
extern void delay_us(int32_t iUs);實(shí)際上,由于函數(shù)調(diào)用的開(kāi)銷(xiāo),delay_us在時(shí)間判斷上會(huì)存在一個(gè)“不積累”的誤差——根據(jù)優(yōu)化等級(jí)的不同,其具體CPU周期數(shù)存在差異,如果我們以Library方式進(jìn)行部署時(shí),這一誤差大約在 /-25個(gè)CPU周期左右——這一信息實(shí)際上告訴我們:

  • 在使用Library的情況下,當(dāng)你的CPU頻率超過(guò)50MHz時(shí),delay_us()?可以提供最小<1us的延時(shí)誤差;

  • 當(dāng)你的系統(tǒng)頻率不滿(mǎn)足上述條件時(shí),以系統(tǒng)頻率 12MHz為參考,則可以認(rèn)為delay_us誤差為不積累的 /- 2us


具體評(píng)估方法,請(qǐng)參考我往期的文章《【實(shí)時(shí)性迷思】CPU究竟跑的有多快?》,這里就不做贅述。


全局系統(tǒng)時(shí)間perf_counter提供了API函數(shù)get_system_ticks(),用于方便用戶(hù)獲取自?SysTick啟動(dòng)以來(lái)系統(tǒng)已經(jīng)經(jīng)歷過(guò)的總周期數(shù),其函數(shù)原型如下:

__attribute__((nothrow)) extern int64_t get_system_ticks(void);可以看到,其返回值是一個(gè) 64位的有符號(hào)整數(shù),即便拋開(kāi)符號(hào)位,也基本可以確信:無(wú)論芯片頻率如何,在人類(lèi)滅絕之前,不會(huì)發(fā)生溢出問(wèn)題。



考慮到?SystemCoreClock?記錄了當(dāng)前的系統(tǒng)頻率,我們可以借助它將由?get_system_ticks()?所獲得的周期數(shù)轉(zhuǎn)化為物理時(shí)間,比如,我們可以自己定義如下的宏
#define get_system_ms() \????(get_system_ticks()?/?(SystemCoreClock?/?1000ul))#define get_system_us() \ (get_system_ticks() / (SystemCoreClock / 1000000ul))比如,這里的?get_system_ms()?可以告訴我們從SysTick啟動(dòng)以來(lái)(一般大約可以等效為從系統(tǒng)復(fù)位開(kāi)始)已經(jīng)過(guò)去了多少毫秒。是不是特別方便?

非阻塞式多重延時(shí)在狀態(tài)機(jī)中,非阻塞式的延時(shí)往往是必不可少的功能,包括但不限于:
  • 機(jī)構(gòu)控制的延時(shí);

  • 電路的時(shí)序控制;

  • 通信協(xié)議的超時(shí)處理;

  • ……


下圖就是一個(gè)支持多實(shí)例的非阻塞延時(shí)的狀態(tài)機(jī),即便你沒(méi)有看過(guò)我的狀態(tài)機(jī)系列文章,對(duì)應(yīng)的邏輯應(yīng)該也算是淺顯易懂。



這里的核心思想是:

  • 在延時(shí)的開(kāi)始時(shí)刻,通過(guò)?get_system_ticks()?的衍生方法?get_system_ms()?來(lái)獲取當(dāng)前的系統(tǒng)時(shí)間戳;

  • 計(jì)算目標(biāo)時(shí)刻的系統(tǒng)時(shí)間戳并保存在狀態(tài)機(jī)類(lèi)中(保存在 iTargetTime里);

  • 在隨后的狀態(tài)中以非阻塞的方式輪詢(xún)?get_system_ms()?以檢查約定的時(shí)間是否已經(jīng)到來(lái)。


下面的狀態(tài)圖展示了如何在執(zhí)行某些動(dòng)作(或者子狀態(tài)機(jī))的同時(shí),進(jìn)行超時(shí)判斷:

這里值得注意的細(xì)節(jié)是:

  • 在延時(shí)的開(kāi)始時(shí)刻,通過(guò)?get_system_ticks()?的衍生方法?get_system_ms()?來(lái)獲取當(dāng)前的系統(tǒng)時(shí)間戳;

  • 計(jì)算目標(biāo)時(shí)刻的系統(tǒng)時(shí)間戳并保存在狀態(tài)機(jī)類(lèi)中(保存在 iTargetTime里);

  • 在讀取字符失敗時(shí),通過(guò)對(duì)比當(dāng)前的系統(tǒng)時(shí)間戳來(lái)判斷是否超時(shí)。


具體狀態(tài)圖的解讀和翻譯方式,還不熟悉的小伙伴可以單擊這里來(lái)閱讀狀態(tài)機(jī)系列文章,這里就不再贅述。

隨機(jī)數(shù)發(fā)生幾乎所有的C語(yǔ)言教程都在介紹過(guò)隨機(jī)數(shù)的發(fā)生,比如:

#include?#include?
int?main?(void)?{ int i, n; time_t t; n = 5; /* Intializes random number generator */ srand((unsigned) time(
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉