出于实际编程中应用和交流方便,经过一段时间的总结,对于编程风格做了一些总结,可以大致分为以下几个板块:常量变量及函数命名规格、数据运算、缓存区划分、过程变量赋值、正计时与倒计时、数组与指针等运用的取舍、程序排版、程序测试。
㈠常量及变量命名规格:
1) 在程序编程过程中,对于经常用到或者在程序测试过程中需要多次修改的常量,尽量做成宏定义的形式,方便修改和阅读,举例如下:
#define VER_MI 0x0f //AMCU 小版本号
#define Password02_addr 284 // 02 级密码地址 3bytes
#define Password04_addr 287 // 04 级密码地址 3bytes
建议:为了良好的区分变量与常量,常量在命名时尽量采用大写的形式;
2) 对于变量命名,必须表征变量的数据长度和变量的意义,其中局部变量建议全小写形式给出,全局变量大小写间隔的驼峰命名方式,在表征变量意义上面尽量选择该英文单词的几个关键字母,举例如下:
uint8 *DL645_1st68; //DL645规约第一个68指针
uint8 UC_DL645_Addr[6]; //DL645规约地址域A0-A1-A2------A5
uint8 UC_DL645_C; //DL645规约控制码C
uint8 UC_DL645_L; //DL645规约长度L
uint8 UC_RstMode_Time; //复位模块时间
建议:在变量第一次给出定义时,特别是关键变量最好给出变量的含义或者解释;
3) 对于具有函数的命名,同变量命名类似,区别是某函数一般都是固定完成某个任务或者事情,建议在命名时考虑动词+名词 大小写字母间隔的方式,其中中断函数前面建议加ISR关键字、初始化函数建议加Init关键字举例如下:
static voidISR_INF(void); //红外通信中断函数
static voidISR_PLC(void); //载波通信中断函数
void INF_Init(void); //红外通信初始化函数
void PLC_Init(void); //载波通信初始化函数
void Process_1S_Task(void);//1S 任务调度------典型的动词加名词形式命名
建议:在比较复杂的函数定义前加上该函数的接口参数、返回值、功能及调用注意事项;
其中在变量的数据长度定义时,可引用如下定义:
typedef unsigned char uint8; //变量UC_XX/uc_xx 或者U8_XX/u8_xx
typedef unsigned int uint16; //变量 UI_XX/ui_xx 或者U16_XX/u16_xx
typedef unsigned long int uint32; //变量 UL_XX/ul_xx 或者U32_XX/u32_xx
typedef signed char int8 ; //变量SC_XX/sc_xx 或者S8_XX/s8_xx
typedef signed int int16; //变量SI_XX/si_xx 或者S16_XX/s16_xx
typedef signed long int int32; //变量 SL_XX/sl_xx 或者S32_XX/s32_xx
㈡数据运算:
1) 对于运算优先级,建议在涉及到优先级选择的时候多加一些括号,避免优先级**不清楚而导致最后的结果出错;
2) 对于数据的乘法运算,需考虑数据溢出的问题,举例如下:
UI_NowTemp=300 * 500/100; //这样运算的结果是先算300 * 500=150000>65536
//所以会截取150000=0x249F0的低16位为 0x49F0,再用0x49F0=18928 除以100等于189;
UI_NowTemp=((uint32)300) * 500 /100; //这样书写后运算的结果就会使1500
很明显以上两种书写方式的运算结果截然不同,而且出错了也很隐蔽,所以在数据运算的时候需要特别注意数据溢出及中间结果的数据长度问题。
3)对于 2的整倍数乘法、除法运算,尽量使用移位操作来进行处理;
㈢缓存区划分:
1) 对于缓存通信内容的数组缓存区,开辟缓存的时候需要留1—10字节的余量;
2) 对于存E2PROM的块数据,块数据下面分子数据项的话,最好子数据项留1—3字节预留扩展,数据块间留1—10字节地址间距,防止块间数据操作时意外覆盖其他块;
3) 需要频繁调用的E2PROM里面的数据,需额外开辟同等大小的数组来保持与E2PROM同步,因为在读取E2PROM的时候会使主循环运行周期时间加长;
4)缓存区的划分必须综合考虑MCU的RAM、程序功能和容错;
5)在填充数组数据时,必须检查填充数据的长度是否≤数组长度,避免数组溢出;
㈣过程变量赋值:
1) 在编程过程中很多涉及到对某个过程(或进程)的描述,如状态1、状态2、状态3、状态4等,在给这些状态赋值时,尽量从0x01开始,递增赋值,且递增趋势必须与过程(或进程)的处理流程或者逻辑顺序相一致,举例如下:
uint8 UC_PowerWorkSta; //MCU电源的工作状态相关变量
#define POWER220V_OK 1 //220V 电源正常
#define POWER220V_3V 2 //220V 电源掉电---->向电池供电切换
#define POWER3V_SLEEP 3 // 电池供电切入休眠状态
#define POWER3V_WORK 4 //电池供电正常工作状态
#define POWER3V_220V 5 //电池供电向 220V 电源切换
2) 建议在定义这些状态变量的时候,在变量的下方直接标明该变量可能出现的几种值的宏定义,方便程序阅读;
㈤正计时与倒计时
1) 已知时间的时候采用倒计时的方式比较好,未知的计时采用累加的方式,但累加必须考虑计数值的上限,必要的话需加容错保护;
㈥数组与指针等运用的取舍
1) 在申请块变量时,往往需要在数组与结构体中进行选择,优先考虑数组的形式,然 后数组的下标采用宏定义的方式给出,此时在赋值和RAM空间计算有点明显;
2) 在函数调用的过程中,指针和数组都是可以作为形参给出的,出于对操作和书写方便的考虑,可以优先考虑数组作为形参给出,此时数组的大小必须为空,这样操作和运用指针的形式都是耗用2字节(前提是8位MCU)的堆栈空间;
3) 申请的指针必须要有初始化流程,否则该指针指向的地址空间可能是任意的一个地址,这样对于程序的稳定性会存在隐患;
㈦程序排版
1) C语言的排版风格讲究对其,特别是“{”与“}”,好的排版可以让程序逻辑清晰、明了;
2) 在同一个子函数里面语意块之间最好要有一排空格隔开,这里所说的语意块可以理解为两个不用语意或者变量判定,但又从属于某个子函数的同一级逻辑;
3) 寄存器操作的底层与程序应用层之间的程序调用最好不要超过5级,否则跟硬件相关的参数查找及修改就显得比较困难,而且多级调用子函数也会增加堆栈的压力;
4) 子函数的局部参数最好不要超过100字节,至于main函数里面变量,可以的话也尽量放在”main.h”或者类似.h文件中声明;
5) If语句判断条件中尽量把常量放在前面,变量放在后面,如
If(1==uc_i) 编译后的结果应该优于 if(uc_i==1);
If(u8_i==(u8_k1+u8_k2+u8_k3)) 必须写成 If(u8_i==(uint8)(u8_k1+u8_k2+u8_k3)),
至于这种判断语句,哪个放在前面比较适合,个人的意见是两者都可以,当然有经验的人欢迎指正。
㈧程序测试
1) 在作者自己编写程序进行底层模块测试的时候,每一个if逻辑的判断都必须保证, 毕竟交测试组的是程序的运行结果,而由于测试过程中某些条件的错误判断很难从 短期的结果中表现出来,所以编程作者在编写底层逻辑判断是必须创造条件自测每 一个逻辑判断语句的执行;
2) 串口打印相关参数适用于某些时间上不是很连续的事件,可以开辟测试数组,对可 能发生的流程中的每一个环节做标记,然后创造条件使这个流程再次发生,打印出 相关的测试数组的值,来锁定出哪一段程序块的问题;
3) 中间截断打桩测试适用于已经知道某段程序块存在问题;
最后,需要说明的是因为做软件有时候是与硬件、开发平台版本相关联的,而且很多适合离职后可能还会涉及到维护问题,所以建议在每个工程的头部加上如下注释信息:
/*====================================================
*ProjectName :
*IDE :
*HD/PCBNo. :
*Description :
*SoftEdition :
*Author :
*LastWrTime :
=====================================================*/
|