汽车电子expert成长之路

本博客发布的个人原创精品----嵌入式系统技术文章,欢迎大家参考学习,并转发分享!

浅谈嵌入式MCU软件开发之代码风格与代码优化

0
阅读(3721)

内容提要

引言

一、函数必须有返回值

二、函数的形参建议不要超过三个,而且对形参进行类型和范围检查

三、关于局部变量

四、中断服务函数ISR

五、关于函数名和变量名定义

六、关于代码注释

七、关于头文件的定义

八、若MCU支持位操作,使用位定义,可以提高RAM的使用效率

九、关于static、extern和volatile关键词的使用

十、8位和16位MCU中分页存储器的访问

十一、针对8位和16位MCU的代码优化

十二、使用直接对相应位写1的方式清除中断标志

十三、使用IDE的代码格式管理功能统一代码风格

十四、关于MCU编译器、链接器的优化


引言


       嵌入式系统设计中软件的效率高低与代码风格和代码优化有着密切的关系。


       好的代码风格不但能够让看代码的人耳目一新, 见代码如见其人,最重要的是能够能够让程序运行的效率更高。这也是程序员,所谓“码农”与真正工程师的差别之一。


       嵌入式系统软件编程不同于其他基于PC的软件编程,它是与MCU硬件密切相关的。要写好嵌入式工程软件代码。就必须了解清楚所用嵌入式MCU的硬件特性。这包含内核CPU架构的知识,是否支持指令数据缓存,片上各类存储器寻址空间与访问效率, 支持的指令寻址模式/执行的效率、中断处理过程等,以及与之对应的各种编译器、链接器的工作原理和优化规则,汇编/C语言的各种结构和数据类型在嵌入式MCU存储器资源中的具体存储和表现形式等。


        针对嵌入式软件编程的代码风格,目前工业界已有很多成熟的软件代码标准可以参考,比如目前广泛采用的专门针对嵌入式系统C语言代码安全性、保密性、可移植性和可靠性实现的MISRA-2004/2012 C语言标准(MISRA C is a set of software development guidelines for the C programming language developed by MISRA (Motor Industry Software Reliability Association--汽车工业软件可靠性联盟). Its aims are to facilitate code safety, security, portability and reliability in the context of embedded systems, specifically those systems programmed in ISO C / C90 / C99.)。里面大概列出了若干注意事项和建议代码风格建议以及相应的实例。具体读者可以参考维基百科相关介绍(https://en.wikipedia.org/wiki/MISRA_C)或者在MISRA官方网站(https://www.misra.org.uk/)下载相关指南文档学习,这里就不做详细介绍了。

      这里我结合自己的工程实践经验,列出一些我自己总结的关于提高嵌入式开发软件供代码风格和程序优化的建议供大家参考:


        一、函数必须有返回值


返回值用于反馈其执行是否成功或者执行时遇到的具体错误类型等。其能够让调用的调用该函数的函数能够知道其执行的结果从而进行相应的判断处理或者诊断。很多工程师喜欢写返回void型的函数,这样当函数运行出错时,只能眼前一抹黑。


        二、函数的形参建议不要超过三个,而且对形参进行类型和范围检查


        如果形参超过三个,建议使用数组或者结构体或者链表。务必对每个形参都进行类型和范围检查,以防止非法地址访问(比如对片上Flash的编程地址不在所使用嵌入式MCU的Flash地址范围内)和堆栈溢出问题(比如对全局数组的索引变量大于其真实定义长度)。


        三、关于局部变量


        根据C语言的知识,我们知道局部变量,其在函数退出后就不存在了。所以,在嵌入式MCU中,为了保证访问效率,编译器优先分配其使用CPU寄存器(但其可用的数量极少),若内核CPU寄存器放不下,则会将其分配在系统堆栈(stack)上(由于函数调用的出栈和压栈,这些在栈上临时给局部变量分配的RAM空间在其函数退出后就可以其他数据/局部变量使用)。虽然临时变量能够提高系统RAM的使用效率,但若在一个函数中定义过多局部变量,容易造成堆栈溢(stack)出问题。因此函数的局部变量,建议不要超过10个字节,若需要更多的数据,则建议使用全局变量代替。


        四、中断服务函数ISR


        为了保证嵌入式系统的外设中断实时性,ISR要尽量短,不能在ISR里面写等待函数,更不能在里面写死循环语句,否则会造成MCU“假死”。另外,ISR中不能放看门狗喂狗程序,否则不能起到监控主程序正常执行的作用(曾经见到过客户工程师在在定时器中断ISR喂狗的代码,我也是无语了,可能真的是“上有政策,下有对策”吧~!)。


       五、关于函数名和变量名定义


        对函数变量的命名要根据实际的意义来命名,比如说数据的位宽以及是否为有符号类型。常用的经典的命名规则包括如下三种:


  1. 匈牙利命名法。该命名法是在每个变量名的前面加上若干表示数据类型的字符。基本原则是:变量名=属性+类型+对象描述。如i表示int,所有i开头的变量命都表示int类型。s表示String,所有变量命以s开头的都表示String类型变量。

  2. 骆驼命名法。正如它的名称所表示的那样,是指混合使用大小写字母来构成变量和函数的名字。驼峰命名法跟帕斯卡命名法相似,只是首字母为小写,如userName。因为看上去像驼峰,因此而得名。

  3. 帕斯卡命名法 即pascal命名法。做法是首字母大写,如UserName,常用在类的变量命名中。

 

 

      我个人比较推荐使用匈牙利命名法,具体大家可以百度学习。


        六、关于代码注释


       虽然写注释会花费更多的时间和精力,但能够保证你的代码在若干年后重新review时候或者团队其他人使用时也能够很快看懂并使用。建议如下:


        1. 函数的注释要尽量详细介绍其功能、使用条件、函数的参数返回值的意义等;

        2. 算法代码和控制流程的注释要描述清楚原理和逻辑;

        3. 使用英文进行代码注释,因为中文在不同的编辑器中编码可能不同,容易出现乱码现象,可移植性差,而英文就不存在这个问题;

        4. 使用段注释(/*.....*/)而不是行注释(//...);


        七、关于头文件的定义


        建议使用如下条件编译的方式定义,以避免重复包含问题:

#ifndef     #define head_file_name_H     #include “xxx.h”

............

#define user_micro       #enddef /*end head_file_name_H*/


        八、若MCU支持位操作,使用位定义,以提高RAM的使用效率


        九、关于static、extern和volatile关键词的使用


        使用static修饰模块驱动的变量和函数,限定其使用范围;而且建议对不同的MCU外设,编写其驱动时使用各自外设命名的.c和.h文件保存,以方便使用和移植;


        使用extern修饰模块外部可用的函数或变量,一个典型的应用就是通过extern定义静态库的外部接口;


        使用volatile修饰存储外状态设寄存器、输入端口寄存器、通信模块RX数据寄存器等随着硬件外设模块工作随时可能发生改变的易变变量,以保证其能够准确反映外设工作状态,获取最新结果。


        十、8位和16位MCU中分页存储器的访问


        在8位和16位MCU中,由于内核CPU直接寻址能力(由内核CPU寄存器SP和PC的位宽决定)的限制,其常常使用分页访问机制来扩展超出其直接寻址空间的片上存储器.对于这些分页存储器的访问需要使用__far指针访问:


        一个典型的S12G128的分页Flash访问例子如下:


        指针Ptr未使用__far修饰,所为为一般指针,其宽度为16位(2个字节),而Far_Ptr为far指针,其存储宽度为24位(3个字节),所以只有使用Far_Ptr才能正常访问存储在S12G128分页区Page_09(地址0x98000)的10个字节用户常量数据:

10.jpg

        

        十一、针对8位和16位MCU的代码优化


        基于8位和16位MCU存在分页访问的存储器工作机制,为了提高代码的运行效率,有以下建议:


        1. 将应用工程中经常调用的全局变量或者功能函数放置在为分页存储系统中,以提高内核CPU对其的调用、访问效率;


        2. 中断向量表和中断ISR必须放在未分页区;

        一方面,中断向量表和中断ISR为内核CPU中断产生时需要频繁调用,另一方面时因为MCU中断向量基地址寄存器IVBR为8位宽度,其只能管理中断向量表地址的高8位,然后与固定的低8位地址组合产生中断向量表基地址,分页区空间地址为24位,无法确定其唯一性。


        十二、使用直接对相应位写1的方式清除中断标志


        在大多数嵌入式MCU中,对硬件外设中断标志的清除都是写1清,而写0无效,所以建议在清除中断标志位时,采用直接写对相应的寄存器位写1的方式,而不是采用位操作或者或等于(|=),否则如果一个寄存器中有多个中断标志位,会造成其他标志位意外清除而漏中断的问题:


        比如S12G128的TIM定时器中断标志寄存器定义如下:

11.jpg

推荐的正确中断标志清除方法为直接对相应位写1:


TFLG1 = 0x02;    /*clean bit C1F*/

TFLG1 = 0x80;    /*clean bit C7F*/


而不是位操作或者或等于(|=)方式:


TFLG1_C1F = 1;    /*clean bit C1F*/

或者TFLG1 |= 0x02;    /*clean bit C1F*/


        十三、使用IDE的代码格式管理功能统一代码风格


        嵌入式MCU的软件编程IDE中,一般都提供了代码编辑器的代码格式管理功能,其不仅可以帮助用户高效的管理自己的代码风格,而且可以提高编程效率。


        比如通过CodeWarrior 5.1 IDE的菜单-->Edit-->Preference...-->Editor-->Code Formatting就可以对CodeWarrior 5.1 IDE中C/C++编辑器的代码格式进行配置,比如自动补全C语言中的小括号、大括号,代码缩进对齐等功能:

12.jpg

        十四、关于MCU编译器、链接器的优化


        不同的MCU编译器和链接器有不同的优化选项和优化等级,优化目标也常分为速度(speed)和存储器大小(size)。一般编译器和链接器的优化设置都在其IDE的工程属性设置里,并在其IDE安装目录下有详细的用户手册和帮助文档,感兴趣的朋友可以自己参考学习(PS:这些帮助文档和使用手册是了解学习各种编译器和链接器最好的资料):


        比如CodeWarrior 5.1 IDE的编译器优化选项如下:

13.jpg

    

胡恩伟

NXP汽车电子FAE

2017年7月22日于山城·重庆

2018年3月13日修订并声明原创保护,使能留言功能重新发布