Shop OBEX P1 Docs P2 Docs Learn Events
Badge: working with the pads — Parallax Forums

Badge: working with the pads

SeairthSeairth Posts: 2,474
edited 2015-08-31 03:17 in Propeller 1
The new badge has 6 pads (actually 7, but I'm focusing on 6 for this post) that are similar to the pads available on the QuickStart board. Though JonnyMac has written some initial code for the badge, I've been trying to write my own code for the pads (mostly to get to know the hardware better). From what I understand, the process for reading the pad state is:
  1. Output high to the pad for 1ms (the pad plates act as a simple capacitor), then
  2. Set to input and wait 1ms (the plates will slowly discharge through a resistor), then
  3. Read the pin. A low value means pad is being touched (is shorted to ground).

With that in mind, I am having trouble getting reliable detection of a "pressed" pad. Given the following code:
  repeat
    case State
      |<15: serial.Str(STRING("P15."))
      |<16: serial.Str(STRING("P16."))
      |<17: serial.Str(STRING("P17."))
      |<25: serial.Str(STRING("P25."))
      |<26: serial.Str(STRING("P26."))
      |<27: serial.Str(STRING("P27."))
      other: next
  
    serial.Tx($0D)                                                 

    repeat while State <> 0                       ' wait for the user to stop pressing a button

I want to look for a "press", then output a message. The idea is to require the State to return to zero before continuing the outer loop. However, that does not seem to be happening. It seems that the State value is toggling between non-zero (a pad is "pressed") and zero (no pad is "pressed").

The code that update State looks like:
              org
Entry                                                                                                                                    
              rdlong    WaitTime, par
              
              or        outa, Mask              ' set pad pin outputs high (but keep as inputs)

              mov       Wait, cnt               ' preset the counter                                        
              add       Wait, ChargeTime                                                         

Loop                                                                                                        
              xor       dira, Mask              ' set pins as outputs (high)                                
              waitcnt   Wait, DecayTime         ' charge the pads for 1ms                                   

              xor       dira, Mask              ' set pins as inputs                                        
              waitcnt   Wait, WaitTime          ' wait 2ms for voltage decay                                

              mov       Reading, Mask           ' load mask of applicable pins                              
              andn      Reading, ina            ' clear pins from the mask that has not decayed (still high)
              wrlong    Reading, par            ' write the result to Hub RAM (State)
                                     
              waitcnt   Wait, ChargeTime        ' wait for WaitTime before checking pin state again
              jmp       #Loop

Mask          long      %00001110_00000011_10000000_00000000
ChargeTime    long      _clkfreq / 1000			' 1ms
DecayTime     long      _clkfreq / 1000			' 1ms
WaitTime      res       1
Reading       res       1
Wait          res       1


I've tried several variations for WaitTime, ChargeTime, and DecayTime. However, none have provided stable detection.

So, where am I going wrong? Did I miss something in the code, or is there a more reliable approach to working with these pads?

Comments

  • JonnyMacJonnyMac Posts: 8,923
    edited 2015-08-31 04:21
    You're making dangerous assumptions about discharge timing. When we did the DEF CON 22 badge that had smallish pad buttons, I did timing tests which are very easy to do using reads of the cnt register at the beginning of the discharge cycle and when the pin is detected as low. You want to set a discharge window that will effectively detect a finger, but not an untouched pad, even in less than perfect conditions (e.g., humid environment).

    I used something on the order of 15 milliseconds to discharge a pad with one's finger; much less than that (as you're doing) may not work. Again, testing is the key.

    Commentary: Why use XOR to change the state of the pads when OR and ANDN are clearer and there's no jeopardy of going in the wrong direction? I realize that I don't write particularly sophisticated code, but I always strive for clean and obvious.

    This works on the QuickStart -- well, as well as those pads will work....
    dat
    
    scan_pads               or      outa, padsmask                   ' charge pads
                            or      dira, padsmask
    
                            mov     t1, cnt
                            add     t1, chargems
                            waitcnt t1, #0
    
                            andn    dira, padsmask                   ' float pads pins to discharge
    
                            mov     t1, cnt
                            add     t1, dischargems
                            waitcnt t1, #0
    
                            mov     t1, ina                          ' read state
                            xor     t1, #$FF                         ' invert; 1 = pressed
                            and     t1, #$FF                         ' clean-up
                                                                      
                            wrbyte  t1, par                          ' write to hub
    
                            jmp     #scan_pads
    
    ' -------------------------------------------------------------------------------------------------
    
    padsmask                long    %11111111                        ' p7..p0 on QuickStart
    
    chargems                long    MS_001                           ' charge
    dischargems             long    MS_001 * 15                      ' delay 15ms before scan
    
    t1                      res     1
    
                            fit     496
    

    So, where am I going wrong? Did I miss something in the code, or is there a more reliable approach to working with these pads?

    Those kinds of pads are NEVER going to be reliable; they're for simple, demo apps only.
  • After implementing JonnyMac suggestions, you might also try repeating the "sensing" loop multiple times.

    For example, only treat button as pressed when your code detects the pressed-state 3 times in a row. 3 could be 30, you'll need to experiment. But this could help reduce false positives caused by charged "objects" passing by buttons.
  • SeairthSeairth Posts: 2,474
    edited 2015-08-31 11:39
    JonnyMac wrote: »
    You're making dangerous assumptions about discharge timing. When we did the DEF CON 22 badge that had smallish pad buttons, I did timing tests which are very easy to do using reads of the cnt register at the beginning of the discharge cycle and when the pin is detected as low. You want to set a discharge window that will effectively detect a finger, but not an untouched pad, even in less than perfect conditions (e.g., humid environment).

    I used something on the order of 15 milliseconds to discharge a pad with one's finger; much less than that (as you're doing) may not work. Again, testing is the key.

    Okay. So I'm making bad assumptions about consistent timing. Incidentally, the 1ms/1ms approach came from something I was reading related to QuickStart (can't seem to find it at the moment). But, as I noted, I tried a few other combinations as well.
    VonSzarvas wrote: »
    After implementing JonnyMac suggestions, you might also try repeating the "sensing" loop multiple times.

    For example, only treat button as pressed when your code detects the pressed-state 3 times in a row. 3 could be 30, you'll need to experiment. But this could help reduce false positives caused by charged "objects" passing by buttons.

    Actually, my first attempt to use the QuickStart sample code, which does something much like that. It too was exhibiting similar behavior (one false negative made the entire sampling negative), which is why I started writing my own version. Of course, it looks very similar to both the original and JonnyMac's version, since the general approach hasn't changed.

  • JonnyMac wrote: »
    Commentary: Why use XOR to change the state of the pads when OR and ANDN are clearer and there's no jeopardy of going in the wrong direction? I realize that I don't write particularly sophisticated code, but I always strive for clean and obvious.

    That's a fair point. I see XOR as "bit flip" or "toggle", which is how I was thinking of the code: toggle on, toggle off. In this case, OR (set) and ANDN (clear) makes just as much sense. I just wasn't thinking of it as set/clear. And your point about confused states is a good one. In more complex code, expecting a known prior state with XOR (toggle) might get you in trouble.
  • @JonnyMac,

    When you were doing your timing tests, do you recall if the decay times were consistent between pads? In other words, did all of the pads decay at approximately the same rate?
  • Out of curiosity, why are the pads designed in this manner? Why not use a weak pull-down (> 100k) with the pad connected to Vdd and the pin always set as an input?
  • Certainly in this product, keeping pull-ups/downs off the IO's arguably makes them more useful for hackers who might wish to determine that short of thing.

  • JonnyMacJonnyMac Posts: 8,923
    edited 2015-08-31 19:51
    When you were doing your timing tests, do you recall if the decay times were consistent between pads? In other words, did all of the pads decay at approximately the same rate?

    They are never consistent, which is why this is only a demo circuit -- I doubt a person would deploy it in a real world application.
    Out of curiosity, why are the pads designed in this manner? Why not use a weak pull-down (> 100k) with the pad connected to Vdd and the pin always set as an input?

    My guess is that the primary driver of that design is virtually 0 cost, and on the QuickStart, those pins can be used for other things without interfering.
  • jmgjmg Posts: 15,144
    JonnyMac wrote: »
    When you were doing your timing tests, do you recall if the decay times were consistent between pads? In other words, did all of the pads decay at approximately the same rate?

    They are never consistent, which is why this is only a demo circuit -- I doubt a person would deploy it in a real world application.

    Is there a Picture of these pads, and the components associated ?
    It seems releasing something that is known to be sub-standard is never a good idea.

    Cap-sense buttons are very widely deployed these days, and it seems the right code could make these more reliable ?

    If they work as a combination of [Cap+Leakage] then a charge impulse driver should be Prop compatible, and give some sense ability ?

  • jmg wrote: »
    Is there a Picture of these pads, and the components associated ?

    The pads are the same as those on the QuickStart board. There is a 100k series resistor between the pad and the propeller pin. The other part of the pad is connected to ground.

    Since I never used the QuickStart, I have no experience working with these. My naive expectation is that they should "just work", as long as you have the right code. But the QuickStart demo code doesn't seem to be the right code. It's close, but does not result in consistent behavior (in my opinion). I have not tried JonnyMac's version above, which might work just fine. It looks like the big difference between his code and mine is that his decay period is much longer.

    And it's that decay period that's the critical part. Basically, you need a delay that is between Tmin and Tmax, where
    • Tmax is shorter than the "unpressed" voltage decay down to just above 1/2 Vdd, and
    • Tmin is longer than the worst case "pressed" voltage decay below 1/2 Vdd

    Unfortunately, it seems there are a couple of factors that could affect both Tmin and Tmax:
    • Skin can have a resistance upwards of 100k. Ideally, you want this value to be as small as possible (best case scenario is about 1k).
    • The 100k series resistors are not precise. Hopefully, they used 1% resistors. But if they used 5% resistors, Tmax could be longer. Further there could be significant variation between pads.

    So, I suppose if you could figure out how quickly you'd hit 1/2 Vdd with a 100k resistance (your finger) to ground, then you could get a reasonable Tmin. From there, set Tmax to some value that is just greater than Tmin (and hopefully still below the "unpressed" decay period) that provides for a small margin of error.

    At least, that's how I understand all of this. In the above example, JonnyMac's Tmax is 15ms.
  • jmgjmg Posts: 15,144
    Seairth wrote: »
    jmg wrote: »
    Is there a Picture of these pads, and the components associated ?

    The pads are the same as those on the QuickStart board. There is a 100k series resistor between the pad and the propeller pin. The other part of the pad is connected to ground.

    ....
    • Skin can have a resistance upwards of 100k. Ideally, you want this value to be as small as possible (best case scenario is about 1k).
    • The 100k series resistors are not precise. Hopefully, they used 1% resistors. But if they used 5% resistors, Tmax could be longer. Further there could be significant variation between pads.
    I doubt 5% matters here :)

    The approach I would use is a charge impulse one.

    Fingers are going to be a vague combination of C and Parallel R. (with maybe some bonus hum injection )

    A ball-park check gives for a shortest possible Prop impulse,
    dV=(1.5/100k)*50n/5p = 0.15
    ie if you start with the pin at either rail, and then repeat (apply impulse and check).
    Numbers above suggest ~10 impulses will move a pin 50% of Vcc
    One count is appx ~500fF

    On a non loaded pin the Rise and Fall times should be shortest and (roughly) equal, and with not much variation.
    As a finger is added, the Tr.Tf will skew, and variation will climb.




  • jmg wrote: »
    The approach I would use is a charge impulse one.

    Interesting! So I'm clear on what you are suggesting, the process would go something like this:
    // pseudocode
    
    threshold = 10    // or some other precalculated/calibrated value
    counter = 0
    
    clear_pin_to_ground
    
    do
        output_impulse
        counter++
    while (read_input == 0 && counter <= threshold)
    
    if (counter > threshold)
        button_pressed
    else
        button_not_pressed
    

    With this approach, even low conductivity (e.g. 100k resistance) across the skin would be relatively easy to detect. In fact, at 100k, Tr should be about twice as long. Using your estimates above, and adding 1-2 impulse counts to threshold for an error margin, I think you'd be good to detect anything with a resistance less than about 150k.

    If this all looks right, I'll try coding this up this evening and seeing how it does.
  • Actually, since there are multiple pads to test for, this might be a simpler approach:
    // pseudocode
    
    counter = threshold
    
    ground_pins
    
    do
       pulse_pins
    while (--counter > 0)
    
    button_state = read_pins ^ pin_mask
    

    In other words, just repeatedly pulse until you know you are minimally past the "unpressed" threshold for all pins. Then, sample the input and apply the mask. Any bit that's set (i.e. was still zero when reading the pins) indicates a button press. Rinse and repeat...
  • jmgjmg Posts: 15,144
    Seairth wrote: »
    With this approach, even low conductivity (e.g. 100k resistance) across the skin would be relatively easy to detect. In fact, at 100k, Tr should be about twice as long. Using your estimates above, and adding 1-2 impulse counts to threshold for an error margin, I think you'd be good to detect anything with a resistance less than about 150k.

    If this all looks right, I'll try coding this up this evening and seeing how it does.

    Yup, That's the broad idea.

    I ran this up on Spice, and it works nicely for cases where the Pin C is << Button C

    However, as the Capacitance on the Pin-Side of the R is increased, the impulses get turned into sawtooths, and the resolution drops.

    ie That depends on the PCB layout. (R is better close to the pin)
    A case of try it and see.

    If the Pin C is too large, then an alternative approach is to run a SW ADC, where you float for a few cycles (to allow both C's to settle), read and 1 cycle pulse toward mid point.

    Repeat that over some ms, and record the ADC which should be very close to 50%. As Rf drops, the ADC count skews and SW that checked for changes would tolerate PCB leakage/process thresholds more.
    A 10% shift in ADC is ~ 1Meg sense. 3% is ~ 3Meg


  • pjvpjv Posts: 1,903
    One thing to keep in mind here is that with a finger on the pad, there is a tremendous amount of AC noise induced. And that really messes with the trip values.

    So my answer to that is to take samples 8.8 milliseconds apart, and count hi' s plus lo's for a sufficient period of time, to knock out the AC.

    Then enough lo's will indicate a valid "push".

    Cheers,

    Peter (pjv)
  • jmgjmg Posts: 15,144
    pjv wrote: »
    One thing to keep in mind here is that with a finger on the pad, there is a tremendous amount of AC noise induced. And that really messes with the trip values.

    So my answer to that is to take samples 8.8 milliseconds apart, and count hi' s plus lo's for a sufficient period of time, to knock out the AC.

    Then enough lo's will indicate a valid "push".
    Other choices are to sample over a full mains cycle(s) (N * 16.66ms in USA), which averages any injection to roughly zero, or interleave samples over half cycles (E,O 8.333ms) and look for separation between even/odd sets of samples.
    The latter approach uses the AC noise as an additional signal.

  • pjvpjv Posts: 1,903
    The main reason for my response was to make folks aware of the induced AC issue. I'm sure there are many ways to skin that cat.

    I actually use a somewhat different approach as it blends in well with my Scheduler kernel.

    Cheers,

    Peter (pjv)
  • So, I finally sat down and wrote some code using the pulsed method. Only, things didn't go as I expected. It appears that the pins are reaching 1/2 Vdd within 50ns (i.e in 4 clock cycles or one instruction cycle). To make sure, I started the measurement with the following:
    Drain
                  mov       outa, #0
                  or        dira, Mask
                  mov       Counter, #$1FF
    :loop         djnz      Counter, #:loop              
                  andn      dira, Mask
    

    Basically, set the direction to a low output for several clock cycles ($7FF) in order to drain any charge there might be.

    Then, to figure out the minimum number of pulses required, I have a small snippet that looks like:
    Setup
                  mov       Threshold, #0
                  or        outa, Mask
    
    :loop         add       Threshold, #1
                  or        dira, Mask
                  andn      dira, Mask
                  mov       Reading, ina 
                  andn      Mask, Reading   nr, wz
          if_nz   jmp       #:loop
    

    When I capture INA before the "or dira, Mask", the pins are low. When I capture INA afterwards, all the pins are high.

    So, before I abandon this approach, is there something I may be missing?
  • jmgjmg Posts: 15,144
    Seairth wrote: »
    So, before I abandon this approach, is there something I may be missing?

    Spice shows the Pin-capacitance has an impact, so you need to pause after the impulse, or you will just read back the PinC, (which is still hi) - try a few us of settling time, before reading the pins.

    Given this Pin-C effect, I think an ADC balancing effect may be better, there you apply a similar (say 10us) charge balance/settling time, and impulse in the direction of the Threshold.
    10ms will give ~1000 samples
  • Okay. I tried the first change, up to about 25us settle time. No difference.

    Can you explain the other part a bit more?
  • jmgjmg Posts: 15,144
    Seairth wrote: »
    Can you explain the other part a bit more?
    You read the pin, then impulse drive in the opposite direction, and wait before next read.
    Count +ve impulses. Repeat that (say) 1000x
    A pin with zero load, and a 50.0% chip threshold will have Count of 500.
    A change in result is what you look for. ~ 550 is a 1M to GND
    There may have been threads on this before as 1 pin ADC's ?
  • jmgjmg Posts: 15,144
    I can't quickly Spice a SW loop, but I did have an idea where I can Spice a balanced impulse, then skew with a load R, and then de-skew by changes of the impulse widths.

    Turns out the design is more sensitive to loading, as I missed that the 100k is time-switched, so appears as an average higher R value.
    A 1M test load thus gives significant skewing, and needs more impulse movement to correct.

    All of which is good, if you try to sense a touch.
      ---- 3pF and 10pF  ----
      50ns+ & 50ns- alternating impulses, each 1000ns spaced.
    tRun = 250us,  No Skew Peaks 1.91055V & 1.38932V = 1.649935
    tRun = 250us 1M Load, Peaks  1.50751V & 1.00039V  = 1.25395
    Now Change PW to balance back to ~1.91 & 1.389 is 
    250ns POS and 50ns NEG gives 1.97606V & 1.31401V = 1.645035 for 1M skew
    
    -- 3pF and 100pF --
    250ns POS and 50ns NEG gives 1.60485V  1.67752V  for 1M
    50ns / 50ns 999M  gives      1.62115V  1.67767V, 
    
  • Okay! I finally made some progress, though things didn't go quite as I expected. I tried out @jmg's suggestion to pulse the pin in one direction or the other, depending on what their last sampled value was. However, it turned out that I couldn't get a reliable read when the finger was touching the pad. Instead of requiring more pulses in one direction, it varied all over the place. But! It turns out that wasn't an issue, because it consistently varied all over the place! Here's what I mean by that. Given 1000 samples, I got two behaviors:
    • When not touching the pad, I would always get 500 pulses in each direction.
    • When touching the pad, I would almost always count random pulses in each direction (475 high/525 low, 502 high/498 low, etc), but rarely exactly 500 (the untouched state).

    Further, if I repeated the 1000 samples 3-4 times while touching the pad, it turned out that the odds of hitting 500 high/low samples every time were somewhere near zero. Then I discovered that I got just as consistent results when i performed only 2 samples (still 3-4 times). In the end, I settled on the following:
    • Sampling: perform 4 pulses (1us settle time between the pulse and measure)
    • Require 5 consistent samplings (either 5 that are all 2 high/2 low, or 5 that are not) to toggle button state (with 1ms between samplings)

    Now, I suspect that this can still be tuned a bit. As it stands, we are talking about a bit more than a 5ms detection time. For a bit less accuracy, you could easily take it down to ~3ms just by reducing the "Sensitivity" value to 3 (note: the OSH button got a bit jittery at 3ms).

    Anyhow, it's a work in progress, but I am happy with it's current performance. I've attached the current code, which will handle all 7 of the badge pads. I was too lazy to do some MOVDs, so you will see a couple "unrolled loops".
  • jmgjmg Posts: 15,144
    Seairth wrote: »
    Now, I suspect that this can still be tuned a bit. As it stands, we are talking about a bit more than a 5ms detection time. For a bit less accuracy, you could easily take it down to ~3ms just by reducing the "Sensitivity" value to 3

    Sounds good. Is anyone going to notice anything sub 10ms ?
    Before you design too much around AC injection effects, you could try a battery powered unit operating outside. (where AC injection is likely to be lowest)
    A sample window of nominal full mains cycle should give some AC rejection, and be more location tolerant.


  • jmg wrote: »
    Sounds good. Is anyone going to notice anything sub 10ms ?

    You may be right. If I were using these pads to play a game, the responsiveness might be just a bit slower than I'd like, with a complete button press (on and off detection) takes a little over 10ms (5ms down, 5ms up). The problem is that if you tap back down before that 5th "unpressed" sample is taken, you run the risk of never detecting the "up" part. One compromise might be to use 5 samples for pressed and only 3 samples for unpressed.
    jmg wrote: »
    Before you design too much around AC injection effects, you could try a battery powered unit operating outside. (where AC injection is likely to be lowest)
    A sample window of nominal full mains cycle should give some AC rejection, and be more location tolerant.

    Hah! I forgot that I was testing this "tethered" (so I could use FDS). So, this morning I rewrote the test code to use the LEDs and the OLED. And it all works just fine. I did notice that the "ringing" totally went away. For instance, at 4 measurements per sample, unpressed was steady at 2, while pressed was consistently 3 to 4. Testing with 10 measurements per sample, unpressed was steady at 5, while pressed was consistently 6 to 7 (or 9 to 10 if I moistened my finger). In both cases, once the count deviated from the center, it tended to stay that way (very little fluctuation). In fact, without the oscillations, I was able to reliably do 3 samples instead of 5.

    For now, I intend to keep the sampling method unchanged, as this allows the badge to work reasonably well in the presence of AC.
  • jmgjmg Posts: 15,144
    Seairth wrote: »
    Hah! I forgot that I was testing this "tethered" (so I could use FDS). So, this morning I rewrote the test code to use the LEDs and the OLED. And it all works just fine. I did notice that the "ringing" totally went away. For instance, at 4 measurements per sample, unpressed was steady at 2, while pressed was consistently 3 to 4. Testing with 10 measurements per sample, unpressed was steady at 5, while pressed was consistently 6 to 7 (or 9 to 10 if I moistened my finger). In both cases, once the count deviated from the center, it tended to stay that way (very little fluctuation). In fact, without the oscillations, I was able to reliably do 3 samples instead of 5.

    For now, I intend to keep the sampling method unchanged, as this allows the badge to work reasonably well in the presence of AC.

    Sounds great :)
    Spice said the 'virtual resistance' amplifies by the ON:Float ratio, so it should be sensitive, as the tests indicate.

  • jmg wrote: »
    Sounds great :)
    Spice said the 'virtual resistance' amplifies by the ON:Float ratio, so it should be sensitive, as the tests indicate.

    And thank you for all of your help and feedback! I learned a lot from that little exercise. And, frankly, the learning part is half the fun!

Sign In or Register to comment.