突然地心血來潮,為 MaixPy( k210 micropython ) 添加看門狗(WDT) C 模塊的開發過程記錄,給后來的人做開發參考。


事情是前幾天群里有人說做個看門狗不難吧,5分鍾的事情,然后我就懟了幾句,后來才發現,原來真的沒有看門狗模塊鴨。

那好吧,那我就寫一下好了,今天是(2020年4月30日)想着最后一天了,不如做點什么有價值的事情貢獻一下代碼好了。

做這個事情前吧,先思考一下模塊的接口設計,可以參考一下 esp32 的設計,因為是 micropython 后來的代碼,所以在設計上充分考慮了跨平台性。

那么我就以如下的代碼為參考開始吧。

import time
from machine import WDT

# test default wdt
wdt0 = WDT(id=0, timeout=3000)
print('into', wdt0)
time.sleep(2)
print(time.ticks_ms())
# 1.test wdt feed
wdt0.feed()
time.sleep(2)
print(time.ticks_ms())
# 2.test wdt stop
wdt0.stop()
print('stop', wdt0)
# 3.wait wdt work
while True:
    print('idle', time.ticks_ms())
    time.sleep(1)

可以看到這是最朴素的看門狗設計,只有 new 、feed、stop 接口,這足夠一般使用了。

接着我在堪智的 code 的接口中注意到有一個有趣的設計,也就是如下的 C code

#include "printf.h"
static int wdt0_irq(void *ctx) {
    static int s_wdt_irq_cnt = 0;
    printk("%s\n", __func__);
    s_wdt_irq_cnt ++;
    if(s_wdt_irq_cnt < 2)
        wdt_clear_interrupt((wdt_device_number_t)0);
    else
        while(1);
    return 0;
}

static void unit_test() {
    mp_printf(&mp_plat_print, "wdt start!\n");
    int timeout = 0;
    plic_init();
    sysctl_enable_irq();
    mp_printf(&mp_plat_print, "wdt time is %d ms\n", wdt_init((wdt_device_number_t)0, 4000, wdt0_irq,NULL));
    while(1) {
        vTaskDelay(1000);
        if(timeout++ < 3) {
            wdt_feed((wdt_device_number_t)0);
        } else {
            printf("wdt_stop\n");
            wdt_stop((wdt_device_number_t)0);
            sysctl_clock_disable(0 ? SYSCTL_CLOCK_WDT1 : SYSCTL_CLOCK_WDT0); // patch for fix stop
            while(1) 
            {
                printf("wdt_idle\n");
                sleep(1);
            }
        }
    }
}

這個已經是我測試過的底層 code 了,順便一提的是 SDK 的 stop 沒有關掉 wdt 的時鍾(sysctl_clock_disable),所以 stop 接口是不工作的,關於這個已經提交 issue 了,之后就會修復上。(2020年5月1日)

我們繼續看到這個設計,它允許導入一個 callback 和 context 參數(上下文用途),這個我看到的時候就思考了一下,這個看門狗的使用場景可能有如下幾種。

  1. 滿足一般的看門狗的基本功能,不喂狗就復位。
  2. 允許接入回調函數,在即將復位之前,進入中斷函數,此時由用戶決定如下三種狀態。
    • 沒有可以處理該異常(未能成功喂狗)的方法,最終只能 pass ,這將導致硬件復位。
    • 存在解決方案,並成功解決了問題,則取消這次的復位(繼續喂狗)。
    • 發現不需要看門狗了,可能手動觸發復位或者是其他考慮,則關閉看門狗模塊(stop)。

綜上所述,我們要能夠在代碼中體現的要素,配置看門狗的啟動(new)、喂狗(feed)、回調(callback+context)、停止(stop)即可。

則拓展處第二種代碼設計為


def on_wdt(self):
    print(self.context(), self)
    #self.feed()
    ## release WDT
    #self.stop()

# test callback wdt
wdt1 = WDT(id=1, timeout=4000, callback=on_wdt, context={})
print('into', wdt1)
time.sleep(2)
print(time.ticks_ms())
# 1.test wdt feed
wdt1.feed()
time.sleep(2)
print(time.ticks_ms())
# 2.test wdt stop
wdt1.stop()
print('stop', wdt1)
# 3.wait wdt work
while True:
    print('idle', time.ticks_ms())
    time.sleep(1)

當然,這都是在我寫完,和測試完之后做的總結了,所以過程是省略了不少,但會挑一下重點來講。

第一個是,參考以往的代碼框架,如 esp32/machine_wdt.c 用來構建 wdt 的基礎接口框架,如第一份代碼的設計,接着因為回調的原因,這個設計類似於 timer 定時器,所以定時器的 code 也要拿來參考,如 esp32/machine_timer.c ,基本上就可以做出來拉,這並不難。

不難歸不難,但也不會是 5 分鍾就可以寫完的代碼,大概也要差不多一天吧(標准8小時工作時間),如果一切順利的話。

注意寫的流程,建議滿足如下流程。

  • 確認原始 SDK 的功能正常,符合基本的單元測試,可以確定模塊的創建、啟動、配置、停止、釋放等要素。

  • 確認 micropython 的接口函數定義,可以先假定接口但不實現,主要是分離開發。

  • 基於前者的單元測試,丟入 Micropython 環境中執行,如配套的單元測試,確保可以在不破壞其他變量的條件下實現。

  • 此時確保 C 方面的基礎接口符合基本的使用,准備接入 Python 代碼進行單元測試,直到功能實現沒有明顯的死角。

走完上述流程后,基本上第一份看門狗 CODE 就可以實現了,很簡單,就 new 和 feed ,測試的 code 都不需要很復雜,只要確保不喂狗的時候復位了就好了。

接下來補一點細節和思考,如何添加回調,理解 C 與 MicroPython 函數之間的回調機制關系,主要就是 C 如何調用 Python 中設計的 函數 和傳遞參數。

這個部分看 timer.c 基本就可以搞定了,不了解的就看提交 MaixPy/commit/c4568e4f174de1c9eaf083506c2019ffbe8c7bf5 ,一是綁定一個本地函數,二是通過 C 接口調用函數傳遞 mp_obj_t 的 Python 對象。


STATIC void machine_wdt_isr(void *self_in) {
    machine_wdt_obj_t *self = self_in;
    if (self->callback != mp_const_none) {
        // printk("wdt id is %d\n", self->id);
        if (self->is_interrupt == false) {
            wdt_clear_interrupt(self->id);
            self->is_interrupt = true;
            mp_sched_schedule(self->callback, self);
        }
        mp_hal_wake_main_task_from_isr();
    }
}

wdt_init(self->id, self->timeout, (plic_irq_callback_t)machine_wdt_isr, (void *)self);

在這里 machine_wdt_isr 是用來接收 WDT 的 C 中斷,它將會在 WDT 即將復位之前反復重入(執行),為了在進入中斷后不重入則需要清理中斷的信號 wdt_clear_interrupt(self->id); 。

接着 mp_sched_schedule(self->callback, self); 用來執行 Python 對應的函數對象,參數指為自身,我設計中的上下文通過 self.context() 來處理,這樣的好處就是可以在回調函數中決定如何管理當下的看門狗模塊狀態。

注意 mp_hal_wake_main_task_from_isr(); 是用來繼續執行 MicroPython 環境的代碼,可以這樣理解,中斷將會吃掉所有函數的執行,也包括 MicroPython 的主進程,也就是 repl 接口的(main)代碼,也就是說,若是不在這里繼續執行 MicroPython 環境,整個芯片將會停止工作,全部都陷入了一個空轉的中斷函數中。

而至於我為什么加上了 is_interrupt 這是因為考慮到第二種設計所添加的標記量,它主要解決以下場景的問題。

is_interrupt 會在 machine_wdt_feed 中設置為 self->is_interrupt = false; 表示這個狀態要撤銷,結合上述的 machine_wdt_isr 邏輯來看。

我們回顧回調處理的場景之一,沒有可以處理該異常(未能成功喂狗)的方法,最終只能 pass ,這將導致硬件復位。

第一次觸發進入 is_interrupt 為防止重入則打上標記量 self->is_interrupt = true; 此時在清理信號后,將正常執行 Python 中的回調函數,若回調函數什么也不做,就會進一步觸發復位,從這之后若是再次進入中斷后,將會反復執行 mp_hal_wake_main_task_from_isr ,直到硬件復位。

此時若是在回調中 feed 喂狗重置了 is_interrupt 標記則允許重入 Python 的回調函數,從而防止進一步的看門狗復位。

此時若是在回調中 stop 了,則整個模塊都釋放了。

此時若是什么也不做,放一會就復位了。

以上,就這些,想知道更多就去看提交的代碼吧,會有不一定的理解的(也許?)。

最后補一下單元測試 Code 。

import time
from machine import WDT

# '''
# test default wdt
wdt0 = WDT(id=0, timeout=3000)
print('into', wdt0)
time.sleep(2)
print(time.ticks_ms())
# 1.test wdt feed
wdt0.feed()
time.sleep(2)
print(time.ticks_ms())
# 2.test wdt stop
wdt0.stop()
print('stop', wdt0)
# 3.wait wdt work
while True:
    print('idle', time.ticks_ms())
    time.sleep(1)
# '''

# '''
def on_wdt(self):
    print(self.context(), self)
    #self.feed()
    ## release WDT
    #self.stop()

# test callback wdt
wdt1 = WDT(id=1, timeout=4000, callback=on_wdt, context={})
print('into', wdt1)
time.sleep(2)
print(time.ticks_ms())
# 1.test wdt feed
wdt1.feed()
time.sleep(2)
print(time.ticks_ms())
# 2.test wdt stop
wdt1.stop()
print('stop', wdt1)
# 3.wait wdt work
while True:
    print('idle', time.ticks_ms())
    time.sleep(1)
# '''

#'''
## test default and callback wdt
def on_wdt(self):
    print(self.context(), self)
    #self.feed()
    ## release WDT
    #self.stop()

wdt0 = WDT(id=0, timeout=3000, callback=on_wdt, context=[])
wdt1 = WDT(id=1, timeout=4000, callback=on_wdt, context={})
## 3.wait wdt work
while True:
    #wdt0.feed()
    print('idle', time.ticks_ms())
    time.sleep(1)
#'''

其實只是給我自己備份 Code 而已,2020年5月1日留。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM