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版本就会出问题了。可见嵌入式开发的难点不在于代码的编写,而在于调试。
本文转自yefanqiu51CTO博客,原文链接:http://blog.51cto.com/yfsoft/321221,如需转载请自行联系原作者