Badge: working with the pads
Seairth
Posts: 2,474
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:
With that in mind, I am having trouble getting reliable detection of a "pressed" pad. Given the following code:
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:
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?
- Output high to the pad for 1ms (the pad plates act as a simple capacitor), then
- Set to input and wait 1ms (the plates will slowly discharge through a resistor), then
- 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
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....
Those kinds of pads are NEVER going to be reliable; they're for simple, demo apps only.
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.
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.
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.
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.
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.
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.
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 ?
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
Unfortunately, it seems there are a couple of factors that could affect both Tmin and Tmax:
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.
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.
Interesting! So I'm clear on what you are suggesting, the process would go something like this:
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.
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...
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
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)
The latter approach uses the AC noise as an additional signal.
I actually use a somewhat different approach as it blends in well with my Scheduler kernel.
Cheers,
Peter (pjv)
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:
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?
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
Can you explain the other part a bit more?
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 ?
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.
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:
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".
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.
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.
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.
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!