More Subtleties in Handling Interrupts

Introduction

Subsequent to preparing the initial discussion relative to interrupts, I have encountered a few other problems and hopefully this discussion will save you from the many frustrating hours I sat scratching my head.

Turning off Interrupts. Be Certain they are Really Off.

We had an application where we could handle interrupts while perfroming various tasks (TASK_SEQ_1), but did not desire interrupts while performing other tasks (TASK_SEQ_2). This is illustrated in simplied form below;

LOOP:

TASK_SEQ_1:	
	BSF INTCON, GIE
	;perform various tasks, okay to accept inerrupts
	; done with task_seq_1

	BCF INTCON, GIE		; disable interrupts
TASK_SEQ_2:
	; perform various tasks, no interrupts
	; done with task_seq_2
	GOTO LOOP

Much to our amazement, we did get interrupted while performing TASK_SEQ_2. This was more than annoying. In our case, the tasks we were performing in TASK_SEQ_2 included changing the state of input RB.0, and thus, we were actually causing our own interrupts. For some reason, the BCF INTCON, GIE wasn't doing the job.

The problem was traced to the occurance of an interrupt while executing;

	BCF INTCON, GIE

Recall, that on interrupt, execution of the current instruction will be completed and the program will vector to the interrupt service routine. In executing the RETFIE, the program then returned to the first line in TASK_SEQ_2 with interrupts enabled. Thus, GIE bit was indeed cleared, but execution of the interrupt service routine caused it to be set and we were in fact then executing TASK_SEQ_2 with interrupts enabled.

The solution;

LOOP:

TASK_SEQ_1:	
	BSF INTCON, GIE
	;perform various tasks, okay to accept inerrupts
	; done with task_seq_1
TURN_OFF_INTS
	BCF INTCON, GIE		; disable interrupts
	BTFSC INTCON, GIE
	GOTO TURN_OFF_INTS
TASK_SEQ_2:
	; perform various tasks, no interrupts
	; done with task_seq_2
	GOTO LOOP

Note that the GIE bit is cleared and then tested to be sure it is cleared, and the process is repeated until it is indeed clear.

Thus, if an interrupt occurs while executing BCF INTCON, GIE, the interrupt service routine will turn the bit on and the program will continue with the BTFSC INTCON, GIE detecting that GIE is set and another attempt will be made to clear the GIE bit. Thus, the program will only flow to TASK_SEQ_2 after a successful verification that the GIE bit is indeed clear.

Register Banks.

When using the 16C84, we were executing a program where interrupts were permitted when we were in register bank 0 and in register bank 1.

At the beginning of the interrupt service routine, the W and STATUS registers were saved to W_SAVE and STATUS_SAVE. Assume these variables are at 20H and 21H. If the interrupt occurred when we were in Bank 0, they were saved to 20 and 21H. However, if the interrupt occurred when in Bank 1, they were saved to A0 and A1H.

Our interrupt service routine then switched to Bank 0, did its task and then restored W and STATUS, but note that we fetched the old values from A0 and A1H.

This is not a problem on the 16C84 as the user registers in the two banks are mirrors of one another. That is, 20H and A0H are in fact the same location.

But, we usually use the 16C554 or 558 for the various kits we develop and with the 16C55X family, the register banks are not mirrored. Thus 20H and A0H are two independent locations and restoring from 20 and 21H when W and STATUS were saved to A0 and A1H is a big problem.

There may well be a clever solution to this, but we decided to permit interrupts only while our main program was in Bank 0. In fact, one usually only uses Bank 1 to set the tristate registers which takes very little time and in most cases disabling interrupts during this brief will have no impact.

For example;

	...
	; going to Bank 1
TURN_OFF_INTS
	BCF INTCON, GIE		; disable interrupts
	BTFSC INTCON, GIE
	GOTO TURN_OFF_INTS

	BSF STATUS, RP0		; bank 1
	; do things with TRISA or TRISB
	BCF STATUS, RP0		; back to bank 0

	BSF INTCON, GIE		; now enable interrupts

Commentary for Aspring Designers

Both of these problems are pretty obvious once you are aware of them, but they are mighty subtle when you are not. In our case, each took a few days to solve.

If you are designing "one of a kind" and this includes tinkering as a hobby, one can slide by with fixing a problem. I see my students doing this everyday; get the design working in time for the demo and don't worry about why it wasn't working. In fairness to them, there are scarcely enough hours in the day to get their projects working, let alone question why it wasn't.

But, if you are designing a product where lives or large sums of money are involved, fixing the problem isn't sufficient. You must adopt an attitude of "why?". It's okay to experiment in finding the fix, but in the end, you must come up with a rationale explanation of what the problem was and why the fix worked.

The reasons this is critical is because the fix may well be a very weak bandaid. If you don't clearly understand the problem, the fix may have left open a few holes which have a habit of coming back to haunt you. Further, by understanding the problem, you become more efficient in avoiding the problem in the future.

Clearly understanding the problem may well take days, weeks or months or maybe it won't happen. Of course, no one has the time to devote one's entire energies to thinking through such problems, but good engineers do mull over such problems as if they were puzzles, and over time you may have ten or more such puzzles all at the same time.

As noted, this is a skill that we just don't treat in college and I am uncertain teaching it is all that practical, even if there were the time. My personal feeling is that outstanding designs compared to mediocre designs are more a product of the designers' personalities and habits. Not all skills can be taught.

But, if you aspire to better than mediocre, get in the habit of finding the fix, but always strive to then find and fully understand the problem.

copyright, Peter H. Anderson, Sept 21, '97