There have been several posts recently about using Interrupts within an Atom Pro Basic program. I will try to summarize some of this information into this thread. Note: I will not describe all of the capabilities that are supported by the BAP, only those that I have used. Others who have used other hardware interrupt capabilities may wish to add to this message.
For those of you who are not overly familiar with interrupts, I would suggest that you first look over the section on Interrupts in the Basic Atom Pros Syntax manual starting at about page 174.
A complete description on all of the interrupts supported by the BAP is listed there, as well as an example for a timer interrupt. Another important page to look at is the Module Pin outs on page 187, which shows which interrupt Is connected to what external pin. You will also need the H8 (3664) manual, which will describe the different registers and the assembly language instruction set. Both of these manuals are available from the Help manual of the Atom Pros IDE.
Timers
Probably one of the simplest and very useful interrupt is to process one of the hardware Timers that the underlying H8 microcontroller supports. The simplest one is the 8 bit timer (TimerA), which is what the BAP manual has as an example:
ONINTERRUPT TIMERAINT,IntHandler
TMA = (TMA & 0xF0) | 0x4 ;Sets TimerA to increment once
;every 256 clock cycles.
timer var long
ENABLE TIMERAINT
main
serout S_OUT,i9600,[dec timer,13]
pause 100
goto main
IntHandler
timer = timer + 1
resume
In this example the ONINTERRUPT command is the glue that ties what code will be called when the hardware interrupt happens. In this case it called when the timers 8 bit count overflows. The TMA statement refers to a hardware register called TMA that is described in the H83664 manual (section 10.3.1). Using the description in this section you can easily change the counter to increment on different multiples of the clock cycles. While this example does not show it, another useful register is TCA which is the actual counter for the timer. Using both the Overflows as well as TCA gives you nice timer. The ENABLE command is what tells the processor that you now wish to process those interrupts. The DISABLE command allows you to turn off processing of a specific or all interrupts, which can be turned back on with another ENABLE command. There are times when you may not want to process interrupts. For example I have found SEROUT commands may not always be reliable when interrupts are being processed.
Recent versions of the IDE now allow you to have assembly language handlers for the interrupts. This allows you to process these interrupts with lower overhead. The following is an example of this, which again processes the TimerA overflow interrupt, and keeps a current time. However this puts a higher burden on you. For example in your interrupt handler, you will need to clear the appropriate bit or bits in the interrupt pending registers and be very careful not to tromp on any registers. You need to surround your assembly language interrupt handler with the commands BEGINASMSUB and ENDASMSUB. As Nathan said in a different thread:
iOverflowTime var sword
iCurrentTime var sword
ONASMINTERRUPT TIMERAINT, HANDLE_TIMERA_ASM
; TimerA regs
iOverflowTime = 0
TMA = 0x6 ; clk/32 (Wish there was /16 but try to cut down interrupts...)
�
iCurrentTime = iOverflowTime + TCA
�
BEGINASMSUB
HANDLE_TIMERA_ASM
push.w r1 ; first save away ER1 as we will mess with it.
bclr #6,@IRR1:8 ; clear the cooresponding bit in the interrupt pending mask
mov.w @IOVERFLOWTIME:16,r1 ; Add 256 to our counter
;add.w #0x100:16,r1
inc.b r1h
mov.w r1, @IOVERFLOWTIME:16
pop.w r1
rte
ENDASMSUB
There are two other timers TimerV(16 bit) and TimerW, that I won�t detail here as TimerW is used by HSERVO and I have not used TimerV.
IRQ1, IRQ2, IRQ3
Probably the next easiest to describe and use interrupts are for the external interrupts IRQ1-3, which are connected to the BAP external pins P8, P18 and P19. These correspond to the H8 I/O pins (P15,P16 and P17) as shown the table on page 187 of the BAP manual. To use these you will need to again define your interrupt handler and use the enable commands. In addition, you will need to tell the underlying H8 Microcontroller that you wish to use the underlying pin for interrupts instead of general Input/Output and do you want the interrupt to happen on the leading or trailing edges. Here is an example for the processing of IRQ2. I am showing the assembly language version here:
ONASMINTERRUPT IRQ2INT, HANDLE_IRQ2
PMR1.bit6 = 1 ; enable pin to IRQ2 interrupt instead of normal I/O
IEGR1.bit2 = 1 ; Interrupt IRQ2 on rising edge
ENABLE IRQ2INT
�
; - later we may try to handle the falling as well
BEGINASMSUB
HANDLE_IRQ2
push.l er1 ; first save away ER1 as we will mess with it.
bclr #2,@IRR1:8 ; clear the IRQ2 bit in the interrupt pending mask
andc #0x7f,ccr ; allow other interrupts to happen
mov.l @LENCODER1CNT:32,er1 ; increment our count of encoders we have seen
inc.l #1,er1
mov.l er1, @LENCODER1CNT:32
pop.l er1
rte
ENDASMSUB
;
The basic version of this handler would look like:
HANDLE_IRQ2
Enable ; allow other interrupts to happen
lEncoder1Cnt = lEncoder1Cnt + 1
resume
To code to process IRQ1 or IRQ3 is very similar. Your initialization code would need to change different bits in PMR1(5,7) and IEGR1(1,3) and if you were using an assembly language handler it would need to clear a different bit in IRR1(1,3).
WKP0 through WKP5
There are six more external interrupts WKP0 through WKP5 that are similar to the external interrupts I mentioned in the previous section. They are connected to the IOPINS P0-P5 of the Basic Atom Pro and correspond internally to the H8 microcontroller to IO pins P50-P55. When coding an interrupt handler for these in Basic, they look very similar to IRQ2 function I described above. For example here is a handler for WKP0 that I wrote earlier to intercept the PWM signal from a Hitec Laser6 receiver. Please pardon that code may not be fully optimized:
;Interrupt init
ONINTERRUPT WKPINT_0,handle_intp0
PMR5.bit0 = 1 ;enables pin as WKP interrupt instead of normal I/O
IEGR2.bit0 = 1 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
ENABLE WKPINT_0
�
handle_WKP0
if fProcessingPulses = FALSE then
If (IEGR2 & 0x01) then ; process rising edge
; need to reset timer
TMA = 0xc ; reset timer
TMA = 0x6 ; clk/32
iOverflowTime = 0
fPulseTimingsBusy = 1
IEGR2.bit0 = 0 ;interrupt on a falling edge.
else
enable
aiPulseTimings(0) = iOverflowTime + TCA
IEGR2.bit0 = 1 ;interrupt on a rising edge.
endif
endif
resume
Many of the details in this handler are probably not interesting. What this implementation does is when the pulse comes in on the rising edge, I reset timerA and I reconfigure the interrupt handler to now interrupt on the trailing edge. So when the next interrupt comes in on the trailing edge, I then remember the time and reset to again interrupt on the next rising edge.
The interrupt handling (not my code) for WKP1-5 is identical to the above, except you need to change which pit in PMR5 you set (1-5), and likewise the bit you set or clear for rising or falling edge in IEGR2 will also change(1-5).
If however you wish to process one or all of the WKP interrupts in assembler than you will find some additional changes. In particular there is only one Interrupt Vector for all six of these external interrupt pins. WKPINT. It is up to the Interrupt handler to determine which of these pins caused the interrupt to occur. Nathan(AcidTech) provided an example of this in the thread �HSERVO�, where he shows how the TV Brat WKP3 interrupt code could be handled in assembler. His code relies on his experimental version of HSERVO which maintains a timer tick for you. The initialization code is pretty close to the same, except you ENABLE WKPINT.
When a WKP interrupt happens the corresponding bit (0-5) will be set in the register IWPR. Before returning from your handling of the WKPINT you will need to clear this bit in IWPR. There are several ways in assembly language to determine which bit was set. My example loads the IWPR into a work register and then logically shifting this register right and checking to see if the carry bit is set (see H8 manual for details). You could just as easily do a bit test instruction to see if a particular bit is set.
I will include here a work in progress that shows my handling of WKP0-5 in assembly language. This version also relies on his timer tick, but could easily be converted back to using a timerA interrupt and maintaining my own timer. The code appears to work, but I make no promises. Remember this is a work in progress.
;---------------------------- Data --------------------------------------------------------------
lLastPulse var long
alPulseTimingTICKS var long(6)
fPulseTimingsBusy var byte
fPulseDataValid var byte
fProcessingPulses var byte
;----------------------------- Init System components ---------------------------------------
PMR5.bit0 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit1 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit2 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit3 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit4 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit5 = 1 ;enables pin as WKP interrupt instead of normal I/O
IEGR2.bit0 = 1 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit1 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit2 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit3 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit4 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit5 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
ENABLE WKPINT
�
'============================================================================================
'
' Handle interrupts for each of the channels
' It appears like the channels happen sequentially. So we will only watch the rising edge of Ch0 and then record
' the times for falling edges for all 6 channels. After channel ch6 completes we will trigger main program to do any other
' processing that may be needed.
BEGINASMSUB
handle_WKP
;save workspace registers
push.l er0
push.l er1
push.l er2
;Get current timer tick - Try to handle any timer wrap around that may happen
mov.w @_TIMERWTICK:16,e0
mov.w @TCNT,r0
btst #7,@TIERW:8
beq _DONTINC:8
inc.w #1,e0
_DONTINC:
; we want to enable interrupts as quickly as possible. So let�s get the interrupt information, clear it as quickly as possible and then
; reenable interrupts.
mov.b @IWPR:8,r1l
; clear the interrupt flag as quickly as possible and enable interrupts.
mov.b r1l, r1h
and.b #0xc0,r1h
mov.b r1h,@IWPR:8
andc #0x7f,ccr ; clear the I bit
;Figure out which WKP happened
; Now see if it is WKP0
; Use Shift Right to find which interupt happened.
shlr.b r1l
bcc _NOTWKP0:8
mov.w #0, e1
bra _WKPKNOWN:8
_NOTWKP0:
; we only want to process and of the other WKPs if we processed the rising edge of WKP0
mov.b @FPULSETIMINGSBUSY:16, r1h
;or.b r1h,r1h ; mov sets zero state
beq _WKPEXIT:16
shlr.b r1l
bcc _NOTWKP1:8
mov.w #1*4,e1 ;might as well have e1 with the offset from the array instead of just the index
bra _WKPKNOWN:8
_NOTWKP1:
shlr.b r1l
bcc _NOTWKP2:8
mov.w #2*4,e1
bra _WKPKNOWN:8
_NOTWKP2:
shlr.b r1l
bcc _NOTWKP3:8
mov.w #3*4,e1
bra _WKPKNOWN:8
_NOTWKP3:
shlr.b r1l
bcc _NOTWKP4:8
mov.w #4*4,e1
bra _WKPKNOWN:8
_NOTWKP4:
; we will assume it must be 5 if we got to here!
mov.w #5*4,e1
xor.b r1l, r1l
mov.b r1l,@FPULSETIMINGSBUSY:16 ; Clear the PULSE PROCESSING Busy
mov.b #0xff, r1l
mov.b r1l,@FPULSEDATAVALID:16 ; Tell the main function it now has valid data
_WKPKNOWN
; OK, we now have register E01 with which WKP happend and E00 with
mov.b @FPROCESSINGPULSES, r1l ; If the main code is processing the input then don't do anything
bne _WKPEXIT
; processing of WKP0 is special as we need to check if we are doing rising edge or not.
cmp.w #0,e1
bne _PH2_NOTWKP0:8
btst.b #0,@IEGR2:8
beq _PH2_WKP0_FALL_EDGE:8
bclr.b #0,@IEGR2:8 ; set to interrupt on falling edge
mov.l er0, @LLASTPULSE ; Ok save away the starting pulse time
; Our 32 bit timer may roll over during some of this code. As a hack we might simply ignore the
; cycle of pulses and wait for an easier one to handle.
cmp.w #0xFFE2,e0 ; I think this will be a safe starting point.
ble _WKPEXIT ;
mov.b #0xff,r1l
; mov.b r1l,@FPULSETIMINGSBUSY:16 ; SET PULSE PROCESSING Busy
bra _WKPEXIT
_PH2_WKP0_FALL_EDGE:
bset.b #0,@IEGR2:8 ; Set to interrupt on Rising Edge again
; fall through and process like other interrupts
_PH2_NOTWKP0:
bra _WKPEXIT ; BUGBUG:DEBUG
; calculate the aiPulseTimings for this array value.
mov.w e1,r1
xor.w e1,e1
add.w #ALPULSETIMINGTICKS, r1
mov.l @LLASTPULSE,er2 ; get the last pulse time
mov.l er0, @LLASTPULSE ; save away this pulse time
sub.l er2, er0 ; Calculate the time difference
mov.l er0,@er1 ; Save away the time difference in the array
; Fall through
;restore workspace registers
_WKPEXIT
pop.l er2
pop.l er1
pop.l er0
rte
ENDASMSUB
That is all for now!