本文通过对星载51系列单片机定时器常见问题进行分析,归纳出三类典型问题,分别给出解决方案,并在此基础上总结出一套基于C51语言的星载嵌入式软件定时器管理方法,该方法可以解决因中断冲突和定时器进位而产生的问题,同时可推广到其他嵌入式领域的定时器使用中。 Based on the analysis of the problem of 51 series single-chip timer on board, this paper summa-rizes the three types of typical problems, and gives a solution. On this basis, we summed up a management method of onboard embedded software timer based on C51 Language. The Method can solve the problem caused by interrupt conflict and timer carry, and can be extended to other embedded field of use of timer.
王晶1,胡磊2,王振华1
1北京控制工程研究所,北京
2北京航天航空大学,北京
收稿日期:2017年6月28日;录用日期:2017年7月10日;发布日期:2017年7月17日
本文通过对星载51系列单片机定时器常见问题进行分析,归纳出三类典型问题,分别给出解决方案,并在此基础上总结出一套基于C51语言的星载嵌入式软件定时器管理方法,该方法可以解决因中断冲突和定时器进位而产生的问题,同时可推广到其他嵌入式领域的定时器使用中。
关键词 :嵌入式软件,C51语言,定时器
Copyright © 2017 by authors and Hans Publishers Inc.
This work is licensed under the Creative Commons Attribution International License (CC BY).
http://creativecommons.org/licenses/by/4.0/
在航天嵌入式软件领域,尤其是星载51系列单片机软件中,采用C51语言进行开发的配置项一直占有较高的比例 [
本文结合实例对上述问题进行机理分析,根据不同情况,给出有效的解决方法,并结合航天器软件的设计要求,给出一种通用且可靠的定时器管理方法。
51系列单片机硬件集成有16位可编程定时器。使用最多的是2个通用定时器,即定时器T0和T1。其核心是一个16位的加计数器,由2个8位计数器组成的,即T0由TH0和TL0构成,T1由TH1和TL1构成 [
通过对近年来航天器C51软件定时器问题进行总结,归纳出因定时器产生的问题主要分为3类,分别为:中断冲突下定时器操作错误,非中断冲突下定时器操作错误,定时器时间码读取错误。某些情况下这三种情况会叠加出现,为简化论述,分别对其进行分析。
目前航天器软件多采用中断驱动型架构,该架构下,当主程序及中断程序存在共用资源 [
图1. 51系列单片机定时器内部框图
究其根源在于对共用资源的非预期更改上 [
当发生如下低概率事件:中断出现时刻恰为箭头指向位置。则在MAIN()与INT()中会出现对共享资源的操作,则会出现如下几种工况:
工况1:中断函数INT()中读取的全局变量tm,其数据高8位为新数据,而低8位由于还未执行tm.byte.l = TL0语句,依旧是旧数据。工况4:主程序MAIN()中获得的tm,其数据低8位是中断函数INT()中获取的新数据,而高8位是旧数据。工况6:主程序MAIN()中读取的定时/计数器数据中,低8位TL0是中断函数中获得的新数据,而高8位TH0则是旧数据。工况7:中断函数INT()中读取的定时/计数器,其数据高8位为新数据,而低8位则由于还未执行TL0 = tm.byte.l操作,仍然为旧数据。工况1、4、6、7均出现由新旧数据拼成的16位非预期数据。工况2和8:MAIN()与INT()均对共享资源tm或定时器执行写操作,该工况一定会出现非预期数据。工况3:INT()中对定时器进行读操作,虽然不更改TH0与TL0,但中断函数自身存在执行时间,会造成时延,导致主程序中最终赋值给tm的定时器数据不是同一时刻数据,也属非预期数据,这也是定时器区别于普通共享资源的一个重要特征。工况5:MAIN()及INT()均不改变tm取值,主程序中对TH0及TL0的重新赋值不受影响,该工况下不存在冲突问题。
由分析可知,上述8种工况中有7种存在共用资源冲突的可能性。此外,上述案例伪码针对定时器操作均是先高后低,如果实际代码中顺序相反,结论依然成立。
区别于3.1节,本节针对非中断冲突下定时器操作错误进行分析,即主程序及中断函数不存在共享资源的情况下易出现的时序问题。与3.1节对应,同样分为两类进行分析,见表2。
工况1:主程序赋值过程中高8位赋值完毕,低8位尚未赋值之时有中断到来。即使中断函数中不存在对tm、TH0、TL0的任何操作,但因中断函数自身存在执行时间,导致最终赋值给tm的高低8位不为同一时刻数据,是非预期数据。工况2:在箭头处有中断到来,中断函数中不存在对tm、TH、TL0的任何操作,tm 数据不会变化,tm无随时间变化的特性,故最终赋值给TH0、TL0的数据是正常数据。
工况1 | 工况2 | 工况3 | 工况4 | 工况5 | 工况6 | 工况7 | 工况8 | |
---|---|---|---|---|---|---|---|---|
中断函数INT() | 读共享全局变量tm | 写共享全局变量tm | 读定时器T0 | 写定时器T0 | 读共享全局变量tm | 写共享全局变量tm | 读定时器T0 | 写定时器T0 |
主程序MAIN() | 定时器数据赋值给某共享全局变量 tm.byte.h = TH0; tm.byte.l = TL0; | 共享全局变量赋值给定时器数据 TH0 = tm.byte.h; TL0 = tm.byte.l; |
表1. 中断冲突下定时器操作工况分类
注:tm为16位无符号整形全局变量,中断出现时机为箭头指向位置,以T0为例,下同。
工况1:主程序中将定时器数据赋值给变量tm | 工况2:主程序中将变量tm赋值给TH0与TL0 |
---|---|
tm.byte.h = TH0; tm.byte.l = TL0; | TH0 = tm.byte.h; TL0 = tm.byte.l; |
表2. 非中断冲突下定时器操作工况分类
1) 16位时间码读取错误
定时器使用中,除上述两类因中断出现的问题外,存在另一种易忽略的低概率事件导致读取定时器错误。以T0为例,定时器是自加计数,读取T0过程中,当有进位情况出现,即低8位TL0由255变为0,高8位TH0加1,如果进位恰好出现在T0高8位读取完毕,而低8位还未读取时,则读取的数据与真实计数器数据最多会出现255的差别。如下述代码:
假设执行语句tm.byte.h = TH0时,其真实时刻为0x03ff(即TH0 为 0x03;TL0 为 0xff),则读取的定时器高8位为0x03,而执行tm.byte.l = TL0语句时,因为定时器继续自增产生进位,所以当前真实时刻为0x0400(TH0 = 0x04;TL0 = 0x00),而读取的定时器低8位为0x00,因此最终读取的16位数据为0x0300,与真实时刻0x0400相差256,读取时间码错误。
2) 32位时间码读取错误
对上述工况扩展,如软件所需时间度量较大,16位定时器量程不够,通常采用32位整形表示时间或计数。如图2所示。
该32位时间码的高16位为中断溢出次数,低16位为定时器,因此不仅存在D0~D7与D8~D15的进位问题,同时也存在D0~D15与D16~D31的定时器溢出进位问题。原理同上,当低16位定时器溢出清零时刻读取时间码,将导致最终读取的数据与真实定时器数据间出现65536的差别,引入计算会发生非预期的结果。
针对第3节中提出的定时器使用问题,我们按照是否因中断引发将其分为2类:定时器读写操作过程中有中断出现,即3.1节、3.2节所述问题;读取时间码时有进位发生,即3.3节所述问题。下面就解决方案分别进行描述。
图2. 扩展的32位时间码
针对由中断导致的定时器读写操作错误问题,即3.1节提到的8种工况,可以采用如下两种方式解决。
1) 在执行定时器读写操作时关闭中断,避免因中断出现而导致定时器读写异常。其伪码如下:
该处理方式有较多应用,简单便捷,是解决中断冲突的首选方法。但若工程应用中对中断响应实时性需求较高,不允许关闭中断,考虑下一种解决方案。
2) 设立中断标志Intflag,在中断函数中置为真,在主程序读写定时器之前将中断标志设置为假,在读写操作后判断flag,若为真,则可判定在读写定时器过程中有中断到来,认为操作无效,重新读写定时器,其伪码如下:
针对3.3节提出的2类问题分别给出解决方案。
1) 16位时间码读取错误解决方案:前后读取两遍定时器数据,利用数值判断首次读取过程中是否有进位产生,如果存在进位,则进行补偿。其伪码如下:
2) 32位时间码读取错误的解决方案:扩展到32位时间码后,需获取低16位时间码进位情况,采用判断定时器溢出标志的方式,当标志为真,存在进位,则需要考虑补偿。因高低8位时间码仍然存在进位可能,将上述伪码进行改进,同时避免高低8位及高低16位进位的影响,其伪码如下:
第4节针对定时器/计数器使用中的问题提出解决方案,所述问题与时序相关,出现概率极低,我们针对其问题总结如下3条原则:
原则1:主程序与中断函数中存在共用资源时需考虑保护,不仅包括“读写”、“写读”、“写写” [
基于上述原则,总结出一套针对定时器读写操作的通用管理方法。
1) 将主程序及中断函数中涉及定时器读写操作遍历出来
2) 针对不同的操作进行判断
① 若主程序中“定时器数据赋值给共享全局变量”且存在共用资源,中断函数中存在如下4类行为需要保护:a读共享全局变量;b写共享全局变量;c读定时器;d写定时器。则主程序中对定时器的读操作需要保护,首选关中断方式进行保护,如有特殊需求不允许关中断,则采用设立中断标志,使用重新读写的方式进行保护。
② 若主程序中“共享全局变量赋值给定时器数据”且存在共用资源,中断函数中存在如下3类行为需要保护:a写共享全局变量;b读定时器;c写定时器。则主程序中对定时器的写操作需要保护,保护方式同上。
③ 若主程序中“将定时器数据赋值给某变量”且不存在共用资源,主程序中的定时器读操作同样需要进行保护,保护方式同上。
3) 针对步骤1中遍历出来的读定时器操作,无论位于主程序还是中断函数,均需进行定时器进位保护,按照使用的具体情况,分别对16位时间码及32位时间码进行进位保护。具体保护方式的伪码见4.2节。
定时器读写操作通用方法流程图如下,见图3及图4。
图3. 中断保护通用方法流程图
图4. 进位保护通用方法流程图
为简化论述,我们将中断保护及进位保护分别论述,真实状态下两类保护方式应叠加出现,即进位保护通用方法应出现在所有进行定时器读取之处。其具体实现方式的伪码见4.1及4.2节。该方法具有可扩展性,当中断存在嵌套时,采用该方式同样可完成定时器管理操作。
本文描述了航天器星载51系列单片机软件中定时器使用问题,该类问题存在偶发性,与时序相关。文中按照问题产生机理将其分为3类,采用静态分析的方式详细解析了问题成因并提出解决方案,给出一种可靠的定时器管理方法,成功应用于实际型号工作中,极大地提升了定时器使用的可靠性。文中提及的关于定时器的一些使用方法和保护原则可以推广到其他嵌入式工业领域的定时器的研制使用中。
王 晶,胡 磊,王振华,王振华. 基于C51语言的星载嵌入式软件定时器管理方法The Management Method of Onboard Embedded Software Timer Based on C51 Language[J]. 计算机科学与应用, 2017, 07(07): 654-661. http://dx.doi.org/10.12677/CSA.2017.77076