Processing Interrupts with a Basic Atom Pro

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!

Very nice, kurte!

One use of interrupts I can think of is using the capture pins for reading encoders attached to wheels or motor shafts, such as the QME-01 that works with the GHM-04 motor. This is what I have on W.A.L.T.E.R., but I have not installed the encoders yet. I don’t know if I want to attempt this with the Atom PRO though.

Another use for an external interrupt would be to catch the interrupt on change from a chip such as the Microchip MCP23017 I2C I/O expander.

I am considering reviving both of these past projects of mine. I am not too thrilled at possibly having to get into assembly code though.

8-Dale

You don’t have to use assembly to use interrupts. It’s just potentially a lot faster is all.

Quick question. The sample interrupt code provided here increments a variable every 256 clock cycles. I understand that the h8 that that basic atom is based on runs at 16mhz. What is the actual speed of the atom pro I seem to recall reading that the extra code loaded on the atom causes it to run 4 times as slow. I am trying to convert the timer A value to milliseconds. So this would mean that the timer increments every .000064 seconds. Is this correct?

Also how would i change the prescaler value so the interrupt ran something like 4 times as slow?

The part about the speed of the Pro is probably better for AcidTech to answer. But as for the timer interrupt. It is based directly on the H8s processor clock. You need 16 clock cycles for a microsecond, and we are scalled to only increment TCA every 256 clock cycles. In the sample code, we have an interrupt set for every time TCA overflows. In the interrupt handler we increment a variable timer. Since TCA is only 8 bits in length it overflows once every 256 increments of TCA. So our variable timer is incremented every 256*256 clock cycles.

To figure this out, you need to look the the H8 hardware manual. In there you would find setting to 5 would set the prescaller to 512 or setting it to 6 would set it to 2048. So you have a choice of either 2 times as slow or 8 times as slow.

Good Luck

I am not sure how many other people are making use of the information about interrupts on the Atom Pro. But just in case someone else may find this information of use… I wonder if this information is best here or in the Wiki?

As part of my Brat project where I was trying to improve my communications with the PC using the DB9 serial Port(S_IN, S_OUT) I found:

IRQ0
The IO pin on the H8 that is associated with IRQ0 is P14 and on the Atom Pro, this is connected to both S_IN and S_OUT through a half duplex diode circuit. You can use this to your advantage if you would like your software on the Atom Pro to be able to detect when your PC program is trying to communicate with it, without having to leave your Pro code sitting in a SERIN. There are maybe better ways to implement this, but I will describe what has worked for me.

On the Atom Pro, I enable the interrupt on the trailing edge and implement a simple interrupt handler that reverts the I/O pin back to a normal IO pin and sets a state variable. This code looks something like:

[code]ONINTERRUPT IRQ0INT, HANDLE_IRQ0

fHostcommandPending var byte

; Initialize communications with PC
; lets setup to get interrupted if we get a rising edge on IRQ0. We can probably have the PC send a 0xff character
; which will show up as one spike up and then all zeros after as a prelude to our message header…
;
fHostcommandPending = 0
PMR1.bit4 = 1 ; enable pin to IRQ0 interrupt instead of normal I/O
IEGR1.bit0 = 0 ; Interrupt IRQ0 on falling edge
enable irq0
ienr1.bit0 = 1

HANDLE_IRQ0:
toggle p5 ; not needed, but sets led on ABB to let me know…
; disable our own interrupt.
PMR1.bit4 = 0 ; restore pin n to normal IO
fHostCommandPending = 1 ; let the main loop know that we have a packet pending
resume ; and return
[/code]

My Main loop in the Atom Pro simply tests for fHostCommandPending and calls off to a function to process the command. Something like:


main 
	' Check to see if the host PC has signalled us to process a host command
	if fHostCommandPending then
		gosub ProcessHostCommand
	endif
...
        goto main

; My processing of the code loos something like:
ProcessHostCommand:
	
	serin s_in, i2400, 1000, GCHTimeout, [str bPacketHeader\4]
	bcmd = bPacketHeader(0)
	wSeqnum.lowbyte = bPacketHeader(1)
	wSeqnum.highbyte = bPacketHeader(2)
	cbExtra = bPacketHeader(3)
	' do some simple validation of the input

	
	if ((bCmd & 0xf0) <> 0xf0) or (cbExtra >= 30) then GCHPPacketError
	
	' Now try to read in the rest of the packet, two cases, one with extra data and one without...
	if cbExtra <> 0 then
		serin s_in, i2400, 1000, GCHTimeoutExtra, [str bBuffer\cbExtra, bChksum]
	else
		serin s_in, i2400, 1000, GCHTimeoutExtra, [bchksum]
	endif
; do the processing of the command
; at end fall through... 

EndProcessHostCommand:

	; now we will reenable us as an interrupt and resume
	fHostCommandPending = 0
	PMR1.bit4 = 1 ; restore pin interrupt state
	ienr1.bit0 = 1
	return

On the PC side (VB), I first simply send a single character of hex 0xff. This turns out that at the S_IN pin as a simply pulse for the start bit. This works great to trigger the interrupt. I then have the VB code wait a bit of time for the Atom Pro to get to a point to be in a SERIN and then I output my packet of information and wait for the Atom Pro to send back an Acknowlegement packet. More handshaking could be added here, but my current code on the PC looks something like:

[code] Public Function FSendPacketandCheckforAck(ByRef com1 As IO.Ports.SerialPort, ByRef ab As Byte(), ByVal cbPacket As Short, ByVal iRetryCnt As Short) As Boolean
Dim iLoop As Short
Dim abT(1) As Byte
abT(0) = &HFF

    For iLoop = 0 To iRetryCnt
        Try
            com1.DiscardInBuffer()
            ' first output an &hff to try to get the brats attention...
            com1.Write(abT, 0, 1)
            System.Threading.Thread.Sleep(100) ' give the brat some time to get to the handler

            ' I will output this in two parts.  First the fixed part that the brat will try to read as one.
            com1.Write(ab, 0, 4) ' write out cmd, seql, seqh, cbExtra
            '                System.Threading.Thread.Sleep(30)
            com1.Write(ab, 4, cbPacket - 4)
            com1.BaseStream.Flush()

            If FCheckforAck(com1, ab(0), cbPacket, (ab(2) << 8) + ab(1)) Then
                Return True        ' we succeeded.
                System.Threading.Thread.Sleep(250) ' If an error happens wait a bit before we retry

            End If

        Catch ex As Exception

        End Try
        ' now lets wait for a hopefull ACK!!!
    Next

    Return False

End Function[/code]

Again I am not sure how many people will find this information useful. But while helping someone on the BasicMicro forum I learned more about using WTIMER. In particular about using it for capturing input signals such as PWM.

WTIMER - Input Capture

The WTimer has several capabilities, which includes the capability to interact with 4 IO pins (FTIOA-FTIOB) which on the Atom Pro is on IO pins P9-P12, these IO pins can be used for the output of wave forms or for the capturing of input signals. I will not fully describe all of these capabilities as they are described in Chapter 12 of the Renesas 3694 document.

One use of these capabilities is the ability to use the hardware to capture the pulse width of a PWM signal. You can configure the IO pins to capture the timing for the rising edge, falling edge or both. You can choose to poll the state or you can have the system cause an interrupt when a signal is detected. There are 4 registers GRA-GRD which when the signal is detected for the corresponding IO Pin FTIOA-FTIOB, will have the Wtimer’s counter stored in them by the hardware. There is also a capability to for the values for the first two Pins, where when configured, the previous value of GRA would be transferred to GRC for FTIOA(P9) or GRB will be transferred to GRD for FTIOB(P10).

Here is a little test program that shows how you might capture the input signal on P10.


TimerSave 		var word
DeltaTime		var	word
DeltaTimeLast	var	word


; Some quick and dirty inits...
TimerSave = 0
DeltaTimeLast = 0

PCR8 = 0		; Make sure all port 8 pins are input...
TCRW = 0                  ;clears TCNT and sets the timer to inc every clock cycle 
TMRW = 0x80 ;starts the timer counting 
TIOR0=0x70               ; Input capture on B and capture both rising and falling.

;TCRW= ??? - May need to set if you want to change the timer to anything but clock
;TMRW= 0xA0    ; Start counter, Have GRD buffer GRB
;TIERW=0x8A      ; Not sure here, this sets interrupts for overflow?  Input B and Input D - ???


ONINTERRUPT TIMERWINT_IMIEB,HandleFTIOB
 
ENABLE TIMERWINT_IMIEB  ;Enable the input capture 

main 
	if (DeltaTime > (DeltaTimeLast+10)) or (DeltaTimeLast > (DeltaTime + 10)) then
		DeltaTimeLast = DeltaTime;
		Serout S_OUT, i9600, [dec DeltaTimeLast,13]
		pause 500
	endif
	goto main 


handleFTIOB
	; Check the state of the IO pin to see if this was the rising or falling.
	; If rising save away the captured time.  If falling then calculate the
	; pulse width
	if IN10 <> 0 then
		; Rising edge
		TimerSave = GRB
	else
		DeltaTime = GRB - TimerSave
	endif
	
	resume

In the above program, I store away the values at the CPU clock speed. You may wish to prescale WTIMER to maybe Clock/8 or the like. This can be done by setting the appropriate value in TCRW. Also this sample program does not take into account that WTIMER may roll over during the input wave, so when you are calculating the DeltaTime you would need to verify that GRB is greater than your saved value. If it is not it would not be hard to calculate the value.

Again I want to reiterate that WTimer is used by HSERVO and may be used for other purposes in the future, so good luck.

Kurt

Again probably not of great interest, but I slight update to the above WTIMER program to scale it for input from on of my Laser 6 PWM channels, where the center will display near 1500.


TimerSave 		var word
DeltaTime		var	word
DeltaTimeLast	var	word


; Some quick and dirty inits...
TimerSave = 0
DeltaTimeLast = 0

PCR8 = 0			; Make sure all port 8 pins are input...
;TCRW = 0 			;clears TCNT and sets the timer to inc every clock cycle 
TCRW = 0x0			;clears TCNT and sets the timer to inc clock/8
TMRW = 0x80 		;starts the timer counting 
TIOR0=0x70     		; Input capture on B and capture both rising and falling.

;TCRW= ??? - May need to set if you want to change the timer to anything but clock
;TMRW= 0xA0    ; Start counter, Have GRD buffer GRB
;TIERW=0x8A      ; Not sure here, this sets interrupts for overflow?  Input B and Input D - ???


ONINTERRUPT TIMERWINT_IMIEB,HandleFTIOB
 
ENABLE TIMERWINT_IMIEB  ;Enable the input capture 

main 
	if (DeltaTime > (DeltaTimeLast+4)) or (DeltaTimeLast > (DeltaTime + 4)) then
		DeltaTimeLast = DeltaTime;
		Serout S_OUT, i9600, [dec DeltaTimeLast,13]
		pause 500
	endif
	goto main 

TSL var long
GRBL	var long
handleFTIOB
	; Check the state of the IO pin to see if this was the rising or falling.
	; If rising save away the captured time.  If falling then calculate the
	; pulse width
	low p12
	if IN10 <> 0 then
		; Rising edge
		TimerSave = GRB
		toggle p13
	else
		TSL = TimerSave
		GRBL = GRB
		if GRBL < TimerSave then
			GRBL = GRBL + 0x10000
		endif
		DeltaTime = (GRBL - TSL)/16
		toggle p14
	endif
	
	resume

Kurt

All good stuff, Kurte. I’d never have time to delve into this as much as you so I just wanted to let you know I appriciate the work.

Recently I saw a cleaner basic function for handling an encoder, which got rid of several branches by simply testing the second channels value versus the edge we are interrupting on and if they are the same we are rotating one direction, if they are different we are rotating the opposit direction. (So an XOR should handle it). So I thought I would try it in assembly language, with two IO pins P8 which IRQ1 and P10 (skipping P9 as it is our sound output…). I think I have it working now:

The basic init code:

SButtonCounter var sword
ONASMINTERRUPT IRQ1INT, HANDLE_IRQ1 

input p8		; just to be sure...
PMR1.bit5 = 1 ; enable pin to IRQ1 interrupt instead of normal I/O 
IEGR1.bit1 = 1 ; Interrupt IRQ1 on rising edge 
ENABLE IRQ1INT 

; Also make sure pin 10 is setup for input
input p10
SButtonCounter = 0 ; make sre we initialize it to zero

The assembly language handler:

[code]BEGINASMSUB
HANDLE_IRQ1
push.w r1 ; first save away R1 as we will mess with it.
bclr #1,@IRR1:8 ; clear the IRQ1 bit in the interrupt pending mask
andc #0x7f,ccr ; allow other interrupts to happen
; bset.b #5,@PCR8:8 ; make sure set to output, basic should have done earlier!
; bnot.b #5, @PDR8:8 ; Ok lets try setting one LED ON to debug

; make sure P10 is in input mode. - Should have been done earlier in basic…
; mov.b @(PDR8-0xd0+0x70), r0l ; get the current value for PCRx byte from the shadow location
; bclr.b #2, r0l ; make sure P82 is cleared
; mov.b r0l, @(PDR8-0xd0+0x70) ; update the shadow location
; mov.b r0l, @PCR8 ; update the actual direction

mov.w	@SBUTTONCOUNTER:16,r1	; Get our current count 

; get the state of Pin 10->P82, will get directly should probably setup more general...
bld.b	#2, @PDR8:8				; Get the state of Pin10 into carry
bxor.b	#1,@IEGR1:8				; XOR it with which edge we are interrupting on...
bcc		_HI1EQ:8				; Edge is equal to Pin10

dec.w	#1,r1					; edges different decrement our count
jmp		_HI1UPDC:8				; go to save our updated count

_HI1EQ:
inc.w #1,r1 ; edge is equal to increment our count

_HI1UPDC:
mov.w r1,@SBUTTONCOUNTER:16
bnot.b #1,@IEGR1:8 ; And update which edge we are looking at
pop.w r1

rte 

ENDASMSUB
[/code]

Note this code is only doing a 16 bit counter. It would be trivial to change to a 32 bit counter, by changing all of the references to the 16 bit register r1 to the 32 bit register er1 and changing the instuction from a .w to a .l
(I hope that makes sense).

FYI - The Basic code would looks something like:

... (init code would look the same except ONINTERRUPT instead)
ONASMINTERRUPT IRQ1INT, HANDLE_IRQ1 
...

HANDLE_IRQ1:
    input BSButtonPin
    BSButton = IN10
    IF (IEGR2.bit0=BSButton) THEN
        SButtonCounter=SButtonCounter+1 ;CW
    ELSE
        SButtonCounter=SButtonCounter-1 ;CCW
    ENDIF

    IEGR1.bit1 = IEGR1.bit1^1 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.

  resume

Kurt

Actually this is really going to be useful for me so please keep posting :wink:

I am going to have my 4WD driven by a small Vaio pc sitting on the back of it.

And I intend to use the serial communication between the BotBoardII and the PC of course.

In your PC side basic code you are doing a Sleep(100), I wonder if by adding some small synchronization it would not be more robust.
A simple idea coming to mind would be to send 1 byte from the board as soon as it is ready to receive the packet.
On the PC side, having just the read of 1 byte would replace the Sleep and you could even check that the answer is correct.

Well at least I hope to use that variant (C code in my case, but similar issue).

I am tempted to include a full ring buffer implementation into the interrupt but it might take too long to process although the code is quite simple.

Anyway keep up the good work, this is really helping beginners like me :smiley:

I might post my code once I got it running to contribute a bit too.

The maintenance of the ring buffer shouldn’t be done in the interrupt, just set a flag that a character is ready, and handle the buffer in a background task.

Alan KM6VV

Yes that’s what I thought too. Always keep the code inside the interupt handler to a minimum :slight_smile:

Thanks for the confirmation :wink:

I am using Basic Micro Studio 1.0.0.15. I am trying to get the interrupt handlers to execute using a Basic Atom Pro.
I have tried using TimerA with a Pro28 and TimerB1 with a Pro40. The issue is that the interrupt bit is set indicating an interrupt occurred, but the interrupt handler does not seem to execute.
Using the code below, I can see that the timer counter (TCA for Pro28 or TCB1 for Pro40) is advancing and overflowing. The overflow bit gets set. The lTimer variable never gets set in the interrupt handler so the variable lTimer remains 0.

Thanks for any ideas.
-Bob

Basic Atom Pro28 code

lTimer var long
main
	pause 2000			; wait for connect to S_OUT port
	lTimer = 0			; clear the timer variable
	; define the interrupt handler
	ONINTERRUPT TIMERAINT, IntHandler
	; enable the interrupt
	ENABLE TIMERAINT
	IRR1.bit6 = 0		; clear the interrupt bit
mainloop
	if (lTimer <> 0) then
		serout S_OUT, I9600, "lTimer: ", dec lTimer, 13]
	endif
	serout S_OUT, I9600, "TCA: ", dec TCA, 13]
	; if interrupt, then clear the interrupt bit
	if (IRR1.bit6 = 1) then
		serout S_OUT, I9600, "timer interrupt", 13]
		IRR1.bit6 = 0
	endif
	pause 10
	goto mainloop
IntHandler
	lTimer = 1
	resume

Basic Atom Pro40 code

lTimer var long
main
	pause 2000			; wait for connect to S_OUT port
	lTimer = 0			; clear the timer variable
	; define the interrupt handler
	ONINTERRUPT TIMERB1INT, IntHandler
	; enable the interrupt
	ENABLE TIMERB1INT
	IRR2.bit5 = 0		; clear the interrupt bit
mainloop
	if (lTimer <> 0) then
		serout S_OUT, I9600, "lTimer: ", dec lTimer, 13]
	endif
	serout S_OUT, I9600, "TCB1: ", dec TCB1, 13]
	; if interrupt, then clear the interrupt bit
	if (IRR2.bit5 = 1) then
		serout S_OUT, I9600, "timer interrupt", 13]
		IRR2.bit5 = 0
	endif
	pause 10
	goto mainloop
IntHandler
	lTimer = 1
	resume

In both cases I think the problem is that you may not have enabled the processing of global interrupts. That is Enable Timeraint says that you wish to process the timerA interrupt when interrupts are enabled, but also need to tell the system thatyou wish to process interrupts. This is done by a simple: enable command. Like:

Note: I don’t think you need to clear the interrupt has occurred flag, this is done automatically by the basic interrupt handler.

Kurt

lTimer var long main pause 2000 ; wait for connect to S_OUT port lTimer = 0 ; clear the timer variable ; define the interrupt handler ONINTERRUPT TIMERAINT, IntHandler ; enable the interrupt ENABLE TIMERAINT ENABLE IRR1.bit6 = 0 ; clear the interrupt bit mainloop if (lTimer <> 0) then serout S_OUT, I9600, "lTimer: ", dec lTimer, 13] endif serout S_OUT, I9600, "TCA: ", dec TCA, 13] ; if interrupt, then clear the interrupt bit if (IRR1.bit6 = 1) then serout S_OUT, I9600, "timer interrupt", 13] IRR1.bit6 = 0 endif pause 10 goto mainloop IntHandler lTimer = 1 resume

Thank you Kurt, that did it.

I seem to be having the same issue with the C code that I am using. So I have been searching for the C equivalent of the basic ENABLE (with no arguments) to enable global interrupts.

I read this comment in a post by Nathan: “manually enable the global interrupt flag(its in the CCR register and can only be set or cleared using the special CCR specific asm commands).”
so I am trying to use assembly to set the I (Interrupt Enable) bit of the Condition Code Register (CCR)

the code below compiles in a basic file, but I can’t figure out how to compile this in a C file:

asm
{
.macro _ints_enable
bset #0,@SYSF:8
andc #0x7F,ccr
.endm
}

Any ideas?

Edit: I still don’t know how to call asm routines from C, but adding this code to start.s will enable the global interrupts in a C program by clearing the interrupt enable bit in the condition code register:
andc #0x7F,ccr

-Bob

I added predefined macros for C/C++ that let you read/modify/write the ccr register in 1.0.0.20.

Thanks Nathan,

I missed the previous post.

Thanks for the macros Nathan, that’s just what I needed.
Unfortunately, I cannot run any release beyond 1.0.0.16.
Here’s the message I get from version 1.0.0.17 through 1.0.0.23:

Starting Compiler…
This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem.

I’ve created new basic and c projects but still get the same error.

-Bob

I’m going to need more info. What OS are you using(32bit or 64bit as well). Have you installed any software that is out of the ordinary? Have you had any problems with other software recently? Do you have a second PC you can test on? Are you uninstalling all the old IDEs/Studios before install the new ones? Are you using a User account or an Admin account when running the software? What virus scanning software are you using? Are you using any other security software?

At this point no one else has reported this and I can’t reproduce it. If anyone else is having this problem please post it here.

Based on this error message and other posts I’ve found online about this error message this appears to be a VC 2008 runtime problem on your PC. Our installer installs these runtimes automatically into the installation folder but there could be something wrong with your PC causing the locally installed copy to not be detected/used. Try installing them using this link which installes them for the entire PC:

microsoft.com/downloads/deta … laylang=en

One other thing. Make sure your PC has all the updates.service packs for tour OS.