单片机程序设计编程规范.docx
《单片机程序设计编程规范.docx》由会员分享,可在线阅读,更多相关《单片机程序设计编程规范.docx(24页珍藏版)》请在冰豆网上搜索。
单片机程序设计编程规范
单片机程序设计编程规范
本规范适用于松翰科技8-bitMCU部门汇编程序编写准则,同样适用于代理商及重要客户工程师编程规范参考。
本规范的目的为统一编程风格,保证程序编写质量,提高程序的可移植性和维护性。
大部分的规范严格,品质要求高的软件公司对员工编写代码的风格都有硬性规定,例如缩排的使用,TAB的长度,函数变量的命名方式。
这些规定的明显好处是可以统一规范不同程序员所编制的代码,提升程序代码的可读性与可维护性,同时统一格式的编程风格也为codereview提供方便。
一、设计总则
二、排版风格
三、程序可读性及可维护性
四、注释
五、变量命名规则
六、常量命名规则
七、标号命名规则
八、文件命名规则及文件分割
九、标准程序模块
十、附录
一、设计总则
1.程序质量的评估
程序的优劣可以从两个方面进行评估,定量指标和定性指标。
定量指标包括:
1)程序代码执行效率;
2)程序占用资源多少。
定性指标包括:
1)可调试性,即是否方便排除程序语法错误;
2)可测试性,即是否方便验证程序功能的正确性;
3)可维护性,即是否方便程序的修改和升级;
4)可移植性;
5)可读性。
2、程序架构
为了便于维护和移植,推荐使用层次化的软件设计方法。
可把整个软件分为三层:
应用层、界面层和底层驱动层。
各层之间的关系如下图所示。
层次化设计说明:
1)底层驱动层主要包含直接和硬件相关的驱动程序,如数码管显示、按键、峰鸣器、继电器和电机控制等。
底层的各个模块间要保持各自的独立性,不产生直接的数据交互,底层也不直接访问应用层,如果有需要,都要通过界面层进行数据交互。
2)界面层主要提供数据交互,为应用层和底层驱动之间以及底层驱动层各模块之间提供数据的交互。
3)应用层主要完成具体功能的实现,它要通过界面层控制底层驱动层各模块来完成所需功能,而不能越过界面层直接访问底层驱动层。
所有的用户接口要在应用层来实现。
4)一个好的架构必须将底层硬件包装起来,为应用程序提供一组丰富的函数操作(bufferorparameter),例如在中断的处理中,应用程序不需要资料中断的堆栈如何保护不需要知道地址操作,只需要读取中断产生的旗标动作。
5)在即时性软件系统里面,对达到高效率的实时性与反应力,所以程序使用大量的事件触发方式来设计任务。
事件有可能来自外部的触发(key,rxdata,sensordetect,…)也可能是系统内部自行产生的(Timer,alarm,flag),与事件触发方式相对应的是定时查询方式(polling),一般来说polling效率较差因为有多余的动作而且系统反应时间与查询polling时间间隔有关,但是在小型的MCU系统里面用Polling方式反而简单许多。
3、设计基本原则
1)尽量减少各个子程序功能模块间的耦合度(耦合度是指一个程序的执行对另一个程序的影响力),保证各自的独立性。
一般情况下,建议子程序模块功能的划分要尽可能细化,功能尽量单一,减少子程序模块间的数据交互。
2)在满足功能需求的情况下,可适当牺牲代码的执行速度,以保证程序的透明度。
3)主要子程序模块间的交互,要通过特定的界面跟应用层进行沟通,可使用FIFO(Firstin,Firstout)或是Buffer两种方式。
每种子程序模块都可以有自己的FIFO。
例如:
就按键来说,一般有Keybuffer、KeyFIFO或直接进入APFIFO三种设计方式。
Keybuffer:
一般用于保存数字按键信息。
例如:
在电话机的设计中,需要记录按键内容用于LCD显示、最后数字确认、数字存储等,这时候需要把按键值的信息(0123456789*#)记录在Keybuffer中。
KeyFIFO:
一些功能按键可以将相应信息列入到KeyFIFO中,等待应用层的取用,这样可根据不同的工作模式进行不同的处理及动作。
这些类似的观念可以应用在许多周边中:
输入类:
Key、RFDatainput、UARTdatainput、Switchinput等。
输出类:
LCDdisplay、LEDdisplay、UARTdataoutput等。
APPFIFO(应用界面层):
主要是将发生的事件储存在APPFIFO里面等待适当的时间依序处理,不然有可能造成系统在某一程序物件里面循环,从而降低系统的实时性(Real-Time)。
4)每个子程序模块只能有唯一一个程序入口地址在程序的首部,只能有唯一一个程序出口地址在程序的尾部。
例如:
以下的写法是不规范的(两个RET出口,存在调试时不易设立断点,程序可读性降低等问题):
lable:
b0bts0fz
ret
…
clry
ret
应该改为(只有一个出口位置,标号为lable90,便于程序检查):
lable:
b0bts0fz
jmplable90
…
clry
lable90:
ret
5)上电复位时要对所有的RAM空间进行初始化(建议用户寄存器清零,系统寄存器进行必要设定),不要使用未经初始化的变量。
RAM未经过完整的初始化,容易导致程序执行的不确定性,这一不良现象往往在批量生产中有所体现。
(这点是工程师经常犯错的地方,须特别注意)
6)系统中如果需要等待一些未知的应答信号,如通信或等待输入信号时,必须进行超时或异常处理,以防止程序进入“死等”状态。
例如在红外接收中,由于信号的突然消失或干扰从而无法得到一帧完整或正确的信息,这时需要复位接收程序的入口条件并退出接收程序,而不是一直等待信号的来临。
不然无法进行下次接收甚至会影响到其它程序的执行。
7)通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。
这种方式是解决软件空间效率的根本办法。
8)保证循环体内的工作量最小化。
应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的执行效率。
9)在多重循环中,应将最忙的循环放在最内层。
10)中断处理程序应尽量短。
有效的作法为:
在中断中进行标记,在主程序中进行处理。
但一些实时性要求较高的程序例外。
此外,进入中断时应该保存涉及到的变量和寄存器。
11)看门狗的正确使用。
看门狗主要用于微控制器死机时的时间溢出复位,需要程序适时清除。
正确的处理方式为:
整个系统程序中尽量保证只有一处清看门狗位置,而且应处在主循环的主干位置。
切记不可在定时中断中清狗,因为微控制器有时只是在主循环中死掉。
(所有AC电源的应用程序都必须强迫加入看门狗选项尤其是条件式的看门狗有利于系统发生异常后的重启动)
二、排版风格
1、程序采用缩进风格编写,缩进为1个Tab键,1个Tab键定义为8个空格位。
2、程序中的标号要从第一列开始书写。
以“.”开头的预编译命令也要从第一列开始书写,其他预编译命令采用缩进风格编写。
例如:
1)以“.”开头的预编译命令要从第一列开始书写,其他预编译命令采用缩进风格书写。
.LIST;从第一列开始书写
INCLUDESTDmacro1.h
.CONST
NUMBEREQU55h
.DATA
wk00DS1
.CODE
ORG0h
2)标号要从第一列开始书写。
main:
;从第一列开始
…
jmpmain;缩进8个空格位
3)变量或常量的定义采用缩进风格。
例如:
.DATA
keybufDS1
.CONST
NUMBEREQU8
4)定义变量或常量时,变量名或常量名与命令符之间使用2个Tab键(相当于16个空格位)分开,命令符与后面的操作数用1个Tab键(相当于8个空格位)分开。
例如:
keybufDS1
NUMBEREQU55h
5)操作码与操作数之间用1个Tab键(相当于8个空格位)分开。
例如:
ORG80h
mova,NUMBER
table:
DW1234h
6.)程序中两个操作数之间用一个“,”作为分隔符,“#”号与立即数之间不需要分隔符。
例如:
Mova,#55h
7)标号要单独占一行。
8)相对独立的程序块之间必须加空行。
例如:
ORG10h
INCLUDEsys.asm
INCLUDEint.asm
INCLUDEkey.asm
9)程序语句后面若有注释,所有的注释要遵守上下对齐的原则。
例如:
b0movl,#7fh;usedp0x(hl)pointer
mova,#00;setpointer=007fh
应该书写为:
b0movl,#7fh;usedp0x(hl)pointer
mova,#00;setpointer=007fh
三、程序可读性与可维护性
1.程序中的语句、标号、变量使用小写英文字母,常量与预编译命令使用大写英文字母,以便和一般的语句进行区分。
例如:
NUMBEREQU55h
.DATA
accbufDS1
.CODE
…
mova,#NUMBER
2.表示不同进制的立即数,要在立即数后面加上不同的进制符号。
例如:
mova,#00100011b
mova,#23h
不建议使用:
mova,#0x23
3.一般情况下,变量和常量要分开定义,不要混在一起。
变量在“.DATA”段中定义,采用命令符“DS”,常量在”.CONST”段中定义,采用命令符“EQU”。
例如:
.CONST
NUMBEREQU10
.DATA
AccbufDS1
4.程序中不使用未定义或意义不明确的常量。
例如:
下面的赋值方法要避免使用:
…
mova,#3;不要直接使用意义不明确的数字
在常量中做定义
movr,a
…
建议采用如下赋值方法:
.CONST
NUMBEREQU3;循环次数
…
.CODE
…
mova,#NUMBER
movr,a
5.整个程序的结尾要以“ENDP”语句结束。
6.当一段代码在程序中有多个地方使用时,建议采用子程序调用或宏命令的方式来替代。
如此,对该代码段的修改就可在一处完成,增强代码的可维护性。
7.程序中关系较为密切的子程序代码尽可能相邻。
8.避免程序中的垃圾代码,预留代码应以注释的方式出现。
程序中的垃圾代码不仅占用额外的空间,而且还可能影响到程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。
四、注释
1.程序应该包括两个部分注释,说明部分和语句注释。
一般情况下,源程序有效注释量必须在30%以上。
2.说明部分:
1)源文件说明部分位于每个源文件的最前面,主要描述:
文件名、作者、生成日期、联络方式、功能描述、版本号、软硬件平台、版权说明、修改记录等的简要说明,以英文书写。
例如:
/**********************************
Filename:
;文件名
Author:
;作者
Date:
;日期
Email:
;邮箱地址
Description:
;功能描述
Version:
;版本号
Hardware&IDE;软硬件平台
Copyright(C),SONIXTECHNOLOGYCo.,Ltd.
History:
;修改记录
**********************************/
2)子程序说明部分位于每个子程序的最前面,主要描述:
子程序名称、功能、设计原理、所用变量、入口条件、出口信息、调用模块、堆栈层数、影响资源、算法简述、使用说明和修改记录等。
例如:
/*****************************
Subroutine:
;子程序名称
Description:
;子程序功能的描述
Principium:
;程序设计原理
Calls:
;被本子程序调用的子程序清单
Variables:
;本子程序中所用到的临时变量
Input:
;子程序调用所需要基本参数的说明
Output:
;子程序调用后运算结果的说明
Stack:
;占用的堆栈层数
History:
;修改记录
******************************/
3.边写代码边注释,修改代码的同时修改相应注释,以保证注释与代码的一致性。
不再有用的注释要删除。
4.要避免在注释中使用缩写,特别是非常用缩写。
5.程序在必要的地方必须有注释,注释要准确、易懂、简洁。
注释要有意义,如果有需要,还要详细描述相关含义。
例如:
以下是无意义的注释
mova,#5;把5赋给acc
movwk00,a;把acc赋给wk00
应该如下注释:
mova,#5;设置循环次数为5次
movwk00,a
6.注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)的相邻位置,不可放在下方,如放于上方则需与其上面的代码用空行隔开。
例如:
clrwk00
;CheckReadindex=FiFoLimit
cmprsa,r;IsReadindex=Limit
Jmp@f
7.注释格式尽量统一,对多行注释建议使用“/*……*/”,对单行的注释建议使用“;”。
8.注释应考虑程序易读及外观排版因素,语言尽量统一。
对不能进行准确英文表达的建议使用中文。
9.对有含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释,说明其含义。
五、变量命名规则
1.变量的名称要采用有意义的英文单词小写缩写。
可以采用以下几种方式进行缩写命名:
1)去掉所有不在词头的元音字母。
如screen可以简写为scrn。
2)使用每个单词的头几个字母。
如channelactivation可以简写为chanactiv。
3)使用变量名中具有典型意义的单词。
如numberofcycle可以简写为cyclenumber。
4)去掉无用的单词后缀ing、ed等。
如pagingrequest可以简写为pagreq。
5)尽量使用标准或惯用的缩写形式,缩写应该保持一致性。
如serialperipheralinterface可以简写为spi。
6)部门已经完成函式的变量名称,如果没有特别原因,发展人员延续使用以增加可读性
2.在定义部分要加入注释来说明变量的含义。
3.变量的定义要在“.DATA”段中。
4.变量标识符的长度不超过16个字符。
5.序会用到起码三种变量
1)全局变量名称前面不加任何修饰。
2)局部变量利用wk00,wk01….wk0n来表示,每个独立程序里面用到的
Localvar.可有效节省RAM。
3)中断局部变量中断里面用的Interruptlocalvar.利用Iwk00,Iwk01以作为区隔(注意中断使用的变量必定要小心的跟主程序区隔开,不然影响系统稳定性相当大)。
例如:
.DATA
…
accbufds1;全局变量
l_numberds5;局部变量
6.位定义。
程序多处会经常对寄存器的某一位进行操作,可以在变量定义时对需要用到的位进行定义,建议名称以”f_”开头。
7.临时工作寄存器的定义。
程序中经常需要用到一些临时存储数据的寄存器,我们称之为临时工作寄存器,这些临时工作寄存器的命名方法为“wk”加上一个二位数字组成,这个数字可以从“00”开始随着需要定义的临时工作寄存器的数量的增加而增加。
需要特别说明的是,临时工作寄存器的使用可以大量节省RAM空间,但是要注意相应的生命周期,必须在子程序退出之前,将空间释放以便其它子程序使用。
8.在中断处理程序中用到的寄存器,为了和一般的临时工作寄存器作以区分,可以在寄存器前面加英文字母“i”来命名。
在使用时需要加以注意,不要和主程序中的变量复用。
例如:
.DATA
…
wk00ds1
wk01ds1
wk02ds1
iwk00ds1;中断中要用到的临时工作寄存器
iwk01ds1;中断中要用到的临时工作寄存器
…
.CODE
…
Mova,r
Movwk00,a
mova,wk01
movr,a
…
六、常量命名规则
1.常量的名称要采用有意义的英文单词大写缩写。
2.常量要定义在“.CONST”段中。
3.在定义部分要加入注释来说明常量含义。
4.量标识符的长度不超过16个字符。
5.系统寄存器中常用到的某些位可以再额外进行定义,但要有意义:
P_key1EQUP1.0;P_打头表示为Port定义
Pm_key1EQUP1m.0;Pm_打头表示为Port方向定义
…
七、标号命名规则
1.标号的名称要采用有意义的英文单词小写缩写。
2.子程序标号定义。
在同一个子程序中,所有的标号应该有规律可寻。
建议第一个标号为子程序名,下面所用到的标号用子程序名添加数字表示,从而便于今后程序的添加和修改。
数字尽量使用两位数,在子程序的退出位置,数字一般为”90”。
例如:
在一段按键扫描程序中,如下的标号是不可取的。
Label:
LabelOK
LabelFail
LabelQuit
Labelfun:
LabelEnd:
Ret
(设计师要花许多精神命名label,同时不容易看出子程序的结构关西,所以建议在子程序里面别在花精神命名,都用号码表示)(详细的动作都用注解来说明)应该改为:
;***************************************************
;Sub-routine,Name:
label
;***************************************************
Label:
IftheconditionfailthengotoLabel90
;programbeginhere
Label10:
;注解都写在这里
Label20:
;最后结尾结束的label都用90当做结尾
:
有助于程序的阅读
Label90:
Ret
3.为了使程序的结构更加清晰,子程序的命名要尽量能显示出相互间的调用关系。
由main主循环中直接调用的程序,要以“mn_”作为标号的开头,下面仅列出一些常用的子程序名称,其它类似情况可同样处理。
mn_app;系统应用程序
mn_intgnd;中断与主程序之间的界面子程序
mn_key;按键扫描子程序
mn_lcd;LCD显示子程序
mn_led;LED显示子程序
mn_tone;声音处理子程序
mn_epp;EEPROM读写操作
mn_bio;基本输入/输出控制
mn_adc;ADC输入及处理
mn_debug;debug处理程序
4.程序的几个常用入口地址命名如下:
复位入口:
reset
中断入口:
isr
主程序入口:
main
例如:
org0
jmpreset
org8
jmpsr;ISR(InterruptServiceRoutine)
org10
;有效程序开始地址
reset:
…
;进入主程序前的一些准备工作(预处理)
premain:
…
;主程序循环圈
main:
…
Jmpmain
八、文件名的命名规则及文件分割
1.在一个项目中要包括两种文件:
源文件和头文件。
源文件是程序体,扩展名为“.asm”,头文件包括了变量、常量、宏命令的定义,扩展名为”.inc”。
2.头文件的命名
头文件用三个不同的文件来分别定义常量、变量、宏命令,其命名方法如下:
常量定义文件:
xxx_equ.inc
变量定义文件:
xxx_ram.inc
宏命令定义文件:
xxx_macro.inc
其中,xxx表示项目的名称。
例如:
作一个电话机的完整程序,头文件可如下定义:
常量定义文件:
phone_equ.inc
变量定义文件:
phone_ram.inc
宏命令定义文件:
phone_macro.inc
3.源文件分为主文件、子文件。
主文件包括了项目的主程序,它描述了芯片信息、CODEOPTION信息及项目包含的其它子文件模块。
子文件是由各个子程序模块组成,功能相近的子程序要放在一个文件中,文件名可以用
“项目名+模块功能的缩写+扩展名”表示。
下面列举一些常用的文件名:
主程序文件:
xxx_main.asm
常用的子文件定义:
中断服务程序模块:
xxx_int.asm
进程处理模块:
xxx_pro.asm
系统处理程序:
xxx_sys.asm
按键处理模块:
xxx_key.asm
LCD显示模块:
xxx_lcd.asm
其中,xxx表示项目的名称。
九、标准程序模块
常用功能模块尽量采用附件提供的标准程序,如果无法使用标准的程序模块,那么所编写的程序一定要符合规范要求。
所提供的标准程序如下,详细的源代码在附录中。
1.mn_key;单键扫描子程序
2.mn_mulkey;多键处理子程序
3.mn_tone;声音处理子程序
4.mn_lcd;LCD显示子程序
5.mn_i2c;I2C操作子程序
6.mn_sio;SIO操作子程序
7.mn_1wire;单总线操作子程序
8.mn_uart;UART子程序
9.wr_fifo;写FIFO
10.rd_fifo;读FIFO
11.ram2ram;RAM到RAM的数据传递
12.rom2ram;ROM到RAM的数据传递
13.math;基本运算程序
实例说明
1.初始化
1)当程序上电复位时,一个完整的用户寄存器初始化或清零动作是非常重要的,否则容易造成程序执行的不确定性。
例如:
pre_clrRAM:
b0movy,#0;清RAMbank0
b0movz,#48
@@:
Clr@yz
decmsz
jmp@b
clr@yz
ret
2)系统寄存器必须进行初始化,比如说I/O口方向和输出电平等。
需要特别说明的是输出口的初始化必须按照下列顺序来处理:
(1)设定相应I/O口输出高低电平
(2)将相应I/O口为输出口如此可确保MCU的I/O口从输入模式转为输出模式时不会有脉冲的存在。
而在I/O口控制频繁变化(如通信)时更要引起注意。
例如:
(3)mova,#11111111b
;如果p1.0和p1.1原来为输入高电平,当直接切换为输出方式时会有高电平脉冲的输出,而这不是所希望的。
b0movp2m,a
mova,#11111100b
b0movp2,a
(4)mova,#00001100b
b0movp2,a
;这样处理p1.0和p1.1就避免了高电平脉冲的输出
mova,#11111111b;
b0movp2m,a
开机时后IOtoggle的作法:
利用暂时没有用到的IO做toggle可有效观察以下几点
1)系统是否正常开机
2)是否发生resetorwatchdogreset现象
3)