Shop OBEX P1 Docs P2 Docs Learn Events
my button debounce code in SPIN using CTRA — Parallax Forums

my button debounce code in SPIN using CTRA

iammegatroniammegatron Posts: 40
edited 2018-09-03 17:38 in Propeller 1
I used posedge detection of the counter, according to "AN001", to debounce a switch. I didn't see it mentioned anywhere. So here it is
{{
  assuming positive edge trigger; add some pull-down on the input pin

                
 switch ───────┳─ P0 (Propeller)           │
               100K
             │  
        pull down to GND

}}

CON
        _clkmode = xtal1 + pll16x                                               'Standard clock mode * crystal frequency = 80 MHz
        _xinfreq = 5_000_000

VAR
  long  current_phsa_cnt
   
OBJ
    serial        : "FullDuplexSerial"
  
PUB main

  serial.Start(31, 30, %0000, 115200)               

  CTRA := constant(%01010 << 26)   'use PIN0 as input; posedge trigger                        
  FRQA := 1                        'set FRQA to add 1

  current_phsa_cnt := PHSA

  
  repeat
    waitcnt(cnt + (1 * clkfreq))             
    serial.Str(STRING("phsa count:"))  
    serial.Tx($0D)
    
    serial.Hex(PHSA, 4)
    serial.Tx($0D)
    
    if (PHSA - current_phsa_cnt) > 0
      serial.Str(STRING("triggered"))
      serial.Tx($0D)
      
      'perform your task once here
       waitcnt(cnt + clkfreq/1000 ) 'for example: takes 1ms
      'end task

      'then update
      current_phsa_cnt := PHSA
  



Please help improve this for other debounce applications.

Comments

  • So I see this as doing the debouncing really:
    waitcnt(cnt + (1 * clkfreq))
    

    Whereas the timer is just persisting that the signal changed at least once.

    If your code were to run inside a fast event loop it would trigger on every bounce that wasn't shorter
    in duration than the event loop period.

    Or have I misunderstood?
  • The key part of the "debounce" code is
    if (PHSA - current_phsa_cnt) > 0
          'perform your task once here
          'end task
    
          'then update
          current_phsa_cnt := PHSA
    

    All the switch bounces that produce a positive edge will increment PHSA by one count (because of FRQA=1).

    However because "current_phsa_cnt" doesn't get updated until your_one_time_triggered_task finishes. So the "inactive" period is the time to finish the task.

    Does that sound right?
  • Are you just trying to find a good way to debounce or are you trying to learn how to do it with a counter?

    I have always just used a few IF statements with a delay between, this is great for most cases.
    If ina[1] == 1
        waitcnt(1_000_000 + cnt)
            IF ina[1] == 1
               ' do something
    
    
  • JonnyMacJonnyMac Posts: 9,103
    edited 2018-09-04 02:24
    A couple years ago I started writing programs with a 1ms background loop. This lets me keep a free-running timer going and other low-bandwidth activities. Yesterday I coded a little project for a haunted house. The client wanted to deboucne the buttons on a prop, and force their release for a specific period after activation. Here's the code that is called from the 1ms background loop -- it's super easy and works like a charm. Examining the value of inXtimer tells me how long it's been in the current state (positive for pressed, negative for released).
    pri scan_buttons 
          
      if (ina[IN0])
        in0timer := 0 #> ++in0timer <# posx                         ' increment from 0 if pressed
      else
        in0timer := negx #> --in0timer <# 0                         ' decrement from 0 if released
    
      if (ina[IN1])
        in1timer := 0 #> ++in1timer <# posx
      else
        in1timer := negx #> --in1timer <# 0
    

  • Jon,
    would you translate that into simple Texan?

    in0timer := 0 #> ++in0timer <# posx

    the #> and <# part?
  • Well the funny spin syntax.


    #> and <# are clamping the value that it is in between both given values

    in long form this does the following:

    in0timer := in0timer + 1
    if in0timer<0 then in0timer := 0
    if in0timer>posx then in0timer := posx

    Mike
  • cavelambcavelamb Posts: 720
    edited 2018-09-05 02:07
    thank you, Mike.

    Looks like:
    Pull-up resistor switched to ground for buttons?

    negx and posx look like constants.
    Limits of how long the button is pressed, or released.

    Jon? What are those limits - normally?
    Debounce and auto-repeat, short press - long press?


  • JonnyMacJonnyMac Posts: 9,103
    edited 2018-09-05 03:10
    This...
      if (ina[IN0])
        in0timer := 0 #> ++in0timer <# posx                         ' increment from 0 if pressed
      else
        in0timer := negx #> --in0timer <# 0                         ' decrement from 0 if released
    
    ...is shorthand for:
      if (ina[IN0] == 1)
        in0timer := in0timer + 1  
        if (in0timer < 0)
          in0timer := 0
        elseif (intimer > posx)
          in0timer := posx
      else
        in0timer := in0timer - 1
        if (in0timer > 0)
          in0timer := 0
        elseif (in0timer < negx)
          in0timer := negx
    
    You can see why I prefer the first version. The #> (MIN) and <# (MAX) operators are used to limit a value. Funny thing, explaining my code revealed two flaws (of the same nature). Do you see them?
    The problem is that in0timer can never be advanced past posx or negx because the value will roll over. Is this a problem in the application? No. This code gets called every millisecond. The value of posx is 2147483648 (2^31). That's a lot of milliseconds.

    2147483648 milliseconds =
    2147483. 648 seconds =
    35791.394 minutes =
    596.523 hours =
    21.304 days

    This code lives in a Halloween prop that will be powered down every day. Still, I will fix it and send it to my friend (who is installing it in Russia this week). This is the fix:
      if (ina[IN0])
        in0timer := 0 #> ++in0timer <# constant(posx >> 1)
      else
        in0timer := constant(negx ~> 1) #> --in0timer <# 0
    
    By right-shifting the limits by one bit I cut them in half -- still WAY more range than will ever be seen in operation

    In simple terms, in0timer will be positive when the button is pressed (it's an active-high input), or negative when the button is released. In the program we want a 25ms debounce for the trigger; we know we have this when in0timer is 25 or higher -- this triggers the event. We also want to force the user to release the trigger for 500ms before being able to run again. The code watches for -500 or less in in0timer before allowing another activation. This code works here because there is no auto-repeat desired; that takes different code (yes, I've done it).
  • negx and posx are Spin constants for the maximum negative or positive value of a 32 bit 2-complement binary - long

    Mike
  • JonnyMacJonnyMac Posts: 9,103
    edited 2018-09-05 15:33
    Here's how I do button inputs that need to repeat when held down. Again, I am calling this from my 1ms "background" cog.
    pri process_trigger
    
      if ((trtimer < 0) or (ina[TRIGGER))
        ++trtimer
      else
        trtimer := 0
    
    It would generally be called from a "foreground" loop like this:
      repeat
        if (trtimer => 25)                                          ' valid trigger?
          trtimer := -500                                           ' set hold-off period
          do_something  
    
    Note that the button input code will advance the timer is the timer value is negative (hold-off/repeat delay) or the button is pressed. If the button is released and the value is positive, the timer will be cleared. The key is to detect a press and then set the timer to a negative value that is the desired delay between repeats. No, this code doesn't allow the user to release to button when the repeat delay is active -- though that would be very easy to code.
  • As Spock would say, "Fascinating".
    Thanks, Jon.

  • iammegatron, you used posedge, which will catch any noise glitch or bounce that happens outside the execution time of the task. Might be okay. You could alternatively use posdet or negdet for a sort of debouncing, where a button has to be held for a minimum duration in order to activate a process. Something like this...
    main | taskDone
      taskDone := false
      ctra := %01000<<26 + MY_PIN . ' posdet mode, phsa advances on high level
      frqa := 1
      repeat
        if phsa  > constant(clkfreq<<2)  and taskDone == false ' check duration active
           ' do task here if need be and the button is pressed > 1/4 second
           taskDone := true .  ' going to avoid repeating the task until button goes back low
        if ina[MY_PIN] == 0   ' any low on pin resets the process, instantly
           taskDone := false
           phsa~~  ' reset the timer
        ' other tasks in the loop can continue to execute
    
    That is sort of like Jon's in that it uses a timer.

    Jon, The limits in your code could be constant(posx-1) and constant(negx+1) instead of the shifts, if it ever matters. The code I just posted will do weird things too if the button is stuck high.
Sign In or Register to comment.