You are here: Home DOCUMENTATION Adapt9S12X Using Your Adapt9S12XDP Microcontroller Module - Software Considerations - Interrupts

Technological Arts Inc.

Your Shopping Cart

Your Cart is currently empty.

Using Your Adapt9S12XDP Microcontroller Module - Software Considerations - Interrupts

Article Index
Using Your Adapt9S12XDP Microcontroller Module
Overview of features
Getting Started
Setting Up the Hardware
Application Programming
Software Debugging
Software Considerations
Software Considerations - Memory Map
Software Considerations - Interrupts
Software Considerations - XGATE
Software Considerations - S12X Clock
Hardware Details
All Pages


Embedded systems are called such because they interface directly with the real world.  The interfaces used rarely are the video displays and keyboards a standard desktop computer has.  More likely they are things like temperature sensors, motor drivers, and so forth.  A common consequence of this is that the programs written for an embedded system have to be able to respond quickly to external events occurring asynchronously to the program execution state.  The best way to process these events and have them work with your program is to set up interrupts.

An interrupt is a way to make an asynchronous event synchronous with your program.  Basically, what happens is that some event external to the CPU signals the CPU to stop doing whatever it is doing, and to spend some short amount of time dealing with the external signal.  After the CPU finishes what it needs to do, it can go back to whatever it was doing before the interrupt happened.

The typical way of doing interrupts would be to have a software routine, usually in assembly, set up to be called at a specific address.  (The address could be fixed or specified in a table.)  An external pin on the processor would be pulled low by the device requiring handling.  The only other fact to remember is that there is a flag set aside in the condition code register to mask the interrupt, or allow it to be processed.  Usually the interrupt would only be masked when it was absolutely necessary in the main program.  An example is when the program reads a variable that is itself changed by the interrupt routine.  The interrupt must be masked while the read takes place and then enabled again after the read is completed.

The S12X can process an external interrupt this way.  However, since the processor comes with several additional hardware interfaces built into it, it makes sense that these interfaces are also set up to be able to trigger their own interrupts.  The result is that the system has now gone from one available interrupt to a large number of possible interrupts.  Therefore some way has to be set up so that if two interrupts happen at the same time, one of the two can be specified as being at a higher priority and thus will be handled first.  Also, not all the available hardware possibilities will be used in any one design.  So the unused interrupts must not be enabled.

There is one more complication added by the S12X.  The MC9S12XDP512 Flash comes with the serial monitor program at the top of memory.  Instead of relying on one interrupt routine to determine what device requested an interrupt, each S12X interrupt source automatically causes the associated handling routine address to be fetched from a table, called the Interrupt Vector Table.  For the S12X, this table is by default at the very top of memory, right in that same 2K memory space that the serial monitor is in.  Since that memory is protected, it can't be changed by the serial monitor.  (Okay, a BDM pod can do it but that defeats the purpose of working with the monitor.)  There are solutions to these issues, and I'll cover them here.

As an example, I'll use SCI0 as the interface we want to set up to handle an interrupt from.

The steps we need to do are:
0) Set the stack pointer.
1) Initialize any pointers, buffers, etc, needed by the interrupt routine.
2) Initialize the S12X to handle interrupt input.
3) Initialize the hardware to enable its interrupt output.
4) Enable CPU interrupt flag in Condition Code register.


Stack Pointer

Before any interrupts can be handled, the CPU stack pointer has to be initialized.  This is done with a LDS instruction, and need only be done once in your setup code.  Be sure you set it up to point to a valid location in RAM, and that you reserve plenty of space for it to grow into.  (Stacks grow down towards lower memory addresses.)  Oh, yeah-- not using a valid RAM location will crash your system.

Usually the stack pointer is used to store the return address when calling a subroutine, or to store temporary variables.  However in the case of an interrupt, all the CPU register values are stored on the stack when the interrupt begins processing.  That way when the interrupt processing routine finishes, the entire CPU state is restored.  Whatever routine got interrupted should never know it.

Therefore when writing an interrupt processing routine, it has to end with an 'RTI', or Return from Interrupt instruction.  RTI expects to find all the CPU registers stored on the stack in a certain order, so it can restore the CPU state as required when it is executed.  If your interrupt routine does not leave the stack exactly as it found it, you will crash the system.  (Been there, done that!)


Interrupt Routine Initialization

Your setup code needs to make sure that before any interrupt handling is enabled, that any variables that the interrupt routine accesses are initialized correctly.  This includes counters, buffer pointers, buffer contents, etc.  This may seem obvious, but a lot of software problems come from not noticing or taking care of the obvious stuff.


S12X Interrupt Initialization

Now we start getting to the fun stuff!  The first item to set up is where the table of interrupt vectors is in memory, and to do that we need to understand what exactly is an interrupt vector table.

In the typical processor discussed previously, there is only one interrupt address to deal with-- for the one interrupt pin on the processor chip.  The S12X could have stayed with this, and left it to the interrupt routine to determine which hardware device triggered it.  However, this approach ends up being wasteful of CPU cycles, and that can really hurt a system designed for high performance embedded use.  The alternative is to have each integrated hardware device provide the CPU directly with the address of the routine to handle its interrupt needs.  This is what the Interrupt Vector Table is for.

When an S12X device triggers an interrupt, that request is handled by the Interrupt module.  The Vector table the module manages can occupy any 256 byte page in memory.  The location of that page is specified by the IVBR register at address $121.  Since each routine address is specified by two bytes of memory, the table can hold up to 128 interrupt routine addresses.  You will need to reference the S12X documentation to find out where each device's interrupt address is kept in the table.

Following reset, the IVBR register contains a value of $FF, which places the table right at the top of memory in the serial monitor area.  However the register contents can be changed ONCE after reset to point to any other page in memory.  Usually this is not necessary, but it is a good idea to set IVBR to $FF even if it doesn't need to be different from the default value.  Keep in mind that even though this is set to be $FF, if the serial monitor is being used for troubleshooting it will look for your interrupt routine address in a pseudo-table set aside at the $F7 page in memory.  (The serial monitor fixes the IVBR internally to $FF.)

The reason for the pseudo-table is because the serial monitor has to be able to manage its interrupts to function correctly.  The pseudo-table will also allow detection of an interrupt triggered that no routine is written for it, and let the serial monitor take over.  This helps in the troubleshooting process.  The pseudo-table exists from $F710 through $F7FF.  Your 'reset' vector will need to be programmed at $F7FE, or your application will not start on powerup, even if the Load/Run switch is in the Run position.

You might ask yourself, "What happens if two devices simultaneously ask for an interrupt to be handled?"  There are two parts to the answer to this question, and I'll handle the first part here.  The position of an interrupt address in the Vector table gives it a priority in relation to all the other interrupts.  So if two interrupts come in simultaneously, the one that has the vector address higher in the table will be handled first.  That is why Reset, which is considered an Interrupt, has its vector at the top of the table at $FFFE.  It trumps all other interrupts.

For our example, the SCI0 device is supposed to have it's interrupt handler address stored at the $D6 location in the table.  If IVBR is set to be $FF, then the SCI0 address should be in Flash at $FFD6 and $FFD7.  (Relocated to $F7D6/7 with the serial monitor in use.)

The next step is to tell the Interrupt module the priority of this interrupt.  This is the second part of determining the order of handling interrupts, and it supersedes table position.  On reset, all interrupt vectors are set to have a default priority of 1.  Priorities can go from 0 to 7, with 7 being the highest, and 0 effectively disabling the interrupt.  If two interrupts have the same priority, then table position will determine the higher priority one.

For memory space considerations though, the S12X Interrupt module does not use up 128 addresses for a table to set the priority of the 128 possible interrupts in the Vector Table.  Instead the table is kept internal to the module, and only 8 addresses in that table are visible at any time.  We select which part of the priority table to view with the INT_CFADDR register at $127.  The eight register window is from $128 to $12F.

So for SCI0, we need to set INT_CFADDR to $D0 since the Interrupt vector is at $D6 in the Interrupt table.  Then we choose a priority, say the value 3, to write into the table register at $12B.  How was that chosen?  Well, with INT_CFADDR set to $D0 consider our table window translates to this:

$128 -> $D0 (ATD1)
$129 -> $D2 (ATD0)
$12A -> $D4 (SCI1)
$12B -> $D6 (SCI0)
$12C -> $D8 (SPI0)
$12D -> $DA (Pulse Accumulator Input Edge)
$12E -> $DC (Pulse Accumulator A Overflow)
$12F -> $DE (Enhanced Capture Timer Overflow)

That's how $12B was chosen to write our interrupt priority to for SCI0.


Hardware Interrupt Initialization

The next step is to set up the hardware interface so that it generates an interrupt when we want it to.  By default, hardware generally will not send an interrupt to the CPU, and so we need to configure it to do this.  Obviously this step is very dependent on the particular hardware we want to generate the interrupt.  Therefore you will need to read closely the documentation for the hardware device you are using to make sure that it is done correctly.  Otherwise strange results will occur, if any results occur at all.

For SCI0, we need to set the baud rate first.  This is done at $00C8 and $00C9.  You will need to read the documentation to determine the values to write here, to get the baud speed you want based on the CPU bus clock.  The default value for Control Register 1 is good, so we keep this by setting SCICR1 at $00CA to $00.

Finally, we enable the transmitter and receiver for SCI0, and enable their respective interrupts at the same time by writing a value of $AC to the SCICR2 control register at $00CB.


CPU Interrupt Enable

The final step is to enable the S12X CPU to handle interrupts.  Following Reset, the Codition Code Register has the I (Interrupt) mask bit set.  This bit must be cleared before the S12X will start handling interrupts.  This can be done with the 'cli' assembler directive, which translates to 'andcc #$EF'.  The bit is set whenever an interrupt routine is called by the Interrupt module.

It is possible for you to write your code to clear this flag on purpose while your interrupt routine is being processed.  Doing so will allow interrupts of a higher priority to interrupt the current routine, while still preventing lower priority routines from stepping in.  It will also keep high priority interrupts processed in a timely fashion.

As you can see, setting up interrupts is not for the faint of heart, and requires careful planning for it to be done correctly.  Furthermore, debugging interrupt code can be a nightmare.  (The IDE simulator with its breakpoints can help here, but it only goes so far.)  For difficult problems this can require the use of a BDM pod with matching troubleshooting software, and even a logic analyzer.  However don't let these difficulties keep you from using interrupts.  You can still do a lot, even without a BDM pod.  It just requires using that space between your ears more.


Other Interrupt Caveats

Some of the interrupts, like SWI and XIRQ, have slightly different rules for using them.  However the basics outlined here still apply.  Again, you will need to read up on the documentation to use these effectively.

Last Updated ( Friday, 08 February 2019 18:19 )