天天看點

AWorksLP 樣例詳解(MR6450)—— HWTimer

AWorksLP 對外設進行了高度抽象化,為同一類外設提供了相同的接口,應用程式可以輕松跨平台。本文以MR6450平台為例,介紹AWorksLP HWTimer 外設基本用法。

AWorksLP 樣例詳解(MR6450)—— HWTimer

簡介

在AWorksLP中将硬體定時器分為了4類,即延時型、計數型、周期型和輸入捕獲型硬體定時器。

  • 延時型硬體定時器:
  • 由硬體定時器外設提供的延時功能。
  • 計數型硬體定時器:
  • 提供較精确的類似時間戳的功能。
  • 周期型硬體定時器:
  • 可設定中斷頻率的計數器,不僅能提供計數器的功能,也能根據中斷頻率提供更精确的定時。
  • 輸入捕獲定時器:
  • 可測量脈沖寬度或者測量頻率。

接口介紹

延時型硬體定時器:

函數原型 簡要描述
aw_err_t aw_hwtimer_delay (int fd, struct aw_timespec *p_tv); 延時
aw_err_t aw_hwtimer_delay_cancel (int fd); 取消延時

計數型硬體定時器:

函數原型 簡要描述
aw_err_t aw_hwtimer_count_start (int fd); 啟動一個計數型硬體定時器
aw_err_t aw_hwtimer_count_stop (int fd); 停止一個計數型硬體定時器
aw_err_t aw_hwtimer_count_get (int fd, uint64_t *p_count); 讀取計數值
aw_err_t aw_hwtimer_count_rate_get (int fd, uint32_t *p_rate); 擷取計數時鐘頻率
aw_err_t aw_hwtimer_count_rate_set (int fd, uint32_t rate); 設定計數時鐘頻率
aw_err_t aw_hwtimer_count_rate_set_accurate (int fd, uint32_t rate_numerator, uint32_t rate_denominator); 以精确化的方式設定計數時鐘頻率
aw_err_t aw_hwtimer_count_rate_get_accurate (int fd, uint32_t *p_rate_numerator, uint32_t *p_rate_denominator); 擷取計數時鐘頻率的精确描述

周期型硬體定時器:

函數原型 簡要描述
aw_err_t aw_hwtimer_period_wait (int fd, uint32_t wait_ms); 等待定時器周期中斷
aw_err_t aw_hwtimer_period_intr (int fd); 打斷周期型定時器的等待操作
aw_err_t aw_hwtimer_period_start (int fd); 啟動定時器
aw_err_t aw_hwtimer_period_stop (int fd); 停止定時器
aw_err_t aw_hwtimer_period_count_get (int fd, uint64_t *p_count); 讀取計數值
aw_err_t aw_hwtimer_period_count_freq_get (int fd, uint32_t *p_rate); 擷取周期型定時器的硬體計數頻率(不是中斷頻率)
aw_err_t aw_hwtimer_period_count_freq_get_frac (int fd, aw_hwtimer_rate_t *p_rate); 以更精确的分數形式擷取周期型定時器的硬體計數頻率(不是中斷頻率)
aw_err_t aw_hwtimer_period_intr_freq_set (int fd, uint32_t intr_freq); 設定中斷頻率
aw_err_t aw_hwtimer_period_intr_freq_get (int fd, uint32_t *p_intr_freq); 擷取中斷頻率
aw_err_t aw_hwtimer_period_intr_freq_set_frac (int fd, aw_const aw_hwtimer_rate_t *p_intr_freq); 設定中斷頻率(以更精确的分數形式)
aw_err_t aw_hwtimer_period_intr_freq_get_frac (int fd, aw_hwtimer_rate_t *p_intr_freq); 擷取中斷頻率(以更精确的分數形式)

輸入捕獲型硬體定時器:

函數原型 簡要描述
aw_err_t aw_hwtimer_cap_start (int fd); 啟動輸入捕獲型硬體定時器
aw_err_t aw_hwtimer_cap_stop (int fd); 停止輸入捕獲型硬體定時器
aw_err_t aw_hwtimer_cap_read (int fd, uint64_t *p_cap_val, uint32_t timeout_ms); 讀取一個捕獲到的事件的計數值
aw_err_t aw_hwtimer_cap_intr (int fd); 打斷阻塞read讀操作
aw_err_t aw_hwtimer_cap_config_set (int fd, aw_const aw_hwtimer_cap_config_t *p_config); 配置輸入捕獲型硬體定時器
aw_err_t aw_hwtimer_cap_config_get (int fd, aw_hwtimer_cap_config_t *p_config); 擷取輸入捕獲型硬體定時器的配置

使用樣例

AWorksLP SDK相關使用請參考《AWorksLP SDK快速入門(MR6450)——開箱體驗》一文,本文不在贅述。

1. 周期型定時器

{SDK}\demos\peripheral\hwtimer路徑下為硬體定時器例程,預設運作的是demo_hwtimer.c 周期型定時器的代碼,例程關鍵代碼如下:

/**
 * \brief 硬體定時器中斷服務函數。
 * \param[in] p_arg : 任務參數
 */
static void mytimer_isr (void *p_arg)
{
    aw_gpio_toggle((int)p_arg);
    aw_kprintf("enter isr \n\r");
}


/**
 * \brief hwtimer 測試函數
 */
aw_local void* __task_handle (void *arg)
{
    int                         fd;
    aw_err_t                    ret;
    uint32_t                    count = 5;
    aw_hwtimer_rate_t          p_intr_freq;


    p_intr_freq.rate_denominator = 5;
    p_intr_freq.rate_numerator = 1;


    fd = aw_open(CONFIG_DEMO_HWTIMER_PEROID_DEV_NAME, AW_O_RDWR, 0);
    if (fd < 0) {
        aw_kprintf("hwtimer open failed:%d \n\r", fd);
        while(1);
    }


    ret = aw_hwtimer_period_intr_freq_set_frac(fd, &p_intr_freq);
    while (count) {
        aw_hwtimer_period_wait(fd, 500);
        mytimer_isr(arg);
        count --;
    }


    // 配置每秒中斷2次
    ret = aw_hwtimer_period_intr_freq_set(fd, 2);


    ret = aw_hwtimer_period_start(fd);
    if (ret != AW_OK) {
        aw_kprintf("Timer allocation fail!\n");
    }


    ret = aw_hwtimer_period_wait(fd, AW_WAIT_FOREVER);


    while (1) {
        aw_hwtimer_period_wait(fd, AW_WAIT_FOREVER);
        mytimer_isr(arg);
    }


    for (;;) {
        aw_mdelay(1000);
    }
    aw_close(fd);


    return 0;
}           

在代碼中先使用了aw_hwtimer_period_intr_freq_set_frac 接口,以分數的形式設定中斷頻率,使用aw_hwtimer_period_start接口啟動定時器。在循環中使用aw_hwtimer_period_wait 接口阻塞等待中斷的産生、中斷産生後繼續執行mytimer_isr函數使LED 燈狀态翻轉,由于設定的頻率為五分之一,是以5秒LED 燈的狀态翻轉一次;循環一定次數後用aw_hwtimer_period_intr_freq_set 接口設定中斷頻率為2HZ,循環等待中斷、翻轉LED。

實驗現象為LED燈先以5s的頻率閃爍,同時序列槽列印同時資訊。閃爍一定次數後以0.5s的頻率LED閃爍,同時序列槽列印資訊。

下表為使用硬體周期型定時器,在中斷中進行引腳翻轉,通過邏輯分析儀所測量出的實際資料,在使用設計時可作為部分參考依據。

定時時間(s) 實際時間(s)
0.010000000 0.010000115
0.020000000 0.019999940
0.030000000 0.029999980
0.040000000 0.040000035
0.050000000 0.049999830
0.100000000 0.100000075
0.200000000 0.200000020
0.500000000 0.500000070
1.000000000 1.000000760
2.000000000 1.999999340
3.000000000 3.000002760
4.000000000 4.000001980
5.000000000 5.000004310
10.000000000 10.000008300

2. 計數型定時器

在config配置腳本中選擇hwtimer count計數型定時器測試如圖1所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖1 計數型定時器例程

儲存後重新Build工程,編譯好後運作的是demo_hwtimer_count.c的代碼,例程關鍵代碼如下:

aw_local void* __task_handle (void *arg)
{
    uint32_t     count = 0;
    int          fd, led_fd;
    int          ret;
    uint32_t     start_count;
    
    fd = aw_open(CONFIG_DEMO_HWTIMER_PEROID_DEV_NAME, AW_O_RDWR, 0);
    if (fd < 0) {
        aw_kprintf("hwtimer open fail! :%d\n",fd);
        return;
    }
    /* 打開裝置會點亮LED */
    led_fd = aw_open("/dev/led_run", AW_O_RDWR, 0);
    if (led_fd < 0) {
        aw_kprintf("led open fail! :%d\n", led_fd);
        aw_close(fd);
        return;
    }
    ret = aw_hwtimer_count_rate_get(fd, &start_count);
    if (ret != AW_OK) {
        aw_kprintf("Timer count rate get fail!\n");
        aw_close(fd);
        aw_close(led_fd);
        return;
    }
    // 設定時鐘頻率
    ret = aw_hwtimer_count_rate_set(fd, start_count/2);
    if (ret != AW_OK) {
        aw_kprintf("Timer count rate set fail!\n");
        aw_close(fd);
        aw_close(led_fd);
        return;
    }
    ret = aw_hwtimer_count_start(fd);
    if (ret != AW_OK) {
        aw_kprintf("Timer start fail!\n");
        aw_close(fd);
        aw_close(led_fd);
        return;
    }
    for (;;) {  
        aw_led_toggle(led_fd);
        aw_mdelay(500);
        aw_led_toggle(led_fd);
        aw_hwtimer_count_get(fd, &count);
        aw_kprintf("Count is %d\r\n", count);
    }
    aw_close(fd);
    aw_close(led_fd);
    return 0;
}           

在上述代碼中使用了aw_hwtimer_count_rate_get接口擷取改定時器時鐘頻率,可以在調試模式下檢視擷取到的參數,為100M 如圖2所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖2 檢視參數

使用aw_hwtimer_count_rate_set接口設定定時器時鐘的頻率為50M,使用aw_hwtimer_count_start接口開啟定時器,使用aw_hwtimer_count_get接口在循環中每延時500ms擷取一次計數值,并在序列槽中列印,列印結果如圖3所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖3 序列槽列印計數值

列印出的計數值中,相鄰兩個計數值之差為25M,是由于設定定時器頻率為50M,每延時500ms計數值增加25M。

3. 延時型定時器

在config配置腳本中選擇hwtimer delay延時型定時器測試如圖4所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖4 計數型定時器例程

儲存後重新Build工程,編譯好後運作的是demo_hwtimer_count.c的代碼,例程關鍵代碼如下:

aw_local void* __task_handle (void *arg)
{
    int                 i;
    int                 fd;
    aw_err_t            ret;
    aw_timespec_t       timespec;
    aw_timestamp_t      start_timestamp, stop_timestamp;
    aw_timestamp_freq_t timestamp_freq;
    uint64_t            delay_ns, diff;
    uint32_t            ns_numerator = 1000000000;


    timestamp_freq = aw_timestamp_freq_get();
    while (0 == (timestamp_freq % 10)) {
        timestamp_freq /= 10;
        ns_numerator /= 10;
}


    fd = aw_open(CONFIG_DEMO_HWTIMER_DELAY_DEV_NAME, AW_O_RDWR, 0);
    if (fd < 0) {
        aw_kprintf("hwtimer open failed:%d \n\r", fd);
        while(1);
    }


    delay_ns = 2001000;
    for (i = 0; i < 100; i++) {
        timespec.tv_sec = delay_ns / 1000000000u;
        timespec.tv_nsec = (uint32_t)(delay_ns % 1000000000u);


        start_timestamp = aw_timestamp_get();
        ret = aw_hwtimer_delay(fd, ×pec);
        if (ret !=AW_OK) {
            aw_kprintf("hwtimer delay failed:%d \n\r", ret);
        }
        aw_barrier();
        stop_timestamp = aw_timestamp_get();


        stop_timestamp -= start_timestamp;
        diff = stop_timestamp;
        diff *= ns_numerator;
        diff /= timestamp_freq;


        diff = diff - delay_ns;
        aw_kprintf(
                "hwtimer_delay delay = %u,diff = %u ns\n",
                (uint32_t)delay_ns,
                (uint32_t)diff);
        delay_ns += 100000;
    }
    aw_close(fd);
    return 0;
}           

上述代碼中在延時開始前使用aw_timestamp_get接口記錄時間戳,使用aw_hwtimer_delay接口進行延時,延時結束後記錄結束時間戳,用兩個時間戳的內插補點通過換算,用于對比延時不同時間下與timestamp相比的誤差,并在序列槽中列印,列印後增加延時時間,再次循環,序列槽列印結果如下圖所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖5 序列槽列印結果

因外設接口調用時代碼執行需要時間以及晶振等硬體會導緻誤差,分析例程列印資料可得,延時性定時器的軟體開銷在同一硬體以及接口下,其誤差基本是一緻的。

4. 捕獲型定時器

{SDK}\demos\peripheral\cap路徑下為捕獲型定時器例程,例程關鍵代碼如下:

/* 單邊沿觸發*/
static void test_cap_single_edge(
        int fd,
        int gpio_cap,
        uint32_t ms,
        aw_hwtimer_cap_config_t *p_config,
        int is_rising)
{
    uint64_t                    cap_val1, cap_val2;
    aw_err_t                    ret;


    // 制造兩次上升沿
    mk_edge(gpio_cap, 5);
    aw_task_delay(ms);
    mk_edge(gpio_cap, 5);


    // 此時應該産生了兩次捕獲事件
    // 把它們讀出來
    ret = aw_hwtimer_cap_read(fd, &cap_val1, AW_WAIT_FOREVER);
    if (AW_OK != ret) {
        aw_kprintf("cap read cap_val1 failed \n");
        return;
    }
    ret = aw_hwtimer_cap_read(fd, &cap_val2, AW_WAIT_FOREVER);
    if (AW_OK != ret) {
        aw_kprintf("cap read cap_val2 failed \n");
        return;
    }


    cap_val2 -= cap_val1;
    cap_val2 *= 1000000;
    cap_val2 /= p_config->sample_rate;


    if (is_rising) {
        aw_kprintf("two rising edge between %u ms \n", ms + 5);
    }
    else {
        aw_kprintf("two falling edge between %u ms \n", ms + 5);
    }
    aw_kprintf("two capture events between %llu us \n", cap_val2);
}


static void demo_cap_base(int gpio_cap)
{
    int                         fd;
    aw_err_t                    ret;
    aw_hwtimer_cap_config_t    config;


    // 使得測試GPIO輸出為0
    aw_gpio_set(gpio_cap, 0);


    fd = aw_open(CONFIG_DEMO_HWTIMER_CAP_DEV_NAME, AW_O_RDWR, 0);
    if (fd < 0) {
        aw_kprintf("cap open failed!\n");
        return;
    }


    // 擷取捕獲定時器的配置
    ret = aw_hwtimer_cap_config_get(fd, &config);
    if (ret != AW_OK) {
        aw_kprintf("cap config get failed...\r\n");
        aw_close(fd);
        return ;
    }


#if CONFIG_SINGLE_EDGE
    int is_rising;
    // 配置為上升沿觸發捕獲
    config.cap_edge_flags = AW_CAPTURE_RISING_EDGE;
    is_rising = 1;
    ret = aw_hwtimer_cap_config_set(fd, &config);
    if (ret != AW_OK) {
        aw_kprintf("cap config set failed...\r\n");
        aw_close(fd);
        return ;
    }


    ret = aw_hwtimer_cap_start(fd);
    if (ret != AW_OK) {
        aw_kprintf("cap start failed...\r\n");
        aw_close(fd);
        return ;
    }
    test_cap_single_edge(fd, gpio_cap, 20, &config, is_rising);
#endif


    aw_close(fd);
}           

在CAP 例程中預設使用的是timer5_chan0,這個通道對應的引腳是PF08,可以通過檢視工程下timer5_chan0 對應的.h檔案得知所使用的引腳的編号為168,通過檢視hpm_pin.h頭檔案可知編号168對應的引腳為PF08 如下圖所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖6 預設通道

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖7 對應引腳編号

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖8 對應引腳

本實驗中還用到了PF09 這個引腳,用于産生捕獲事件,PF09 和 PF08 這兩個引腳在開發闆上并沒有引出來,不利于這次實驗,需要修改這兩個引腳。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖9 捕獲産生引腳

參考{SDK} platforms\platform-hpm-aworks-lp\boards\EPC6450-AWI\dts 下的pins.dts 引腳描述檔案,找到timer4_chan1 如圖10所示,timer4_chan1 使用的引腳是PE25, 對應着開發闆排針 UTX1 絲印的位置。

圖10 捕獲産生引腳

打開配置界面将timer5_chan0 修改為timer4_chan1 如圖11所示,修改後點選儲存,重新build工程。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖11 配置界面

将CAP_GPIO 對應的引腳改為PIN_PE24,對應着開發闆排針 URX1 絲印的位置,如圖12所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖12 CAP引腳

将 PE25 , PE24 這兩個引腳,也就是排針上 URX1 和 UTX1 短接。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖13 引腳位置

上訴代碼中使用aw_hwtimer_cap_config_get接口擷取捕獲定時器的配置資訊,配置AW_CAPTURE_RISING_EDGE單通道模式後使用aw_hwtimer_cap_config_set接口配置捕獲定時器。使用aw_hwtimer_cap_start接口啟動定時器。在test_cap_single_edge函數中調用mk_edge函數制造兩次上升沿,使用aw_hwtimer_cap_read接口讀取這兩次事件捕獲到的計數值,計算出內插補點後在序列槽上顯示。

在test_cap_single_edge函數中使用mk_edge函數中控制CAP_GPIO引腳輸出高電平後延時5ms再輸出低電平。延時20ms後再次調用mk_edge函數,是以兩次上升沿事件間隔應為25ms。序列槽列印結果如下圖所示。

AWorksLP 樣例詳解(MR6450)—— HWTimer

圖14 序列槽列印結果

至此,所有類型的硬體定時器樣例均已展示完畢,在軟體應用設計中可根據實際需求選取不同類型的定時器進行使用。更多其他類型外設的用法介紹,請關注後續同系列推文

ZLG官方商城:https://sale.zlg.cn/

ZLG開發者社群:https://z.zlg.cn/

更多詳細産品資訊、技術幹貨,可關注微信公衆号“ZLG緻遠電子”