Microelectronics: SOFTWARE -- Structured programs



Home | Forum | DAQ Fundamentals | DAQ Hardware | DAQ Software

Input Devices
| Data Loggers + Recorders | Books | Links + Resources


AMAZON multi-meters discounts AMAZON oscilloscope discounts


Flowcharts and design structure diagrams are two techniques for displaying the structure of a program. They help the programmer to write efficient programs that will work as intended. Long programs are best broken down into a number of sub-programs, known as modules.

Subroutines and functions are modules of a special kind. The operation of the stack and the action of interrupts are two other features of importance to programmers.

The programs described in SECTION 10 are so short that is it easy to read through them and understand what they do. Mostly, they list a sequence of steps that the processor must take, starting at the beginning, continuing to the end and then stopping. Even in some of these simple programs it is necessary for the processor to jump to different parts of the program or to spend some time running round loops. As soon as we introduce jumps and loops into a program, it becomes more difficult for the person reading the program to understand what is going on.

One way of making the action of a program clearer, is to draw a diagram to show the paths that the CPU takes while working its way through the program. A diagram is useful after a program has been written. It is also useful to draw the diagram when the program is being designed and written. Indeed, with a program of more than 10 or so lines, it is essential to draw a diagram first, then write the program.

Diagrams of programs

Diagrams help planning, and proper planning is essential if programs are to:

• operate correctly.

• operate efficiently.

• be easily readable and understood by other people.

• be easily readable and understood by the program writer a few weeks later.

• be easily adapted and modified at a later date.

Flowcharts

A flowchart is a method of representing the way a program is constructed. Each small section of the program (perhaps only two or three opcodes or mnemonics long) is represented by a box (FIG. 1).

Inside the box we write, in a concise way, what happens at that stage.

Boxes are connected by arrows, which indicate the direction of flow of the program. The best way to understand the meaning of a flowchart is to look at one drawn to represent one of the simple programs we have already studied.


FIG. 1 Symbols used for drawing flowcharts.


FIG. 2 A flowchart of the delay routine clearly shows the nested loops. The line labels are added to the flowchart to make it easier to relate the flowchart to the listed program.

FIG. 2 illustrates the delay program of SECTION 10. Begin at the terminal box labeled 'Start' and follow the arrows. When you come to a decision box decide whether the answer to the question is 'yes' or 'no' and follow the appropriate arrow. Eventually, after going round the inner loop 65025 times, and round the round the outer loop 255 times, you will finish at the terminal box labeled 'End'. You will find that there is no other route from 'Start' to 'End'. There is no way in which the CPU can take a short cut. This proves that the program, if it follows the structure shown in FIG. 2, will operate as required.

FIG. 3 illustrates the cooler program of SECTION 10. The structure of this is different from the delay program. It has a single loop but the decision box leads the CPU one way or the other, depending on the temperature of the sensor. Note that there is no 'End' terminal in this program. It runs indefinitely (for ever).


FIG. 3 The alternative states of the cooling system are evident in the flowchart.

==========

EXERCISE 1 -- Flowcharts

1. Draw a flowchart of the security program in SECTION 10.

2. Draw a flowchart of any other program that you have written.

3. Add a third loop to FIG. 2. Run it for longer delays.

===========

Design structure diagrams

DSDs are another way of representing programs. Some of the more useful symbols for DSDs are given in Figs 4 and 5. The program is represented by an unbranched vertical line or 'stem' running from the

'Start' box at the top to the 'End' box at the bottom. Program flow is always from top to bottom, so there is no need for an arrow. The stages of the program are represented by boxes that branch off along the main stem.


FIG. 4 Some of the symbols used in drawing design structure diagrams. The sequence of the program runs from the START to the END.


FIG. 5 DSD symbols for processes that depend on a condition being true or not. In the upper diagram, the process occurs only if the condition is true. In the lower diagram a process occurs if the condition is true and a different process occurs if the condition is not true.

An example makes the DSD system clear. FIG. 6 shows the delay routine as a DSD. Instead of following arrows, as in a flowchart, different rules apply. Begin at the top and follow down the 'stem' until you reach a branch. Follow the line that enters the branch until you reach a box. Text in the box describes either a process, a definition, a subroutine or function (more about these later), a loop or a decision. In the figure, the line goes to a box labeled 'FOR first number = 255 to 0'. This is a loop, as indicated by the line beneath it, looping back to the box. The text in the box states how many times you have to run the loop. In this case, you loop until the first number becomes zero.

Like the main stem of the program, the loop line has branches to describe what the CPU has to do as it runs the loop. The first thing is to run another loop, in which the second number is reduced from 255 to 0. This box too has a loop line below it. There is only one branch on this loop. This describes a process, which is 'Decrement the second number'. As you come to this line, enter it, obey the instruction in the box, and leave by the same line. So the CPU runs round the loop line, decrementing the second number each time until it becomes zero. At this point the action of the second loop is completed. So you leave the second loop by the way you entered it. This returns you to the first loop, and you are still on your way round it for the first time. The next branch you enter holds the instruction to decrement the first number.

After that, return to the loop box and begin the next time round the first loop. Every time round the first loop you enter and run the second loop, then decrement the first number.

The diagram does not show the nested loop structure in the same way as a flowchart, but it is clear that the CPU has to decrement the second number all the way from 255 to 0, every time it runs round the first loop. When the first number is eventually reduced to 0, the task is complete and the CPU returns to the main stem. There is only one process after this (do nothing) and then the program ends.


FIG. 6 A DSD version of the delay program. The second (inner) loop is reached by way of the first (outer loop), and runs completely for each single run of the outer loop.

FIG. 7 illustrates some other features of DSDs. First there is a sequence of three processes which set up the system ready for action.

Then there is a loop, but this is one in which there is no test to see if the task is completed. Consequently, the CPU circles this loop indefinitely.

Each time round the loop, it samples the sensor and a decision is taken on whether to switch the fan off or to switch it on. When the decision has been taken and acted upon, the CPU runs back along the way it has come until it reaches the loop. Then it continues around the loop, back to the 'DO' box, and on around the loop again. It samples the temperature and switches the fan accordingly every time round the room. It never reaches the 'End' terminator.


FIG. 7 The cooler program, drawn as a DSD, can be seen to run forever, never reaching the end.

Program structures

The examples we have studied have illustrated the three basic program structures:

• Sequences: a series of processes take place one after the other.

Figs. 2, 3 and 7 all begin with a sequence of three processes. Some programs consist of nothing but a single sequence, and run straight through, from 'Start to 'End'.

• Iteration: the program has a loop. The loop may continue indefinitely (Figs. 3 and 7), or until some condition is true (Figs. 2 and 6).

• Selection: there is a choice between alternative paths, depending on whether a condition is true or not. In some programs only one path (the condition is true) leads to specific action. In other programs (FIG. 7) each path leads to an appropriate action.

Note that in FIG. 7 the selection is within an iteration.

For reference, the three basic structures are shown in FIG. 8 as they appear in flowcharts, and in FIG. 9 as they appear in DSDs.


FIG. 8 Flowcharts of program structures, (a) sequence of processes A,B and C, (b) iteration of A and B indefinitely, (c) iteration of A until condition is true, (d) selection between performing process A or not, (e) selection between performing process A or process B.


FIG. 9 DSDs of program structures, (a) sequence of processes A,B and C, (b) iteration of A and B indefinitely, or iteration of A and B until condition is true, (c) selection between performing process A or not, (e) selection between performing process A or process B.

Designing programs

It is generally stated that the best way to design a program is from the top down. The process starts with an outline definition of what the program is to do. With a little experience it becomes clear that most programs can be divided into a number of stages. Usually the first stage, which could be called initialization, is concerned with defining variables, their types, and possibly their initial values. Then come definitions of constants, including masking constants where used.

Often certain addresses, such as the addresses of ports and data direction registers, are labeled at this stage, and then the ports are configured as inputs and/or outputs. In some programs it may be advisable to repeat at least part of the initialization at later stages (see box).

In some high-level languages there is a fixed order in which the definitions must be made. This first part of the program consists of a sequence (as defined above) and programming it presents no difficulty except that of making sure that the definitions are correct and complete.

===========

Countering corruption

It is a mistake to assume that it is sufficient to initialize control registers (for example, the data direction registers) once and for all at the beginning of a program. Stored data may change, especially in an electrically noisy industrial environment. The change may only be a single bit that changes from 0 to 1 or from 1 to 0, but such a change may have a serious effect on the program. For example, if a port register is set as an input and the data in the direction register becomes corrupted, the line may change from becoming an input to becoming an output. This can have unforeseen effects on the running of the program. It may be worth while to re-initialize control registers and also essential data such as constants by sending the CPU back to initializing routines at regular intervals.

===========

In its simplest form, the remainder of the program falls under three headings: input, processing, output. This is certainly true for the control programs used with programmable logic devices, as explained in SECTION 6. It is also true for many other control programs and for measurement programs too.

The idea of breaking down into shorter, simpler sections is not restricted to the whole program. For example, the input section may in turn be broken down into smaller sections, especially if a certain sequence of key-presses has to be made. In this stage the program usually involves iteration while waiting for input. If more than one form of input can be made (for example, when the input device is a keypad or when it is an analogue quantity that is converted by an ADC) the program may use selection to check that the input is valid. It will also use selection to follow different paths, depending on the input values.

The processing stage may be anything from a simple sequence, perhaps only of one or two program lines, to a complex combination of sequences, iterations and selections. Here it is important to use a graphical technique such as DSDs to structure the program. Then the pathways through the program can be checked to confirm that there are no short cuts, and no dead ends. For further confirmation, especially where there are selections, a dry run will help show how the program behaves for different input values. In some cases, the program will remain in the processing stage indefinitely, as in the cooler program.

The output stage is often a simple sequence, because all the work of processing has been done and it remains only to light an LED or switch on a motor. After that, the program may come to an end, or there may be a loop back to input more data, process it and produce new output.

However, output is not always simple. The complex actions of a robot are an example of this. Whatever the level of complexity, the main point is to break everything (input, processing, output) down to the level of individual mnemonics or high-level commands. Draw diagrams. Do a dry run. Then program it. It will still need to be tested, which is one of the subjects of the next SECTION.

Modular programming

The discussion above assumed that the program is a single input processing-output chain. Most programs are more complicated than this. For example, the program for a cellphone would have several distinct parts:

• waiting to receive a call

• receiving a call

• handling a call in progress

• terminating a call

• initiating a call

• recovering a number from data store

• dialing

• displaying data

• checking battery level.

Each of these activities can have a self-contained program. Such separate programs within the main program are referred to as modules.

Each module is programmed individually, perhaps by different programmers. Yet the same basic principles apply to modules that apply to short programs. Each module has input, processing and output. The difference is that the input may take the form of data made available by other modules, and the output may be data required by other modules. The programmer has to ensure that all the necessary data is available, and to know in what variables or in what addresses in RAM it is stored. Conversely, when the module has been run, it must leave the required data in a form suitable for passing to another module, either as the value of a variable, or as a number of bytes in RAM.

Some languages are particularly suitable for modular programming. In C, a self-contained program module is known as a function. Using C forces the programmer to think in modular style, and so produce well-structured programs. A function begins with the function name and, in brackets, the names of variables the values of which are being passed to it. Similarly, if a function calls another function, values held in its variables can be passed to a variable in the called function. This is one way of the ways that a function can have input and output. There are other ways of passing values in C, such as by addresses in RAM.

Also some function will have input and output through ports in the way we have already studied.

The functions in C are of two kinds. Some are so often needed in so many different programs that they are available in a library file.

Examples of these are printf(), which prints values and text on the monitor screen. The matter to be printed is included inside the brackets. This is the way the data is being passed to the function. A function of a different kind is isalpha(). This is passed a value which may or may not be an alphabetic character. The output of the function is 1 (true) if the value is a character, or 0 (false) if it is not. A function such as this is useful when checking input from a keyboard, to make sure that it is of the expected form. There is an extensive range of ready-made functions in C, which make it possible to write programs that are compact and reasonably easy to follow.

The other kind of C function is written by the programmer. An example is a function that is passed the value of a telephone number as input and produces, as output, the signals to drive the dial tone generator. When the functions that a C program requires have either been found to exist already in the standard function library files or have been written by the programmer, they are tied together by the function that all C programs must have, the main() function. In length, this may be less than a quarter the length of the whole program. It consist principally of calls to the other functions. These in turn may call the functions that they need. We can see how this structuring of the program mirrors the breaking down of a complex operation into its smallest parts.

Functions in most versions of BASIC are more limited than those in C.

BASIC provides a few useful functions, such as SIN, COS and TAN, but the user is able to define other functions as required.

Example:

A measurement system is reading data from an ADC. To convert the data (adc) into a reading of temperature it has to have 0.5 subtracted from it, is then divided by 5.2 and finally have the room temperature (room) added to it:

temp = (adc - 0.5)/5.2 + room

Data is read many times during the program but, instead of having to type in this equation after every reading, it can be defined as a function, using:

DEF FN temp = (adc - 0.5)/5.2 + room

This is included early in the program, preferably in the initialization stage. After that, every time the equation has to be used all that needs be typed is:

FN temp (adc,room)

It could be used in a statement such as:

300 IF FN temp (adc,room) > 29 THEN 460 Unfortunately, most versions of BASIC limit function to one line, though some versions allow multi-line definitions.

Subroutines

There are programs in which a particular sequence of operations has to be repeated at several different points in the program. It may, for example, be a routine to set a data direction register of a port to all inputs, read the input data AND with a mask, then store the result in a given register. Much time and memory space can be saved by entering this routine just once, as a subroutine. It can then be called from any point in the program as many times as it is needed. The box explains what happens.

Below is an example of using a subroutine in an assembler program. The reader will recognize lines and routines from the LED switching programs and the delay program of SECTION 10. The point of interest in this listing is the main program. These are the 5 lines following the flash: label.

========

Subroutines

This illustrates the path of the microprocessor as it runs through a BASIC program that calls a subroutine three times.

10 REM Demonstrating subroutines.

• (First part of program)

150 GOSUB 600 :REM goes to subroutine

160 (Comes back to second part of program)

210 GOSUB 600 :REM goes to subroutine again

220 (Comes back to third part of program)

350 GOSUB 600 :REM goes to subroutine again

360 (Comes back to fourth part of program)

590 END :REM Stops CPU running on

600 (Start of subroutine)

680 (End of subroutine)

690 RET :REM returns to line after

Each time the CPU finds GOSUB 600, it jumps to that line, executes the program from 600 to 680, then at line 690 jumps back to the line after the one it came from.

========

These few lines clearly show the sequence of the program. The LED is repeatedly turned on, then off, with a delay in each state so that the flashes are visible. Note the essential mnemonic ret (return from subroutine) at the end of the subroutine. This is rts in some assemblers.

; Demonstrating .ORG and subroutines

; Initialization

.equ ddrb = $17

.equ portb = $18

.equ bit0 = $1

.def ledoff = r18

.def ledon = r19

.org $000 ;program starts $000.

reset: rjmp start ;reset routine.

.org = $004 ;program starts $004.

start: ldi ledoff, $2 ldi ledon, $0 ldi r20, $FE out ddrb, r20 wait: in r16, portb ;input button.

andi r16,bit0 ;button pressed.

breq wait flash: out portb, ledon ;LED on.

rcall delay ;to subroutine.

out portb, ledoff ;LED off.

rcall delay ;to subroutine.

rjmp flash ;repeat for ever.

; Delay subroutine: same as delay program.

delay:

ldi r16, $FF startouter:

ldi r17,$FF startinnner:

dec r17 brne startinner dec r16 brne startouter ret ;return to main program.

The reason that the flash sequence is so easily understood is that the reader is not confused by having to read twice through the delay routine. The five steps of the routine are clearly set out, with a line for each. The delay routine has been placed at the end of the listing as a subroutine. Whenever a delay occurs, the CPU is told simply to call the subroutine, that is, to jump to the address where the subroutine begins.

The delay is called twice in this program. By making it a subroutine, we need to include it only once. This saves both typing and storage space in memory. This may be limited in amount in a microcontroller.

Note that in this program we do not pass any data to the subroutine or receive any data from it. This could have been done had it been necessary, using variables, registers or RAM addresses.

===============

.org directive

A directive tells the assembler how to assemble the program. It is not part of the program. The .org directive tells the assembler where in memory to start storing the assembled program. Up to now, we have not used this directive but it is important because, in the '1200', the first 4 bytes of program memory are allocated for special purposes. If the program is stored from byte $000 onward, there is a risk that, when the program is run, its first four bytes may become overwritten with data. The program would be corrupted and would not run correctly.

The first byte of the four is reserved for a jump instruction, telling the CPU where the first byte of the actual program is stored. When the assembler gets to the line .org = $000 it starts storing at $000. There it stores the opcodes for rjump start. After that, it is told to store the program from $004 onwards, so it leaves $001, $002 and $003 empty and begins storing the remainder of the program from $004 onwards.

The effect of this is that, if ever the CPU is reset, its program counter is automatically cleared to $000. It goes to $000 and there finds the instruction telling it to jump to $004. This jump takes it to the beginning of the program proper. From then on, it follows the program in the usual way.

===============

The stack

The stack is a region of RAM set aside for the temporary storage of data. It works in a 'last-in-first-out' manner, like the plate dispenser sometimes seen in canteens. The stack pointer in the CPU holds the address of the address next above the top of the stack.

In some microcontrollers, such as the '1200', the stack is small and is automatically operated. When the CPU goes to a subroutine, the address of the program counter is stored in the stack. This is how the CPU 'remembers' which address in the main program it jumped from.

When the CPU comes to the return command (ret) at the end of the subroutine, it goes to the stack, recovers the address and replaces it in its program counter. The value is then incremented by 1 and the CPU goes to the program line following the one from which it jumped.

In other microcontrollers and in most microprocessors, there are opcodes for using the stack. Commonly used mnemonics are psh (= push) to place a given value on the top of the stack, and pop to load a value stored at the top of the stack. Usually the push and pop operations take place between the top of stack and the accumulator register.

As an example of the function of the stack , this table shows a section of RAM and the hex data stored there:


The stack pointer holds 3B2B The accumulator holds 65

The top of the stack is currently at $3B2A but the stack pointer points to the address after this, for this is where the next item of data will be stored. The most recently stored data is 48. Now the PSH opcode occurs in the program and is executed. The data stored is:

The stack pointer holds 3B2C The accumulator still holds 65


The microprocessor may now be engaged in other operations not involving the stack, which remains unchanged. Eventually, with (say) $B7 in the accumulator:


The stack pointer holds 3B2C

The accumulator holds B7

Now the POP opcode occurs in the program and is executed:


The stack pointer holds 3B2A

The accumulator holds 65 again

The stack pointer has been decremented to point to the address one above the new top of the stack. Note that the value 65 is still in 3B2B, but this is now above the top of the stack. The 65 is no longer needed and will be overwritten the next time PSH occurs.

The stack is often used for temporarily storing an intermediate result in a calculation, particularly in processors that have only a few registers.

With a simple PSH, a value is quickly pushed from the accumulator on to the stack and may be recovered at a later stage with a simple POP instruction. The stack is also used for registering the state of the processor when a jump to subroutine instruction is executed, or when the microprocessor is responding to an interrupt. Before going to the subroutine or interrupt routine, the controller stores the current contents of the accumulator, the status register and the program counter on the stack. As soon as the microprocessor returns from the subroutine or interrupt, it recovers all this essential information from the stack. It then continues operating at the same point in the program that it had reached before it jumped or was interrupted.

==============

EXERCISE 2 -- Subroutines

1. Rewrite the assembler subroutine program in another assembler or a high level language.

2. If you are working in assembler with a simulator program, single-step the program and watch the program counter as it goes to the subroutine. Watch the stack pointer. Try to find the location of the stack in your system.

Watch the program counter as the CPU returns from the subroutine.

3. Revise the program so that a value speed is passed to the subroutine to control the length of the delay.

4. Revise the flash routine so that the LED flashes faster on each successive loop.

5. Find out about the operation of the stack in the processor that you are using. Write simple programs to demonstrate its action.

==============

Interrupts

The CPU is always busy handling the program, proceeding from one instruction to another, perhaps cycling indefinitely round a loop.

Events sometimes occur which require that the processor should interrupt what it is doing and take some other action. Such events include:

• Arithmetic error: a typical example is 'division by zero'. A divisor in an expression may on occasion evaluate to zero.

Division by zero is not possible mathematically. This kind of error is detected inside the CPU and causes it to interrupt itself. Instead of going on to the next instruction in the program, it jumps to a special interrupt service routine (ISR). This might cause an error message to be displayed: 'Division by zero error'. The program is then halted and the programmer checks to find why such an error occurred and alters the program so as to prevent it.

• Exception error: Occasionally a CPU may be given instructions that it is impossible to obey, such as storing a batch of data in an area of memory that does not exist. An internal interrupt occurs.

The ISR may instruct the CPU to display an 'Exception error' message and then close down the program.

• Clocked interrupts: If the system includes a real-time clock, this can be programmed to interrupt the processor at regular intervals.

In a data logger, for example, the clock could be programmed to interrupt every minute. On receiving the interrupt, the CPU jumps to the ISR, which instructs it to sample and record input data. In between interrupts is occupied with general routines such as updating the display and accepting commands from the keypad.

• Peripherals: The keyboard buffer may be holding key-presses which the CPU needs to know about, so the buffer sends an interrupt signal (usually a low voltage) level to the interrupt input pin of the CPU. The ISR of the CPU then enables the buffer, allowing it to place the keypresses on the data bus one at a time.

The result is further action by the CPU, perhaps storing the ASCII codes in memory, or displaying characters on the monitor screen.

A printer is another device that frequently interrupts to tell the microprocessor that it is waiting to receive data for printing.

It can be seen that some interrupts are the result of programming or operating errors, but others are a useful way of regulating the system.

When a CPU receives an interrupt, it:

• Finishes its current instruction first.

• Pushes the contents of its registers on the stack. The content of the program counter is important, so that it can return to the same place in the program after the interrupt routine has been completed.

• Jumps to a fixed address in memory, where it finds another jump instruction giving the address of the start of the ISR.

• Jumps to the address of the ISR and executes it.

• Pops the return data from the stack.

• Resumes processing as it was before the interrupt.

Interrupts by different devices are dealt with according to their priority, because the microprocessor can not handle two interrupts at the same time. An interrupt from the keyboard, for example, may have a higher priority than one from the printer. If the printer interrupts while the microprocessor is dealing with an interrupt from the keyboard, the printer is ignored until the keyboard has been dealt with.

This raises the problem of how the CPU finds out which device is interrupting. With some CPUs there are several interrupt input pins, ranked in order of priority. Devices are connected to these pins according to their priority. Priority is fixed by the wiring. Other CPUs may have only one interrupt line, which is part of the control bus. The CPU knows there is an interrupt but has to find out its source. In some systems there is a line in the control bus called interrupt query. When the CPU puts a low signal on this line, the signal automatically goes to all the devices that might possibly be interrupting. The one that is interrupting puts its own device code on the data bus so that the CPU knows what action to take. Another though slower way of obtaining the same result, is for the CPU to interrogate each device in order of priority, reading a flag register in each to find which has its interrupt flag set. Other methods are described later with reference to the Z80.

As an alternative to interrupts, a system may rely on polling the individual devices. Each device has a register in which there is an interrupt flag. In between its main activities, the CPU interrogates each device to find out which one or more of them has its interrupt flag set.

It then takes action. This system is the slowest of all, as a device has to wait to be interrogated and by then it may be too late to avoid a program crash or a loss of data.

It may happen that the CPU is in the middle of a complicated routine which might fail if interrupted. With some CPUs there is a disable interrupts opcode which causes it to ignore all interrupts, until interrupts are enabled again by using another opcode. We use these instructions to mask (shut out) all interruptions while a difficult routine is in progress. This is a matter for the programmer to decide. However, there is usually a highest-priority interrupt that is non-maskable.

The Z80 has two interrupt input terminals. When a logic low level is sent to the NMI terminal, it causes a non-maskable interrupt. The falling edge of the interrupt pulse latches the input, so that there can be no further NMIs until the current one has been dealt with. As soon as it receives the NMI, the Z80 goes to the address $0066. There it finds the address of the non-maskable interrupt routine. Since a non-maskable interrupt is always attended to immediately, there is no need for the CPU to acknowledge that it has been received. No acknowledging signal is sent to the interrupting device.

The NMI is generally reserved for an event which threatens to crash the system. For instance an NMI is generated if the power supply fails.

On receiving this signal, there is a brief time while the capacitors of the power supply contain enough charge to keep the CPU running. In that short time, essential data in RAM can be saved to disk. When programming a non-maskable ISR for the Z80, it should always end with a special RETN instruction. This not only returns the CPU to the program but also resets the latch on the NMI input.

The maskable interrupt pin of the Z80 (INT) has three separate modes of operation, which are set by software instructions. These take second priority to the NMI, so it is essential for the CPU to acknowledge receiving the INT signal. Otherwise, the interrupting device may have ceased sending its signal by the time a current NMI signal has been dealt with and the INT signal will be missed. The Z80 responds to an INT signal by making its IORQ and M1 control outputs low.

The Z80 has three types of maskable interrupt:

Vectored interrupts: If the Z80 has been programmed to operate in Mode 0, it acknowledges the interrupt as described above and this causes the interrupting device to put a restart instruction or vector on the data bus. The Z80 reads this single-byte instruction, which tells it the address of the appropriate ISR. There are eight different restart instructions, sending the CPU to a particular one of eight addresses:

$0000, $0008, and so on to $0038.

These 8-byte blocks of memory can be used to service the routine or send the CPU on to another address where a longer routine is stored. Having these eight address means that up to eight different ISRs can be programmed and the interrupting device can indicate which one of these should be used.

Direct interrupts: If the Z80 is in Mode 1, it is sent automatically to address $0038, without the need for a restart instruction.

Indirect interrupts: In Mode 2, the Z80 receives a vector as in Mode 0 and adds to this a 16-bit value that is already stored in its I register.

By setting different values in the I register, this allows the Z80 to be directed to different blocks of memory, each holding eight restart instructions. This gives the Z80 the flexibility to process a large number of different ISR routines.

Several peripheral devices can be connected to the INT pin, leaving the CPU the problem of finding out which one is interrupting. If two devices interrupt at the same time the CPU has to give one of them priority. It is not necessary for them to interrupt at exactly the same time. The CPU does not check for interrupts until the end of its current execute cycle so, if two interrupts occur during a cycle, they will both be pending when the cycle ends. One solution to this problem is by polling. Either the CPU can interrogate each device in order of priority or there is a register that is attached to the data bus and wired as an input port. When a device interrupts, it sets a bit in this register. The priority of each device can be established by the order of bits in the register. On being interrupted, the CPU reads this register, finds out which devices are interrupting and attends to the interrupt of higher priority.

A second approach to the problem of multiple interrupts is daisy chaining. An example of this is seen with the Z80 PIO and SIO. These have an interrupt enable input (IEI) and an interrupt enable output (IEO). The device can interrupt only if its IEI input is at logic high. In the figures, we show IEI connected to the 5 V line, so the chip is enabled for interrupts. The EIO terminal of the device may be left unconnected if there is only one device in the system. However, if there is a second device, the EIO is connected to the EIE of the second device, which has lower priority. Both devices have their INT output connected to the INT input pin of the CPU. The EIO output is normally high but goes low when the first device is interrupting. The low input to the IEI of the second device disables it.

It can not interrupt until the first device has finished. The low EIE input also makes the EIO go low. The chain can be extended to three or more devices, so that if the second device, for example, is interrupting, the third and subsequent devices can not interrupt. However, in this case the first device can still interrupt and take priority over the others.

This system of connecting the EIO to the EIE of the device of next lower priority creates a daisy chain in which only the device of highest priority can interrupt when more than one is attempting to interrupt.

=================

EXERCISE 3 -- Interrupts

Find out the interrupt facilities of your CPU. Devise a simple program to demonstrate its action. For example, run a program to flash an LED continually. When interrupted the CPU is to sound a siren once, then return to flashing.

If there is more than one level of interrupts, devise a test program to demonstrate this too.

=================

Problems on: structured programs

1. What are the advantages of structured programming?

2. Describe two ways of representing a program as a diagram. Give short examples.

3. Draw a flowchart or DSD of a program that you have written. Add notes to point out structural features such as sequences, iterations and selections.

4. What are nested loops? Give an example to illustrate this structure.

5. What is initialization? List the actions that the CPU performs in a typical initialization sequence. Why is it advisable to repeat some of the initialization steps later in a long program?

6. Explain what is meant by the input, processing, and output stages of a program or routine?

7. What is the top down approach to programming? Give an example.

8. What is modular programming? Give an example of a program that would best be written in modular form.

9. What is a function? Give examples in a high-level language with which you are familiar.

10. Describe the sequence of events when a program calls a subroutine and executes it. What are the advantages of using subroutines in the structure of a program?

11. Describe the operation of the stack. In what ways is the stack used?

12. Under what circumstance may an interrupt be generated?

13. Explain the sequence of events when an interrupt is generated, basing your description on a named CPU.

14. Explain how different interrupting devices may be allocated different levels of priority.

Answers to questions

PREV. | NEXT

Related Articles -- Top of Page -- Home

Updated: Thursday, May 18, 2017 10:09 PST