DIY编程器网

标题: 单片机C51编程几个有用的模块 [打印本页]

作者: liyf    时间: 2011-5-10 22:56
标题: 单片机C51编程几个有用的模块
单片机  C51  编程几个有用的模块

            本文档包括单片机系统中常用到的时钟中断、通讯及键盘扫描等模块(见所附源程序)的说明。这些模块使用前后台系统模型。为达到最大的灵活性, 需要在用户工程中定义config.h文件, 在其中定义各模块可选参数的设置,而不是直接更改源代码。
这些可选内容大部分为宏定义,如果不定义宏相应的功能在编译时被屏蔽,不会增加代码长度。 具体可选内容见各模块中的说明。
在Config.h文件中还要包含一个单片机硬件的资源头文件。
各模块使用了定义在Common.h中的一些数据类型。如:BIT(bit) BYTE(unsigned char)等,具体请参见源程序。

时钟模块
  在单片机软件设计中, 时钟是重要资源, 为了充分利用时钟资源, 故设计本时钟模块。 本模块使用定时器0,在完成用户指定功能的同时, 还能够自动处理一些其它模块中与时钟相关的信息。
  时钟模块由声明文件Timer.h以及实现文件Timer.c组成。
  用户应该在Config.h中定义宏TIMER_RELOAD来设定定时器0的重装载初值。推荐的定时器0的中断时间大于1毫秒。

  在程序的初始化阶段调用时钟模块的初始化函数InitTimerModule()之后,就可以使用时钟模块所以支持的各种功能。具体描述如下:
延时:当用户需要进行一定时间的延时时,可以通过调用Delay()来进行,参数为时钟中断的次数。如时钟中断周期为1ms, 想进行100ms的延时, 则可以调用Delay(100)。
注意:
如果延时的绝对时间小于时钟中断的周期,则不能够用本方法做到延时。

定时:当程序中需要使用定时功能时,如等待某外部事件,如果在一定时间内发生则继续执行,如果在这段时间内发生,则认为出现错误,转向错误处理机制。
在此推荐一种编程模式,但用户可以用自己认为更合理的方式处理此类问题。
这里简单说明一下关于阻塞式函数及非阻塞式函数。简单说,阻塞式函数就是当检测完成条件,如果不能够完成则等待,如:
void CheckSomething()
{
  // gbitSuccessFlag is a global variable
  while(gbitSuccessFlag == FALSE)
  {
    // do nothing but waiting
  }
}
可以看到,当bitSuccessFlag没有被设置为TRUE时,函数保持等待状态不返回,这样就是阻塞式的函数。
另外一种情况:
BIT CheckSomething()
{
  if(gbitSuccessFlag == TRUE)
  {
    // …
    return TRUE;
  }
  return FALSE;
}
在这里,如果所检测的事件有没有完成,函数进行检测之后,立刻返回,通过返回值报告完成情况,如果没有完成,则等待调用者分配再次执行的机会。这样的函数就是非阻塞函数。
在应用定时功能时,首先要将检测函数定义成非阻塞函数。如上面的第二个版本的CheckSomething。
然后下面模式:
BIT bitDone = FALSE;
ResetClock(); // clear timer interrupt times counter
while(GetClock() = (z))
当然,用户也可以将IsPackageHeader和IsPackageTailer定义成为函数,通过BIT类型的返回值来向调用者提供与相应宏相同的信息。

另一种办法需要在Config.h文件中定义宏SCOMM_ComplexPackageFormat。(需要注意的是,不能够同时定义SCOMM_SimplePackageFormat和SCOMM_ComplexPackageFormat宏,否则会造成严重的不可预见性错误。
这时需要提供回调函数QueryPackageFormat,原形如下:
BYTE QueryPackageFormat(BYTE byData, BYTE byCount, BYTE byParam);
函数中三个参数的含义与使用简单数据包格式时判断数据包尾的宏的参数相同。
函数通过返回值来通知作为调用者的接收函数对接收到的数据如何处理,但目前这种方法仅为需要处理复杂数据包格式时的一种可选方法,但不推荐。用户如果想使用这种方法可以自己更改接收函数中相应的
#ifdef SCOM_ComplexPackageFormat
#endif // SCOMM_ComplexPackageFormat
预编译指令之间的内容。
例如指定QueryPackageFormat的返回值的含义:
0:继续找数据包头或继续找数据包尾。
1:找到数据包头。
2:找到数据包尾。
3:数据包出错,需要抛弃。
然后更改源代码来实现上面的协议。

注意:当用户需要使用字符串的时候,可以利用简单的包装函数将字符串转换为字节数组。所以没有必要提供专用的字符串处理函数。

键盘扫描模块
  键盘扫描模块有两种工作方式, 一种为自动的由时钟模块调用, 另一种是由程序员自行调用。
1) 由时钟模块自动调用的方式
将时钟模块实现文件(Timer.h)及键盘扫描模块的实现文件(KBScan。c)包含进工程, 在Config.h 文件中添加TIMER_KBSCANDELAY宏。 时钟模块自动对时钟中断进行计数, 当达到TIMER_KBSCANDELAY宏所定义的值后, 自动调用键盘扫描模块中的函数KBScanProcess()进行键盘扫描,也就是说,这个宏的值可以决定按键消抖动的时间。
用户应该提供两个回调函数OnKBScan()及onKeysPressed()。 在函数OnKBScan中进行键盘扫描, 并返回扫描码。 扫描码的类型缺省为BYTE, 当键盘规模较大时, BYTE不能够完全包含键盘信息时, 可在Config.h文件中重定义宏KBVALUE, 如下:
#define KBVALUE      WORD
这样, 就可以使用16位的键盘扫描码, 如果此时还达不到要求, 可以将键盘扫描码定义成一个结构, 但这样做将会增加代码量及消耗更多的RAM资源, 故不推荐。
  扫描模块调用OnKBScan取得扫描码, 并调用用户可以重定义的宏IsNoKeyPressed来判断是否有键按下, 缺省的IsNoKeyPressed实现如下:
#define IsNoKeyPressed(x)      ((x) == 0x00)  
即认为OnKBScan返回0扫描码时为没有键按下, 如果扫描函数返回其它非零扫描码做为无键按下的扫描码时, 可以在Config.h文件中重定义IsNoKeyPressed宏的实现。
  8位键盘扫描码(缺省值)时, 相应的扫描函数为:
BYTE OnKBScan()
  当扫描模块经过软件消抖动之后, 发现有键按下, 就会调用另一个回调函数onKeysPressed。 函数的声明应该如下:
void onKeyPressed(BYTE byKBValue, BYTE byState)
其中中的参数byKBValue的类型为BYTE, 此为缺省值, 如果使用其它类型的扫描码, 就将此参数变为相应类型。 这个值由OnKBScan返回。 另一个参数byState在通常情况下为零。 但当用户在Config.h中定义宏KBSCAN_BRUSTCOUNT, 同时键盘上的某键被按住不放时, 扫描模块对它自己的调用(注意这里和TIMER_KBSCANDELAY宏不同, TIMER_KBSCANDELAY是时钟中断足够的次数后调用扫描模块, 而KBSCAN_BRUSHCOUNT为扫描模块自身的被调用次数)进行计数,当达到KBSCAN_BRUSTCOUNT时,扫描模块调用onKeysPressed,此时第一个参数的含义不变, 而byState变成1, 同时计数器复位,又经过一段时间后,用值为3的byState 调用onKeysPressed。 这样就可以很方便的实现多功能键或者检测某键的长时间被按下。
2)由用户自行调用
由用户自行在程序中调用扫描模块,而不是由时钟中断自行调用。其它与方式1相同。

注意:
1) 函数KBScanProcess为非阻塞函数,它将在很快的时间内返回,等待再次分配给它执行的机会。
2) 函数KBScanProcess是在时钟中断外部运行的,它的过程可以被任何中断打断,但不影响系统运行。
3) byState的最大值为250,之后被复位为零。

应用举例
  现在来举例说明上述几个模块的使用方法。
  硬件环境描述:
  为了控制一盏灯,需要单片机提供一个做控制功能的开关量,这里不描述外部接口电路,只说明当单片机的P10脚为高电平时,灯灭,当P10脚为低电平时,灯亮。
可以通过计算机由串口发送命令来控制,或通过一个按键(push button不是自锁式的按键)来手动控制(按键接在P11脚上,当键没有按下时,P11电平为高,键按下时,引脚电平被接低),当使用按键手动控制的时候,需要给计算机发送通知。
设定串口通讯指令如下:
数据包由0xff做包头,4个字节长,第二个字节为命令代码,第三个字节为数据,最后一个字节为校验位。
命令和数据代码有如下组合:
(计算机发给单片机)
0x10 0x01: 计算机控制灯亮。(数据位是非零值即可)
0x10 0x00: 计算机控制灯灭。
(单片机发给计算机)
0x11 0x01:单片机正常执行控制指令,返回。(数据位是非零值即可)
0x11 0x00: 单片机不能够正常执行控制指令,或控制指令错(不明含义的数据包或校验错等)。
0x12 0x01:手动控制灯亮。(数据位是非零值即可)
0x12 0x00: 手动控制灯灭。

  建立工程:
  在硬盘上建立文件夹Projects,在Projects下建立Common文件夹及Example文件夹。将各模块的头文件及实现文件拷贝到Common文件夹下(推荐使用这样的文件组织结构,其它工程也可以建立在Projects下,各工程共享Common文件夹中的代码)。
  启动KeilC的IDE,在Example下建立新工程,将各模块的实现文件包含进工程。
  在Example文件夹下建立Output文件夹,更改工程设置,将Output作为输出文件和List文件的输出文件夹(推荐使用这样的结构,当保存工程文件时,可以简单的删除Output文件夹中的内容而不会误删有用的工程文件)。
  建立工程配置头文件Config.h及工程主文件Example.c,并将Exmaple.c文件加入工程。

  输入代码:
  代码的具体编写过程略。下面是最后的Config.h文件及Example.c文件。
//
// file: Config.h
//
#ifndef _CONFIG_H_
#define _CONFIG_H_
#include          // 使用AT89C52做控制
#include “../Common/Common.h”        // 使用自定义的数据类型
#define TIMER_RELOAD      922    // 11.0592MHz晶振,1ms中断周期
#define TIMER_KBSCANDELAY  40    // 40ms重检测按键状态,即40ms消抖
#define SCOMM_AsyncInterface        // 使用异步通讯服务
#define IsPackageHeader(x)    ((x) == 0xff)  // 判断包头是不是0xff
#define IsPackageTailer(x, y, z)  ((y)
#include “../Common/Common.h”
#include “../Common/Timer.h”
#include “../Common/Scomm.h”
#include “../Common/KBScan.h”

BIT gbitLampState = 1;            // 灯的状态,缺省为off

static void Initialize()
{
  InitTimerModule();            // 初始化时钟模块
  InitSCommModule(0xfd, TRUE);      // 初始化通讯模块,11.0592MHz晶振,
                    // 波特率为19200
  EA = 1;                // 开中断
}

void main()
{
  Initialize();              // 初始化
  while(TRUE)              // 主循环
  {
    ImpTimerService();          // 实现时钟中断服务,如键盘扫描
    AsyncRecePackage(4);        // 接收4个字节长的数据包
}
}

// 在中断外部响应时钟中断事件
void OnTimerEvent()            
{
  // do nothing
}

// 控制外部灯
static void TriggerLamp(BIT bEnable)  
{
  P10 = "bEnable;            // 需要反相控制
}

// 键扫描回调函数
BYTE KBScan()  
{
  BIT b;
  P11 = 1;                // 读之前拉高引脚电平
  b = P11;                // 读入引脚状态
  return "b;                // 数据反相做扫描码
}

// 计算校验和
static BYTE CalcCheckSum(BYTE* pbyBuf, BYTE byLen)
{
  BYTE by, bySum = 0;
  for(by = 0; by < byLen; by++)
    bySum += pbyBuf[by];
  return 0 – bySum;
}

// 接收到键盘消息回调函数
void onKeyPressed(BYTE byValue, BYTE byState)
{
  BYTE by[4];
  if(byState == 0)
  {
    switch(byValue)
    {
    case 0x01:
      gbitLampState = "g  bitLampState;  // 灯状态取反
      TriggerLamp(gbitLampState);  // 执行控制
      by[0] = 0xff;          // 构造数据包
      by[1] = 0x12;
      by[2] = (BYTE)gbitLampState;
      by[3] = CalcCheckSum(by, 3);  // 求校验和
      SendPackage(by, 4);      // 发送数据包
      break;
    // 处理其它扫描码
    default:
      break;
  }
}

// 接收到数据包回调函数
void OnRecePackage(BYTE* pbyBuf, BYTE byBufLen)
{
  BYTE by[4];
  by[0] = 0xff;
  by[1] = 0x11;
  if(byBufLen != 4 || pbyBuf[3] != CalcCheckSum(pbyBuf, 3))
  {
    by[2] = 0;
    by[3] = CalcCheckSum(by, 3);
    SendPackage(by, 4);         // 处理长度或校验和不正确
  }

  switch(pbyBuf[1])
  {
  case 0x10:
    gbitLampState = (BIT)pbyBuf[2];
    TriggerLamp(gbitLampState);
    by[2] = 1;
    by[3] = CalcCheckSum(by, 3);
    SendPackage(by, 4);        // 发送成功执行通知
    break;

  default:                // 不知道的命令
    by[2] = 0;
    by[3] = CalcCheckSum(by, 3);
    SendPackage(by, 4);        // 发送没有成功执行通知
    break;
  }
}





欢迎光临 DIY编程器网 (http://diybcq.com/) Powered by Discuz! X3.2