Putting robotics at your service™

Free shipping on orders over $200

Processing Interrupts with a Basic Atom Pro

Print view Share :
Previous topicNext topic

Page 1 of 2 [ 22 posts ]

1, 2
User avatar
Guru ( offline )
Posts: 4921
Posted: 2008-02-21 19:41 
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:
Code:
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:
Quote:
Just so everone is clear, BEGINASMSUB and ENDASMSUB tell the compiler there is no need for the extra code to switch from processing tokens(eg basic) to running actual machine code(asm). The rules the compiler uses to add the switch code is:

1. Basic code followed by Asm code add switch to machine mode before asm code.
2. Asm code followed by Basic code add switch to token mode before Basic code


Code:
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:
Code:

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:
Code:
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:
Code:
;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.

Code:
;---------------------------- 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!


Last edited by kurte on 2008-06-13 10:59, edited 1 time in total.

User avatar
Guru ( offline )
Posts: 2956
Posted: 2008-02-21 20:11 
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

_________________
I can usually handle complexity. It's the [b][i]simple[/i][/b] things that always confound me..
Check out my [url=http://www.hybotics.me]blog[/url] now!
Open your mind, Read, Learn, Think, Apply..


User avatar
Expert ( offline )
Posts: 628
Posted: 2008-02-22 21:17 
linuxguy wrote:
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.


New ( offline )
Posts: 6
Posted: 2008-04-17 23:50 
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?


User avatar
Guru ( offline )
Posts: 4921
Posted: 2008-04-18 10:11 
robotnerd007 wrote:
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?

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.

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

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


User avatar
Guru ( offline )
Posts: 4921
Posted: 2008-05-20 10:11 
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


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

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


User avatar
Guru ( offline )
Posts: 4921
Posted: 2008-10-05 12:16 
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.

Code:

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


User avatar
Guru ( offline )
Posts: 4921
Posted: 2008-10-05 22:13 
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.
Code:

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


User avatar
Expert ( offline )
Posts: 628
Posted: 2008-10-31 18:54 
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.


User avatar
Guru ( offline )
Posts: 4921
Posted: 2009-06-08 10:02 
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:
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


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:
Code:
... (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


Rookie ( offline )
Posts: 10
Posted: 2009-06-15 10:07 
Actually this is really going to be useful for me so please keep posting ;-)

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 :D

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


User avatar
Guru ( offline )
Posts: 4127
Posted: 2009-06-15 13:29 
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

shsanjp wrote:
<snip> 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.

_________________
Visit:
http://groups.yahoo.com/group/SherlineCNC/
http://tech.groups.yahoo.com/group/HexapodRobotIK/


Rookie ( offline )
Posts: 10
Posted: 2009-06-15 20:56 
Yes that's what I thought too. Always keep the code inside the interupt handler to a minimum :)

Thanks for the confirmation ;-)


Rookie ( offline )
Posts: 28
Posted: 2010-02-25 17:42 
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
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
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


User avatar
Guru ( offline )
Posts: 4921
Posted: 2010-02-25 18:59 
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
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
   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


1, 2

All times are UTC - 5 hours [ DST ]. It is currently 2014-12-17 22:22
Feedback Form
Feedback Form