天天看點

【.Net Micro Framework PortingKit – 12】SysTick驅動開發

SysTick驅動對TinyCLR來說非常重要,.Net Micro Framework系統的多線程和多任務(對托管代碼來說是單任務多線程,但是還存在和托管代碼同時運作的任務,如我們用MFDeploy程式Ping TinyCLR或擦寫Flash 的時候,就是另外的任務在執行)就是靠它來實作的。

SysTick驅動有三個功用,一是我們上面所說的多任務和多線程支援;二是獲得系統目前Tick,以此實作延時等待,比如我們常見的Events_WaitForEvents函數就靠它來實作延時功能的;三是為Native代碼提供兩個版本的Sleep函數。

和ARM7或ARM9相比,Cortex-M3系列的CPU提供了SysTick這個feature,是以我們就不需要用Timer來模拟Tick的功能了,直接用系統提供的SysTick就可以了。Cortex-M3的SysTick其定時器計數是遞減的,遞減到0就會觸發中斷(當然要使TICKINT使能),然後自動會加載LOAD寄存器的值,啟動下一次計數循環。

LOAD寄存器可填入的最大值為0x00FFFFFF,對72M主頻的CPU來說,大概會有250毫秒左右的延時。由于VAL寄存器的值是遞減的,是以在移植相關代碼的時候要特别注意,我們概念中的Tick的值應該是(LOAD-VAL)。

在CortexM3.h檔案中添加如下代碼,以便于配置SysTick寄存器。

struct CortexM3_SysTick

{

    static const UINT32 c_Base = 0xE000E010;

    /****/ volatile UINT32 CTRL;  //0xE000E010

    static const    UINT32 CTRL_COUNTFLAG= ((UINT32)0x00010000); 

    static const    UINT32 CTRL_CLKSOURCE= ((UINT32)0x00000004);

    static const    UINT32 CTRL_TICKINT= ((UINT32)0x00000002);

    static const    UINT32 CTRL_ENABLE= ((UINT32)0x00000001);

    /****/ volatile UINT32 LOAD;  //0xE000E014

    static const    UINT32 LOAD_RELOAD= ((UINT32)0x00FFFFFF); 

    /****/ volatile UINT32 VAL;  //0xE000E018

    static const    UINT32 VAL_CURRENT= ((UINT32)0x00FFFFFF); 

    /****/ volatile UINT32 CALIB;  //0xE000E01C

    static const    UINT32 CALIB_NOREF= ((UINT32)0x80000000); 

    static const    UINT32 CALIB_SKEW= ((UINT32)0x40000000); 

    static const    UINT32 CALIB_TENMS= ((UINT32)0x00FFFFFF);     

};           

然後在/DeviceCode/Targets/Native/CortexM3/DeviceCode/SysTick建立四個檔案SysTick.h、SysTick.cpp、SysTick_Functions.cpp、dotNetMF.proj。

在SysTick.h中建立SYSTICK_Driver結構體,SysTick.cpp存放該結構體的具體實作代碼。

struct SYSTICK_Driver

{

    static const UINT32 c_MaxTimerValue = 0xFFFFFF; //16777215  最大 250ms左右的定時

 

    volatile UINT64 m_Tick;

    volatile UINT64 m_nextCompare;

   

    static BOOL Initialize  ();

    static BOOL Uninitialize();

    static UINT64 CounterValue();

    static void SetCompareValue( UINT64 CompareValue );

    static INT64 TicksToTime( UINT64 Ticks );

    static INT64 CurrentTime();

    static void Sleep_uSec( UINT32 uSec );

    static void Sleep_uSec_Loop( UINT32 uSec );

    static void ISR( void* Param );

};           

Cortex-M3核心下的UINT64 CounterValue()函數的具體實作不同于.Net Micro Framework自帶的各平台上的源碼,是以有必要介紹一下它的實作:

UINT64 SYSTICK_Driver::CounterValue()

{

    GLOBAL_LOCK(irq);

    CortexM3_SysTick &SysTick= CortexM3::SysTick();

    UINT32 value = (SysTick.LOAD - SysTick.VAL);

  

    if(SysTick.CTRL & CortexM3_SysTick::CTRL_COUNTFLAG)

    {

        g_SYSTICK_Driver.m_Tick+=SysTick.LOAD;          

    }

    

    return  g_SYSTICK_Driver.m_Tick + value;

}           

Value的值為(SysTick.LOAD - SysTick.VAL),這是和其它平台的驅動一個差別。此外m_Tick也是我新添加的,它是不斷累加計數的,但是它不能真實反映系統開機以來的Tick數,因為SysTick有可能會被禁止中斷,也就是說ISR函數不能保證整個系統運作期内都被正常觸發。

ISR是一個重點,它是系統實作多任務和多線程的“動力源”。

void SYSTICK_Driver::ISR( void* Param )

{   

    if(CounterValue() >= g_SYSTICK_Driver.m_nextCompare)

    {

       HAL_COMPLETION::DequeueAndExec();

    }

    else

    {

        SetCompareValue( g_SYSTICK_Driver.m_nextCompare );

    }

}           

HAL_COMPLETION::DequeueAndExec()代碼是關鍵,每間隔一個指定的時間就會執行一次任務,常見間隔時間為20ms。

Sleep_uSec函數是通過Tick計數計算延時間隔的。

void __section(SectionForFlashOperations) SYSTICK_Driver::Sleep_uSec( UINT32 uSec )

{

    GLOBAL_LOCK(irq);

    CortexM3_SysTick &SysTick= CortexM3::SysTick();  

    UINT32 maxDiff  = CPU_MicrosecondsToTicks( uSec );   //每微秒的滴答數

        SysTick.LOAD =   maxDiff & 0xFFFFFF;  

    while(!(SysTick.CTRL & CortexM3_SysTick::CTRL_COUNTFLAG));

}           

__section(SectionForFlashOperations)辨別該函數會被拷貝到RAM中去運作(保證執行時間)。

而Sleep_uSec_Loop函數則是通過彙編代碼的循環實作的,延時相對比較精确。

void __section(SectionForFlashOperations) SYSTICK_Driver::Sleep_uSec_Loop( UINT32 uSec )

{

    // iterations must be signed so that negative iterations will result in the minimum delay

    uSec *= (SYSTEM_CYCLE_CLOCK_HZ / CLOCK_COMMON_FACTOR);

    uSec /= (ONE_MHZ               / CLOCK_COMMON_FACTOR);

 

    // iterations is equal to the number of CPU instruction cycles in the required time minus

    // overhead cycles required to call this subroutine.

    int iterations = (int)uSec - 14;      // Subtract off call & calculation overhead

    CYCLE_DELAY_LOOP2(iterations);

}           

CYCLE_DELAY_LOOP2的實作代碼是彙編,我把它放在FirstEntry.s檔案裡了,具體代碼如下:

IDelayLoop2

    EXPORT  IDelayLoop2

    subs    r0,r0, #2          ;; 1 cycle

    bgt     IDelayLoop2        ;; 1 cycle

      mov     pc, lr              ;; 5 cycles            

Sleep_uSec_Loop函數實作代碼中的uSec – 14是從其它CPU代碼中拷貝來的,針對Cortex-M3應該是多少,我還沒有細算過,以後有時間再補上這一課。

在NativeSample.proj中添加如下條目,就可以測試SysTick驅動了:

  <ItemGroup>

    <RequiredProjects Include="$(SPOCLIENT)/DeviceCode/Targets/Native/CortexM3/DeviceCode/SysTick/dotNetMF.proj" />

    <DriverLibs Include="SysTick.$(LIB_EXT)" />

  </ItemGroup>           

在NativeSample.cpp中我們隻能通過Events_WaitForEvents( 0, 1000 )代碼測試SysTick驅動的一部分功能,全部的功能要在TinyCLR項目去測試了。

小插曲:在實作LCD驅動的時候,初始化LCD寄存器需要延時,在采用CYCLE_DELAY_LOOP2時,debug版本和release版本有很大的差別,debug可正常運作,但是release會有問題,在Sleep_uSec_Loop函數開始添加GLOBAL_LOCK(irq)代碼就可以了,但是這樣一改在TinyCLR代碼中能正常運作的debug版本就會出問題了。可見嵌入式開發的難點不在于代碼的編寫,而在于調試。

繼續閱讀