大家好,,我是驚覺,。失蹤了三個月,,我回來了,。給大家?guī)硪粋€好消息和一個壞消息。壞消息是,,我尚未滿血復(fù)活,,Ardupilot第四篇將繼續(xù)延期。好消息是,,公眾號恢復(fù)更新,,先出一系列提升編碼能力的文章。 全國電賽在即,,昨天母校老師聯(lián)系我,,想讓我給學(xué)弟們做下賽前培訓(xùn)。我做過很多年的培訓(xùn),,很早就發(fā)現(xiàn)了一個問題:同學(xué)們在為比賽做準備時,,往往只注重去學(xué)習(xí)使用各種各樣的傳感器,,自動控制算法,,各種驅(qū)動。同學(xué)們只關(guān)注如何去實現(xiàn)功能,,而忽視了如何把代碼寫得更好,,更健壯,更易擴展和維護,。如果在比賽之前,,先準備好高質(zhì)量的代碼框架,基礎(chǔ)模塊,,熟練掌握調(diào)度技巧,,將極大提高賽時的開發(fā)和調(diào)試效率。 所謂高質(zhì)量,,涉及到很多方面,,比如:
筆者不打算一一講解這些設(shè)計原則,,而是介紹一些實際的基礎(chǔ)模塊,講解它們的設(shè)計思路,,注意事項和編程技巧,,并在此過程中讓大家理解相關(guān)的設(shè)計原則。 毫秒級定時模塊作為系列開篇,本文先介紹一個非?;A(chǔ)的模塊:毫秒級定時模塊,。 友情提醒,本模塊比較基礎(chǔ),,可能有的同學(xué)對此非常熟悉,,不過文末還有一個重要的小技巧噢。有基礎(chǔ)的同學(xué),,可直接往后翻,,跳到“再看comm_delay”一節(jié)。 此模塊提供基礎(chǔ)的定時功能,,可細分為兩種:
可能有的同學(xué)會想,,直接用單片機的定時器嘛,,一個任務(wù)用一個定時器,有啥好講的,。其實不然,。定時模塊肯定要依賴于硬件定時器,但是一個任務(wù)用一個定時器的話,,會有如下問題:
因此,,我們需要一個統(tǒng)一的,,可移植性強的定時模塊。 我們再回頭看下兩個基礎(chǔ)的功能,,延時和定時,。 延時示例,1秒打印1次hello,。comm_delay實現(xiàn)毫秒極延時,。 void comm_delay(uint32_t ms); 定時示例,,1秒打印1次hello。comm_get_ms返回當(dāng)前系統(tǒng)時間,,即系統(tǒng)從啟動到現(xiàn)在經(jīng)過了多少毫秒,。
可能有的同學(xué)覺得上述兩項功能差不多,而定時比延時的代碼要復(fù)雜,。定時的代碼確實多一些,,不過它具有并發(fā)能力,即支持多個定時任務(wù)同時進行,。 定時示例,,1秒打印1次you,2秒打印1次me,。 static void show_you(void) 小結(jié):
即:
其實延時函數(shù)很簡單,,因為它也可以看成是一個定時任務(wù): void comm_delay(uint32_t ms) 系統(tǒng)時間實現(xiàn)comm_get_ms,,即記錄系統(tǒng)時間,自然要靠硬件定時器啦,。大家用的單片機,,無論是TI,STM32,,NXP等,,大部分都是cortex-m的內(nèi)核,該內(nèi)核有一個專門干這事情的定時器:SysTick timer,。其名稱為系統(tǒng)滴答定時器,,只有簡單的定時功能,。配置好reload計數(shù)并使能后,,其由reload值遞減至0,觸發(fā)中斷,,再從reload遞減,。如果根據(jù)其時鐘配置相應(yīng)的reload值,實現(xiàn)每1ms觸發(fā)1次中斷,,那就可以記錄毫秒 級的系統(tǒng)時間,。 其配置函數(shù)位于單片機驅(qū)動庫的CMSIS組件中,一般的工程都包含了這個組件,,比如筆者使用TRUEStudio創(chuàng)建的工程: 下面是stm32的示例,。SystemCoreClock為單片機的主頻,這也是SysTick的輸入時鐘,。SystemCoreClock / 1000即為1ms的定時計數(shù),,將reload配置為此值即可實現(xiàn)每1ms觸發(fā)1次定時中斷,。其他單片機方法類似。
用法非常簡單,,單片機啟動時調(diào)用sys_tick_init配置并使能SysTick,,每1ms觸發(fā)1次SysTick_Handler,其內(nèi)對當(dāng)前時間sys_tick進行加1操作,。應(yīng)用層通過sys_tick_get獲取當(dāng)前時間,。 SysTick_Handler在中斷向量表中指定,大家根據(jù)具體的MCU對號入座,。 comm_get_ms只需對sys_tick_get進行簡單的封裝: uint32_t comm_get_ms(void) 再看comm_delay我們再看一下毫秒極延時的實現(xiàn),,大家覺得它有問題嗎?
對于只需要進行幾分鐘演示的電賽來說,,它沒有問題,。不過,電賽只是同學(xué)們實踐所學(xué)的一條途徑,。正兒八經(jīng)的產(chǎn)品,,需要具有足夠的健壯性,可長期穩(wěn)定地運行,。上述代碼能長期運行嗎,? static uint32_t sys_tick = 0; comm_get_ms是對sys_tick_get的簡單封裝,而sys_tick_get返回的是一個32位無符號整型變量,,它記錄的是系統(tǒng)從啟動到現(xiàn)在所經(jīng)過的毫秒數(shù),。32位無符號整型變量最大能表示多長的時間呢?
其可記錄49天,。在49天后,,sys_tick將會溢出,從零重新開始累加,。為了方便描述,,要使用到一個宏UINT32_MAX。UINT32_MAX表示32位無符號整型變量的最大值,,即0xffffffff,。 假設(shè)我們要延時1分鐘,即60000ms,。當(dāng)前時間為UINT32_MAX - 59999,,下面計算timeout。 uint32_t timeout = comm_get_ms() + ms; timeout發(fā)生溢出,,計算結(jié)果為0,。
那么下面的等待循環(huán)將立刻退出,而不需要等待1分鐘,。 while(comm_get_ms() < timeout); 在接下來的1分鐘內(nèi),,comm_get_ms(60000)都是失效的,,每1分鐘執(zhí)行1次的任務(wù)將不停地執(zhí)行。還有其他溢出的場景,,這里不再一一描述,。我們只要明確一點就好:comm_delay不能長期運行。 健壯的comm_delay怎么修改comm_delay以解決溢出問題呢,?其實很簡單,,直接給出答案:
我們簡單地驗證幾個場景: 當(dāng)前時間為10ms,延時2ms,。先計算timeout: 下面看看從現(xiàn)在開始,,什么時候
此種場景,,成功實現(xiàn)延時2ms。 當(dāng)前時間為(UINT32_MAX - 1)ms,,延時2ms,。之所以定成UINT32_MAX - 1,是想測試時間溢出的場景,,2ms后時間溢出,。 先計算timeout,,timeout在加2時溢出,,最終結(jié)果為0。 下面看看從現(xiàn)在開始,,什么時候
此種場景,,成功實現(xiàn)延時2ms。 總結(jié)經(jīng)過兩種情況的測試,,我們發(fā)現(xiàn),,無論計算過程中時間有無溢出,改進后的comm_delay都圓滿完成延時,。 這種實現(xiàn)的原理是什么呢,?原理很重要噢,否則大家在使用時,,可能把大于和小于關(guān)系搞反,,或者是把被減數(shù)與減數(shù)的關(guān)系搞反。至于原理是什么呢,,今天來不及講了,,請待下回分解。劇透一下,,下篇的名字叫:張三與李四誰跑的快,。 |
|
來自: 西北望msm66g9f > 《培訓(xùn)》