2007-04-06
评论4
楼主 2007/4/3 19:38:04
单片机C51编程几个有用的模块(2)
应用举例
现在来举例说明上述几个模块的使用方法。
硬件环境描述:
为了控制一盏灯,需要单片机提供一个做控制功能的开关量,这里不描述外部接口电路,只说明当单片机的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 <Atmel/At89x52.h> // 使用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) <= (z)) // 判断包的长度是不是足够
#endif // _CONFIG_H_
//
// File: Example.c
//
#include <Atmail/At89x52.h>
#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;
}
}
楼主 2007/4/3 19:39:12
8051的汇编控制指令,占用字节,执行周期列表 |
|
楼主 2007/4/3 19:40:07
现在单片机的程序设计,C51已经得到广泛的推广和应用,算是单片机的主流设计程序,甚至可以说作为单片机开发人员必须要掌握的一门语言了。
作为一门工具,最终的目的就是实现功能。在满足这个前提条件下,我们希望我们的程序能很容易地被别人读懂,或者能够很容易地读懂别人的程序,在团体合作开发中就能起到事半功倍之效。在网上请求帮助时,如能以规范的写法贴出程序,网友会比较容易地明白你的问题,则会比较快的得到网友的帮助,否则让人看上半天也不明所以然,这样就达不到预期的效果了。因此,为了便于源程序的交流,减少合作开发中的障碍,希望大家能够探讨一下C51的编程规范。把各人认为好的建议提出来,然后做一个总结,作为一种大家一致认同的规范,我认为将会是一件很有意义的事。我先提出一些自已的想法,以此抛砖引玉。
一、注释
1,采用中文;
2,开始的注释:
文件(模块)注释内容:
公司名称、版权、作者名称、修改时间、模块功能、背景介绍等,复杂的算法需要加上流程说明;
比如:
/*********************************************************************/
/*公司名称: */
/*模 块 名: LCD 模块 LCD 型号:HD44780 */
/*创 建 人:zhaojunjie 日期:2001-06-08 */
/*修 改 人: 日期:2001-06-08 */
/*功能描述: */
/*其他说明: */
/*版 本:
/**********************************************************************/
函数开头的注释内容:
函数名称、功能、说明 输入、返回、函数描述、流程处理、全局变量、调用样例等,复杂的函数需要加上变量用途说明;
/*********************************************************************
*
* 函 数 名: v_LcdInit
* 功能描述: LCD初始化
* 函数说明: 初始化命令:0x3c, 0x08, 0x01, 0x06, 0x10, 0x0c
* 调用函数: v_Delaymsec(),v_LcdCmd()
* 全局变量:
* 输 入: 无
* 返 回: 无
* 设 计 者:zhao 日期:2001-12-09
* 修 改 者:zhao 日期:2001-12-09
* 版 本:
***********************************************************************/
3、程序中的注释内容:
修改时间和作者、方便理解的注释等。注释内容应简炼、清楚、明了,一目了然的语句不加注释。
二、命名:
命名必须具有一定的实际意义。
1、常量的命名:全部用大写。
2、变量的命名:
变量名加前缀,前缀反映变量的数据类型,用小写,反映变量意义的第一个字母大写,其他小写。
其中变量数据类型:
unsigned char 前缀 uc signed char 前缀 sc
unsigned int 前缀 ui signed int 前缀 si
unsigned long 前缀 ul signed long 前缀 sl
bit 前缀 b 指针 前缀 p
例:ucReceivData 接收数据
3、结构体命名:
4、函数的命名:
函数名首字大写,若包含有两个单词的每个单词首字母大写。
函数原型说明包括:引用外来函数及内部函数,外部引用必须在右侧注明函数来源: 模块名及文件名, 内部函数,只要注释其定义文件名;
三、编辑风格
1、缩进:缩进以 Tab 为单位,一个 Tab 为四个空格大小。预处理语句、全局数据、函数原型、标题、附加说明、函数说明、标号等均顶格书写。语句块的“{”“}”配对对齐,并与其前一行对齐;
2、空格:数据和函数在其类型,修饰名称之间适当空格并据情况对齐。关键字原则上空一格,如:
if ( ... ) 等,运算符的空格规定如下:“->”、“[”、“]”、“++”、“--”、“~”、“!”、“+”、“-”(指正负号),“&”(取址或引用)、“*”(指使用指针时)等几个运算符两边不空格(其中单目运算符系指与操作数相连的一边),其它运算符(包括大多数二目运算符和三目运算符“?:”两边均空一格,“(”、“)”运算符在其内侧空一格,在作函数定义时还可据情况多空或不空格来对齐,但在函数实现时可以不用。“,”运算符只在其后空一格,需对齐时也可不空或多空格,对语句行后加的注释应用适当空格与语句隔开并尽可能对齐。
3、对齐:原则上关系密切的行应对齐,对齐包括类型、修饰、名称、参数等各部分对齐。另每一行的长度不应超过屏幕太多,必要时适当换行,换行时尽可能在“,”处或运算符处,换行后最好以运算符打头,并且以下各行均以该语句首行缩进,但该语句仍以首行的缩进为准,即如其下一行为“{”应与首行对齐。
4、空行:程序文件结构各部分之间空两行,若不必要也可只空一行,各函数实现之间一般空两行
5、修改:版本封存以后的修改一定要将老语句用/* */ 封闭,不能自行删除或修改,并要在文件及函数的修改记录中加以记录。
6、形参:在定义函数时,在函数名后面括号中直接进行形式参数说明,不再另行说明。