Interrupt on timer

#1
For my project I need to measure the 'bouncetime' of a relay. My plan is to use the PULSIN-command which generates a timeout when a pulse is too long (the relay stopped bouncing). I will use this occurence to read out a second timer, that was started along with the PULSIN, to calculate te bouncetime.

Now, I realize that this second timer could exceed its full range before PULSIN generates a timeout. My thought was to use SETINFLAGS to monitor timer overflow and increase a variable that counts the number of overflows that occurred before PULSIN timed out.

However, the description of the command SETINTFLAGS states that only 1 input pattern is allowed at any time and I presume that PULSIN already uses timeroverflow interrupt. That means I couldn't use SETINFLAGS to monitor a second timer.

Is there a way to get around this problem? The bouncetime I'm going to measure is less then 15 msec and I'm using picaxe20x2.
 

hippy

Technical Support
Staff member
#2
I think the first thing you will need to do is define what 'bounce time' is, what characteristics it has, and how it would be measured and determined.

And maybe exactly what period you are measuring ...
Code:
                   ___________________________
Activation    : __|                           |___
                         _   _   ______________
Relay closure : ________| |_| |_|              |__

                  |<-A->|<--B-->|
                  |<-C--------->|
It should be possible to determine any or all three of those time periods in software by polling or by using hardware.

How accurately you need to measure the periods might also be important.
 

hippy

Technical Support
Staff member
#3
Here's a proof of concept using polling which can be run in the PE6 simulator. Turn C.3 on to indicate contact closure once C.2 lights, on then off then leave on to simulate contact bounce. Turn C.3 off after the report appears to test again.

It's not perfect because it uses 'loop counts' rather than actual time and the time through the loop isn't consistent in all circumstances.

Code:
#Picaxe 08M2
#Terminal 4800

Symbol RELAY_OUT   = C.2
Symbol CONTACT_IN  = pinC.3

Symbol elapsedTime = w1
Symbol firstTime   = w2
Symbol bounceTime  = w3
Symbol highTime    = w4
Symbol bounces     = w5

Symbol STABLE      = 10

Do
  Gosub DeactivateRelay
  Pause 5000
  Gosub ActivateRelay
  Gosub ReportResults
Loop

DeactivateRelay:
  Low RELAY_OUT
  Do : Loop Until CONTACT_IN = 0
  Return

ActivateRelay:
  elapsedTime = 0
  firstTime = 0
  highTime = 0
  bounces = 0
  High RELAY_OUT
  Do
    elapsedTime = elapsedTime + 1
    If CONTACT_IN = 0 Then
      highTime = 0
    Else
      If highTime = 0 Then
        If firstTime = 0 Then
          firstTime = elapsedTime
        End If
        bounces = bounces + 1
      End If
      highTime = highTime + 1
    End If
  Loop While highTime < STABLE
  elapsedTime = elapsedTime - STABLE + 1
  bounceTime = elapsedTime - firstTime
  bounces = bounces - 1
  Return

ReportResults:
  Select Case bounces
    case 0
      SerTxd( "  __________________",                                 CR, LF )
      SerTxd( "_|    ______________",                                 CR, LF )
      SerTxd( "_____|              ",                                 CR, LF )
      SerTxd( " |<->|              ", #firstTime,                     CR, LF )
    Case 1
      SerTxd( "  __________________",                                 CR, LF )
      SerTxd( "_|    _   __________",                                 CR, LF )
      SerTxd( "_____| |_|          ",                                 CR, LF )
      SerTxd( " |<->|              ", #firstTime,                     CR, LF )
      SerTxd( " |<----->|          ", #elapsedTime,                   CR, LF )
      SerTxd( "     |<->|          ", #bounceTime, " (",#bounces,")", CR, LF )
    Case 2
      SerTxd( "  __________________",                                 CR, LF )
      SerTxd( "_|    _   _   ______",                                 CR, LF )
      SerTxd( "_____| |_| |_|      ",                                 CR, LF )
      SerTxd( " |<->|              ", #firstTime,                     CR, LF )
      SerTxd( " |<--------->|      ", #elapsedTime,                   CR, LF )
      SerTxd( "     |<----->|      ", #bounceTime, " (",#bounces,")", CR, LF )
    Case 3
      SerTxd( "  __________________",                                 CR, LF )
      SerTxd( "_|    _   _   _   __",                                 CR, LF )
      SerTxd( "_____| |_| |_| |_|  ",                                 CR, LF )
      SerTxd( " |<->|              ", #firstTime,                     CR, LF )
      SerTxd( " |<------------->|  ", #elapsedTime,                   CR, LF )
      SerTxd( "     |<--------->|  ", #bounceTime, " (",#bounces,")", CR, LF )
    Else
      SerTxd( "  __________________",                                 CR, LF )
      SerTxd( "_|    _       _   __",                                 CR, LF )
      SerTxd( "_____| |_..._| |_|  ",                                 CR, LF )
      SerTxd( " |<->|              ", #firstTime,                     CR, LF )
      SerTxd( " |<------------->|  ", #elapsedTime,                   CR, LF )
      SerTxd( "     |<--------->|  ", #bounceTime, " (",#bounces,")", CR, LF )
  End Select
  Return
 

AllyCat

Senior Member
#4
Hi,

You cannot make a "direct" measurement of the bounce time, because you don't know when the bouncing has actually finished until some time later (i.e. when the contacts have not opened again). Thus you basically need a background timer/counter, preferably started (or read) when the relay coil is activated. Then you record (copy to two registers) the first time that the contacts close and the last time that they are open. To achieve the latter, the timer needs to be repeatedly copied to the register, all the time that the contacts are open (which needs dedicated hardware or a very tight program loop).

As hippy says, a timer implemented by a program loop will have inaccuracies (and also calibration may be an issue) so I suggest an alternative: I don't use X2s myself, but they probably have a similar architecture to M2s. With an M2, "Timer 1" is used by the Operating System to time the 20 ms periods between Servo Pulses (and also as a prescaler for the "time" variable). It counts in microseconds and on overflow (after 65535) the OS effectively resets it to 45535 (i.e. -20,000) ready for the next cycle. Your program can PEEKSFR this value (the High byte might be sufficient) to measure the elapsed time. If the program is not using Servos you can probably reset Timer 1 with a POKESFR 0 when the coil is activated, which increases the timing period to 65 ms. If full 16 bit resolution is required, you may need to read the High byte twice (either side of the Low byte) to recognise where the carry from Low to High occurs.

Another potential problem is that any polled (program loop) system may miss any very short pulses of bounce, whilst a hardware latch can be triggered by microsecond or even nanosecond pulses. A possible solution to this is to read (PEEKSFR) the Interrupt Latch register for the input pin, which is active even if the interrupt itself is not being used.

Cheers, Alan.
 
#5
Thanks for the replies.
@AllyCat: I know that a "direct" measurement isn't possible. The description of PULSIN says that at 64MHz, a timeout will occur after 40.96 milliseconds. This is the value I have to subtract from the "testresult". An accuracy of 100 till 300 nsec is also more than sufficient since the testbox will be used "in the field" where I can't use my storagescope.
The PULSIN-command looked interresting because I had the impression that it worked like some "interrupt polling routine" that ends after 40.96 msec of inactivity on the inputpin. And why would I "invent" a routine when it already exists :)
The program of hippy looks fairly similar to what I came up with a couple of weeks ago. But I' m that kind of people that isn't easily satisfied with a program they write. Someone posted here a memorymap of the Picaxe20x2 and it took me back to the old days of commodore 64 and I asked myself if it is possible to Poke and peek a register to obtain the same or better result. It seemed straightforward but as said in my first post a ran into problems when I realized that a timer could overflow.
I could be wrong (and I probably am wrong) to say that a timerregister will increment by 1 every 15.6 nsec at a frequency of 64 MHz. If you reset a timerregister to 0, it will overflow after 65535*15.6nsec= 1.024 µsec. Maybe I should abandon the idea to use PULSIN to detect when the relay is stable and use the proposition of hippy. Than I can use the timer overflow interrupts to increase the variable overflow_count. 1 overflowcount is then around 1msec. To increase the accuracy I can peek the timerregister and take that in account to calculate the bouncetime.
 
#6
Hi,

I think you may be misunderstanding the PULSIN command. It is a piece of embedded "machine code" (assembler) which polls the specified input pin in a tight loop of 10 instruction cycles (= 40 clock cycles). Thus its timing resolution (at 64 MHz) is about 625 ns. If the input level does not change, or it does not restore, within a count of 65535 then it returns a "timeout " value,of zero. Note that it is a "blocking" command so no other processing happens while it is being executed (I think system interrupts might be disabled also).

Remember that PICaxe Basic is an interpreted language, which is quite slow; a single, simple command takes about 25 us to execute (at 64 MHz) so the fastest software counting/timing loop will take some hundreds of us. I believe the fastest on-chip (hardware) counters run at the instruction cycle rate (clock / 4) but "Timer 1" may have a prescaler included (by the Operating system) to maintain the 20 ms overflow rate for the Servo pulses.

Why do you want / need to measure the bounce time and what is the minimum (individual) pulse (bounce) width that you need to detect? That may help us to determine the best software approach.

Cheers, Alan.
 

hippy

Technical Support
Staff member
#7
The fastest rate an internal on-chip timer can run at is one quarter of its operating frequency. So at 64MHz it can run at 16MHz, and increment every 62.5ns, which will overflow every 4 us or so. A PICAXE cannot keep up with that but there's no need to run it or the PICAXE that fast.

As long as the PICAXE can keep up with the timer it is possible to keep track of overflows so its range can be extended beyond 16-bits, to days in fact and still with 1us or better resolution.

And it should be possible to get a 'time stamp' of when the relay contact input has gone high or low. So it should be possible to modify the earlier code so it's not counting loops but taking time stamps. Something like -
Code:
MeasureBounce:
  Gosub StartTimer
  High RELAY_OUT
  Do
    Do
      Gosub TrackTimer
    Loop Until RELAY_IN = 1
    If firstTime = 0 Then
      firstTime = timeStamp
    End If
    bounces = bounces + 1
    Do
      Gosub TrackTimer
      If timeStamp >= TOO_LONG Then Measured
    Loop Until RELAY_IN = 0
  Loop

Measured:
  elapsedTime = timeStamp
  ... twiddle measurements ...
  Return
That needs some expansion to handle the fact that 'timeStamp' is going to be a multi word variable entity, and the 'StartTimer' and 'TrackTimer' routines are better being in-line macro routines. And the code can be doubled-up for better 'firstTime' detection.

There will be some delays between seeing a pin go high or low to when the timestamp is grabbed but we're probably talking microseconds which I doubt would have any real bearing on things.

And note that a 20X2 runs from an internal crystal oscillator so, while it's fairly accurate when I've looked at it in the short term, that could have an impact.

But all that can only really be assessed through knowing what accuracy and resolution of measurement is required and what bounce characteristics are anticipated.
 
#8
Code:
   Do
      Gosub TrackTimer
      If timeStamp >= TOO_LONG Then Measured
    Loop Until RELAY_IN = 0
  Loop

Measured:
  elapsedTime = timeStamp
  ... twiddle measurements ...
  Return
GOSUB Measured ?
 
#10
Hi,
at 64MHz it can run at 16MHz, and increment every 62.5ns, which will overflow every 4 us
Isn't that 4 ms (for a 16-bit counter) ?

But bear in mind that "contact bounce" is a mechanical phenomenon which will vary considerably with physical parameters such as the relay coil voltage, current and temperature etc., so I don't see any need for great absolute timing precision. Making multiple (automated) measurements might be more relevant than absolute accuracy. However, I am concerned that a basic polling system might not "see" very narrow pulses* (that might still trigger any subsequent electronic latches).

* I was originally trained in the telecommunications industry (aka "Post Office" Telephone Exchanges) which employed very many relays. One of the things that particularly interested me was learning that relay contacts (at least then) were designed to slide sideways as they closed or opened, to give a self-cleaning action. That means that if the contacts are dirty or pitted there is a possibility that the interruption of current flow due to so-called contact bounce may occur at a very high frequency, not just at the mechanical resonance of the moving contact.

Cheers, Alan.
 

hippy

Technical Support
Staff member
#11
Isn't that 4 ms (for a 16-bit counter) ?
You're right on that, I'm out by a factor of 1,000 : 65536 * 62.5 ns = 4.096 ms

So, for a PICAXE running at 64MHz, with the basic token execution I recall being about 17us, it should have plenty of time to handle overflow and deliver maximum resolution.

More usefully though, using the 8:1 pre-scaler would give a 32ms overflow, which I believe should be longer than closure and bounce times, meaning a single word variable could be used for timing with 0.5us resolution, and all without worrying about overflows. And that reduces latency errors in reading the timer.

The PICAXE can do a test and programmatically change pre-scaler and adjust operating frequency to get the highest resolution result.

We can grab the timestamp every rising edge, and that gives us the final rising edge time, so no need to have an elapsed time and subtract a magic number to get that, and the timer overflow can also be to determine end of test.

In fact, grab and store all timestamps and we could then time each bounce pulse -
Code:
    _____________________
___|123456789-123456789-1    
         _   _   _   ____
________| |_| |_| |_|
        5   9   13  17
On the short-pulse issue I'm not sure how best to deal with that. Using the latched changed bit would catch some of those which happened which would be missed if purely polling, but I would guess not all. That really comes down to how quickly we're checking those and clearing the seen bit..

Probably best to use Timer 3 driven from the input so it counts the pulses. That should be good for up to 16MHz.

That would have to be clocked from the Download Serial In pin but it shouldn't be too problematic to have the relay contacts between that and V+, using DISCONNECT before the test.

I think we may have cracked it. All that would be left is to turn the results into meaning numbers.
 

hippy

Technical Support
Staff member
#12
Converting a number of 62.5ns periods into a total ...
Code:
Symbol number = w0

Symbol ms     = w1
Symbol ns     = w2
Symbol us     = w3
Symbol ps     = w4

number = 65535 ; How many 62.5ns periods

#Macro Add( n, addms, addus, addns, addps )
  If number >= n Then
    number = number - n
    ms = ms + addms
    us = us + addus
    ns = ns + addns
    ps = ps + addps
  End If
#EndMacro

SerTxd( #number, " * 62.5 ns = " )

Do While number > 0
  Add( 10000, 000, 625, 000, 000 )
  Add(  1000, 000, 062, 500, 000 )
  Add(   100, 000, 006, 250, 000 )
  Add(    10, 000, 000, 625, 000 )
  Add(     1, 000, 000, 062, 500 )
  ns = ps / 1000 + ns : ps = ps // 1000
  us = ns / 1000 + us : ns = ns // 1000
  ms = us / 1000 + ms : us = us // 1000
Loop

BinToAscii ms, b15,b14,b13,b12,b11 : SerTxd( b13,b12,b11 )
BinToAscii us, b15,b14,b13,b12,b11 : SerTxd( b13,b12,b11 )
BinToAscii ns, b15,b14,b13,b12,b11 : SerTxd( b13,b12,b11 )
BinToAscii ps, b15,b14,b13,b12,b11 : SerTxd( ".",b13," ns", CR, LF)
65535 * 62.5 ns = 004095937.5 ns
 
Top