一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

嵌入式基礎(chǔ)--毫秒級(jí)定時(shí)模塊

 西北望msm66g9f 2021-07-11

大家好,我是驚覺。失蹤了三個(gè)月,我回來了。給大家?guī)硪粋€(gè)好消息和一個(gè)壞消息。壞消息是,我尚未滿血復(fù)活,Ardupilot第四篇將繼續(xù)延期。好消息是,公眾號(hào)恢復(fù)更新,先出一系列提升編碼能力的文章。

全國電賽在即,昨天母校老師聯(lián)系我,想讓我給學(xué)弟們做下賽前培訓(xùn)。我做過很多年的培訓(xùn),很早就發(fā)現(xiàn)了一個(gè)問題:同學(xué)們?cè)跒楸荣愖鰷?zhǔn)備時(shí),往往只注重去學(xué)習(xí)使用各種各樣的傳感器,自動(dòng)控制算法,各種驅(qū)動(dòng)。同學(xué)們只關(guān)注如何去實(shí)現(xiàn)功能,而忽視了如何把代碼寫得更好,更健壯,更易擴(kuò)展和維護(hù)。如果在比賽之前,先準(zhǔn)備好高質(zhì)量的代碼框架,基礎(chǔ)模塊,熟練掌握調(diào)度技巧,將極大提高賽時(shí)的開發(fā)和調(diào)試效率。

所謂高質(zhì)量,涉及到很多方面,比如:

  • 函數(shù)、變量的命名有統(tǒng)一的規(guī)則
  • 基礎(chǔ)接口要簡(jiǎn)單易用
  • 設(shè)計(jì)模塊時(shí)需層次分明,高內(nèi)聚低耦合
  • 盡量避免重復(fù)代碼

筆者不打算一一講解這些設(shè)計(jì)原則,而是介紹一些實(shí)際的基礎(chǔ)模塊,講解它們的設(shè)計(jì)思路,注意事項(xiàng)和編程技巧,并在此過程中讓大家理解相關(guān)的設(shè)計(jì)原則。

毫秒級(jí)定時(shí)模塊

作為系列開篇,本文先介紹一個(gè)非?;A(chǔ)的模塊:毫秒級(jí)定時(shí)模塊。

友情提醒,本模塊比較基礎(chǔ),可能有的同學(xué)對(duì)此非常熟悉,不過文末還有一個(gè)重要的小技巧噢。有基礎(chǔ)的同學(xué),可直接往后翻,跳到“再看comm_delay”一節(jié)。

此模塊提供基礎(chǔ)的定時(shí)功能,可細(xì)分為兩種:

  • 延時(shí)功能:毫秒極延時(shí)。
  • 定時(shí)功能:過一段時(shí)間后執(zhí)行一項(xiàng)操作。

可能有的同學(xué)會(huì)想,直接用單片機(jī)的定時(shí)器嘛,一個(gè)任務(wù)用一個(gè)定時(shí)器,有啥好講的。其實(shí)不然。定時(shí)模塊肯定要依賴于硬件定時(shí)器,但是一個(gè)任務(wù)用一個(gè)定時(shí)器的話,會(huì)有如下問題:

  • 浪費(fèi)資源。硬件定時(shí)器不僅僅只有定時(shí)功能,還有捕獲輸入信號(hào),輸出PWM,編碼器等功能。如果濫用基礎(chǔ)定時(shí)功能的話,等用到上述功能時(shí),將無資源可用。
  • 某個(gè)任務(wù)與某個(gè)定時(shí)器綁定在一起,提高了系統(tǒng)耦合程度,可移植性差。

因此,我們需要一個(gè)統(tǒng)一的,可移植性強(qiáng)的定時(shí)模塊。

我們?cè)倩仡^看下兩個(gè)基礎(chǔ)的功能,延時(shí)和定時(shí)。

延時(shí)示例,1秒打印1次hello。comm_delay實(shí)現(xiàn)毫秒極延時(shí)。

void comm_delay(uint32_t ms);

while (1)
{
    printf('hello\r\n');
    comm_mdelay(1000);
}

定時(shí)示例,1秒打印1次hello。comm_get_ms返回當(dāng)前系統(tǒng)時(shí)間,即系統(tǒng)從啟動(dòng)到現(xiàn)在經(jīng)過了多少毫秒。

uint32_t comm_get_ms(void);

while (1)
{
    cur_time = comm_get_ms();
    if (cur_time >= timeout)
    {
        printf('hello\r\n');
        timeout = cur_time + 1000;
    }
}

可能有的同學(xué)覺得上述兩項(xiàng)功能差不多,而定時(shí)比延時(shí)的代碼要復(fù)雜。定時(shí)的代碼確實(shí)多一些,不過它具有并發(fā)能力,即支持多個(gè)定時(shí)任務(wù)同時(shí)進(jìn)行。

定時(shí)示例,1秒打印1次you,2秒打印1次me。

static void show_you(void)
{
    static uint32_t timeout = 0;
    
    uint32_t cur_time = 0;
    
    cur_time = comm_get_ms();
    if(cur_time < timeout)
    {
        return;
    }
    
    printf('you\r\n');
    timeout = cur_time + 1000;
}

static void show_me(void)
{
    static uint32_t timeout = 0;
    
    uint32_t cur_time = 0;
    
    cur_time = comm_get_ms();
    if(cur_time < timeout)
    {
        return;
    }
    
    printf('me\r\n');
    timeout = cur_time + 2000;
}

int main(int argc, char **argv)
{
    while (1)
    {
        show_you();
        show_me();
    }
    
    return 0;
}

小結(jié):

  • 延時(shí)功能需要提供一個(gè)延時(shí)函數(shù)。
  • 定時(shí)功能需要提供獲取系統(tǒng)時(shí)間的函數(shù)。

即:

void comm_delay(uint32_t ms);
uint32_t comm_get_ms(void);

其實(shí)延時(shí)函數(shù)很簡(jiǎn)單,因?yàn)樗部梢钥闯墒且粋€(gè)定時(shí)任務(wù):

void comm_delay(uint32_t ms)
{
    uint32_t timeout = comm_get_ms() + ms;
    while(comm_get_ms() < timeout);
}

系統(tǒng)時(shí)間

實(shí)現(xiàn)comm_get_ms,即記錄系統(tǒng)時(shí)間,自然要靠硬件定時(shí)器啦。大家用的單片機(jī),無論是TI,STM32,NXP等,大部分都是cortex-m的內(nèi)核,該內(nèi)核有一個(gè)專門干這事情的定時(shí)器:SysTick timer。其名稱為系統(tǒng)滴答定時(shí)器,只有簡(jiǎn)單的定時(shí)功能。配置好reload計(jì)數(shù)并使能后,其由reload值遞減至0,觸發(fā)中斷,再從reload遞減。如果根據(jù)其時(shí)鐘配置相應(yīng)的reload值,實(shí)現(xiàn)每1ms觸發(fā)1次中斷,那就可以記錄毫秒 級(jí)的系統(tǒng)時(shí)間。

其配置函數(shù)位于單片機(jī)驅(qū)動(dòng)庫的CMSIS組件中,一般的工程都包含了這個(gè)組件,比如筆者使用TRUEStudio創(chuàng)建的工程:圖片

下面是stm32的示例。SystemCoreClock為單片機(jī)的主頻,這也是SysTick的輸入時(shí)鐘。SystemCoreClock / 1000即為1ms的定時(shí)計(jì)數(shù),將reload配置為此值即可實(shí)現(xiàn)每1ms觸發(fā)1次定時(shí)中斷。其他單片機(jī)方法類似。

#include 'stm32l1xx.h'
#include <stdint.h>

static uint32_t  sys_tick = 0;

void sys_tick_init(void)
{
    if(SysTick_Config(SystemCoreClock / 1000));
    NVIC_SetPriority(SysTick_IRQn, 0);
}

uint32_t sys_tick_get(void)
{
    return sys_tick;
}

void SysTick_Handler(void)
{
    sys_tick++;
}

用法非常簡(jiǎn)單,單片機(jī)啟動(dòng)時(shí)調(diào)用sys_tick_init配置并使能SysTick,每1ms觸發(fā)1次SysTick_Handler,其內(nèi)對(duì)當(dāng)前時(shí)間sys_tick進(jìn)行加1操作。應(yīng)用層通過sys_tick_get獲取當(dāng)前時(shí)間。

SysTick_Handler在中斷向量表中指定,大家根據(jù)具體的MCU對(duì)號(hào)入座。圖片

comm_get_ms只需對(duì)sys_tick_get進(jìn)行簡(jiǎn)單的封裝:

uint32_t comm_get_ms(void)
{
    return sys_tick_get();
}

再看comm_delay

我們?cè)倏匆幌潞撩霕O延時(shí)的實(shí)現(xiàn),大家覺得它有問題嗎?

void comm_delay(uint32_t ms)
{
    uint32_t timeout = comm_get_ms() + ms;
    while(comm_get_ms() < timeout);
}

對(duì)于只需要進(jìn)行幾分鐘演示的電賽來說,它沒有問題。不過,電賽只是同學(xué)們實(shí)踐所學(xué)的一條途徑。正兒八經(jīng)的產(chǎn)品,需要具有足夠的健壯性,可長期穩(wěn)定地運(yùn)行。上述代碼能長期運(yùn)行嗎?

static uint32_t  sys_tick = 0;

comm_get_ms是對(duì)sys_tick_get的簡(jiǎn)單封裝,而sys_tick_get返回的是一個(gè)32位無符號(hào)整型變量,它記錄的是系統(tǒng)從啟動(dòng)到現(xiàn)在所經(jīng)過的毫秒數(shù)。32位無符號(hào)整型變量最大能表示多長的時(shí)間呢?

2^32 / 1000 / 3600 / 24 = 49.71

其可記錄49天。在49天后,sys_tick將會(huì)溢出,從零重新開始累加。為了方便描述,要使用到一個(gè)宏UINT32_MAX。UINT32_MAX表示32位無符號(hào)整型變量的最大值,即0xffffffff。

假設(shè)我們要延時(shí)1分鐘,即60000ms。當(dāng)前時(shí)間為UINT32_MAX - 59999,下面計(jì)算timeout。

uint32_t timeout = comm_get_ms() + ms;

timeout發(fā)生溢出,計(jì)算結(jié)果為0。

UINT32_MAX - 59999 + 60000 = UINT32_MAX + 1 = 0

那么下面的等待循環(huán)將立刻退出,而不需要等待1分鐘。

while(comm_get_ms() < timeout);

在接下來的1分鐘內(nèi),comm_get_ms(60000)都是失效的,每1分鐘執(zhí)行1次的任務(wù)將不停地執(zhí)行。還有其他溢出的場(chǎng)景,這里不再一一描述。我們只要明確一點(diǎn)就好:comm_delay不能長期運(yùn)行。

健壯的comm_delay

怎么修改comm_delay以解決溢出問題呢?其實(shí)很簡(jiǎn)單,直接給出答案:

void comm_delay(uint32_t ms)
{
    uint32_t timeout = comm_get_ms() + ms;
    while(comm_get_ms() - timeout > UINT32_MAX / 2);
}

我們簡(jiǎn)單地驗(yàn)證幾個(gè)場(chǎng)景:

當(dāng)前時(shí)間為10ms,延時(shí)2ms。

先計(jì)算timeout:timeout = 10 + 2 = 12

下面看看從現(xiàn)在開始,什么時(shí)候while(comm_get_ms() - timeout > UINT32_MAX / 2);會(huì)退出。

  • 第0ms秒時(shí),當(dāng)前時(shí)間為10,10 - 12 = -2,請(qǐng)注意,comm_get_ms() - timeout中的操作數(shù)都是uint32_t類型,即32位無符號(hào)整型,它們相減的結(jié)果還是無符號(hào)整型。所以-2 --> UINT32_MAX - 1 > UINT32_MAX / 2,循環(huán)繼續(xù)。
  • 第2秒時(shí),當(dāng)前時(shí)間為12,12 - 12 = 0 < UINT32_MAX / 2,循環(huán)等待結(jié)束。

此種場(chǎng)景,成功實(shí)現(xiàn)延時(shí)2ms。

當(dāng)前時(shí)間為(UINT32_MAX - 1)ms,延時(shí)2ms。

之所以定成UINT32_MAX - 1,是想測(cè)試時(shí)間溢出的場(chǎng)景,2ms后時(shí)間溢出。

先計(jì)算timeout,timeout在加2時(shí)溢出,最終結(jié)果為0。timeout = (UINT32_MAX - 1) + 2 = UINT32_MAX + 1 = 0

下面看看從現(xiàn)在開始,什么時(shí)候while(comm_get_ms() - timeout > UINT32_MAX / 2);會(huì)退出。

  • 第0秒時(shí),當(dāng)前時(shí)間為(UINT32_MAX - 1),(UINT32_MAX - 1) - 0 = (UINT32_MAX - 1) > UINT32_MAX / 2,循環(huán)繼續(xù)。
  • 第2秒時(shí),當(dāng)前時(shí)間如timeout一樣加2溢出,最終為0。0 - 0 = 0 < UINT32_MAX / 2,循環(huán)等待結(jié)束。

此種場(chǎng)景,成功實(shí)現(xiàn)延時(shí)2ms。

總結(jié)

經(jīng)過兩種情況的測(cè)試,我們發(fā)現(xiàn),無論計(jì)算過程中時(shí)間有無溢出,改進(jìn)后的comm_delay都圓滿完成延時(shí)。

這種實(shí)現(xiàn)的原理是什么呢?原理很重要噢,否則大家在使用時(shí),可能把大于和小于關(guān)系搞反,或者是把被減數(shù)與減數(shù)的關(guān)系搞反。至于原理是什么呢,今天來不及講了,請(qǐng)待下回分解。劇透一下,下篇的名字叫:張三與李四誰跑的快。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    日韩精品第一区二区三区| 亚洲熟妇中文字幕五十路| 亚洲一区在线观看蜜桃| 亚洲视频一区自拍偷拍另类| 日本一本不卡免费视频| 国产精品一区二区丝袜| 国产福利在线播放麻豆| 亚洲熟女少妇精品一区二区三区| 伊人天堂午夜精品草草网| 国产精品免费福利在线| 老鸭窝精彩从这里蔓延| 国产又长又粗又爽免费视频| 欧美精品日韩精品一区| 69老司机精品视频在线观看| 99热在线精品视频观看| 蜜桃臀欧美日韩国产精品| 九九九热在线免费视频| 成人午夜免费观看视频| 国产麻豆一区二区三区在| 日本女人亚洲国产性高潮视频| 国产又长又粗又爽免费视频| 不卡一区二区高清视频| 精品国产品国语在线不卡| 91午夜少妇极品福利| 国产精品伦一区二区三区四季| 国产精品久久女同磨豆腐| 好吊妞视频只有这里有精品| 人妻少妇久久中文字幕久久| 午夜福利直播在线视频| 一区二区三区国产日韩| 日韩中文字幕在线不卡一区| 久久精品久久久精品久久| 国产性情片一区二区三区| 国产不卡的视频在线观看| 午夜精品一区二区三区国产| 精品老司机视频在线观看| 久久亚洲精品成人国产| 国产精品一区二区丝袜| 久久热在线视频免费观看| 国产成人精品一区在线观看| 99久久精品午夜一区二区|