作为一个初学者如何具有良好的程序设计风格呢?我想引用一个关于初学者请教编程大师的故事让读者自己去领悟。
有一位编程大师,他写非结构化的程序,一位初学者刻意模仿他,也写非结构化的程序。当他让大师看他的进步时,大师批评了他的非结构化程序:“ 对一位编程大师合适的东西未必对一个初学者同样合适,在超越结构化之前,你必须理解编程之道。” 我个人认为作为一个初学者应该踏踏实实的打好程序设计的基础,不要急功近利,舍本逐末。我走过不少弯路,希望大家能和我一样能牢记编程大师的忠告:“对编程大师合适的东西未必对一个初学者同样合适”。
本文所描述的优秀编程风格适合于大部分语言,文章中可能提到你不是很了解的概念,没有关系,你放心的读下去,当你使用AVR一个月之后,你什么都明白了。
AVRc语言优秀编程风格
文件结构
模块化的程序应该是有一个很好的程序结构的。AVRC语言程序有两种用户文件,.c程序文件,.h头文件,程序中编写过程中需要在.c文件中包含.h头文件。初学者往往出现重复包含或者头文件包含错误的问题,我当时也时常为这种错误而发愁。下面我以我写的电机驱动例程来给大家说明一下,优秀的编程文件结构。
这个工程中有8个文件,一个说明文件,如下图:下载程序例子 电机控制案例 。
我写的成型的程序的文件个数基本上都是偶数,因为每一个结构化的函数定义.c文件都会对应一个.h文件。main.c对应config.h。我们来看看各文件的包含关系。下面我们看看这些文件的包含关系与内容:[推荐的文件包含顺序与关系]
所有.c文件都包含了config.h文件。如:#i nclude “config.h”
在config.h 中有如下代码:
#i nclude “delay.h”
#i nclude “device_init.h”
#i nclude “motor.h”
这样做就不容易出现错误的包含关系,为了预防万一,我们还引入了宏定义与预编译。如下:
#ifndef _UNIT_H__
#define _UNIT_H__ 1
//100us
extern void Delay100us(uint8 n);
//1s
extern void Delay1s(uint16 n); // n <= 6 ,when n==7, it is 1.
//1ms
extern void Delay1ms(uint16 n);
#endif
第一次包含本文件的时候正确编译,并且#define _UNIT_H__ 1,第二次包含本文件#ifndef _UNIT_H__就不再成立,跳过文件。
预编译还有更多的用途,比如可以根据不同的值编译不同的语句,如下:
//#pragma REGPARMS
#if CPU_TYPE == M128
#i nclude< P>
#endif
#if CPU_TYPE == M64
#i nclude< P>
#endif
#if CPU_TYPE == M32
#i nclude< P>
#endif
#if CPU_TYPE == M16
#i nclude< P>
#endif
#if CPU_TYPE == M8
#i nclude< P>
#endif
#i ncludeP 的文件,后者是包含程序目录下的文件。< :前者是包含系统目录include下 的区别 “filename” nclude #i 与>
变量名与函数名
变量以及函数命名应该按照尽量短,按需长,具有实际意义。可以通过下划线或者大小写结合的方法组合动词和名词组成变量函数名。下面对比好的命名方法与不好的命名方法:
好的: Delay100us();
不好的: Yanshi();
好的: init_devices();
不好的: Chengxuchushihua();
好的: int temp;
不好的: int dd;
外部调用
首先在模块化程序的.h文件中定义extern
//端口初始化
extern void port_init(void);
//T2初始化
void timer2_init(void);
//各种参数初始化
extern void init_devices(void);模块化程序的.c文件中定义函数,不要在模块化的程序中调用程序,及不要出现向timer2_init();这样函数的使用,因为你以后不知道你到底什么地方调用了函数,导致程序调试难度增加。可以在定义函数的过程中调用其他函数作为函数体。
/**************************采用timer2 产生波形***********************/
// PWM频率 = 系统时钟频率/(分频系数*2*计数器上限值))
void timer2_init(void)
{
TCCR2 = 0x00; //stop
TCNT2= 0x01; //set count
OCR2 = 0x66; //set compare
TCCR2 = (1<
//占空比=高比低为:(OCR2-0X01)/(0XFF-OCR2) OX01++++++(OCR2)__________OXFF (+表示输出高,_表示输出低)
//即OCR2越大,输出越大
}在少数几个文件中调用函数,在main.c中调用大部分函数,在interupts.c中根据不同的中断调用服务函数。
void main(void)
{
/******************************************************************************/
//初始工作
/******************************************************************************
init_devices();
while(1)
{
for_ward(0); //默认速度运转 正
Delay1s(5); //延时5s
motor_stop(); //停止
Delay1s(5); //延时5s
back_ward(0); //默认速度运转 反
Delay1s(5); //延时5s
speed_add(20); //加速
Delay1s(5); //延时5s
speed_subtract(20); //减速
Delay1s(5); //延时5s
}
}
宏定义
宏定义主要用于两个地方:
一是用得非常多的命令或语句,利用宏将其简化。
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef NULL
#define NULL 0
#endif
#define MIN(a,b) ((a
#define MAX(a,b) ((a>b)?(a):(b))
#define ABS(x) ((x>)?(x):(-x))
typedef unsigned char uint8; /* 定义可移植的无符号8位整数关键字 */
typedef signed char int8; /* 定义可移植的有符号8位整数关键字 */
typedef unsigned int uint16; /* 定义可移植的无符号16位整数关键字 */
typedef signed int int16; /* 定义可移植的有符号16位整数关键字 */
typedef unsigned long uint32; /* 定义可移植的无符号32位整数关键字 */
typedef signed long int32; /* 定义可移植的有符号32位整数关键字 */
二是利用宏定义方便的进行硬件接口操作,再程序需要修改时,只需要修改宏定义即可,而不需要满篇去找命令行,进行修改。
//PD4,PD5 电机方向控制 如果更改管脚控制电机方向,更改PORTD |= 0x10即可。
#define moto_en1 PORTD |= 0x10
#define moto_en2 PORTD |= 0x20
#define moto_uen1 PORTD &=~ 0x10
#define moto_uen2 PORTD &=~ 0x20
//启动TC2定时比较和溢出
#define TC2_EN TIMSK |= (<<1OCIE2)|(1<
//禁止TC2再定时比较和溢出
#define TC2_DIS TIMSK &=~ (1<
为了增加程序的可读性,方便合作者读动程序,或者程序作者在一段时间之后还能看懂程序,我们需要在程序中写 注释。
在比较特殊的函数使用或者命令调用的地方加单行注释。使用方法为:
Tbuf_putchar(c,RTbuf); // 将数据加入到发送缓冲区并开中断
extern void Delay1s(uint16 n); // n <= 6 ,when n==7, it is 1. 在模块化的函数中使用详细段落注释:
/***********************
** 函数名称: Com_putchar
** 功能描述: 从串行口输出一个字符c
** 输入: c:输出字符
** 输出 : 0:失败 1:成功
** 全局变量: 无
** 调用模块:
** 说明:
** 注意:
********************/
在文件头上加文件名,文件用途,作者,日期等信息。
/*********************************************************************************************************
** serial driver
** (c) Copyright 2005-2006, limaokui
** All Rights Reserved
**
** V1.1.0
**--------------文件信息--------------------------------------------------------------------------------
**文 件 名:sio.c
**创 建 人: 李茂奎
**最后修改日期: 2005年7月13日
**描 述: serial driver
**--------------历史版本信息----------------------------------------------------------------------------
** 创建人: 李茂奎
** 版 本: V1.00
** 日期: 2005年7月13日
** 描述: 原始版本
*********************************************************************************************************/
要清楚,注释是为了方便阅读,增强程序的可度性,不要本末倒置,不要给很简单大家都能看明白的程序加注释,不要让注释淹没了你的程序结构。对于函数,变量等尽量使用文件名自注释的方法,及通过文件名就可以知道意思。