嵌入式C51规范

85 篇文章 4 订阅

转自:http://www.cnblogs.com/hustlzp/archive/2011/03/05/1970936.html

 

为单片机编写C51代码,程序的可行性当然是必须保证的。但是包括笔者在内的很多新手,都忽略了程序的另一面——可读性、可维护性以及可扩展性。只要稍微有些嵌入式开发经验的读者,若看到笔者在“Zigbee之旅”系列博文中的源码,可能都会从其代码编写习惯中得出一个结论——“菜鸟”。呵呵,笔者决定抽时间学习一下C51嵌入式开发的编程规范,于是在网上收集了一些资料,结合自己的经验,一并分享如下。

一、注释

(1)文件注释

  这里说的文件,一般是 .h.c 文件。

   
   
/* **********************************************************
文件名称: hal.h
作 者: hustlzp
日 期:  2011/3/5
版 本: 1.1
功能说明: 硬件抽象层
函数列表: (略)
修改记录:
********************************************************** */

  其实一个人学习的话,诸如“文件名称”、“作者”、“版本”、“日期”这些内容,不是特别必要。上述的规范一般在公司内要求比较严格(在多人作业的情况下,对于软件开发的流程控制非常重要)。

  但“说明”和“函数列表”这两项,我想还是方便的话写写比较好。当你对这个项目比较淡忘的时候,你只需要扫一下文件头部注释,就能一下子知道这个文件到底是干什么的,明白都有哪些函数。

(2)函数注释

  如果对上述的文件注释不怎么感冒的话,我想大家函数注释都应比较熟悉。博客园的园友中大多都是使用vs的,相信vs中对函数注释的支持一定不会忘记(敲三个///,啥都出来了,只需要一个个填就行)。虽然嵌入式开发IDE没有如此强大的功能,但是还是很有必要对“函数功能”、“入口参数”、“出口参数”进行说明:

   
   
/* ********************************************************** 函数名称: SetTimer1Period 函数功能: 设置定时器1的定时时长 入口参数: DWORD period
      定时时长 出口参数: WORD
      确定PWM脉冲宽度  
********************************************************** */
WORD halSetTimer1Period(DWORD period);

(3)代码注释

  至于函数内部的代码注释,需要注意几点:

  • 同一函数内的注释最好左对齐
  • 少量注释放在代码右边,较多则放在正上方
  • 在代码的功能层次上注释,逻辑结构分明
   
   
void setSleepTimer(unsigned int sec)
{
  unsigned long sleepTimer = 0 ;
  sleepTimer |= ST0; // 取得目前的睡眠定时器的计数值
  sleepTimer |= (unsigned long)ST1 << 8;
  sleepTimer |= (unsigned long)ST2 << 16;
   sleepTimer += ((unsigned long)sec * 32768); //加上所需要的定时时长
  ST2 = (unsigned char)(sleepTimer >> 16); // 设置睡眠定时器的比较值
  ST1 = (unsigned char)(sleepTimer >> 8);
  ST0 = (unsigned char)sleepTimer;
}

二、命名

(1)要具有明确含义

  这一点我想不用多说,相信稍具经验的C#程序员都已熟练掌握:

  • 尽量使用完整的英文单词,不会的查灵格斯,no拼音
  • 可使用一些大家公认的缩写,若使用自定义的缩写,需注释说明
  • 函数的命名一般选择动宾短语,即动词+宾语,这样做可较好地说明函数的功能

(2)命名风格的选择

  C51编程中,一般会有两种用得较多的命名风格:

  大小写风格:

   
   
unsigned char get_value();
{...}

  下划线风格:

   
   
unsigned char getValue()
{... }

  其实选用何种风格并不重要,重要的是坚持在整个项目代码中统一风格,尽量避免混用。

(3)宏及常数的命名

  宏及常数的命名必须全用大写字母,而且词与词之间用下划线分隔:

   
   
#define GRADIENT 0.03114 #define OFFSET -303 #define ADC14_TO_CELSIUS(ADC_VALUE) (( float )ADC_VALUE * ( float )GRADIENT + OFFSET)

三、排版

(1)缩进

  • 在花括号内{}的代码,都应相对于花括号做出一定的缩进
   
   
void LCD_write_english_string(unsigned char X,unsigned char Y, char * s) { unsigned char i = 0 ; LCD_set_XY( 0 ,Y); for (i = 0 ;i < 84 ;i ++ ) { LCD_write_byte( 0 , 1 ); } LCD_set_XY(X,Y); while ( * s) { LCD_write_char( * s); s ++ ; i ++ ; if (i >= 14 ) return ; } }
  • 充分利用IDE的缩进功能

  • 手动缩进时,建时议使用空格键,而不使用 Tab 键,以免用不同的编辑器阅读程序时,因 Tab 键所设置的空格数目不同而造成程序布局的不整齐

(2)空格

  • 定义变量时的逗号,只在后面加空格
   
   
int a, b, c;
  • 比较操作符 , 赋值操作符 "=" 、"+=" ,算术操作符 "+" 、"%" ,逻辑操作符 "&&" 、"&" ,位域操作符 "<<" 、 "^" 等双目操作符的前后建议加空格
   
   
a = b + c; a *= 2 ; a = b ^ 2 ;

(3)行

  • 一行只写一句代码
  • 过长的表达式(超过80个字符)要分成多行书写,在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进适当的缩进,使排版整齐,语句可读。对齐建议使用空格键,不使用 Tab 键,以免用不同的编辑器阅读程序时,因 Tab 键所设置的空格数目不同而造成程序布局混乱:
   
   
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
          && (n7stat_stat_item_valid (stat_item))           && (act_task_table[taskno].result_data != 0));
  • 函数中参数较长,则以“,”进行适当的分行
   
   
stateCompare(state_object,        act_task_table[taskno].stat_object),        sizeof (STAT_OBJECT));
  • 函数中相对独立的程序块之间,建议加空行
   
   
if ( ! valid_ni(ni)) {   ... // program code }
repssn_ind = ssn_data[index].repssn; repssn_ni = ssn_data[index].ni;

四、宏定义

(1)定义片内/片外资源

  对片内资源的定义就不多说了,看一下 ioCC2430.h 就知道了~  

  还可定义一些片外资源,例如,定义接在P0端口的LED灯:

   
   
#define led1 P1_0 #define led2 P1_1 #define led3 P1_2 #define led4 P1_3

(2)定义有意义的常数

  有具体意义的常数应在宏中定义,以便日后集中修改:

   
   
#define GRADIENT 0.03114 #define OFFSET -303

(3)定义控制参数

  在“Zigbee之旅”系列博文中,经常出现需要对某一SFR进行赋值以达到控制的目的,我之前的处理方法是,直接用一个具体的数去赋值,如下所示:

   
   
/* UART0通信初始化 ------------------------------------------------------------------ */ void Uart0Init(unsigned char StopBits,unsigned char Parity) { P0SEL |= 0x0C ; // 初始化UART0端口,设置P0.2与P0.3为外部设备IO口 PERCFG &= ~ 0x01 ; // 选择UART0为可选位置一,即RXD接P0.2,TXD接P0.3 U0CSR = 0xC0 ; // 设置为UART模式,并使能接受器 U0GCR = 11 ; U0BAUD = 216 ; // 设置UART0波特率为115200bps U0UCR |= StopBits | Parity; // 设置停止位与奇偶校验 }
/* 主函数 ------------------------------------------------------------------ */ void main( void ) { ... Uart0Init( 0x00 , 0x00 ); // 初始化UART0,设置1个停止位,无奇偶校验 ... }

  在上面的代码中,首先定义了一个初始化串口的函数 Uart0Unit。然后在 main 函数中使用它,参数分别为0x00(一个停止位),0x00(无奇偶校验)。

  这样做的确可以图一时的方便,但是会造成较差的可读性:假如另外一个不太懂CC2430的SFR具体用法的程序员来修改你的代码,他怎么知道这0x00有着什么含义?

  为了解决这个问题,我们建议的处理方法是:将可能的选项值全部用宏定义,如下:

   
   
// 停止位的设置 #define TWO_STOP_BITS 0x04 #define ONE_STOP_BITS 0x00
// 奇偶校验的使能 #define PARITY_ENABLE 0x08 #define PARITY_DISABLE 0x00

  OK,下面我们来使用这些宏作为函数 Uart0Unit 的参数:

 

   
   
void main( void ) { ... Uart0Unit(ONE_STOP_BITS, PARITY_DISABLE); ... }

  当别人看到这样的代码时,就会瞬间明白:一位停止位、无奇偶校验。修改起来也大为方便了,只需去查找宏定义就OK~

(4)定义一些常用的、功能单一的、有具体意义的代码组合

  • 常用的代码组合,如一些赋值、配置/初始化系统资源等代码,都可用宏来定义,如下:

   
   
// 设置电源模式 #define SET_POWER_MODE(mode) \ do { \ SLEEP &= ~ 0x03 ; \ SLEEP |= mode; \ PCON |= 0x01 ; \ } while ( 0 )
   
   
//将某16位变量的值分别赋给两个8位变量
#define SET_WORD(regH, regL, word) do { \ (regH) = HIBYTE( word ); \ (regL) = LOBYTE( word ); \ } while ( 0)

  使用的时候,直接将其当做一般的函数调用就行。

CC2430 小贴士

(1)do{...}while(0)

  看到这里,很多读者朋友可能对宏定义中的 do{...}while(0) 语句不太理解:既然是while(0),那么去掉do{...}while(0),程序也应该是对的呀?为什么还留着呢?

  其实,应用这种看似无用的do/while将代码框起来,是为了提高代码的健壮性,减少编译时可能产生的错误,具体可以参考此处

(2)反斜杠

  细心的读者还会发现,在多行宏定义中,除最后一行外每一行都有一个反斜杠 "/",这有啥用呢?

  在宏定义中,规定必须在一行内完成,但是用一行的话会大大降低代码的可读性。于是,我们可以加一个 "/" 表示续行的意思。当然,最后一行不能加。

五、程序结构

(1)根据功能模块划分文件

  一个项目,最好按功能分成几个逻辑清晰的模块,每一个模块还可以由一到多个职责各不相同的 .c 源文件来实现。这样一来可降低模块间的耦合性,提高可读性与维护性。

  此时,我们可以为每一个模块内的 .c 文件建立共同的 .h 头文件,然后再用一个 total.h 文件引用之前各模块的头文件。以后需引用时,只需引入 total.h 即可。

  例如,我们打算实施一个“温度采集系统”,则可以按下面的流程进行:

  • 将项目分为三大模块:LCD(屏显)、HAL(硬件抽象)、RF(无线收发)
  • 建立三个模块对应的头文件:LCD.h、HAL.h、RF.h
  • 建立total.h文件,引用LCD.h、HAL.h、RF.h
  • 然后建立3个模块对应的的 .c 源文件
  • 在含有main函数的.c文件中,通过引用 total.h 来使用各模块中的代码,实现系统功能

(2)头文件结构

  .h头文件的结构顺序一般为:

    头文件引入(include) → 宏定义(define) → 函数签名

  如下所示:

   
   
/* ****************************************************************************** Filename: lcd.h Target: cc2430 Author: KJA Revised: 16/12-2005 Revision: 1.0 Description: Function declarations for common LCD functions for use with the SmartRF04EB. All functions defined here are implemented in lcd.c. ***************************************************************************** */
#include " total.h "
#define LINE_SIZE 14 // Line length of LCD
#define LINE1_ADDR 0x80 // Upper line of LCD #define LINE2_ADDR 0xC0 // Lower line of LCD
//symbol codes #define ARROW_LEFT 124 #define ARROW_RIGHT 125 #define ARROW_UP 126 #define ARROW_DOWN 127 //Setup I/O, configure display and clear LCD. void initLcd(void);
// Converts the two text strings from ASCII to the character void lcdUpdate( char * pLine1, char * pLine2);
// Write one line of text to LCD. void lcdUpdateLine(UINT8 line, char * line_p);
// Write a single character to LCD. void lcdUpdateChar(UINT8 line, UINT8 position, char c);

六、结语

  OK,C51编码规范的学习到此稍稍停一下,关键还是要在编码实践中去遵循,以后我也会回过头来逐步修改完善本篇日志的。

  由于笔者的编码经验还很不足,所以以上内容仅供参考啦,不妥之处还请大家多多批评指正!

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中给出的代码中包含了对Sleep函数的调用,该函数位于Windows.h头文件中。引用\[2\]中提到在Codeblocks环境下无法使用sleep函数,而是建议使用Sleep函数来实现延时。引用\[3\]中提到可以通过执行命令man 3 sleep来查看sleep函数的说明文档。综上所述,sleep函数的头文件应该是Windows.h。 #### 引用[.reference_title] - *1* [c++ Sleep函数头文件](https://blog.csdn.net/weixin_46060711/article/details/124522185)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Linux下的sleep函数 要用的话得包涵什么头文件啊?](https://blog.csdn.net/weixin_30864831/article/details/117273466)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【C/C++】 - Linux下查找函数头文件 以及 man命令拓展](https://blog.csdn.net/qq_26176515/article/details/126168767)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值