0717-7821348
关于我们

爱彩人官网下载

您现在的位置: 首页 > 关于我们 > 爱彩人官网下载
C51编程中几个重要模块
2019-05-11 23:18:15
Keil 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() < MAX_WAITINGTIME)
{
if(CheckSomething() == TRUE)
{
bitDone = TRUE;
break;
}
}
if(bitDone == FALSE)
{
// process time out
}
或许简略写成:
BIT bitDone = FALSE;
ResetClock();
while(GetClock() < MAX_WAITINGTIME && (bitDone = CheckSomething));
if(bitDone == FLASE)
{
// …
}
软件看门狗:完结具有局限性的看门狗功C51编程中几个重要模块用。在程序中适宜的当地参加对软件看门狗的复位函数ResetWatchDog(),在Config.h中参加宏TIMER_WATCHDOGTIMEOUT。当程序运转时,假如在发作TIMER_WATCHDOGTIMEOUT次时钟中止之内没有复位软件看门狗, 则体系复位。
留意:
假如没有参加TIMER_WATCHDOGTIMEOUT宏,程序中的ResetWatchDog没有任何用途,不必删去。
假如体系不能完结时钟中止,则软件看门狗也一起失掉功用。
现在版别的的时钟模块的复位功用并不是彻底复位,首要体现在当复位之后,体系将不再呼应任何中止。所以软件看门狗仅仅一个程序的调试功用,不应该将它用于正式作业的程序,此刻应该运用硬件看门狗。
用户自界说使命:假如想在时钟中止内履行一些耗时较短的使命,能够界说回调函数OnTimerInterrupt。函数原形为:void OnTimerInterrupt();
假如想在发作时钟中止时履行一些功用,而这些功用又耗时相对较长,不适宜放在中止呼应函数内部,则能够在程序中的主循环中的恣意当地添加: ImpTimerService(),一起供给原形为void OnTimerEvent()的回调函数。具体的程序如下所示:
void main()
{
Initialize();
while(TRUE)
{
// … working
ImpTimerService();
// … working
}
}
void OnTimerEvent()
{
// do some task
}
对通讯模块供给支撑:如通讯中的各种超时等,见通讯模块中的具体阐明。
对键盘扫描模块供给支撑:能够主动调用键盘扫描模块,见键盘扫描模块中的具体阐明。
对程序调试供给支撑:在程序开发进程中,有时为了判别程序是不是在作业,常用运用单片机体系的某一闲暇引脚通过一个限流电阻接一个发光二极管,在程序中距离固守时刻替换操控发光管的明暗。完结这个功用只要在Config.h文件中界说TIMER_FLASHLED宏,如:
#define TIMER_FLASHLED P1_0
则当时钟中止发作256次之后,改动发光管的状况。
通讯模块
串口资源做为单片机与外界通讯的常用手法,通讯模块供给了彻底缓冲的串口通讯底层机制,适用于长度不大的数据包的发送及接纳。假如处理要害数据,需求用户自己供给纠错协议。
通讯模块由声明文件SComm.h及完结文件SComm.c组成。
初始化:调用函数InitSCommModule()来初始化通讯模块:
void InitSCommModule(BYTE byTimerReload, BIT bitTurbo)
参数阐明:
byTimerReload: 守时器1的重装载初始值。
bitTurob: 当此参数为TRUE时,串行通讯在守时器1的溢出速率基础上加倍。为FALSE时,串行通讯速率为守时器1的溢出速率。
缓冲区:模块运用了由宏SCOMM_SENDBUFSIZE、SCOMM_RECEBUFSIZE及SCOMM_PKGBUFSIZE所指定长度的三个缓冲区,别离为发送、接纳及数据包(用于处理接纳到的数据)缓冲区(假如没有运用异步接纳功用,则不需求运用数据包缓冲区)。
在缺省时,这三个宏都被界说为10,但用户能够自已依照体系的RAM资源占用状况在Config.h中重界说缓冲区的巨细。需求留意的是,假如缓冲的长度不行,当发送或接纳长数据包的时分可能会发作问题,关于数据缓冲区的最小值的设置能够参阅下面的阐明。
留意:需求赶快取出接纳缓冲区中的数据,不然当缓冲区满之后,新的数据将被简略的丢掉。

字节级服务函数: 在Config.h文件中界说了宏SCOMM_DriverInterface(如:#define SCOMM_DriverInterface),则能够运用字节级服务函数,即通讯模块的底层函数。
共有两个函数能够运用:
void SendByte(BYTE byData);
发送一个字节,假如当时缓冲区满,则等候。参数byData为要发送的数据。
BYTE ReceByte();
接纳一个字节,假如当时缓冲区中没有数据,则此函数堵塞,直到接纳到数据停止。接纳到数据通过回来值回来。
能够通过调用IsSendBufEmpty() IsSendBufFull() IsReceBufEmpty() IsReceBufFull() 宏来判别缓冲区的空或满,以防体系堵塞。
不引荐直接运用这一级的服务函数,应该运用高层次上的服务函数或许在这一级服务函数的基础上结构自己的通讯函数。

数据包级服务函数:在Config.h文件中界说宏SCOMM_PackageInterface(如: #define SCOMM_PackageInterface)则能够运用数据包级服务函数。
共有两个函数能够运用:
void SendPackage(BYTE* pbyData, BYTE byLen);
发送数据包,参数pbyData为即将发送的数据包缓冲区(数组)的指针,byLen为即将发送的数据包的长度。
当没有界说SCOMM_DriverInterface时,数据被彻底缓冲。即不能够发送长度超越发送缓冲区长度的数据包。当界说了SCOMM_DriverInterface时,选用单字节发送,这时不约束需求发送的数据的长度。

BYTE RecePackage(BYTE* pbyData, BYTE byLen);
接纳数据包,参数pbyData为寄存即将接纳的数据的缓冲区,byLen为缓冲区长度。回来值为接纳到的字节数,当模块的接纳缓冲区为空时,函数非堵塞,当即回来,回来值为零。
同步发送接纳服务函数:
比方在一个串行总线多机通讯体系中,主机需求守时循检各从机的状况,往往是发一个包括从机地址及指令的数据包给从机,之后等候必定的时刻,从机需求在这段时刻之内给主机一个应对,假如没有这个应对,则以为从机作业状况犯错,转去进行相应的处理。在这个模型里,主机不能够不进行等候而给另一台从机发送指令,也不能够不论从机在好久没有应对的状况下持续等候。还有一种状况,比方当运用485总线进行通讯时,假如是两条通讯线则体系只能作业在半双工形式下,总线在同一时刻内只能作业在发送或接纳, 为了避免发送和接纳彼此搅扰,这时的通讯常常需求运用同步发送和接纳。
当在Config.h文件中界说宏SCOMM_SyncInterface后,则能够运用通讯模块供给同步发送接纳函数:
void SendPackage(BYTE* pbyData, BYTE byLen);
发送数据包,参数pbyData为即将改进的数据包的缓冲区指针,byLen为即将发送的数据包的长度。
这个函数能够确保等候一个完好的数据包彻底发送出去之后,它才回来,在这段时刻内,它会堵塞运转。
BYTE SyncRecePackage(BYTC51编程中几个重要模块E* pbyBuf, BYTE byBufLen, WORD wTimeout, BYTE byParam);
接纳数据包。回来值为接纳到的数据包长度。参数pbyBuf为即将接纳数据包的缓冲区的指针,byBufLen为供给的缓冲区的长度,wTimeout为通讯超时值,假如在发作了由wTimeout所指定次数的时钟中止而还没有接纳到或没有接纳到完好的数据包时,函数回来零,最终一个参数byParam的意义见后边的解说。

异步发送接纳服务函数:
在一个简略的体系或多机通讯体系中的从机上,一般状况下不需求杂乱的停等的作业形式,并且往往单片机需求对硬件进行操控和检测,不允许长时刻的停下来检测通讯,但又要求当需求通讯时需求赶快的反应速度,这时就需求运用异步发送和接纳服务函数。
运用异步发送和接纳服务函数需求在Config.h文件中界说SCOMM_AsyncInterface宏。
相同供给两个服务函数:
void SendPackage(BYTE* pbyData, BYTE byLen);
发送数据包,参数pbyData为即将改进的数据包的缓冲区指针,byLen为即将发送的数据包的长度。
这儿的函数的接口与同步发送和接纳的服务函数相同。关于这儿的细节,见后边对同步和异步服务函数的阐明。
void AsyncRecePackage(BYTE byParsoundsam);
接纳数据包,参数byParam的意义见后边的描绘。
运用异步通讯需求用户界说一个回调函数,原型如下:
void OnRecePackage(BYTE* pbyData, BYTE byBufLen);
当异步接纳服务函数接纳到数据包之后,调用OnRecePackage回调函数,在pbyData指定的缓冲区中寄存数据包,byBufLen为数据包的长度。
在Config.h文件中界说宏SCOMM_TIMEOUT能够设定异步接纳的超时值,当开端接纳数据包,但没有收完数据而发作了SCOMM_TIMEOUT次时钟中止后,以为接纳超时, 将已接纳到的数据删去。

同步和异步通讯服务函数:
有些状况下,比方一个通讯体系中,由一台计算机通过串口操控主机,主机通过串口衔接许多从机,主机的串口选用分时复用,在这样的模型中,主机和操控计算机之间的通讯能够运用,异步通讯办C51编程中几个重要模块法,而主机与从机能够运用同步通讯办法。而同步和异步的发送函数接口是相同的,在这样的状况下,发送都是同步的。在这样的模型中,当运用不同的接纳函数之前,需求留意铲除接纳缓冲区中的内容,通讯模块供给函数:ClearReceBuffer来做到这一点,此函数原型如下:
void ClearReceBuffer();
通讯进程中,数据包往往是有固定的格局的,这种格局需求依据用户所运用的协议的不同而不同。同步和异步接纳服务函数支撑从接纳到的数据中辨认出必定格局的数据包。
举例阐明:现在运用的协议决议数据包的格局为固定的包头0xff,固定的长度4个字节。其它的细节在这儿不重要,所以疏忽掉。
为了能够运用用SyncRecePackage或AsyncRecePackage函数从接纳到的数据中辨认出如上格局的数据包,有两种办法:
第一种办法是在Config.h文件中界说宏SCOMM_SimplePackageFormat,阐明数据包为一种简略格局,比方上面的协议。
之后还要界说两个宏别离用来辨认数据包头和数据包尾,两个宏别离是:
IsPackageHeader(x)和IsPackageTailer(x, y, z)
接纳函数(SyncRecePackage和AsyncRecePackage)在没有开端接纳数据包(精确的说是还没有从接纳到的数据包中找到包头的时分),会对接纳到的每一个字节的数据调用IsPackageHeader宏,将相应的数据作为参数,假如IsPackageHeader宏的成果为TRUE,则以为找到了数据包头,不然持续对下一个字节进行判别。
上面的协议对应的IsPackageHeader宏能够写为:
#define IsPackageHeader(x) ((x) == 0xff)
当接纳到包头之后,接纳函数会对接下来的每一个字节数据调用IsPackagTailer宏来判别是不是现已接纳完数据包,三个参数别离为:
x: 当时判别的数据。
y: 从包头开端到当时被判别的数据止的计数值,即当时现已接纳到的字节数。
z:用户在调用SyncRecePackage或AsyncRecePackage时指定的byParam参数。
与IsPackageHeader类似,假如宏IsPackageTailer的运算成果为TRUE,则以为接纳 到完好的数据包,则调用相应的回调函数(关于异步接纳函数)或回来(关于同步接纳函数)。假如运算成果为FALSE则持续判别下一个字节的数据。
上面的协议对应的IsPackageTailer宏能够写为:
#define IsPackageTailer(x, y, z) ((y) >= (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,之后被复位为零。