"); //-->
题目:规范化和模块化编程
0 引言
通过一年多的编程经历,经常会为杂乱无章的程序弄的晕头转向,影响编程质量和进度。同时也为了程序的可移植性和可读性,规范化和模块化编程应该在开始编写的第一个程序时就要有规范化和模块化编程的思想,并在实践中运用,养成规范化和模块化编程的好习惯。
1 规范化编程
谈到规范性编程这里我们是在符合c语言基本运用原理的基础上加以说明,以下我们主要讲以下几个方面:
1.1 定义一个自己config.h文件
首先我把我使用的config文件列出:
typedef signed char S8;
typedef signed int S16;
typedef signed long S32;
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
typedef volatile signed char vS8;
typedef volatile signed int vS16;
typedef volatile signed long vS32;
typedef volatile unsigned char vu8;
typedef volatile unsigned int vu16;
typedef volatile unsigned long vu32;
typedef const u8 FLASH;
typedef enum{FALSE=0,TRUE=!FALSE} BOOL;
为什么要定义一个自己的这样一个文件,主要有两个原因:
eq \o\ac(○,1)1节约编程时间
eq \o\ac(○,2)2更高的可移植性
同样也是为本工程形成一种规范,这是一种局部规范,读者可以定义一个适合自己的config文件。
1.2 变量名的选取
首先要知道变量名的组成成分:字母,下划线,数字;而且要注意的是数字不能作为开头,并且字母区分大小写,下划线主要的功能用于分隔两个有意义的单词或者是区别形参和实参等用途。
其次就是怎么正确选择的问题了,在开始编程时大家都可能喜欢用a,b,c等简单字母作为变量名,这样只是单纯的定义了一个变量,读者并不能从中获取很多信息量,比如这个变量的用途等。所以为了能表达的更准确并能获得更多的信息量,应该选取有意义的英文单词或者中文拼音,可以用下划线作为单词之间,也可使用首字母大写区分,具体可根据个人编程习惯。
例:取一个关于定时器定时计数的变量,可以有以下几种模式(仅供参考):
eq \o\ac(○,1)1U16 TimerCounter;
eq \o\ac(○,2)2U16 timer_counter;
这样选取的变量名不仅达到了有意义的要求,而且更美观。从接触C到开始编程就要养成一个良好的习惯,选取变量名是往往程序首先要做的事,所以变量名的选取也是规范化编程的第一步,很关键。
1.3 与硬件资源相关用define去定义
在说明这个问题之前,我们先看个例子:
#include <reg51.h>
#include "config.h"
sbit led = P0^0;
void fun1(void);
void delay(void);
void main(void)
{
while(1)
{
delay();
fun1();
delay();
}
}
void fun1(void)
{
U8 i;
U8 temp = 0xfe;
led = 0;
for(i=0;i<8;i++)
{
P1 = temp;
temp = temp << 1;
delay();
}
led = 1;
}
void delay(void)
{
U8 i,j;
for(i=0;i<200;i++)
{
for(j=0;i<200;j++);
}
}
为了能形成对比,我们再看运用规范化编程原理的程序:
#include <reg51.h>
#include "config.h"
sbit led = P0^0;
#define Led_On led = 0
#define Led_Off led = 1
#define LedCyclePort P1
void Soft_DealyTimer(void);
void LedCycleProc(void);
void main(void)
{
while(1)
{
Soft_DealyTimer();
LedCycleProc();
Soft_DealyTimer();
}
}
void LedCycleProc(void)
{
U8 i;
U8 temp = 0xfe;
Led_On;
for(i=0;i<8;i++)
{
LedCyclePort = temp;
temp = temp << 1;
Soft_DealyTimer();
}
Led_Off;
}
void Soft_DealyTimer(void)
{
U8 i,j;
for(i=0;i<200;i++)
{
for(j=0;i<200;j++);
}
}
通过以上两个程序我们可以看出来具体区别是什么,程序中没有了类似于P1这种标识,而是巧妙的利用define定义P1,以及函数名的修改,都是为了体现有意义和可移植性的要求。以上只是一个很简单有关于define这个关键字的用法,巧妙运用能使程序的可读性和可移植性大大增强,也是规范性编程不可或缺的关键因素。
1.4 合理选取变量的数据类型,防止掉入C陷进
在说明之前先看一个简单的例子:
#include <reg51.h>
#include "config.h"
sbit led = P0^0;
#define Led_On led = 0
#define Led_Off led = 1
void Soft_DealyTimer(void);
void main(void)
{
while(1)
{
Led_On;
Soft_DealyTimer();
Led_Off;
Soft_DealyTimer();
}
}
void Soft_DealyTimer(void)
{
U8 i,j;
for(i=0;i<=256;i++)
{
for(j=0;i<=200;j++);
}
}
初看觉得没什么问题,可是当你下载到MCU运行时,你会发现灯永远是亮的,不会熄灭,为什么呢?我们来分析一下,灯亮说明至少运行到了while(1)中的Led_On语句,说明应该问题就出在软件延时函数,细看我们发现i的取值大了,因为U8 i的范围是0~255,虽然我们知道unsigned char 是无符号8位,2eq \o(\s\up 5(8 ),\s\do 2( ))8值是256,但是要注意的是单片机初始值都是从0开始的,所以要注意这些细节问题。
有些人看了上面的例子会想,我都用long型或者int型就不是没有问题了吗?但是你这样的话就增大了MCU内存的开销,不利于程序快速运行,所以合理选择变量数据类型也是很重要的。
1.5 在结构体中按变量从小到大排列
先看个例子:
Struct
{
Int s; //占用第0和第1个字节
Char c1; //占用第2个字节,由于对其原因,第3个字节为空
Long l; //占用第4,5,6,7个共四个字节
Char c2; //占用第8个字节,第9个字节为空
}s;
由此可以看出浪费了2个字节空间,所以我们应该调整变量顺序,如下:
Struct
{
Int s; //占用第0和第1个字节
Char c1; //占用第2个字节
Char c2; //占用第3个字节
Long l; //占用第4,5,6,7个共四个字节
}s;
为什么会有上述情况出现,原因是结构体变量是字对齐,但是在有些单片机中可以软件设置为字节对齐,这样也可以解决上述问题,但是按顺序存放明显是规范性编程中的一员,一个好习惯不会因为疏忽造成内存开销增大。
2 模块化编程
为什么要模块化编程,主要原因当然也是可读性和可移植性。
模块化编程思路:
eq \o\ac(○,1)1分析系统项目功能模块,一般的系统可能有以下几个模块:最小系统模块(能让MCU工作的编程模块),键盘和显示模块(一般会用译码锁存器件,如智能调节仪所使用的是CH452),AD模块(采集传感器信号),继电器模块(控制一些器件工作,相当于开关),通讯模块(UART)等。
eq \o\ac(○,2)2将每个模块分别用.c和.h建立模块编程,.h文件用来存放模块相关资源定义,以及函数声明等功能,.c文件用于存放该模块功能程序代码。
eq \o\ac(○,3)3用main.c将各个模块串结成一个完整的系统,在main函数中代码要简洁,最好只有两三个函数,比如:
Void main(void)
{
System_Init();
While(1)
{
If(Key_Value)
Key_Handle();
else
System_Handle();
}
}
以上分析了模块化编程的基本思路,然后我们再来具体看个例子,以通讯模块为例:
先看.h文件:
#ifndef __uart_H
#define __uart_H
#include "config.h"
#define Buf_Max_Len 32
#define UART0_TX_ENABLE
#define UART0_TX_DISABLE
#define UART0_RX_ENABLE
#define UART0_RX_DISAbLE
typedef enum
{
Select_Uart0 = 0,
Select_Uart1
}UART_SelectTypeDef;
typedef enum
{
B9600_Freuency,
B2400_Freuency
}UART_CommMode;
typedef volatile struct
{
VU8 ReadIndex;
VU8 SendIndex;
VU8 CharCount;
VU8 Buffer[Buf_Max_Len];
}UART_TypeDef;
#endif
还有.c文件:
#include <reg51.h>
#include "uart.h"
void UART_Init(UART_TypeDef *self,UART_SelectTypeDef in_sel,UART_CommMode in_mode)
{
switch(in_mode)//波特率设置
{
case B9600_Freuency:
// 相应设置代码
break;
case B2400_Freuency:
// 相应设置代码
break;
default:
break;
}
switch(in_sel)//串口选择0或1
{
case Select_Uart0:
break;
case Select_Uart1:
break;
default:break;
}
}
void Buffer_Init(UART_TypeDef *self)
{
self->ReadIndex = self->SendIndex = self->CharCount = 0;
}
void UART_SendType(UART_TypeDef *self,U8 in_char)
{
if(self->CharCount < Buf_Max_Len)
{
self->Buffer[self->SendIndex] = in_char;
self->SendIndex++;
self->CharCount++;
}
}
void UART_GetType(UART_TypeDef *self)
{
U8 ctmp = 0;
if(self->CharCount)
{
ctmp = self->Buffer[self->ReadIndex];
self->ReadIndex--;
self->CharCount--;
}
}
void UART_SendChar(UART_TypeDef *self,U8 in_char)
{
UART0_TX_ENABLE;
SBUF = UART_SendType(&self->Buffer,in_char);
UART0_TX_DISABLE;
}
void UART_GetChar(UART_TypeDef *self,U8 in_char)
{
U8 ctmp;
UART0_RX_ENABLE;
UART_SendType(&self->Buffer,SBUF);
UART0_RX_DISABLE;
}
以上的例子只是简单的说明了模块化编程原理及一般流程,可能我们已经注意到形参使用的是指针结构体,如此可以节约系统时间并减少系统内存开销。
3 总结
编程习惯很重要,由于面对大型的工程和团队合作,养成一个规范化编程和模块化编程的好习惯相当重要,也可以说是直接影响团队的工程进程和新代码成员的跟进进度,所以在开始学习编写程序代码前必须养成一个良好的编程习惯,规范化和模块化编程是其精髓。
以上所有程序是我零时编写,可能有一些欠妥之处,请指正。谢谢大家。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。