By Kevin Ross
Microcontrollers have many features that are extremely useful. One of the most mysterious and least understood features of these parts are Interrupts. In this article, I would like to present an overview of interrupts and how you can use them. The basics of interrupts are the same on most Microcontrollers, so most of the information I will present will apply to other chips. For this article, I am going to focus on the 68HC12 family. The 68HC11 is almost identical, so everything you read here will apply on that series of chips very readily.
I will go through the basics of the 68HC12 interrupts assuming you have no prior knowledge. It would be very helpful, however, if you had the 68HC912B32TS document or the 68HC812A4TS document handy. You can grab it from the web. This article is intended to help you understand what is in that document.
The purpose of interrupts on a Microcontroller is to allow your software to perform certain tasks at specific times or after specific events have happened without having to contort your control software too much. A well written set of interrupt routines can make your software much easier to write and maintain. On the flip side, debugging a poorly structured set of interrupt routines is a nightmare that I wouldn't wish on anyone.
So, just what is an interrupt anyway. The best way to describe them is by example. Suppose you are working hard trying to build your project, and the phone rings. If you are willing to accept interruptions at this point, you will put down all of your tools and answer the phone. After dealing with whatever the phone call requires of you, to return to your project, pick up all of your tools, and continue working as if nothing happened, except a small amount of time was spent doing something else.
Interrupts on a Microcontroller work much in the same way. When an interrupt occurs, be it an external signal or an internal source, the current state of the controller is saved away, and an interrupt routine is executed. When the interrupt routine is finished, then the saved state of the controller is restored, and execution continues at the point of execution.
The rest of this article will explain some interrupt terminology, the different types of interrupts, and how to enable and use interrupts for various features.
There is a fair amount of terminology used in describing interrupts, and I will introduce you to some of the terms in this section.
Interrupt Sources - An interrupt source is some signal or condition that indicates that an interrupt is ready to be performed. In the phone example, the interrupt source is the telephone ring. On the 68HC12, there are quite a few different interrupt sources. Sources of interrupts include timers, changes on I/O pins, completion of serial (SCI) transmissions, arrival of characters on the SCI port, and a whole list of other things that I will explain a little later.
Interrupt Handlers (Routines) - Interrupt handlers, which are often also known as interrupt routines, are specific software routines that are called when the interrupt occurs. These routines are expected to deal correctly with the specifics of the interrupt, then return using a special return instruction called an RTI. More about RTI's later.
Interrupt Vectors - Each interrupt source has associated with it something called an interrupt vector. There is a table kept in memory of interrupt vectors. A vector is merely the address of the Interrupt Handler. When an interrupt occurs, the chip automatically jumps to the address stored for that interrupt.
Maskable Interrupts - There are two basic flavors of interrupt: Maskable and Non-maskable. A Maskable interrupt is one that the software can choose to ignore or disable. A Non-maskable interrupt is one that the software MUST accept at any time, and cannot be turned off. There are lots of Maskable interrupts, but only a few Non-maskable ones. This will be explained a little later.
State - The state of the CPU is a term that will be used quite a bit. The state of the CPU consists of all of the register values, condition codes, and the program counter. Therefore, to save the state of the current machine, you need to store all of these values somewhere. On the 68HC11/12 series, all state is saved onto the stack. The state of one of the registers, specifically the stack pointer, is saved implicitly. This means that the stack pointers value is not actually saved on the stack, but its previous value is the current value plus 9 bytes.
In most controller architectures, the subject of Exceptions, Interrupts, and Resets are closely related. An Exception is an event that causes the chip to automatically intercept execution of instructions and to start executing at a new location. Both Interrupts and Resets are considered Exceptions. They almost always share the same basic mechanism for being processed. This is also true on the 68HC11/12 series of chips.
A Reset is distinguished from an Interrupt in that a Reset doesn't save any state. Rather than interrupting a routine, a Reset is a call for the chip to reinitialize itself into a running state. There are five sources of reset. Power-On Reset (POR), external reset on the RESET pin, and reset from the alternate reset pin (ARST) share the normal reset vector. The Computer Operating Properly (COP) reset and the clock monitor reset each has its own vector in the vector table.
An Interrupt, on the other hand, is an exception that the chip expects the software to recover from. Therefore, the current state of the CPU at the time of the interrupt is saved onto the stack. This is done automatically by the chip. The following shows the contents of the stack on entry to an interrupt handler
|Memory Location||CPU Registers|
|SP 2||RTN H : RTN L|
|SP 4||Y H : Y L|
|SP 6||X H : X L|
|SP 8||B : A|
Stacking Order on Entry to Interrupts
When an interrupt is dispatched by the chip, it does a few important steps automatically. First, it disables other interrupts. Second, it saves the state onto the stack, as shown in the above table. Third, it looks up the appropriate vector in the vector table, and jumps to that address. It is now up to the handler to process the interrupt accordingly.
When the handler is finished processing the interrupt, it needs to return control to the interrupted routine. To do this, it must use a special instruction called RTI (ReTurn from Interrupt). The RTI instruction knows the format of the saved state on the stack, and correctly restores the state back into the registers. It also enables future interrupts. Once it returns, the originally interrupted software routines will have no idea that an interrupt occurred. The only indication it might have is that some additional time had passed. Pretty nifty. I wish I was personally able to recover from interrupts so efficiently!
Lets do something by example now. This will help show you what needs to be setup to make an interrupt happen, a routine to handle the interrupt, and how to acknowledge an interrupt. To do this, we are going to use one of the internal interrupt sources. The 68HC11/12 series of chips have a facility onboard called the Real Time Interrupt. Unfortunately, in the documentation it is abbreviated as RTI, which is also the name of the return from interrupt instruction! So, I am going to call it the RT interrupt for now, just to try to not confuse the issue.
First, lets define a routine that will act as the interrupt handler. The interrupt handler in this case is going to decrement a RAM based variable until it reaches zero. This is usually known as a countdown timer, and are generally pretty useful for doing things like keeping track of timeouts. In this example, the main line code will use this countdown timer to determine when approximately 1 second has expired. Then it will set pin PA0 to be high. Then, it waits for 2 seconds, and sets PA0 low.
tStartOfTimers equ * ; This defines a block of tRtiDelay rmb 2 ; WORD sized variables that tHeartBeat rmb 2 ; will be handled by the RTI tEndOfTimers equ * ; interrupt handler
;****************************************************************** ; The ORG statement is set to $F000, which is the default starting ; address for the 4k of EEPROM on the 68HC812A4. ; ORG $F000
;****************************************************************** ; The Real Time Interrupt is serviced by walking the list of timers ; found starting at tStartOfTimers. For each timer, which is a word ; in length, decrement the counter by one unless the timer is already ; zero ; int_Real_Time_Int bset RTIFLG,#$80 ; Acknowledge Interrupt ldx #tStartOfTimers RTI_LOOP cpx #tEndOfTimers beq RTI_ROUTINES ldd 2,x+ ; Notice the autoincrement beq RTI_LOOP subd #1 ; Decrement the value by 1 std -2,x ; Store at the previous location bra RTI_LOOP ; ; Some of the timers represent routines that need to be called ; on a regular basis. We have already counted down these ; timers. Now, for each timer that is zero, call the subroutine ; that handles the function. This is very useful if you need ; to have some function handled on a regular basis ; RTI_ROUTINES ldd tHeartBeat bne RTI_END jsr HeartBeat RTI_END rti
;****************************************************************** ; HeartBeat is called on a regular basis by the RTI routine. Its function ; is to toggle the low bit on the A port, to indicate that the program is ; still running. ; HeartBeat ldd #HEART_BEAT_RATE ; Setup for the next beat std tHeartBeat ldaa #$01 eora PORTA ; Toggle the heartbeat bit staa PORTA ; ; Note that heartbeat is called from another function. ; Though it is called during an interrupt, we need to ; use the RTS function since we aren't trying to restore ; an interrupt state on exit. That will be done by the ; calling routine ; rts
We have defined a functional interrupt handler above. There are one or two things that are useful to point out here. First is that the interrupt routine is fairly short. Remember that your interrupt routine will be called each time the interrupt source triggers it. If you are writing a handler for a commonly triggered interrupt, you can end up hogging the CPU quite easily by doing too many things in your interrupt routine. Try to execute less than a hundred or two instructions. Any more than that, and you could start impacting how other interrupts are processed, or even keep your main program from running. Usually, interrupt routines do something straight forward, such as we did here, then return quickly. Minimal impact on your main code.
Another thing that needs to be discussed is the acknowledgment of the interrupt. Most interrupts have a flag bit somewhere that indicates if the interrupt condition has been reached. In this case, the interrupt condition consists of the RTI timer reaching the value $0000. When this happens, a flag gets set in the register set that tells the CPU that the Real-time interrupt needs to be processed. As long as that flag is set, the CPU will keep dispatching the real-time interrupt.
As a little more explanation, consider the case where there are two interrupts that happen very close to each other. Assume that a SCI interrupt is being processed by the SCI Interrupt handler. While this is being processed, the RT interrupt source is triggered. Since the SCI handler is executing with interrupts turned off, the RT interrupt has not been dispatched yet. What is needed is for the RT interrupt to happen as soon as the SCI interrupt is completed. To accomplish this task, the chip uses a set of flags to indicate which interrupts need to be processed. When the SCI interrupt is completed, and the Return From Interrupted instruction is executed, the chip will detect that the RT interrupt flag is set, and will dispatch to RT_Handler. To acknowledge that the interrupt has been processed, it is up to the RT interrupt handler to clear the flag when it is executing. If you notice, the BSET instruction is used! Yep, sure enough, to make the flag go to zero, you need to write a 1 to it. This is an interesting thing that the 68HC11/12 CPU's do. When you check out some of the flags, you will find that they want you to write a 1 to the flag to clear it. Once cleared, the interrupt is ready to be triggered again sometime in the future.
As a rule of thumb, it is best to acknowledge the interrupt first thing in your routine. Why? Well, it is possible that your routine might take some extra time to process the interrupt. In the mean time, the interrupt source may trigger again. If you wait until the end of the routine to clear the flag, you may clear the flag and miss the second trigger. So, you are best off to clear the flag as the first instruction to avoid missing interrupts.
Here is the rest of the code that makes this code functional
Start: ; ; Set the top of the stack. Using $0C00 is OK, because the 68HC12 ; decrements BEFORE pushing. Many people set it to $BFF, but having ; a word aligned stack is better. ; lds #$0C00 ; ; The 68HC12, unlike the 68HC11, defaults with the COP turned on. ; The COP is a timer function that requires the software to ; periodically write a specific pair of values to a register. If ; this isn't done in time, the processor will reset every 1.04 ; seconds or so. ; ; For this module, the COP is going to be disabled. The following ; line disables the COP by setting the COP Watchdog Rate to zero ; clr COPCTL ; Store zero in COP Control Register jsr serial_init ldx #ProgStart jsr outstr ; ; Enable the RTI interrupts. The $81 enables interrupts, and sets ; the period for the interrupts to 1.024 milliseconds ; ldaa #$81 staa RTICTL ; ; Initialize the timers used in this example ; ldd #HEART_BEAT_RATE std tHeartBeat ldaa #$01 staa DDRA ; ; Initialization is now complete. We can now enable interrupts! ; cli MainLoop: ldd #PRINT_CHAR_RATE ; Delay approx 2 seconds std tRtiDelay MinorLoop: ; ; Insert whatever code you would like to put here. Once it is ; done, the code below will execute. Only if the countdown ; timer tRtiDelay is zero will it print then call the main ; loop. ; tst tRtiDelay bne MinorLoop ldaa #'R' jsr putchar bra MainLoop org $FFF0 * Address of Real-time Interrupt vector
fdb int_Real_Time_Int * Direct vector to our handler
fdb Start * Reset vector
Note that the vectors use the ORG statement to insure that they are put in the correct place in memory. There is a table in the manual for each chip that specifies where the interrupt and reset vectors are found in memory. If you trace the code from Start, you will see that without the interrupt routine, I have coded a couple of infinite loops. However, since the interrupt routines run in the 'background', the value in tTimer is going to change over time.
If you would like to play with this, you will find a version of the source code file by clicking on Link to Interrupt Sample 1 in ASM. If you would rather play with SBASIC, a the same functionality will be found by following this Link to Interrupt Sample 1 in SBASIC
When an interrupt happens, the state of the machine is placed on the stack. This implies, of course, that before you enable interrupts using the CLI instruction, you need to insure that the stack is set to a valid value in RAM so that there is somewhere to store the state.
We briefly discussed the CPU state earlier. Here, I would like to show you the equivalent set of instructions that save and restore the state of the CPU. Roughly speaking, the following are pretty much the instructions that the CPU processes internally to process an interrupt. These instructions are executed as one big instruction that takes 9 clocks.
Push PC * Push the address of next instruction for return
pshy * Save all of the registers
sei Disable interrupts
jmp [ Vector ]
Thus, your interrupt routine can look to see the state of where it was interrupted at by looking on the stack. Starting at address 0,SP it will find the values as pushed above.
The inverse operation is also there, which is the RTI instruction. The RTI instruction is Return From Interrupt, and does basically the inverse of the above operations:
Note that the PULC will restore the condition codes (CC) from the stack, including the contents of the I bit which is the Interrupt Inhibit bit. There wasn't a need for a CLI instruction, which is why the RTI only takes 8 clocks to execute.
There is some special logic to the RTI instruction having to do with the XIRQ bit that isn't important for this article, but you might want to be aware of it. The X bit is a mask in the CC register that enables/disables the XIRQ interrupt. This bit is a special case in that it cannot be inhibited by the RTI instruction. It can be cleared from 1 to 0, but the bit will not go from 0 to 1 as a result of the RTI. You probably will never care about this, but I thought I would mention it anyway. Enough said.
All interrupts and resets have a thing called a 'vector'. A 'vector' is a fancy pants way of saying an address. A vector table is merely a table of addresses that are stored in a known place in memory. That way, when an interrupt or reset occurs, the CPU can figure out, based on the entry in the vector table, where to execute some code. The HC12 defines 25 vectors starting at address $FFCE. Actually, Motorola reserved memory from $FF80 to $FFFF for interrupt vectors. The current parts define only the last 25, but more parts and perhiperals are on the way, so expect to see other addresses used.
When I write in assembly code, I usually grab a boiler plate vector table from one of my previous projects. Shown below, you are welcome to use it. You will also find it at the end of the source code I provide for this article. It is worth pointing out what I did about unused vectors. In a perfect system, you should never ever execute an interrupt that isn't properly handled. If you aren't using a system, or haven't enabled its interrupts, then in theory you don't need to provide an interrupt routine. My vector table, shown below, uses a special routine called vec_Unexpected. On an HC12 with the BDM enabled, an unexpected interrupt will jump to my vec_Unexpected routine, and stop in the debugger. If the debugger isn't initialized, then it will jmp through the Reset vector and start the program over. Again, this is a bad thing, but it is one way to recover almost gracefully.
;************************************************************************* ; ; Interrupt vectors. When the CPU starts, or encounters an interrupt, it ; will read this table to determine where to jump to in the code. ; ; Note: If you are using the 5G18E pre production mask, there was an ; error in the addressing of the interrupt vectors. Get a newer part, as ; there were plenty of other errors on that chip as well. ; vec_Unexpected: bgnd ldx _int_Reset jmp [0,x] ; Must start at this specific address ORG $FFCE _int_Key_Wakeup_H dw vec_Unexpected _int_Key_Wakeup_J dw vec_Unexpected _int_ATD dw vec_Unexpected _int_SCI1 dw vec_Unexpected _int_SCI0 dw vec_Unexpected _int_SPI_STC dw vec_Unexpected _int_PAIE dw vec_Unexpected _int_PAO dw vec_Unexpected _int_Timer_Overflow dw vec_Unexpected _int_Timer_7 dw vec_Unexpected _int_Timer_6 dw vec_Unexpected _int_Timer_5 dw vec_Unexpected _int_Timer_4 dw vec_Unexpected _int_Timer_3 dw vec_Unexpected _int_Timer_2 dw vec_Unexpected _int_Timer_1 dw vec_Unexpected _int_Timer_0 dw int_Timer_0 _int_Real_Time_Int dw int_Real_Time_Int _int_IRQ_Key_Wakeup_D dw vec_Unexpected _int_XIRQ dw vec_Unexpected _int_SWI dw vec_Unexpected _int_UIT dw vec_Unexpected _int_COP_Failure dw vec_Unexpected _int_COP_Clock_Monitor_Fail dw vec_Unexpected _int_Reset dw Start end
From the previous example, you can see that interrupt handlers really aren't that difficult to design. Here are a few pointers that you need to consider in your handler design:
Most languages designed for Microcontrollers support some way of writing interrupt driven code. SBASIC is no stranger to interrupts, and if you wish, you can indeed use SBASIC for your interrupt routines. There are three things you need to do in your interrupt routine:
1) Your interrupt routine must use the interrupt keyword followed by the vector address.
2) Your interrupt routine must use the end statement to cause the RTI instruction to be emitted
3) You need to enable interrupts with the interrupts on statement.
Your interrupt routine should be short! SBASIC does a good job of compiling down to pretty small sized code. However, some of the 'instructions' in SBASIC can generate a lot of code that takes a lot of time to process. For example, putting a print statement inside your interrupt routine may be a bad idea!
Here, for example, is an SBASIC routine that implements the Real Time Interrupt routine:
include "regs12.lib" 'Declare the RTI ISR, used to generate timed delays. declare wait 'delay counter interrupt $fff0 'RTI ISR if wait <> 0 'if not done yet... wait = wait - 1 'count this tick endif pokeb rtiflg, $80 'clear RTI flag end Main: ' Enable serial port poke sc0bdh, 26 ' Set for 19200 baud pokeb sc0cr2,$0c ' Enable transceiver print "intr1.bas started" pokeb copctl,0 pokeb rtictl, $81 ' Turn on the RTI interrupts on 'allow interrupts do wait = 2000 do loop while wait <> 0 print "r"; loop end
As you can see, this isn't too bad! The inverse of interrupts on is,
as you might have guessed, interrupts off. You can grab this source file
Another great onboard source of interrupts are the timers. We have already played around with one of the timers, the Real Time Interrupt timer. The HC812 has 8 independent timer channels that you can use. This, in theory, might allow you to perform 8 different timed actions. You might check out my article about the 68HC12 Timer Module for details about how the timer module works. I will go through two quick examples here. The first is an example of how you can use the timer module interrupts to generate a variable length square wave with a 50% duty cycle using interrupts. The second example combines the RTI example with this to allow your code to play a song completely driven by interrupts. I will provide examples in both assembler and SBASIC.
The first routine uses Timer Channel 0, in Timer Output Compare mode, to generate a square wave signal on the pin. The hardware is setup via my software to automatically toggle the state of Port T bit 0 when ever the value in TC0 matches the current clock. However, we want to change the value in TC0 as soon as that happens. This allows us to vary the period of the signal. So, we setup Timer Channel 0 to generate an interrupt when the counter matches. This is really cool, since using this method we can generate various tones on TC0 for playing music. Ok, so how to set this up:
; ; Setup timer system ; ldaa #$80 ; Timer system Enable staa TSCR ldaa #$01 ; Set to toggle bit on compare staa TCTL2 staa TIOS ; Set TC0 to be an output compare staa TMSK1 ldd #8192 ; Initial pulse width is 8192 clocks wide std wPeriodIncrement ; On a 8MHZ ECLOCK, that would be a pulse std TC0 ; width of approximately 1.024ms. There are ; two changes per cycle, which means a full ; cycle is 2.048ms. 1/.002048 is in the ; 488hz neighborhood, which is is a good ; frequency to start with on a peizo speaker
Basically, by looking in the timer section, I found that I needed to turn the timer system on using the $80 bit in TSCR. I then setup, in TCTL2, that channel 0 should toggle itself every time there is a match. I then set TC0 to be an output channel (as opposed to being an input channel). In TMSK1 is where the interrupt stuff happens. By setting the $01 bit, the Timer Subsystem will generate an interrupt when TC0 (the compare value for channel 0) equals what is in the TCNT register (the free running counter). Note that each channel has its own interrupt enable bit. Therefore, I can setup some channels to generate interrupts, and some not.
I initialized TC0 to be 8192 clocks wide. A system with a 16mhz crystal has an internal ECLOCK of 8mhz. That means that there is one clock pulse every 125 nanoseconds. By working out that math, I was able to determine that this 8192 value has a resulting frequency of about 488hz. The rest of the details can be found in the example software files. I think you should be able to figure out easily what is happening there. This test program initializes with the sound generated at 488hz, then allows you to use the 'u' and 'd' keys to adjust the frequency up or down as you wish.
There is a slight amount of electronics involved if you actually want to hear this. You will need a Piezo electric buzzer/speaker. You can buy two basic types: One has a built in driver. The other does not. You would like to have the 'does not' type, since we are going to drive the element using TC0. The other part you need is a 470 to 1000 ohm resistor. This will act as a current limiting resistor, and also acts as a volume control. The exact value doesn't matter, though I found that over about 4.7k, the sound is pretty quiet. I do suggest at least 470 ohms though. A 1k would probably be best. Less than that may let a little too much current through.
If you would rather, you can also 'listen' to the changes using a logic probe set to the 'Normal' mode. If yours beeps whenever there is a state change, then you will find that it makes a decent enough sound.
There are two versions of the source code for this example. Click here for Sample 2 in assembler, or you can click here for Sample 2 in SBASIC
Playing a continuos tone is mighty cool, but lets face it, kind of boring if not down right irritating after a minute or so. Lets do something about that. In the next example, I will combine the functions of the RTI timer and the TOC timer to generate notes of a specific frequency. The frequencies chosen are based on a table I found on the Internet showing the standard frequencies for musical notes. Check out http://www.seathree.demon.co.uk/info/freq1.htm for the data I used.
The basic idea of this modification is to use TC0 to control the frequency of the sound, just like we did before. The Real Time Interrupt is used to control the duration of each note. This requires that the RTI function gets a little bit longer. In the real world, this is OK because the RTI function doesn't fire off very often, so the total overhead is acceptable. I am not going to show you the code here in the text, but you are free to look at the assembler file for Sample 3 in assembler. Sorry, I didn't do an SBASIC version of this particular file (not enough time tonight). However, you should be able to create your own based on what I have there. (i.e. Left as exercise for reader
While looking through the file, you will see I included two bits of songs (Beethoven's 5th and the Entertainer). Pressing 'p' on a serial port will cause the Entertainer to run.
Here are links and instructions for getting 68HC12 resources
as12 assembler is available from Karl Lunt, and is the freeware assembler for the HC12 family.
SBasic is Karl Lunts version of BASIC for the 68HC12. This is a good high level language.
Robotic and Microcontrollers Products is my page that allows you to order the SRS 68HC812A4 Development board, as well as my BDM12 interface board, and the new 68HC912B32 development board.
Imagecraft has a great C compiler for the 68HC12. The compiler runs around $150, but is a great bargin if you would like to write software in C for the HC12.
You can get online versions of the 68HC12 documentation from Motorolas website. Here are the files to grab. You can also get printed versions as described below.
CPU12RM-AD Index is an index to a series of .PDF files that create the Motorola CPU12 Reference Manual. This describes the instruction set and programming model for the chip. It is definitely a must have. There is also an Online Version at CPU12RM-AD or http://www.mcu.motsps.com/lit/manuals/cpu12/outline1.html
a4.pdf is the .PDF file of the 68HC812A4 Technical Summary that contains information specific to the 68HC812A4 version of the chip. This is the one with 4k of EEPROM, 1k RAM, and tons of I/O. This explains all of the systems on the chip, where the registers are, etc. You should definitely have one of these if you are using the 68HC812A4.
(250K bytes) is a PDF file with the electrical characteristics of the 68HC812A4. Timing diagrams, electrical capacities and absolutes, accuracies, etc.
b32ts.pdf is the .PDF file of the 68HC912B32 Technical Summary that contains information specific to the 68HC912B32 version of the chip. This is the one with 32k of Flash, 768 bytes EEPROM, 1k RAM, and still lots of I/O. This explains all of the systems on the chip, where the registers are, etc. You should definitely have one of these if you are using the 68HC912B32.
(272K bytes) is a PDF file with the electrical characteristics of the 68HC912B32. Timing diagrams, electrical capacities and absolutes, accuracies, etc.
Motorola will also allow you to order printed documentation online at http://design-net.com/home2/lit_ord.html where you will find a form to fill out your name and address, then a spot on the bottom for document numbers. The following are the documents that you might be interested in
|CPU12RM/AD||The CPU12 Reference Manual (Must Have)|
|MC68HC812A4EC/D||68HC812A4 Electrical Characteristics|
|MC68HC812A4PP/D||68HC812A4 Product Preview (Not worth the effort!)|
|MC68HC812A4TS/D||68HC812A4 Technical Summary (Must Have)|
|MC68HC912B32ECD||68HC912B32 Electrical Characteristics|
|MC68HC912B32PPD||68HC912B32 Product Preview (Not worth the effort!)|
|MC68HC912B32TSD||68HC912B32 Technical Summary (Must Have)