my button debounce code in SPIN using CTRA
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
Please help improve this for other debounce applications.
{{
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
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?
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?
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
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
would you translate that into simple Texan?
in0timer := 0 #> ++in0timer <# posx
the #> and <# part?
#> 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
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?
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 operationIn 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).
Mike
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.Thanks, Jon.
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.