文章目录
- 1数据类型定义
- 2.命名规范
- 3.注释格式
- 4.缩进
- 5分行
- 6括号
- 7.空格
- 6程序设计指引
- a)严禁使用未经初始化的变量作为右值,静态变量在上电时必须显式的初始化。
- b)当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的字节顺序、使用的位域及字节对齐等问题。
- c)所有项目增加栈长度检测函数,测检测栈最大使用长度,如果溢出,则停机报故障,具体检测方法见附件二。
- d)中断函数使用字或者双字全局变量时需特别注意,由于8位单片机需要分高、低位进行赋值,有可能在赋值期间进入中断,导致参数异常。建议有此类操作进行关中断后赋值,赋完值后再开中断 (只限于在中断中有逻辑判断处理的变量)。
- e)函数的规模应限制在200行以内(不包括注释和空格行),一个函数仅完成一个功能,为简单功能编写函数,不要设计多用途面面俱到的函数;避免函数中有不必要语句,防止程序中的垃圾代码。说明:
- f)禁止函数本身或函数间的递归调用。
- g)中断函数和主循环函数不推荐调用同一个函数,防止意外事件发生,如必须调用,必须将子函数设计为可重入函数。
- h)可重入函数的设计原则,函数中尽量使用局部变量,禁止使用全局变量、静止变量。
- i)时刻注意表达式是否会上溢、下溢 。
- j)使用变量时要注意其边界值的情况,并增加边界条件的测试,尽量从变量定义上避免这类情况出现,如难以避免,需要进行强制类型转换 。
- 7.附表A缩写词
1数据类型定义
1)数据类型必须使用typedef在头文件进行预定义,不允许使用C语言原始定义类型,以提高可读性和可移植性,常用数据类型预定义如下:
typedef signed char int8_t; //8位有符号整数
typedef unsigned char uint8_t, flag; //8位无符号整数、布尔标志
typedef signed short int16_t; //16位有符号整数
typedef unsigned short uint16_t; //16位无符号整数
typedef signed long int32_t; //32位有符号整数
typedef unsigned long uint32_t; //32位无符号整数
float、double类型变量不使用预定义。
2)对于结构体、联合体和枚举的预定义,一般按如下格式组合:“模块名称 + 描述 + 后缀。
- 结构体后缀为_ST:
typedef struct
{
flag fgOutput;
flag fgLvlHigh;
flag fgPullup;
} BSP_GPIO_SETTINGS_ST;
- 枚举后缀为_EN:
typedef enum
{
U8_MSG_SLOT_ID_DISP = 0x80, ///<显示板
U8_MSG_SLOT_ID_RUNMODE, ///<运行模式
U8_MSG_SLOT_ID_RELAY, ///<继电器
U8_MSG_SLOT_ID_DIRECTOR, ///<导风条
} MSG_SLOT_ID_EN;
- 联合体后缀为UN:
typedef union
{
uint16_t u16Sector; ///<整个段字
struct
{
uint8_t u8SectorHigh; ///<段的上半部
uint8_t u8SectorLow; ///<段的下半部
} stSector; ///<段字节
} MSP_MIX_TYPE_UN;
2.命名规范
2.1常量命名规范
- 常量定义
全部采用大写字母,尽量使用英文单词,不能使用拼音,并具备自释性,所有常量都应加上注释。
代表常量的宏定义格式:“数据类型 + 模块 + 名称”:
#define U32_FREQ_SCALE_Q16 (4000) ///<压缩机频率控制系数
#define U16_DC_FAN_MAX_SPD (1200) ///<外风机最大风速
#define U16_DC_FAN_MIN_SPD (600) ///<外风机最小转速
#define U16_DC_IN_FAN_MIN_SPD (700) ///<内风机最小转速
#define U16_HAL_ADC_INVALID (0xFFFF) ///<无效的ADC值
#define U16_HAL_TEMP_ADC_LOW_THR (12) ///<温度传感器故障判断的ADC下限值
代表常量的枚举定义格式:“数据类型 + 枚举类型名称 + 描述”:
typedef enum
{
U8_RUNMODE_STM_TURNOFF = 0, ///<关机状态
U8_RUNMODE_STM_COOLMODE, ///<制冷模式状态
U8_RUNMODE_STM_DRYMODE, ///<抽湿模式状态
U8_RUNMODE_STM_HEATMODE, ///<制热模式状态
U8_RUNMODE_STM_FANMODE, ///<送风模式状态
U8_RUNMODE_STM_SELF_CLEANMODE, ///<自清洗模式状态
U8_RUNMODE_STM_DRY_CLEANMODE, ///<干燥清洁状态
U8_RUNMODE_STM_PRE_COOLMODE, ///<预冷状态
U8_RUNMODE_STM_PRE_HEATMODE, ///<预热状态
U8_RUNMODE_STM_REMAIN_COLDMODE, ///<余冷状态
U8_RUNMODE_STM_REMAIN_HEATMODE, ///<余热状态
U8_RUNMODE_STM_NUM ///<状态数量
} RUNMODE_STM_EN;
true/false、success/fail、NULL等常用常量统一简洁定义:
#define TRUE (1) ///<真
#define FALSE (0) ///<假
#define SUCC (0) ///<成功
#define FAIL (-1) ///<失败
#define NULL ((void*)0) ///<空指针
2.2变量命名规范
变量命名规则采用匈牙利命名规则,格式为驼峰格式。前两个字母小写,表示其数据类型(见下表),其后每个词的第一个字母大写。如果该单词为缩写形式且不在变量名的最后,则该单词只需要首字母大写,比如fgPtcIsOn,如果在变量名的最后则可以全部大写,比如u8TimeOfPTC。应使用英语单词表示变量,不能使用拼音。每个变量名称的推荐长度不超过15个字符(含15个),最长不能超过23个字符(含23个)。
前缀以如下方式组合:“作用域 + 是否为数组 + 是否为指针 + 类型 + 描述”。
示例:
u8Foo,表示Foo为单字节的临时变量或结构体成员;
s_u8Foo,表示Foo为单字节的本地变量;
s_au8Foo[10],表示Foo为本地的数组变量,成员为单字节;
s_apu8Foo[10],表示Foo为本地的数组变量,成员为指向单字节的指针。
2.3函数命名规范
函数名应能体现该函数完成的功能,关键部分应采用完整的单词,辅助部分若太长可采用缩写,缩写应符合英文的规范,每个单词的第一个字母大写。对于模块函数,如果是外部可见的接口,则以模块名称缩写作为前缀:
uint8_t BspI2cMasterWriteByte(uint8_t u8Ch, uint8_t u8Byte);
uint8_t BspI2cMasterReadByte(uint8_t u8Ch, uint8_t *pu8ByteOut, flag fgACK);
uint8_t HalEepromCheck(uint8_t u8ID, uint8_t u8RetryTimes);
对于函数宏,应使用全大写加下划线的形式,如果是作为模块对外的接口函数,应以模块的名称缩写作为前缀。对于使用宏的代码段,应使用do…while(0)形式预防隐式逻辑混乱,并且对于过长的宏定义,可以使用“\”来换行:
#define COOL_MODE_GET_TSC() (s16TSC + 0) //此处+0防止作为左值
#define FR_PARAM_GET_DATA(ID) (aucParamData[(ID)] + 0) //此处+0防止作为左值
#define FR_TIMER_TICK(timer) \
do { \
if ((timer) > 0) \
{ \
timer -= 1; \
} \
} while (0)
2.4文件命名规范
源文件命名第一个字母大写,其后的每个单词第一个字母大写,长度小于等于30个字符并尽量体现功能作用,名称中不允许出现空格等非常规字符,防止编译和仿真异常。如果是某个模块的文件名并处于明确的某个层,则应添加层次名为前缀。
例如:CompCtrl.c,其中,Comp代表“压缩机”,Ctrl为control的缩写,代表“控制”,总体可以理解为“压缩机控制模块”;
例如:HalEEPROM.c,其中Hal表示Hal硬件抽象层,EERPOM代表E方设备,总体理解为硬件抽象层的“E方设备模块”。
3.注释格式
本规范要求的注释格式要符合Doxyfile注释格式,以便使用Doxygen工具生成参考文档。Doxygen工具使用教程见附件一。
3.1文件头注释
文件头部应进行注释,在头文件的注释中说明本模块提供的主要功能以及主要逻辑。
/**
* @file POM.h
* 本模块提供了掉电记忆功能的应用接口。
* 本模块在记录和读取掉电记忆数据时将会创建任务来异步完成,完成后向消息中心发送消息。
* */
3.2.函数头注释
函数头部应进行注释,说明函数的功能和用法。对有参数、返回值的,应作相应的说明:
/**
* @brief HalEepromRead
* 读取EEPROM片内指定地址和长度的数据,通过pucDataOut输出。返回U8_HAL_EEPROM_RETC_OK后,HAL_EEPROM_STATUS_ST中的 bBusy将置位,读取操作在另外的任务中进行。在bBusy清零之前 pu8DataOut中的内容可能无效。
* @param u8ID EEPROM的实例ID,须小于U8_CFG_HAL_EEPROM_NUM
* @param u16ReadAddr 与参数u16ReadLen相加必须小于EEPROM的存储空间,该存储空间的大小在HAL_EEPROM_STATUS_ST的u16PageSize*u16PageNum标明
* @param pu8DataOut 数据输出,从EEPROM读取的数据按顺序在此指针指向的缓存放置,不可为NULL
* @param u16ReadLen 与参数u16ReadAddr相加必须小于EEPROM的存储空间,该存储空间的大小在HAL_EEPROM_STATUS_ST的u16PageSize*u16PageNum标明,同时不可为0
* @return U8_HAL_EEPROM_RETC_OK
* @return U8_HAL_EEPROM_RETC_ARGUMENTS_ERR
* @return U8_HAL_EEPROM_RETC_INSTANCE_UNINITED
* @return U8_HAL_EEPROM_RETC_DEVICE_BUSY
* @return U8_HAL_EEPROM_RETC_WRITE_NO_ACK
* @return U8_HAL_EEPROM_RETC_TASK_CREATE_FAIL
*/
3.3.单行注释
if (0 == pstFanInstance->u16TargetSpeed)
{
/** 目标风速为0时,保持各项参数为初始值。*/
pstFanInstance->u16PulseDelayPID = 1570;
pstPID->u16ThisE = 0;
pstPID->u16LastE = 0;
pstPID->u16ThisDeltaE = 0;
pstPID->u16LastDeltaE = 0;
pstPID->u16DoubDeltaE = 0;
}
3.4.多行注释
/** 读E方方法:1、写开始信号;2、写设备地址;3、写内部地址;4、再次写开始信号;
* 4、再次写设备地址;5、循环读取n-1个字节并输出ACK;6、读取第n个字节不需要输出ACK;7、写停止信号。*/
3.5.行尾注释
#define U8_PID_P (8) ///<PID算法参数:比例项
#define U8_PID_I (3) ///<PID算法参数:积分项
#define U8_PID_D (6) ///<PID算法参数:微分项
4.缩进
1)程序块、函数或过程的开始、结构的定义、case语句下的情况处理语句及循环、判断等语句中的代码都要采用缩进风格,也要遵从语句缩进要求,缩进的TAB宽度设置为4个空格,必须使用空格代替制表符。
2)较长的语句(>80字符)以及循环、判断等语句中若有较长的表达式或语句要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读:
if ((U8_HEAT_MODE != ucSetMode) && (U8_COOL_MODE != ucSetMode)
&& (U8_AUTO_FAN != ucSetFan))
{
... //程序块
}
for (ucTempI = 0, ucTempJ = 0;
(ucTempI < U8_FIRST_WORD_LENGTH) && (ucTempJ < U8_SECOND_WORD_LENGTH);
ucTempI++, ucTempJ++)
{
... //程序块
}
3)程序块的分界符(如C语言的大括号‘{’和‘}’)应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式:
for (...)
{
... // program code
}
if (...)
{
... // program code
}
void ForExample( void )
{
... // program code
}
5分行
1)if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{}。
如下例子不符合规范:
if (fgStepStopped)
fgStepStopped = 0;
应如下书写:
if (fgStepStopped)
{
fgStepStopped = 0;
}
2)不允许把多个短语句写在一行中,即一行只写一条语句。
如下例子不符合规范:
ucSetMode = U8_COOL_MODE; ucSetFan = U8_MID_FAN;
应如下书写:
ucSetMode = U8_COOL_MODE;
ucSetFan = U8_MID_FAN;
3)源程序中关系较为紧密的代码应尽可能相邻,便于程序阅读和查找:
//以下代码布局不太合理。
ucSetMode = U8_AUTO_MODE;
ucSetFan = U8_MID_FAN;
ucRunMode = U8_COOL_MODE;
//若按如下形式书写,可能更清晰一些。
ucSetMode = U8_AUTO_MODE;
ucRunMode = U8_COOL_MODE; //设定模式与运行模式关系比较紧密,所以放在一起
ucSetFan = U8_MID_FAN;
6括号
1.函数、控制语句的大括号一定要对齐:
if (fgNewOurtDoorPlat)
{
…
}
2.switch 语句中的case语句实质上是一个标签,因此可对齐switch语句,不需要缩进。case下的逻辑分支代码不需要使用大括号。case语句后面必须增加break语句,如果程序必须顺序执行下个case语句,需增加注释详细说明;语句最后必须增加default语句:
switch (ucLimArea)
{
case 0:
case 5:
……
break;
case 1:
……
break;
default:
……
break;
}
3.注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错:
//下列语句中的表达式较容易理解
ucTempA = (ucTempB << 8) | ucTempC (1)
if ((ucTempA | ucTempB) && (ucTempA & ucTempC)) (2)
if ((ucTempA | ucTempB) < (ucTempC & ucTempD)) (3)
//如果书写为下列语句则不易理解
ucTempB << 8 | ucTempC
ucTempA | ucTempB && ucTempA & ucTempC
ucTempA | ucTempB < ucTempC & ucTemp
7.空格
1)在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如!),后不应加空格。采用这种松散方式编写代码的目的是使代码更加清晰。
2)由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如果语句已足够清晰则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在C语言中括号已经是最清晰的标志了。
3)在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。
示例:
a)逗号、分号只在后面加空格:
for (ucTempI = 0; ucTempI < 8; ucTempI++)
b)比较操作符, 赋值操作符"=“、 “+=”,算术操作符”+“、”-“,逻辑操作符”&&“、”&“,位域操作符”<<“、”^"等双目操作符的前后加空格:
if (uiStepCurPos == uiStepTgtPos)
ucTempA = ucTempB + ucTempC;
ucTempA *= 2;
ucTempA = ucTempB ^ 2;
c)“!”、“~”、“++”、"–"等单目操作符前后不加空格:
if (!fgStepDisable) // 非操作"!"与内容之间
ucTempA++; // "++","--"与内容之间
d)"."前后不加空格:
unStepFlags1.stBits.bit0; // "."前后不加空格
e)if、for、while、switch、case等与后面的括号间应加空格,使if等关键字更为突出、明显:
if ((ucTempA >= ucTempB) && (ucTempC > ucTempD))
6程序设计指引
a)严禁使用未经初始化的变量作为右值,静态变量在上电时必须显式的初始化。
b)当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的字节顺序、使用的位域及字节对齐等问题。
c)所有项目增加栈长度检测函数,测检测栈最大使用长度,如果溢出,则停机报故障,具体检测方法见附件二。
d)中断函数使用字或者双字全局变量时需特别注意,由于8位单片机需要分高、低位进行赋值,有可能在赋值期间进入中断,导致参数异常。建议有此类操作进行关中断后赋值,赋完值后再开中断 (只限于在中断中有逻辑判断处理的变量)。
示例:
uint16_t u16TrgFanSpd; //风机风速,全局变量,中断里有进行判断操作
不符合规范:
u16TrgFanSpd = 0x4B0;
汇编程序:
MOV A, #4Bh
MOV _16rgFanSpd,A; 这里出现中断,并在中断函数对风速判断,就有问题
MOV A, #00h
MOV _u16TrgFanSpd+1,A
符合规范:
DI();
u16TrgFanSpd = 0x4B0;
EI();
e)函数的规模应限制在200行以内(不包括注释和空格行),一个函数仅完成一个功能,为简单功能编写函数,不要设计多用途面面俱到的函数;避免函数中有不必要语句,防止程序中的垃圾代码。说明:
虽然为仅用一两行就可完成的功能去编函数好象没有必要,但使用函数可使功能明确化,增加程序可读性,亦可方便维护、测试。
如果多段代码重复做同一件事情, 若这些代码各语句之间有实质性关联并且是完成同一件功能的,那么可考虑把此段代码构造成一个新的函数。
尽可能不使用C语言提供的库函数,而编写相应功能的函数 ,避免设计多参数函数,尽可能的减少参数的数目。目的是减少函数间接口的复杂度。
1)函数入口和出口唯一性,尽量编写函数只有一个入口,一个出口,增强可读性。
2)参数传递禁止多重指针,除非算法需要。
3)数组使用地址进行传递。
4)如果参数比较多,考虑将参数定义成结构体,进行结构体变量地址传递,降低堆栈消耗。
示例:
//不规范的写法,函数出口不是唯一的。
void HighFreqRun()
{
//高频运行函数
uint8_t u8InTrgFreqTemp;
if (0 == fgHighFreqRunSelt)
{
return; //出口1
}
if (u8InTrgFreq < 85)
{
return; //出口2
}
… //出口3
}
f)禁止函数本身或函数间的递归调用。
说明:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,禁止递归调用。
g)中断函数和主循环函数不推荐调用同一个函数,防止意外事件发生,如必须调用,必须将子函数设计为可重入函数。
说明:中断函数和主循环函数调用同一个函数时,会涉及到存储空间分配问题,全局变量、静止变量值被改变的问题,导致中断返回后出现异常数据值,其运行结果将是不可预测的。
h)可重入函数的设计原则,函数中尽量使用局部变量,禁止使用全局变量、静止变量。
示例:
//不可重入函数
uint8_t u8CheckSumTmp; //全局变量
void CheckSum (uint8_t *pu8PtAddr, uint8_t u8NumTmp)
{
uint8_t i;
u8CheckSumTmp = 0;
for (i = 0; i < u8NumTmp; i++, pu8PtAddr++)
{
//由于对全局变量操作,程序运行到这里,如果出现中断函数调用,那么中断
//函数执行完毕后,u8CheckSumTmp中的值是不确定的,导致主循环调用校验
//和计算出错
u8CheckSumTmp += *pu8PtAddr;
}
u8CheckSumTmp ^= 0xff;
u8CheckSumTmp += 1;
}
//可重入函数
uint8_t CheckSum(uint8_t *pu8PtAddr, uint8_t u8NumTmp)
{
uint8_t i;
uint8_t u8CheckSumTmp; //局部变量
for (i = 0, u8CheckSumTmp = 0; i < u8NumTmp; i++, pu8PtAddr++)
{
//由于采用局部变量,中断调用后,相关数据可以入栈保护,中断返回时数据//不会发生变化,结果无问题。
u8CheckSumTmp += *pu8PtAddr;
}
u8CheckSumTmp ^= 0xff;
u8CheckSumTmp += 1;
return u8CheckSumTmp;
}
i)时刻注意表达式是否会上溢、下溢 。
示例:
如下程序将造成变量下溢。
uint8_t u8Size;
while (u8Size >= 0) // 将出现下溢
{
u8Size--;
... // program code
}
当u8Size等于0时再减1不会小于0而是0xFF,故程序是一个死循环。
应如下修改。
int8_t s8Size; // 从uint8_t 改为int8_t
while (s8Size >= 0)
{
s8Size --;
... // program code
}
j)使用变量时要注意其边界值的情况,并增加边界条件的测试,尽量从变量定义上避免这类情况出现,如难以避免,需要进行强制类型转换 。
示例:如C语言中字符型变量,有效值范围为0到255。故以下表达式的计算存在一定风险。
uint8_t u8Length;
uint8_t u8Width;
uint8_t u8Sum;
u8Length = 127;
u8Width = 200;
u8Sum = 0;
u8Sum = u8Length + u8Width; // 故u8Sum的结果不是327,而是71。
7.附表A缩写词