【.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版本就會出問題了。可見嵌入式開發的難點不在于代碼的編寫,而在于調試。
總結
以上是生活随笔為你收集整理的【.Net Micro Framework PortingKit – 12】SysTick驱动开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SharePoint Explorer
- 下一篇: WPF内存泄露:CollectionVi