Windows编程第十一回 三问计时器

本文详细介绍了Windows计时器的工作原理及其使用方法,包括如何通过SetTimer和KillTimer函数设置和停止计时器,以及如何处理WM_TIMER消息。此外,还讨论了计时器的精确度问题。

啥是计数器?

计时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。当你的程序将时间间隔告诉Windows,例如“每10秒钟通知我一声”,然后Windows给你的程序发送周期性发生的WM_TIMER消息以表示时间到了。

我们可以通过调用SetTimer函数为的Windows程序分配一个定时器。SetTimer有一个时间间隔范围为1毫秒到4,294,967,295毫秒(将近50天)的整型参数,这个值指示Windows每隔多久时间给程序发送WM_TIMER消息。例如,如果间隔为1000毫秒,那么Windows将每秒给程序发送一个WM_TIMER消息。

当你的程序用完定时器时,它调用KillTimer函数来停止计时器消息。在处理WM_TIMER消息时,你可以通过调用KillTimer函数来编写一个“瞬间”的定时器。KillTimer调用除了会销毁以前调用SetTimer创建的定时器事件,还会清除消息队列中尚未被处理的WM_TIMER消息,从而使程序在调用KillTimer之后就不会再接收到WM_TIMER消息。

下面就介绍一下计时器的使用方法吧。

计时器怎么用?

如果你需要在整个程序执行期间都使用计时器,那么你将得从WinMain函数中或者在处理WM_CREATE消息时调用SetTimer,并在退出WinMain或响应WM_DESTROY消息时调用KillTimer。根据调用SetTimer时使用的参数,可以选择以下两种方法之一来使用计时器。

 

方法一

这是最方便的一种方法,它让Windows把WM_TIMER消息发送到应用程序的正常窗口过程中,SetTimer调用如下所示:

SetTimer (hwnd, 1, uiMsecInterval, NULL) ;

第一个参数是其窗口过程将接收WM_TIMER消息的窗口句柄。第二个参数是定时器ID,它是一个非0数值,在整个例子中假定为1。第三个参数是一个32位无符号整数,以毫秒为单位指定一个时间间隔,一个60,000的值将使Windows每分钟发送一次WM_TIMER消息。

你可以通过调用

KillTimer (hwnd, 1) ;

在任何时刻停止WM_TIMER消息(即使正在处理WM_TIMER消息)。此函数的第二个参数是SetTimer调用中所用的同一个定时器ID。在终止程序之前,你应该在响应WM_DESTROY消息中停止任何活动的定时器。

当你的窗口过程收到一个WM_TIMER消息①时,wParam参数等于定时器的ID值(上述情形为1),lParam参数为0。为了使程序更具有可读性,您可以使用#define叙述定义不同的定时器ID:

#define TIMER_SEC 1

#define TIMER_MIN 2

然后你可以使用两个SetTimer调用来设定两个定时器:

SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;

SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;

WM_TIMER的处理如下所示:

caseWM_TIMER:

       

    switch (wParam)

       

    {

       

    case TIMER_SEC:

       

            //每秒一次的处理

       

                  break ;

       

    case TIMER_MIN:

       

            //每分钟一次的处理

       

            break ;

       

    }

       

return 0 ;

       

如果你想将一个已经存在的定时器设定为不同的时间间隔,您可以简单地用不同的时间值再次调用SetTimer。

 

方法二

设定计时器的第一种方法是把WM_TIMER消息发送到通常的窗口过程,而第二种方法是让Windows直接将计时器消息发送给你程序的另一个函数。

接收这些计时器消息的函数被称为回调函数,这是一个在你的程序之中但是由Windows调用的函数(在第四回曾提到)。你先告诉Windows此函数的地址,然后Windows调用此函数。这看起来也很熟悉,因为程序的窗口过程实际上也是一种回调函数。当注册窗口类时,要将函数的地址告诉Windows,当发送消息给程序时,Windows会调用此函数。

像窗口过程一样,回调函数也必须定义为CALLBACK,因为它是由Windows从程序的程序代码段调用的。callback函数的参数和callback函数的返回值取决于callback函数的目的。跟计时器有关的callback函数中,输入参数与窗口过程的输入参数一样。计时器callback函数不向Windows返回值。

我们把以下的callback函数称为TimerProc(你能够选择与其它一些用语不会发生冲突的任何名称),它只处理WM_TIMER消息:

VOID CALLBACK TimerProc (  HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)

       

{

       

           //处理WM_TIMER消息

       

}

       

TimerProc的参数hwnd是在调用SetTimer时指定的窗口句柄。Windows只把WM_TIMER消息送给TimerProc,因此消息参数message总是等于WM_TIMER。iTimerID值是计时器ID,dwTimer值是与从GetTickCount函数的返回值相容的值。这是自Windows启动后所经过的毫秒数。

用第一种方法设定计时器时要求下面格式的SetTimer调用:

SetTimer (hwnd, iTimerID, iMsecInterval, NULL) ;

你使用回调函数处理WM_TIMER消息时,SetTimer的第四个参数由回调函数的地址取代,如下所示:

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;

 

看个例子吧。

  1 #include <windows.h>
  2         
  3 #define ID_TIMER    1
  4         
  5 
  6 LRESULT     CALLBACK       WndProc   (HWND, UINT, WPARAM, LPARAM) ;
  7         
  8 VOID    CALLBACK   TimerProc (HWND, UINT, UINT,   DWORD ) ;
  9         
 10 
 11 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
 12         
 13                                                          PSTR szCmdLine, int iCmdShow)
 14         
 15 {
 16         
 17    static char   szAppName[]           = " Timer Demo " ;
 18         
 19            HWND                                 hwnd ;
 20         
 21            MSG                                  msg ;
 22         
 23            WNDCLASS                      wndclass ;
 24         
 25    
 26         
 27            wndclass.style                                       = CS_HREDRAW | CS_VREDRAW ;
 28         
 29            wndclass.lpfnWndProc                                 = WndProc ;
 30         
 31           wndclass.cbClsExtra                                  = 0 ;
 32         
 33            wndclass.cbWndExtra                                  = 0 ;
 34         
 35            wndclass.hInstance                                   = hInstance ;
 36         
 37            wndclass.hIcon                                       = LoadIcon (NULL, IDI_APPLICATION) ;
 38         
 39            wndclass.hCursor                                     = LoadCursor (NULL, IDC_ARROW) ;
 40         
 41           wndclass.hbrBackground                       = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
 42         
 43            wndclass.lpszMenuName                        = NULL ;
 44         
 45            wndclass.lpszClassName                       = szAppName ;
 46         
 47    
 48         
 49            if (!RegisterClass (&wndclass))
 50         
 51            {
 52         
 53                   MessageBox (  NULL, TEXT ("Program requires Windows NT!"),
 54         
 55                                                                         szAppName, MB_ICONERROR) ;
 56         
 57                   return 0 ;
 58         
 59            }
 60         
 61    
 62         
 63            hwnd = CreateWindow ( szAppName, "Timer Demo",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,
 70         
 71                         NULL, NULL, hInstance, NULL) ;
 72         
 73    
 74         
 75            ShowWindow (hwnd, iCmdShow) ;
 76         
 77            UpdateWindow (hwnd) ;
 78         
 79         
 80         
 81            while (GetMessage (&msg, NULL, 0, 0))
 82         
 83            {
 84         
 85                   TranslateMessage (&msg) ;
 86         
 87                   DispatchMessage (&msg) ;
 88         
 89            }
 90         
 91            return msg.wParam ;
 92         
 93 }
 94         
 95 
 96 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 97         
 98 {
 99         
100    switch (message)
101         
102     {
103         
104            case   WM_CREATE:
105         
106                   SetTimer (hwnd, ID_TIMER, 1000, TimerProc) ;
107         
108                  return 0 ;
109         
110        
111         
112            case   WM_DESTROY:
113         
114                   KillTimer (hwnd, ID_TIMER) ;
115         
116                   PostQuitMessage (0) ;
117         
118                   return 0 ;
119         
120            }
121         
122            return DefWindowProc (hwnd, message, wParam, lParam) ;
123         
124 }
125         
126 
127 VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
128         
129 {
130         
131            static BOOL fFlipFlop = FALSE ;
132         
133            HBRUSH                        hBrush ;
134         
135            HDC                                  hdc ;
136         
137            RECT                                 rc ;
138        
139            fFlipFlop = !fFlipFlop ;
140         
141    
142         
143            GetClientRect (hwnd, &rc) ;
144         
145           hdc = GetDC (hwnd) ;
146         
147     hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ;
148                               //括号里的这种判断语句我估计大家都懂,就不解释了
149    
150         
151     FillRect (hdc, &rc, hBrush) ;
152         
153    ReleaseDC (hwnd, hdc) ;
154         
155    DeleteObject (hBrush) ;
156         
157 }

这里计时器的时间间隔设定为1秒。当它收到WM_TIMER消息时,它将显示区域的颜色由蓝色变为红色或由红色变为蓝色。

程序在窗口过程处理WM_CREATE消息时设定计时器。在处理WM_TIMER消息处理期间,翻转bFlipFlop的值并使窗口无效以产生WM_PAINT消息。在处理WM_PAINT消息处理期间,通过调用GetClientRect获得窗口大小的RECT结构,并通过调用FillRect改变窗口的颜色。

计时器精确吗?

很可惜它不精确,原因如下。

原因一:Windows计时器是PC硬件和ROM BIOS构造的计时器逻辑的一种相对简单的扩展。回到Windows以前的MS-DOS编程,应用程序能够通过捕获称为timer tick的BIOS中断来实现时钟或计时器。这些中断每54.915毫秒产生一 次,或者大约每秒18.2次。一些为MS-DOS编写的程序自己捕获这个硬件中断以实现时钟和计时器。这是原始的IBM PC的微处理器频率值4.772720 MHz被262144所除而得出的结果。Windows应用程序不拦截BIOS中断,相反地,Windows本身处理硬件中断,这样应用程序就不必进行处理。在Windows 98中,计时器与其下的PC计时器一样具有55毫秒的分辨率,在Microsoft Windows NT中,计时器的分辨率为10毫秒。即Windows应用程序不能以高于这些分辨率的频率(在Windows 98下,每秒18.2次,在Windows NT下,每秒大约100次)接收WM_TIMER消息。在SetTimer中指定的时间间隔总是截尾后tick数的整数倍。例如,1000毫秒的间隔除以 54.925毫秒,得到18.207个tick,截尾后是18个tick,它实际上是989毫秒。对每个小于55毫秒的间隔,每个tick都会产生一个 WM_TIMER消息。 

  可见,计时器并不能严格按照指定的时间间隔发送WM_TIMER消息,它总要相差那么几毫秒。 

 

  即使忽略这几个毫秒的差别,计时器仍然不精确。请看原因二: 

   WM_TIMER消息放在正常的消息队列之中,和其他消息排列在一起,因此,如果在SetTimer中指定间隔为1000毫秒,那么不能保证程序每 1000毫秒或者989毫秒就会收到一个WM_TIMER消息。如果其他程序的运行时间超过一秒,在此期间内,你的程序将收不到任何WM_TIMER消息。事实上, Windows对WM_TIMER消息的处理非常类似于对WM_PAINT消息的处理,这两个消息都是低优先级的,程序只有在消息队列中没有其他消息时才接收它们。 

  WM_TIMER还在另一方面和WM_PAINT相似:Windows不能持续向消息队列中放入多个 WM_TIMER消息,而是将多余的WM_TIMER消息组合成一个消息。因此,应用程序不会一次收到多个这样的消息,尽管可能在短时间内得到两个 WM_TIMER消息。应用程序不能确定这种处理方式所导致的WM_TIMER消息“丢失”的数目。 

  可见,WM_TIMER消息并不能及时被应用程序所处理,WM_TIMER在消息队列中的延误可能就不能用毫秒来计算了。 

 

   由以上两点,你不能通过在处理WM_TIMER时一秒一秒计数的方法来计时。如果要实现一个时钟程序,可以使用系统的时间函数如 GetLocalTime ,而在时钟程序中,计时器的作用是定时调用GetLocalTime获得新的时间并刷新时钟画面,当然这个刷新的间隔要等于或小于1秒。 

①WM_TIMER消息。

    wParam为计数器的ID:如果需要设定多个计时器,那么对每个计时器都使用不同的计

时器ID。wParam的值将随传递到窗口过程的WM_TIMER消息的不同而不同。

    lParam为指向TimerProc的指针,如果调用SetTimer时没有指定TimerProc(其参数值

为NULL,即第一种用法),则lParam为0,显然在第二种用法中此值就不为0了。

 注:部分内容参考《Windows计时器》一文

转载于:https://www.cnblogs.com/hu-jiacheng/archive/2012/04/29/2476346.html

<think>嗯,用户的是蓝桥杯单片机组比赛,还有二十多天就要开始了,想知道必须学习哪些技能,特别是针对只想做对一道大题的学生,还有选择题的推荐。首先,我需要分析用户的真实需求。用户可能时间紧迫,想高效备考,重点突破,确保至少拿下一道大题和选择题的部分分数。 首先,蓝桥杯单片机组通常考察的是CT107D开发板,基于8051单片机。用户需要熟悉这块板子的硬件结构,比如LED、数码管、矩阵键盘、EEPROM、DS18B20、PCF8591等外设。对于大题,可能涉及这些外设的综合应用,比如定时器、中断、按键扫描、显示驱动等。 用户可能基础一般,所以需要重点抓高频考点。大题方面,数码管动态显示、LED控制、按键处理、定时器中断这些几乎是必考的。比如,数码管显示可能需要动态扫描,结合按键输入修改显示内容,或者用定时器来做计时,同时处理按键中断。所以,必须掌握这些模块的驱动代码,能够快速写出框架。 然后是外设驱动,比如PCF8591的ADC/DAC,DS18B20的温度读取,EEPROM的存储。这些可能需要I2C或单总线的协议,代码结构比较固定,用户需要理解时序,能默写常用函数。比如DS18B20的初始化、温度读取函数,PCF8591的读写函数。 对于系统设计题,可能需要综合多个模块,比如温度采集后显示,通过按键调整阈值,控制继电器或LED。这时候需要模块化编程,合理分配定时器中断处理各个任务,避免阻塞。所以,状态机编程和定时器分时处理可能是关键。 选择题部分,用户需要复习单片机基础知识,比如定时器配置、中断优先级、存储器结构、指令周期计算,以及常见的电路知识,如74HC138译码器、锁存器选择、数码管段码计算等。刷历年真题和模拟题很重要,可以快速熟悉考点。 时间分配方面,用户应该先确保大题的基础部分,比如数码管和按键,这些占分多且容易掌握。然后针对外设驱动,重点突破一两个,比如PCF8591和DS18B20。选择题则通过刷题记忆知识点,尤其是计算题和配置题。 可能用户的题在于时间紧张,所以需要高效复习策略。建议先掌握必考模块的代码,再结合真题练习,熟悉题型。同时,注意代码的模块化和调试技巧,比如利用断点或LED指示程序运行状态,避免在比赛中卡壳。 另外,用户可能担心编程时的细节错误,比如变量定义、函数声明,或者头文件包含。所以,强调代码结构的规范性,比如模块分离、函数封装,可以减少错误。还有,开发环境的使用,比如Keil的编译设置,下载步骤,也需要熟悉,避免比赛时操作失误。 总结下来,用户的诉求是短期内有效备考,重点拿下大题和选择题。需要明确高频考点,针对性练习,掌握核心代码和知识点,配合真题训练。同时,时间管理和调试技巧也很重要,确保在比赛中能稳定发挥。</think>好的,我来帮你梳理蓝桥杯单片机组备赛的核心要点(针对20天冲刺)。根据比赛特点和评分规则,建议优先攻克以下内容: --- ### **一、必须掌握的技能(针对大题)** **1. 基础模块(必考,占分高)** - **数码管动态显示**:必须熟练写出动态扫描代码(含消影处理),能结合按键修改显示内容。 ```c // 示例:数码管位选和段选控制(需根据具体硬件修改) void Display() { static char pos = 0; P2 = (P2 & 0x1F) | 0xE0; // 位选锁存器使能 P0 = seg_code[display_buffer[pos]]; // 段码输出 P2 &= 0x1F; // 锁存 // 切换位选 P2 = (P2 & 0x1F) | 0xC0; P0 = 0x01 << pos; // 位选信号 P2 &= 0x1F; pos = (pos + 1) % 8; } ``` - **LED控制**:掌握74HC138译码器原理,能通过锁存器控制LED(如流水灯、二进制计数)。 - **矩阵键盘扫描**:必须独立写出4x4矩阵键盘扫描代码(含消抖),并能处理长按/短按。 **2. 外设驱动(高频考点)** - **PCF8591(ADC/DAC)**:掌握I²C协议时序,能读取光敏电阻、电位器数值,输出模拟信号。 - **DS18B20温度传感器**:熟记单总线协议,能写出温度读取函数(注意时序严格性)。 - **EEPROM(AT24C02)**:掌握页写入和随机读取(需处理I²C应答)。 **3. 系统设计(综合题)** - **定时器中断框架**:用定时器0/1实现1ms时基,结合状态机处理多任务(显示、按键、传感器)。 - **模块化编程**:将各外设驱动拆分为独立.c/.h文件,主函数通过状态标志调用。 --- ### **二、选择题备考策略** **1. 高频考点分类** - **硬件基础**:74HC138译码器真值表、锁存器选择(如74HC573)、数码管共阳/共阴段码计算。 - **单片机配置**:定时器初值计算(如12MHz下50ms中断)、中断优先级设置、TMOD寄存器配置。 - **C语言细节**:指针与数组操作、`code`/`xdata`关键字、位操作(如`sbit P00 = P0^0;`)。 **2. 推荐复习资料** - **官方推荐教材**:《单片机原理与接口技术》(重点看定时器、中断、串口章节)。 - **历年真题**:至少刷近3年选择题,重点记录重复考点(如定时器初值计算每年必考)。 - **速记技巧**: - 定时器初值公式:$$ TH = (65536 - t \cdot f_{osc}/12) / 256 $$ - 共阳数码管段码:0xC0对应数字“0”(需根据硬件验证)。 --- ### **、20天冲刺计划** **1. 第一阶段(10天):模块突破** - **Day 1-3**:数码管+LED+按键(完成一个计时器:按键调整时间,数码管显示)。 - **Day 4-6**:PCF8591+DS18B20(实现温度-光照采集,数码管显示)。 - **Day 7-8**:EEPROM存储(保存温度阈值,上电恢复)。 - **Day 9-10**:综合训练(真题大题,限时2小时/题)。 **2. 第二阶段(7天):选择题+调试技巧** - 每天刷50道选择题(错题标记重复刷)。 - 练习Keil调试:断点设置、变量监视、汇编单步执行。 **3. 第阶段(3天):全真模拟** - 使用官方模拟平台,按比赛时长(5小时)完成1套真题。 --- ### **四、考场技巧** 1. **大题保底策略**:优先实现基础功能(如数码管显示、按键输入),再补充高级功能(如EEPROM存储)。 2. **代码复用**:提前准备外设驱动模板(如I²C、单总线),比赛时直接粘贴。 3. **选择题猜题技巧**:优先排除明显错误选项(如涉及`EA=0`关闭总中断的选项通常错误)。 --- **附:推荐资源** - 蓝桥杯官网历届真题(必做) - 《CT107D实训指导书》(重点看第4章外设驱动) - 普中科技51单片机开发板配套例程(模块化代码参考) 按此计划,20天足够攻克至少1道大题和60%选择题!如有具体题可继续提
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值