菜鸟学PIC单片机(三)LCD时钟的总结,并由中断暂禁的后果说开去(转贴自21IC)
碧水长天 发表于 2004-11-26 20:00 PIC 单片机 ←返回版面
|
|
菜鸟学PIC单片机(三)LCD时钟的总结,并由中断暂禁的后果说开去
|
上回说到刚接触PIC没20天的菜鸟碧水长天准备"野心勃勃"写一段LCD显示精确时钟的但遭到无情狙击的故事,幸好得到这里行家的点拨,方能理清一点头绪,于是,今天就接着上回的故事,总结一些通用的注意事项,并对LCD显示精确时钟进行功能实现上的分析.
一、先总结一些细节的问题,再分析功能实现上的缺陷:
1. 关于中断现场的保护和恢复的问题 由于movf指令可以影响STATUS,而W又要在现场保护过程中起中转寄存器的作用,因此,应先保护W,再保护STATUS,最后是保存其他现场变
量。保存的时候应注意,如果W的备份寄存器w_temp若不是位于快速存取区70H~7FH,假如w_temp定位为0x20,那么还需保证bank1,bank2,
bank3中的0xA0,0x120,0x1A0出的单元没有被派做他用。如果fsr_temp,pclath_temp等也不是定义在快速存取区的话,那么,需注意在备份FSR
,PCLATH之前,要确保当前操作在bank0处(当然,在其他bank也可,但必须注意在恢复现场的时候,也要保证在相同的bank中对备份积存器进
行操作,为了方便起见,建议控制在bank0处进行保存和恢复操作)。
至于,备份寄存器若定位与快取区中,那么对bank没有要求,但对次序的要求仍然存在的。 这是经过改进后的恢复和保存现场代码: ORG 0x000 ; processor reset vector nop ; ICD need goto main ; go to beginning of program
ORG 0x004 ; interrupt vector location movwf w_temp ; 先保存W movfw STATUS ; 再保存STATUS到W中 clrf
STATUS ; 注意该指令,确保对status_temp,pclath_temp的操作在bank0中 ; (如果备份寄存器定义在快取区中,可无取消此条clrf及恢复现场那条clrf指令) movwf status_temp ; 保存上上条指令备份在W中的STATUS movfw PCLATH ; 备份PCLATH movwf pclath_temp movfw FSR ; 备份FSR movwf fsr_temp ; 可添加其他欲保护的变量
;******************** 中断服务代码 btfss INTCON,T0IE ; 判断是否为T0中断 goto other_int btfss INTCON,T0IF ;
it 's the time of T0 int goto other_int bcf INTCON,T0IF ; 是T0中断,清除中断标志 movlw 0x10 ; 微秒的高位字节加上定时时间 256x16分频=4096=0x1000的高位(0x10) addwf us+1 goto end_int other_int ; 可添加其他中断服务代码 nop ; other isr code can be added ;********************************** end_int ; 恢复现场 clrf STATUS ; 确保恢复现场的操作在bank0中(如果备份寄存器定义在快取区中,可无取消此条指令) ;
可添加恢复其他变量 movfw fsr_temp ; 恢复FSR movwf FSR movfw pclath_temp ; 恢复PCLATH(FSR和PCLATH的恢复无先后之分) movwf PCLATH movfw status_temp ; 先恢复STATUS movwf STATUS ; swapf w_temp,f swapf w_temp,w ; 最后恢复W,采用swapf是因为其不会影响STATUS retfie ; 中断返回
;*********
2.(保留区域,待添加)
--------------------------------------------
二、分析功能实现上的缺陷,并由中断响应及子程序暂禁中断所引起的问题说开去
先将昨天贴的源程序的main部分的代码拿出来分析: 主程序要实现的功能是显示时钟: HH
MM SS 00:00:00 定时中断每次产生4096us的增量,在中断服务中,将此时间增量累加在(us+1:us)两个相邻的字节中,由_clock子程序 对(us+1:us)进行及时判断,超出50ms即取走一个50ms的增量,并保留余量在(us+1:us)中以保证长时间定时精确.
主程序流程:
main nop call _init ; 调用初始化子程序,清缓冲区,实现液晶显示器和TMR0的初始化操作. call _disp1 ; 调用显示字符" HH MM SS "的子程序 loop call _clock ; 调用时间更新子程序,更新定时中断产生的时间累加值 call _convert ; 调用时钟的小时,分,秒的BCD码转换子程序,并换成字符对应的ASCII码 call _disp2 ;
调用转换后的小时:分:秒字符的显示子程序 goto loop ; 执行主循环
分析如下: 由于_clcok和_convert码制字符转换子程序与时间显示_disp2子程序是前后的顺序关系的,在时间显示时,前两个子程序是不工作的,由于
LCM的慢显特性,使得该子程序执行时间较长,这样,即使中断定时时间已经累计到应改变显示结果的条件,但此刻_disp2若仍在显示上一时间
,使得_clcok不能及时更新时间,并且_convert不能转换代码,那么显示结果仍然没有变化。当loop循环执行一次完毕之后,_clock和_convert才开始更新. 但是这里可能会有个疑问:既然如此,计算_disp2的执行时间大概为500ms,当_disp2子程序执行完毕之后,那么也开始循环执行_clock和
_convert,然后LCM再显示,此刻应该显示的是更新的时间了吧,总时间也大概为1s多一点,为何执行结果大概等到1分钟左右,秒区数字才加1呢? 问题提得很好。
思考原因可能为 :由于_clock不能及时更新时间,及不能及时取走(us+1:us)中大于50ms时的50ms量,但中断服务代码中始终严格执行下面两
条指令: movlw 0x10 ; 256x16分频=4096=0x1000的高位(0x10) addwf us+1 ; 微秒的高位字节加上定时时间 多次累加后(15次累加令us+1单元的内容为从00H到F0H)令us+1单元溢出,丢失定时的时间增量,若当_clock更新时,(us+1:us)发生溢出使得其
值小于50ms(代数值50000),因此也不能使得变量ms50的值增加,那么秒钟变量sec也不会变化,转换后时间显示仍然保持不变. 注意: 当_clock更新时间时,(us+1:us)若满足大于50 000的条件,则ms50变量加一,在main主程序中_clock循环更新时,若捕捉到20次
(us+1:us)单元大于50000(50ms)时,sec的值才能加1。而这个在多次更新过程中捕捉该条件的周期,就是秒区显示加1的周期,我认为这个周
期是固定的,也许是30秒,也许是1分钟,也许更长,只要程序长度和结构没有发生变化。后来在程序中,我增加延时子程序的时间,结果秒区数字加1的间隔时间也跟着延长了。
到了这里,知道了问题所在,那么在基于原程序的框架下,我对几种解决方案都尝试了一下:
方案1: [既然症结是在_clock不能真实捕捉到每一次中断时间累加增量(us+1:us)值大于50ms(50000)的条件,那么,将_clock内嵌中断中去,中
断每一次改变us+1的值然后马上进行时间更新,这样,使得_clock能真实捕捉每一次(us+1:us)值大于50ms(50000)的条件,也能真实更新系统时
间。]
方案1分析:这样确实可以保证每一次都可以捕捉us时间增量,不考虑运行的结果问题,该方案有几个缺点:
1) 中断服务代码由于调用了_clock子程序,显得异常臃肿; 2) 每次中断(4096us)都调用_clock,判断其是否到50ms(值为50000),增加了程序的开销,效率较低; 3) 由于LCM慢显示特性的原因,可能使得结果仍然不能令人满意:
关于3) 我描述一下一下:虽然此刻,秒区的数字能基本上每秒钟跳变一次了,但是调试过程中出现了一个问题: 秒区数字跳变有时会忽略下
一个值,而跳到下下一个值去,比如,当前显示12,然后马上显示14。
那么问题出在什么地方呢?
试想,若_convert在进行格式转换时,发生中断,且更改了sec变量,那么,_convert会按新的值进行转换,这
样,本来这次要转换并送显示的旧值被新值给覆盖了,所以,_disp2在显示的时候,也就根据_convert的转换结果,忠实地显示了一个新值,将
本来应该显示的值给忽略了。
既然如此,有什么办法来解决呢?两个方法:
(a) _convert在对时间变量进行格式转换时,暂时禁止TMR0中断,转换后再开启TMR0中断; (b) 将_conver也归并到中断代码中去,规定次序,使得_clock更新时间后,_convert再进行转换,这样,格式转换区的变量不用担心被
_clock修改; **那么方法(a)会存在什么问题呢?试想:当_convert在转换时,TMR0定时时间到,TMR0向内核提交中断,但由于TMR0中断请求被禁止,即使
_convert转换完毕之后,允许TMR0中断,那么TMR0的中断请求会不会被丢弃呢? 显然,根据PIC的中断系统,当TMR0定时时间到后,首先将
T0IF置1,并由T0IF向内核提出中断请求,如果该中断请求被禁止,那么只要其中断标志T0IF仍然保持为1,当该中断响应解禁之后,内核根据
T0IF立即响应其中断。
因此,方法(a)中"TMR0的中断请求可能会被遗弃的担心"是多余的.
并且,由于_convert的执行时间少于一个中断周期,所以它对中断的暂禁操作不会出现在一个暂禁中断的过程中,中断标志T0IF的多次被置一
的现象,所以不会发生中断响应被冲掉的不良后果。同样,_clock子程序在没有加载到中断服务代码中去时,其对TMR0的暂禁影响与_convert分
析的结果相同.
那么,既然如此,我认为这样的话,由于_disp2的执行时间也不会超过1秒,因此,不会出现当秒跳变时,_convert来不及转换而丢弃上一次待
转换的字符。所以,结果应该是正常. 于是按照这种方法修改程序,结果发现秒区每次都跳变,最小增量为2,最多为为3(跳变周期大约1.2秒)。于是将延迟子程序的外循环值由
64H-〉40H(大概右25ms变成16ms),结果仍然如此,秒区每次都跳变,只是跳变节奏比未修改延时子程序前变快很多(跳变周期大约0.6s),但最
小跳变增量1,最多为2。
[正在分析其根源,也请有兴趣的兄弟一起思考一下.....]
那么那试试方法(b).我按方法(b)修改了程序,结果发现,仍然出现秒区数字跳变的情况。
究其原因,跟3)类似:当_disp2运行的时候,准备从显示缓冲区取字符来显示,如果发生中断,_clock,_convert更改了显示缓冲区的内容
,使得本来即将待显示的内容被替换成下一次显示的内容。所以,该方法依然存在,而且,由于_disp执行时间大于一次中断的255us,如果在
_disp执行过程暂禁TMR0中断将会丢弃中断请求(即:TMR0的中断请求被自己下一次中断请求覆盖,上一次中断请求被忽略,显示时间将变慢)。
----------------------------------------------------------------
方案2:
[中断服务仍然只改变us+1的值,但是格式转换及显示功能内嵌到_clock子程序中去,主程序执行_clock循环。] 下午我按这种方式更改了程序,在软件模拟时发现程序跑飞。原因是:内嵌了这些功能之后,代码由400行变成500多行,在_disp1查表显示
字符时,_table已经超过PCL的256字ROM空间,而查表时未注意PCLATH内容,以致跑飞。解决此问题后,下载到ICD中运行,发现结果倒是正常
了,但是感觉时间好像有一点点慢。
呵呵,细心的站友想必已经看出来了,由于加了显示功能的_clock子程序中依然是暂时禁止TMR0中断的,虽然该时间显示功能只是在时间跳
变时刷新LCD屏幕,但是正是由于在时间跳变时执行时间刷新的周期过长(大于4096us),TMR0 的多次中断请求最后只被响应一次,即T0IF多次
被置1后,却只能在_clock子程序末对TMR0解禁时得到一次中断响应,未被响应的累积时间被丢弃了,没有加到(us+1:us)中去,引起时钟显示变
慢了.
方案3:
由于前两个方案均存在不近人意的问题,难道在用TMR0做秒表时,且当"定时中断的周期小于LCD慢显器件的驱动刷新周期的情况下",就没有一个
完美的解决方案么? 留在这里和有兴趣的站友一起思考...
呵呵,罗罗嗦嗦地写了这么一堆,就做为学习总结吧,也希望对一些和我一样的新手有一些启发...
* - 本贴最后修改时间:2004-11-26 20:26:48 修改者:碧水长天 * - 修改原因:.
『出发到香港』免费游 大抽奖!
--------------------------------
|
|
碧水长天 发表于 2004-11-26 20:20 PIC 单片机 ←返回版面
|
|
贴出按方案2修改后的源程序,有MCD1/MCD2套件的站友可对其编译运行
|
|
;********************************************************************** ; ; Filename:lcd_time.asm ; Date:11-11-2004 ; File Version: v1.0 ; ; Author: Liyu ; Company: 614 ; ; ;********************************************************************** ; ; Notes: 为使分析完善,贴出这个还可以进行许多优化的源程序... ; 该程序是遵循上文提到的方案二而修改的,运行时的情况如上文所 ; 分析。小弟将对程序结构进行优化... ; 有PIC16的MCD1/2的初学者可用该程序直接DEMO板调试运行... ; 一旦汇编语言的代码较长时,就很怀念C语言了,呵呵 ; ;**********************************************************************
list p=16f877 ;
list directive to define processor #include <p16f877.inc> ; processor specific variable definitions
;__CONFIG _CP_OFF & _WDT_ON & _BODEN_ON & _PWRTE_ON & _RC_OSC & _WRT_ENABLE_ON & _LVP_ON & _DEBUG_OFF & _CPD_OFF
; '__CONFIG' directive is used to embed configuration data within .asm file. ; The lables following the directive are located in the respective .inc file. ; See respective data sheet for additional information on configuration word. ;meanings:_CP_OFF,Code_Protect Off;_WDT_ON,WatchDog On;_BOOEN_ON, ;_PWRTE_ON,Power_On delay timer On;_RC_OSC,RC OSC surge;
;***** VARIABLE DEFINITIONS w_temp EQU 0x70 ; variable used for context saving status_temp EQU 0x71 ; variable used for context saving pclath_temp EQU
0x72 fsr_temp EQU 0x73 RS EQU 1 RW EQU 2 E EQU 3 ;************************** cblock 0x20 hour min sec ms50 us :2 count tmp1 x y bin bcd_h bcd_l count1 endc
cblock 0x30 ; 定义待显示的字符 space1 space2 space3 space4 hour_h hour_l colon1 min_h min_l colon2 sec_h sec_l space5 space6 space7 space8 endc ;*******************************************************************888 ORG
0x000 ; processor reset vector nop ; ICD need goto main ; go to beginning of program
ORG 0x004 ; interrupt vector location movwf w_temp ; save off current W register contents movfw STATUS ; move status register into W register clrf STATUS movwf status_temp ; save off contents of STATUS register movfw PCLATH movwf pclath_temp movfw FSR movwf fsr_temp
;
; 中断服务代码 btfss INTCON,T0IE ; 判断是否为T0中断 goto other_int btfss INTCON,T0IF ; it 's the time of T0 int goto other_int bcf INTCON,T0IF ; 是T0中断,清除中断标志 movlw 0x10 ; 微秒的高位字节加上定时时间 256x16分频=4096=0x1000的高位(0x10) addwf us+1 goto end_int other_int ; 其他中断服务代码 nop ; other isr code
can be added
end_int clrf STATUS movfw fsr_temp movwf FSR movfw pclath_temp movwf PCLATH movfw status_temp ; retrieve copy of STATUS register movwf STATUS ; restore pre-isr STATUS register contents swapf w_temp,f swapf w_temp,w ; restore pre-isr W register contents retfie ; return from interrupt ;****************************************** ;LCM显示时间
main nop call _init call _convert call _disp1 call _disp2 loop call _clock goto loop
;****************这里将_table1放在前面是权宜之计,可以不对PCLATH进行处理 _table1
;取第一行的显示码 addwf PCL ;地址偏移量加当前PC值 dt " HH MM SS " retlw 00H ;****************************************** _init movlw 0x20 ; clear bank0 movwf FSR clear clrf INDF incf FSR btfss FSR,7 goto clear banksel OPTION_REG movlw b'00000011' movwf OPTION_REG clrf
INTCON bsf INTCON,T0IE bsf INTCON,GIE banksel PORTC ; T0初始化完毕
banksel ADCON1 movlw 07H movwf ADCON1 ;设置RA口全部为普通数字IO口 clrf TRISA clrf TRISC ; 定义RA口,RC口全部为输出 bcf STATUS,RP0
call _delay50ms ;调用廷时,刚上电LCD复位不一定有PIC快
movlw 01H movwf
PORTC ; 清屏 call _enable
movlw 38H movwf PORTC ; 8位数据,16字x2行,5x7点阵 call _enable
movlw 0CH ; 显示器开、光标不闪 movwf PORTC call _enable
movlw 06H ; 文字不动,光标自动右移 movwf PORTC call _enable
movlw
80H movwf PORTC ; 第一行显示位置 call _enable
return ;**********************************8 _clock bcf INTCON,T0IE movlw 0xb0 addwf us movlw 0x3c skpnc movlw 0x3d addwf us+1 skpnc goto ms_time_out
movlw 0x50 addwf us movlw 0xc3 skpnc movlw 0xc4 addwf us+1 goto clock_exit
ms_time_out incf ms50 ;
50ms计数器加1 movlw d'20' xorwf ms50,w ; ms50=20? skpz ; 是则跳过下条指令 goto clock_exit ; 否,1s时间未到 clrf ms50 ; 到1s,则清50ms计数器 incf sec ; 秒数加1 movlw d'60' xorwf sec,w skpz goto clock_exit1 clrf sec ;*************** movfw sec movwf bin call _bin_bcd movlw 0x30 ;
将秒数的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf sec_h movfw bcd_l movwf sec_l
movlw 0caH movwf PORTC ; 设定秒区显示位置 call _enable movfw sec_h call _write movfw sec_l call _write ; 显示秒值 ;***************************************** incf min movlw d'60' xorwf min,w
skpz goto clock_exit2 clrf min ;
!!!!!!!!!!!!!!!!!!!!
movfw min movwf bin call _bin_bcd movlw 0x30 ; 将分钟数的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf min_h movfw bcd_l movwf min_l
movlw 0c7H movwf PORTC ; 设定分钟区显示位置 call _enable movfw min_h call _write movfw min_l call _write ;
显示分钟值 ;********************************
incf hour movlw d'24' xorwf hour,w skpz goto clock_exit3 clrf hour
;************************************* movfw hour movwf bin call _bin_bcd movlw 0x30 ; 将小时的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf hour_h movfw bcd_l movwf hour_l
movlw
0c4H movwf PORTC ; 设定小时的显示位置 call _enable movfw hour_h call _write movfw hour_l call _write ; 显示小时值 ;*********************************
goto clock_exit
clock_exit1 ;*************** movfw sec movwf bin call _bin_bcd movlw 0x30 ; 将秒数的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf sec_h movfw bcd_l movwf sec_l
movlw
0caH movwf PORTC ; 设定第二行显示位置 call _enable movfw sec_h call _write movfw sec_l call _write
goto clock_exit
;*************** clock_exit2
movfw min movwf bin call _bin_bcd movlw 0x30 ; 将秒数的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf min_h movfw bcd_l movwf min_l
movlw
0c7H movwf PORTC ; 设定第二行显示位置 call _enable movfw min_h call _write movfw min_l call _write
goto clock_exit ;********************************* clock_exit3 movfw hour movwf bin call _bin_bcd movlw 0x30 ; 将秒数的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf hour_h movfw bcd_l movwf hour_l
movlw
0c4H movwf PORTC ; 设定第二行显示位置 call _enable movfw hour_h call _write movfw hour_l call _write goto clock_exit
clock_exit bsf INTCON,T0IE return
;****************************************
_disp1
clrf count ; 送第一行数字程序 again1 movf count,W ; 显示 HH MM SS call _table1 movwf
tmp1 call _write incf count movf tmp1,W xorlw 00H btfss STATUS,Z goto again1 return ;**************************************** _convert movfw hour ; 将小时数转换成两位BCD码 movwf bin call _bin_bcd
movlw 0x30 ; 将小时的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf hour_h movfw bcd_l movwf hour_l movlw 0x3a movwf colon1 ;
将冒号:的ASCII码填入显示缓冲区 movfw min movwf bin call _bin_bcd
movlw 0x30 ; 将分钟的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf min_h movfw bcd_l movwf min_l
movlw 0x3a movwf colon2 ; 将冒号:的ASCII码填入显示缓冲区 movfw sec movwf bin call _bin_bcd movlw 0x30 ;
将秒数的两位BCD码转换成对应的ASCII码字符 addwf bcd_h addwf bcd_l movfw bcd_h movwf sec_h movfw bcd_l movwf sec_l
movlw 0x20 movwf space1 ; 将第二行时间后的显示区域用空格填满 movwf space2 movwf space3 movwf space4 movwf space5 ; 将第二行时间后的显示区域用空格填满 movwf space6 movwf space7 movwf space8
return ;**************************************** _disp2 movlw
0c0H movwf PORTC ; 设定第二行显示位置 call _enable movlw 0x10 ; 共显示16个字符 movwf count1 movlw 0x30 ; 获取显示缓冲区的首地址 movwf FSR ; 取得显示字符的地址 again2 movfw INDF ; 间接寻址获取字符ASCII码 call _write ; 输出到LCD显示 incf FSR decfsz count1 ;
若显示完毕,则退出,否则继续显示下一字符 goto again2 retlw 0
;**************************************** _write ;送数据到LCD子程序 movwf PORTC bsf PORTA,RS bcf PORTA,RW bcf PORTA,E call _delay50ms bsf PORTA,E retlw 0
;*********写入控制命令的子程序 _enable bcf PORTA,RS bcf PORTA,RW bcf PORTA,E call _delay50ms bsf
PORTA,E retlw 0
;******************************88 _delay50ms movlw 0x40 ;晶振为4 Mhz,延时约16ms movwf x loop_x movlw 0xff movwf y loop_y decfsz y goto loop_y decfsz x goto loop_x return
;************************** _bin_bcd ; 因为秒分钟小时的值不大于60,故采用此BCD码子程序来转换 movfw bin clrf bcd_h gtenth movwf bcd_l movlw d'10' subwf bcd_l,w skpc goto exit movwf bcd_l incf bcd_h goto gtenth exit retlw 0
;****************************
end ;源程序结束
* - 本贴最后修改时间:2004-11-29 21:26:16 修改者:碧水长天 * - 修改原因:注释
|
|
|