说明:公司里的工程中,使用VS_UCOS来调试应用程序、业务逻辑。方法是嵌入式和VS分别建一个工程,把底层驱动部分分别添加各自需要的源文件,头文件使用同一个。也就是嵌入式的驱动函数名和参数和VS的函数名和参数是一致的,只是用自己的方式来实现。这样需要调底层驱动时可以嵌入式仿真器来调试,而调应用时,可以直接在VS里修改。但需要注意的是如果修改驱动时,可能VS工程的文件也需要相应的修改;还有PC和嵌入式的差别,如响应速度等。在实际的项目中也可以通过VS工程来查找问题和数据的正确性。嵌入式平时运行的数据可以存储在E2prom或flash里,现场里只需要通过事先编写好的工具读出设备里的数据。就可以用这个数据文件在VS里来还原现场情况,而不需要得到实际设备才能还原现场问题。项目中用到的其他介质,比如IC卡,也可以通过事先编写好的工具读出卡的数据,然后用这个数据文件在VS里来读取。在项目中编写现场人员方便操作的软件工具,能更方便解决问题。

公司里的两个设备一个负责逻辑处理数据通讯A设备。一个设备负责界面操作和显示的B设备,两个设备通过串口操作。其中B设备相当于控制设备,这里也同样用VS工程实现了B,用的是调用WIN32 API的方法实现GUI。

用这种方法更有利于应用程序的调试,对于仿真不便利的芯片和平台,效果更明显。

好了,这点小的心得体会写到这里。下面步入正题,公司里的VS_UCOS出现OSTimeDly (INT16U ticks)系统延时时,OSTime系统滴嗒计数是正确的,但是实际延时丰常短,瞬间延时完成。

找问题的思路:

1、根据UCOS知识,出现这个原因可能是因为心跳太快。找在os_cfg.h配置文件里OS_TICKS_PER_SEC也被宏定义成100,也就是心跳时间应该是10ms。

2、找VS_UCOS里的系统心跳设置

通过Source Insight编辑工具。追踪几个函数:OSTimeTick()->OSTickISR()->OSTickW32()

在此函数里有一个Windows的函数WaitForSingleObject(OSTickEventHandle, 5000)通过函数名猜测是等待某一函数的意思。百度百科一下,

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。

那么根据这个OSTickEventHandle事件句柄追踪一下,这个事件是如何触发的。

在OSStartHighRdy()函数里

OSTickEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL);
OSTickTimer = timeSetEvent((/OS_TICKS_PER_SEC),OSTimeCap.wPeriodMin,(LPTIMECALLBACK)OSTickEventHandle, dwID,TIME_PERIODIC|TIME_CALLBACK_EVENT_SET);

OSTickEventHandle 是被创建的事件句柄。

这里的timeSetEvent()函数很关键。在这里也绕了一些弯,请教过公司里写Windows服务的两个大神,结果都没有用过不能帮助分析。百度百科中只有“timeSetEvent 是一种精确度非常高的定时器,与windows定时器不同的是,多媒体定时器不使用容易丢失的窗口消息,它提供另外两种方式来触发。”这么一句有用的话。

搜索一下,当时找到了MSDN里这个函数介绍的中文翻译的博文,但写此文档时搜索不到了,直接贴MSDN英文版,网址

https://msdn.microsoft.com/en-us/library/windows/desktop/dd757634(v=vs.85).aspx

The topic you requested is included in another documentation set. For convenience, it's displayed below. Choose Switch to see the topic in its original location.

timeSetEvent function

The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. After the event is activated, it calls the specified callback function or sets or pulses the specified event object.

Note  This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.

Syntax

C++

MMRESULT timeSetEvent(

UINT           uDelay,

UINT           uResolution,

LPTIMECALLBACK lpTimeProc,

DWORD_PTR      dwUser,

UINT           fuEvent

);

Parameters

uDelay

Event delay, in milliseconds. If this value is not in the range of the minimum and maximum event delays supported by the timer, the function returns an error.

uResolution

Resolution of the timer event, in milliseconds. The resolution increases with smaller values; a resolution of 0 indicates periodic events should occur with the greatest possible accuracy. To reduce system overhead, however, you should use the maximum value appropriate for your application.

lpTimeProc

Pointer to a callback function that is called once upon expiration of a single event or periodically upon expiration of periodic events.

If fuEvent specifies the TIME_CALLBACK_EVENT_SET or TIME_CALLBACK_EVENT_PULSE flag, then the lpTimeProc parameter is interpreted as a handle to an event object. The event will be set or pulsed upon completion of a single event or periodically upon completion of periodic events.

For any other value of fuEvent, the lpTimeProc parameter is a pointer to a callback function of type LPTIMECALLBACK.

dwUser

User-supplied callback data.

fuEvent

Timer event type. This parameter may include one of the following values.

Value

Meaning

TIME_ONESHOT

Event occurs once, after uDelay milliseconds.

TIME_PERIODIC

Event occurs every uDelay milliseconds.

The fuEvent parameter may also include one of the following values.

Value

Meaning

TIME_CALLBACK_FUNCTION

When the timer expires, Windows calls the function pointed to by the lpTimeProc parameter. This is the default.

TIME_CALLBACK_EVENT_SET

When the timer expires, Windows calls the SetEvent function to set the event pointed to by the lpTimeProc parameter. The dwUser parameter is ignored.

TIME_CALLBACK_EVENT_PULSE

When the timer expires, Windows calls the PulseEvent function to pulse the event pointed to by the lpTimeProc parameter. The dwUser parameter is ignored.

TIME_KILL_SYNCHRONOUS

Passing this flag prevents an event from occurring after the timeKillEvent function is called.

Return value

Returns an identifier for the timer event if successful or an error otherwise. This function returns NULL if it fails and the timer event was not created. (This identifier is also passed to the callback function.)

Remarks

Each call to timeSetEvent for periodic timer events requires a corresponding call to the timeKillEvent function.

Creating an event with the TIME_KILL_SYNCHRONOUS and the TIME_CALLBACK_FUNCTION flag prevents the event from occurring after the timeKillEventfunction is called.

Requirements

Minimum supported client

Windows XP [desktop apps only]

Minimum supported server

Windows Server 2003 [desktop apps only]

Header

TimeAPI.h (include Windows.h);

Mmsystem.h on Windows Server 2008 R2, Windows 7, Windows 7, Windows Server 2008 and Windows Vista (include Windows.h)

Library

Winmm.lib

DLL

Winmm.dll

See also

Multimedia Timers

Multimedia Timer Functions

通过此文档和网上大多的文档介绍,说明此函数实际上就是一个WINDOWS的定时器,最高精度为1ms。fuEvent参数有两组,其中的一组说明可以是定时到了只动作一次,也可以是定时到了后周期的动作。另外一组说明定时到了后动作的参数lpTimeProc是一个回调函数指针还是一个事件的句柄。

并根据“Sunshine的空间”的博文

https://www.cnblogs.com/kangwang1988/archive/2010/09/16/1827872.html

中的例子做了实验

发现可以正确的定时,做出正确的延时。

对本工程里的TIME_PERIODIC|TIME_CALLBACK_EVENT_SET参数疑惑。网上找不到类似的用法。

timeSetEvent((/OS_TICKS_PER_SEC),OSTimeCap.wPeriodMin,(LPTIMECALLBACK)OSTickEventHandle, dwID,TIME_PERIODIC|TIME_CALLBACK_EVENT_SET);

修改参数做调试看现象:

(1)只使用TIME_PERIODIC参数,当运行到此函数后会出现函数崩溃的现象。

(2)只使用TIME_CALLBACK_EVENT_SET参数,OSTickW32()函数中会因为获取不到OSTickEventHandle事件而超时。

(3)使用网上大部分例子的方法,使用TIME_PERIODIC参数,编写一个回调函数触发OSTickEventHandle事件。注意这个回调函数的返回值和形参。

void WINAPI SetEventFunc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2)//
{
SetEvent(OSTickEventHandle);
return;
}

现象和原工程里的现象是相同。

分析总结这几个现象,可以看出这个函数的用法。(1)里只用TIME_PERIODIC参数,参数lpTimeProc的实参需要一个回调函数指针,但本工程里传入的是一个事件的句柄。(2)里只用TIME_CALLBACK_EVENT_SET参数,造成等待事件超时,是因为当定时到了后只发生了一次动作,只使用此参数是默认只动作一次,所以只触发一次,之后该事件一直没有触发,一直等待此事件的函数一直超时。(3)里此种用法和本工程里原用法实际上是相同的。

分析到此只能怀疑是本工程的问题。查找VS_UCOS版本

1、Vladimir Antonenko,此作者不详,本工程里使用就是这个版本的。

2、Micrium 就是UCOS官网版本,在官网下载相应的版本。移植时uC-CPU、uC-LIB、uCOS-II三个目录都需要使用。

3、德国Hochschule Esslingen 大学Zimmermann教授 版本。

说明,官网的版本与邵贝贝出的UCOSII时的X86移植不一样了。可能是编译器不同了,下载的是VS编译器的。现在接口源码与1和3很类似。三个都类似,又有些差别。

在本工程里移植好了2和3结果还是与原来一样的。

在网上下载了一个”uCOS-II+VS2012_完美仿真”的工程,里面的例子正好是延时的,并且是准确的。接口文件与本工程里是一样的。此时怀疑是本工程的设置的原因。把两个工程设置一项一项比对,把不同的对方,互相设置,结果还是与原来一样。

进一步崩溃......。自己重新建立工程,在VS_UCOS工程框架里,加入一个简单的延时例子,结果能正确运行。此时换一个思路来做,在原来的工程里把多余的代码屏蔽,一点一点的放开的方法。由屏蔽创建其他任务、屏蔽bsp初始化,现象出现了,屏蔽bsp初始化后,结果正确。(气死了,早就应该用这种办法的,调试经验就是一点点调来,总结来的吧(安慰一下自己))。

用此办法继续调试发现,在初始化函数里的某一个函数里,另创建了一个timeSetEvent定时器,定时只有2ms。难道是只能创建一个timeSetEvent定时器吗,两个timeSetEvent定时器有冲突吗?网上查找后发现可以创建很多个。自己编写测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#pragma comment(lib,"Winmm.lib") unsigned int udTicks = ;
# define ONE_MILLI_SECOND //定义1ms和2s时钟间隔,以ms为单位 ;
# define TWO_SECOND
# define TIMER_ACCURACY //定义时钟分辨率,以ms为单位 HANDLE OSTickEventHandle;
void PASCAL OneMilliSecondProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2)
{
udTicks++;
} void PASCAL TwoMilliSecondProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2)
{
printf("this is second timer\r\n");
} void main() {
time_t rawtime;
struct tm * timeinfo;
unsigned int i = ;
HANDLE hHandle; UINT wTimerRes_1ms,wTimerRes_2s;//定义时间间隔
UINT wAccuracy; //定义分辨率
UINT TimerID_1ms,TimerID_2s; //定义定时器句柄
wTimerRes_1ms = ;
wTimerRes_2s = ;
OSTickEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL);
//OneMilliSecondProc
if((TimerID_1ms = timeSetEvent(wTimerRes_1ms, ,(LPTIMECALLBACK)OSTickEventHandle, // 回调函数
(DWORD)(), // 用户传送到回调函数的数据;
TIME_PERIODIC|TIME_CALLBACK_EVENT_SET)) == )//周期调用定时处理函数 //TIME_PERIODIC|
{
printf("TimerID_1ms = %d\n",TimerID_1ms);
}
else
{
printf("TimerID_1ms = %d\n",TimerID_1ms);
} if((TimerID_2s = timeSetEvent(wTimerRes_2s, ,(LPTIMECALLBACK)TwoMilliSecondProc, // 回调函数
(DWORD)(), // 用户传送到回调函数的数据;
TIME_PERIODIC)) == )//周期调用定时处理函数 //TIME_PERIODIC|
{
printf("TimerID_2s = %d\n",TimerID_2s);
}
else
{
printf("TimerID_2s = %d\n",TimerID_2s);
} time ( &rawtime );
timeinfo = localtime ( &rawtime );
printf("ticks = %d\r\n",udTicks);
i = udTicks;
printf ( "The current date/time is: %s", asctime (timeinfo) );
while ()
{ if( WaitForSingleObject(OSTickEventHandle, ) == WAIT_TIMEOUT)
{
#ifdef OS_CPU_TRACE
printf("Error: MM OSTick Timeout!\n");
#endif
printf("Error: MM OSTick Timeout!\n");
}
time ( &rawtime );
timeinfo = localtime ( &rawtime ); printf ( "The current date/time is: %s", asctime (timeinfo) );
printf("Right\r\n"); ResetEvent(OSTickEventHandle); }
}

输出结果:

说明两个定时器也是正确的。

此时查看本工程里这个定时器里的回调函数,

void PASCAL TimeIntProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)    //时钟中断回调函数
{
//判断是否有按键输入
INPUT_RECORD Buffer[];
INT32U udCount,i,j;
BSP_GPIOIRQHandler(); //debug if(GetNumberOfConsoleInputEvents(hKeyboard,&udCount) && udCount>)
{
ReadConsoleInput(hKeyboard,Buffer,,&udCount);
for(i=;i<udCount;i++)
{
if(Buffer[i].EventType!=KEY_EVENT)continue;
if(Buffer[i].Event.KeyEvent.bKeyDown)
{
for(j=;j<Buffer[i].Event.KeyEvent.wRepeatCount;j++)
BSP_GPIOIRQHandler();
}
}
} BSP_WdgFeed(); //喂狗
OSTimeTick();
}

此函数是定时执行的里面有喂狗,等等这里有OSTimeTick();很可疑,这个节拍服务函数是在OSTickISR函数中调用的,目的是在时钟节拍到来时,检查每个任务的任务控制块中的.OSTCBDly-1后是否为0,如果是,那么表明这个任务刚才是挂起的状态,此时应改变为就绪态。怪不得系统滴嗒计数这么快,原来这里有2ms的定时呀。屏蔽掉一试,果然正常了。

这里是模拟定时中断的,那就按正常中断服务程序改写

void PASCAL TimeIntProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)    //时钟中断回调函数
{
//判断是否有按键输入
INPUT_RECORD Buffer[];
INT32U udCount,i,j;
OS_ENTER_CRITICAL(); //进入中断需要对中断层数加1
OSIntEnter();
OS_EXIT_CRITICAL();
    BSP_GPIOIRQHandler(); //debug

    if(GetNumberOfConsoleInputEvents(hKeyboard,&udCount) && udCount>)
{
ReadConsoleInput(hKeyboard,Buffer,,&udCount);
for(i=;i<udCount;i++)
{
if(Buffer[i].EventType!=KEY_EVENT)continue;
if(Buffer[i].Event.KeyEvent.bKeyDown)
{
for(j=;j<Buffer[i].Event.KeyEvent.wRepeatCount;j++)
BSP_GPIOIRQHandler();
}
}
} BSP_WdgFeed(); //喂狗 //OSTimeTick(); 中断退出时应该调用OSIntExit();系统时钟中断时才调用OSTimeTick();
OSIntExit();
}

到此整个调试过程完成,其中走了不少的弯路,工程配置比较,不同版本的接口代码比较,这些浪费了不少时间,最终花了一天的时间才算整好,愁死~~

修改公司VS_UCOS工程BUG调试过程说明的更多相关文章

  1. geotrellis使用(七)记录一次惨痛的bug调试经历以及求DEM坡度实践

    眼看就要端午节了,屌丝还在写代码,话说过节也不给轻松,折腾了一天终于解决了一个BUG,并完成了老板安排的求DEM坡度的任务,那么就分两段来表. 一.BUG调试 首先记录一天的BUG调试,简单copy了 ...

  2. 最难忘的Bug调试经历

    摘要:目前,著名的社区问答网站Quora上出现一个很火的讨论:你调试过最难的Bug是什么?大家纷纷留言,把自己最痛苦的一次调试经验写下来. 相信每位程序员都有过一段不堪回首地Bug调试经历,程序员一听 ...

  3. DevC++ 工程没有调试信息的解决办法

    DevC++4.9.9.2中,按 F8 开始调试.提示信息为:工程没有调试信息,您想打开工程的调试选项并重新生成吗?选择是后,再按F8,仍旧是这个信息.什么原因呢? 按照帮助,Frequently A ...

  4. Dev C++ 工程没有调试信息 解决办法

    Dev C++ 工程没有调试信息 解决方法DevC++4.9.9.2中,按 F8 开始调试.提示信息为:工程没有调试信息,您想打开工程的调试选项并重新生成吗?选择是后,再按F8,仍旧是这个信息.什么原 ...

  5. geotrellis使用(十二)再记录一次惨痛的伪BUG调试经历(数据导入以及读取瓦片)

    Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html 目录 前言 BUG还原 查找BUG 解决方案 总结 后记 一.前 ...

  6. Eclipse中把Java工程修改成web工程

    Eclipse中把Java工程修改成web工程 点击项目:右击:选择properties--输入project facets,将“Dynamic Web Module”打勾即可:

  7. 论 BUG调试与(程序猿)初学者

    作为一枚程序猿,BUG调试是最基本的技能,对于初学者更是重中之重.个人而言,要想为自己的程序猿生涯更上一层楼,就得知道什么是BUG调试,而且还必须知道怎么调好BUG.那么BUG究竟是什么呢?在我之前的 ...

  8. 1、使用Xcode修改iOS项目工程名和路径名

    http://blog.sina.com.cn/s/blog_a42013280101blxo.html 对,好:错,改正. ------ 前言 系统 10.7 狮子 开发平台 xcode 4.5.2 ...

  9. Bug调试(lldb)

    原文网址:http://www.cnblogs.com/Twisted-Fate/p/4760156.html 今天博主有一些Bug调试的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步. X ...

随机推荐

  1. MySQL_(Java)分页查询MySQL中的数据

    MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC创建用户名和密码校验查询方法 传送门 MySQL_(Java)使用preparestatement ...

  2. Codeforces Round #567 (Div. 2) E2 A Story of One Country (Hard)

    https://codeforces.com/contest/1181/problem/E2 想到了划分的方法跟题解一样,但是没理清楚复杂度,很难受. 看了题解觉得很有道理,还是自己太菜了. 然后直接 ...

  3. SpringMVC接收请求参数和页面传参

    接收请求参数: 1,使用HttpServletRequest获取 @RequestMapping("/login.do") public String login(HttpServ ...

  4. C++入门经典-例6.22-字符串与数组,string类型的数组

    1:数组中存储的数据也可以是string类型的.代码如下: // 6.22.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include ...

  5. Appium+Robotframework实现iOS应用的自动化测试

    Appium+Robotframework实现iOS应用的自动化测试 连接地址: 地址:https://blog.csdn.net/wd168/article/month/2016/06 1.http ...

  6. 5.Hiveguigun滚(ノ`Д)ノ竟然竞争谨慎谨慎谨慎哈喇子罢工八公

    1.Hive简介 2.Hive部署与安装 3.Hive的使用 4.Hive JDBC编程

  7. git远程删除分支后,本地执行git branch -a依然能看到删除分支到底该咋整?

    使用命令git branch -a可以查看所有本地分支和远程分支(git branch -r 可以只查看远程分支) 如果发现很多在远程仓库已经删除的分支在本地依然可以看到到底该怎么办呢?(反正强迫症受 ...

  8. LoadRunner脚本编写之一

    LoadRunner脚本编写之一 性能测试工程师要懂代码么?答案是必须的.好多测试员认为在loadrunner中编写脚本很难很牛X ,主要是大多测试人员并未做过开发工作,大学的那点程序基础也忘记的差不 ...

  9. linux常用命令(21)tar命令

    通过SSH访问服务器,难免会要用到压缩,解压缩,打包,解包等,这时候tar命令就是是必不可少的一个功能强大的工具.linux中最流行的tar是麻雀虽小,五脏俱全,功能强大. tar命令可以为linux ...

  10. Void pointers in C

    In this article we are learning about “void pointers” in C language. Before going further it will be ...