//=====================================================================
//TITLE:
// C#程序导致MFDeploy无法ping通的问题
//AUTHOR:
// norains
//DATE:
// Thursday 05-May-2011
//Environment:
// .Net Micro Framework Porting V4.1
// Keil MDK 4.02
// STM32F103ZE Redcow Board
// Visual Studio 2010
//=====================================================================
为了测试编写USB驱动是否正常,编写了一个C#程序,其完整的代码如下:
namespace HelloWorld { public class Program { public static void Main() { Debug.Print("Hello, World!/n"); Debug.Print("It's the first C# application in .Net Micro Framework!/n"); } } }
这段代码看起来没什么问题,用VS2010也能正常下载,并且从串口中也能看到打印出来的字符串。但奇怪的事情就此发生了,自从成功下载该程序,并且正常运行之后,我的板子插到电脑上虽然可以枚举,但通过MFDeploy的Ping指令,却经常无法正常通讯。如果用Bus Hound软件来查看,发现PC端发送一个OUT包之后就卡死了,如图所示:
如果将NAND上的C#程序给擦除,那么一切又恢复了正常。这究竟是怎么一回事呢?
通过MDK的断点调试,会发现代码一直在执行Systick中断函数,而查看Systick的寄存器,发现reload的数值非常小,如图所示:
造成频繁执行Systick中断函数的原因是否源于此呢?通过MDK来强制更改NVIC_ST_RELOAD寄存器的数值为0x00FFFFFF来试试。结果不出所料,Bus Bound监控到USB数据又开始交互,MFDeploy也不再假死,而检测到板子为TinyCLR了!
转回来想想,为什么Systick的Reload的数值太小,导致MFDeploy假死呢?原因很简单,Reload是每次systick中断的间隔,而这个时间太小,很可能在刚执行完systick中断函数的时候,下一次中断又发生了,从而导致主线程的代码没有获得CPU时间执行!
原因找到了,那么我们该如何解决这个问题呢?首先我们来看看Systick的中断函数。一般来说,根据.NMF的示例代码,我们的中断函数无非如此:
void ISR(void *pParam) { g_TicksLastRead = CounterValue(); if(g_TicksLastRead >= g_TicksCompare) { // this also schedules the next one, if there is one HAL_COMPLETION::DequeueAndExec(); } else { // // Because we are limited in the resolution of timer, // resetting the compare will properly configure the next interrupt. // // You mustn't comment the source code, or it would not boot normally. SetCompareValue( g_TicksCompare ); } }
这个中断函数逻辑很简单,如果当前的ticks大于要比较的ticks值,就执行HAL_COMPLETION::DequeueAndExec()函数。我们进到该函数看看。因为该函数比较大,有一些东西并不需要我们了解,所以下面罗列的是缩减后的主要部分,如下所示:
void HAL_COMPLETION::DequeueAndExec() { ... HAL_COMPLETION* ptr = (HAL_COMPLETION*)g_HAL_Completion_List.FirstNode(); HAL_COMPLETION* ptrNext = (HAL_COMPLETION*)ptr->Next(); if(ptrNext) { ... HAL_Time_SetCompare( ptrNext->Next() ? ptrNext->EventTimeTicks : HAL_Completion_IdleValue ); ... } }
从中可以看到,该函数其实会调用HAL_Time_SetCompare来设置下一次的中断时间。但如果我们使用MDK单步调试,却会惊讶发现,该函数一直没有被调用,因为ptrNext一直为NULL!如果再仔细点,在还没执行C#程序之前,ptrNext不是为NULL的!
根据经验推测,问题就出在于C#程序。具体点,问题在于C#的Main函数返回了!因为C#的Main函数返回,导致Node的结点被释放,于是ptrNext就为NULL。如果此时又刚好Systick的间隔很小,代码又因为ptrNext为NULL而不执行HAL_Time_SetCompare函数,那么最终结果就是:不停地发生Systick中断,而无瑕执行主线程代码!
原因找到之后,可能大家想到的最简单的解决方式就是在C#的Main函数中执行一个死循环,如下所示:
namespace HelloWorld { public class Program { public static void Main() { Debug.Print("Hello, World!/n"); Debug.Print("It's the first C# application in .Net Micro Framework!/n"); while(true); } } }
结果没错,这样更改之后,的确一切都正常了。似乎一切都很美好,不是么?但是,这样的健壮性是不够的,因为我们无法确保做应用的程序员能够遵守这个约定。而万一他们忘记的话,那么结果将是灾难性的:因为USB已经无法PING通,更谈不上下载程序,所以做应用的程序员无法凭一己之力来挽回自己的错误。除了你,.NMF的移植者,通过MDK来设置Systick的Reload寄存器,让CPU跑到正常的轨道上,除此以外,再无它法。
为了避免这情况,我们可以将中断函数修正一下。我们自己来检测ptrNext,如果它为NULL的话,那么我们手动调用HAL_Time_SetCompare函数即可,如下所示:
void ISR(void *pParam) { g_TicksLastRead = CounterValue(); if(g_TicksLastRead >= g_TicksCompare) { // this also schedules the next one, if there is one HAL_COMPLETION::DequeueAndExec(); //If the ptrNext is NULL,it wouldn't call HAL_Time_SetCompare() in DequeueAndExec() function //which means the main thread may not get the time to run when the systick reload value is very small and //systick interrupt occurs frequently. So I reset the systick reload value here by calling HAL_Time_SetCompare function HAL_COMPLETION* ptr = (HAL_COMPLETION*)g_HAL_Completion_List.FirstNode(); HAL_COMPLETION* ptrNext = (HAL_COMPLETION*)ptr->Next(); if(ptrNext == NULL) { //0x0000FFFFFFFFFFFFull is used by the HAL_Completion_IdleValue variable which defined in the completions.cpp file. HAL_Time_SetCompare(0x0000FFFFFFFFFFFFull); } } else { // // Because we are limited in the resolution of timer, // resetting the compare will properly configure the next interrupt. // // You mustn't comment the source code, or it would not boot normally. SetCompareValue( g_TicksCompare ); } }
经过这么一更改,那么引发MFDeploy无法响应的问题不再存在,我们也不用再担心应用程序员忘记最后写死循环啦!