New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nrf: implement touchio.TouchIn #1048

Closed
dhalbert opened this Issue Jul 17, 2018 · 41 comments

Comments

Projects
None yet
4 participants
@dhalbert
Copy link
Collaborator

dhalbert commented Jul 17, 2018

The COMP (comparator) peripheral description says it does "Single-pin capacitive sensor support".

@dhalbert dhalbert added the nrf52 label Jul 17, 2018

@dhalbert dhalbert added this to the 4.0 Beta milestone Jul 17, 2018

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 4, 2018

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 4, 2018

Hmmm, request from @ptorrone was:

please use the "Single-pin capacitive sensor support" - no external resistor is required then!

... but in https://devzone.nordicsemi.com/tutorials/b/design-examples/posts/capacitive-touch-on-the-nrf52-series there's the following update:

Important updates:

Due to erratum 84 (COMP: ISOURCE not functional), capacitive sensing using the relaxation oscillator method is not usable in a real product with the nRF52832, as it will only work reliably in room temperature. The ISOURCE feature is not present in the nRF52840 nor nRF52810.
After this tutorial was written, SDK 12 was released with support for capacitive touch using the Capacitive Sensor driver and Capacitive Sensor library.

That erratum being:

The programmable current source (ISOURCE) has too high varation. Variance over temp is >20 times specified nominal value
... but ISOURCE not being present at all in the '840 worries me more.

This is also mentioned at: https://www.nordicsemi.com/DocLib/Content/SDK_Doc/nRF5_SDK/v15-2-0/lib_csense_lowlevel

Note
Until Anomaly 84 (ISOURCE not functional) is fixed, the COMP solution cannot provide production-level quality. Use the SAADC solution instead.

@nickzoic nickzoic self-assigned this Dec 4, 2018

nickzoic added a commit to nickzoic/micropython that referenced this issue Dec 4, 2018

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 4, 2018

seems unclear, whats the SAADC method?

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 4, 2018

That's the version with the resistors, using another pin to charge the pads and then the saadc to measure the rate at which it discharges

The comp method may be okay if we're clever about self-calibration, I'll try it out ...

@dhalbert

This comment has been minimized.

Copy link
Collaborator Author

dhalbert commented Dec 4, 2018

The current CircuitPython "calibration" on SAMD21 is pretty simple: when the TouchIn object is created, we assume that it's not being touched. Touch threshold is that value + 100. But TouchIn.raw_value is available for people who want or need it, and the threshold can also be adjusted.

If the nRF errata are saying that the readings are really erratic, yeah, that could be a significant issue.

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 4, 2018

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 5, 2018

yah we require it to work on all nrf boards. if we have to do it, we can make it a common module so any chips can use it? ive done the technique w the original cirplay board. you just need a 1mohm res and fast reading w/no interrupts, so it should be in c not python

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 5, 2018

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 7, 2018

OK so I've got as far as confirming that yes, charging and discharging A0 through a 1Mohm resistor gives enough of a signal that we could call it a touch sensor. Python code is actually (just) quick enough to catch this!

At least, I think that's what I'm seeing ... it's pretty naïve so it could also be measuring electrical noise coupled into the pin. Sadly my 'scope leads are too high capacitance to show a signal variation on the screen. I've got to try with a bigger resistance, too: it'll discharge slower but that'll give me more time to measure a difference. The C implementation should make this clearer though because I'll be able to get a bit more sophisticated with measuring the discharge rate.

A bigger problem though: the touchio.TouchIn API is all about wrapping a single Pin object. This method needs another pin as well, to be the "drive". This can be a spare digital pin, which can drive many touch pads each through a resistor. So for this version we might need to do something like:

t0 = touchio.TouchIn(pad=board.A0, drive=board.D15)
t1 = touchio.TouchIn(pad=board.A1, drive=board.D15)

... which would share D15 between the two pads on A0 and A1. A TouchIn created without a drive pin would have to work some other way (perhaps ISOURCE on the '832 if it works at all) or raise an exception on platforms with no such mechanism (like the '840)

(it occurs to me that you could use a pair of analog pins and a single resistor to provide two touch pads, by switching each pin between being an analog input and a digital output in turn. That would mean driving the capacitance of the touch pad directly though, which is a bit of a waste of electrons and increases the risk of shorting the driven pin accidentally.)

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 7, 2018

yeah i like that API. i think you probably have it working but you need to do it in C for sure, you can see the arduino version of this code here:
https://github.com/PaulStoffregen/CapacitiveSensor

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 7, 2018

Yep, no worries. I should be able to get this done in the next couple of days ...

@tannewt

This comment has been minimized.

Copy link
Collaborator

tannewt commented Dec 7, 2018

Do we expect others to hook up their own resistors or could we build the drive in configuration into the board info? That way we could leave the API as is.

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 7, 2018

i think given you can use any combo, its best to let it be assigned at object instantiation. this will come in handy as we do more chips w/o native captouch - its a Good Thing to support :)

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 7, 2018

I guess it might make sense to have the resistors already in place on a board like a LilyPad etc where soldering isn't usually required.

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 7, 2018

yes on the CPX they will be placed on the pcb!

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 7, 2018

Hey I just had a thought: maybe we could dispense with the 'drive' pin by just having a ~1Mohm drain resistor from 'sense' pin to ground, setting the pin to output digital high for a bit to charge and then set the pin to analog input mode to measure the discharge rate. It'd get rid of the need for a drive pin, and a 1Mohm resistor to ground would make little difference to the pin's behaviour when used for any other purpose. I'll try it out and see if it works ...

UPDATE: Switching the pin between out and hi-Z works, still trying to make the SAADC play nicely.

nickzoic added a commit to nickzoic/micropython that referenced this issue Dec 9, 2018

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 9, 2018

OK with a little experimentation that approach (having a single pin switch from digital out to analog in) works well! Even the 1Mohm 'drain' resistor to ground may not be necessary, as the pin has an R_INPUT of >1MΩ to ground (on my feather, this is about 5MΩ) which is enough to slowly drain the pin.

From there the plan is to rapidly take N readings from the SAADC at the start of the discharge curve and calculate the rate of change of voltage, giving us a pretty direct reading of pad capacitance. The signal is pretty noisy so I'm looking at a couple of different ways of separating the signal from the noise.

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 9, 2018

k on your scope you should def be able to see the discharge curve. also you can try putting the wire in water, that should also be detectable

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 9, 2018

Certainly can:
sds00010

Yellow: the touch pad voltage
Purple: a clock signal I added in to show the sample rate.

Breadboards and flying leads is all a bit too messy to behave nicely with touch sensing so I'll make up a little test board like I did for the ESP32 one: https://nick.zoic.org/art/esp32-capacitive-sensors/ ... the above is just intended as a proof of concept for the "single pin solution" :-)

Shallower but noisier curve when touched:
sds00004

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 9, 2018

kk thanks! i do think its good to have both options, how about default to drive_pin=None and if it is, do the single-wire setup, otherwise, use the driver pin

nickzoic added a commit to nickzoic/micropython that referenced this issue Dec 11, 2018

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 11, 2018

OK so it's working nicely with the single pin setup described above. Here's the test setup:
test setup

(UPDATE: That pad board is 1.6mm single-sided PCB measuring 75mm x 50mm, the top piece is sliced down the middle to make two pads and there's a second piece of board stuck to the back as a ground plane. The pads are covered in pallet strapping tape, which is a little thinner than insulating tape but not much.)

schematic:

A0 --- [ pad 0 ] --- 1MΩ --- [ ground plane ] --- 1MΩ --- [ pad 1 ] --- A1
                                     |
                                    GND

... and here's some test code (the thresholds are set manually)

import board
import touchio
t0 = touchio.TouchIn(board.A0)
t1 = touchio.TouchIn(board.A1)

t0.threshold = 170
t1.threshold = 165

while True:
    print("%4d %d %4d %d" % (t0.raw_value, t0.value, t1.raw_value, t1.value))
@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 11, 2018

kk have you been able to try it with nrf52840?

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 11, 2018

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 11, 2018

ok can you get a nrf52840 DK or something to test with? we aren't targeting the '832 so much - im sure its similar but we'll need to test on the '840!

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 11, 2018

OK yep, ordered a big devkit and a little dongle so hopefully I can actually build to the right targets this time!

Send us a '840 feather when they're available :-)

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 16, 2018

OK, thanks to Mouser for fast shipping :-)

The same code works on the NRF 52840 DK (PCA10056) with only minor modifications to pin numbers and thresholds. The tracks on this board from NRF to pins are really long, but it still picks up a good touch signal.

import board
import touchio
t0 = touchio.TouchIn(board.P0_04)
t1 = touchio.TouchIn(board.P0_29)

t0.threshold = 61700
t1.threshold = 61400

while True:
    print("%4d %d %4d %d" % (t0.raw_value, t0.value, t1.raw_value, t1.value))
@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 17, 2018

looks good, can you toss me a UF2 and ill test it on the nRF52840 feather?

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 18, 2018

OK, sent by email ... and the branch is at https://github.com/nickzoic/micropython/tree/nickzoic/circuitpython-nrf-touchin-1048 if you want to build it yourself. I tested it on the dev kit board with the pad PCB pictured above hooked up by short hookup wires.

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 19, 2018

I've attached UF2 builds for the development kit and for the much awaited feather 52840!

firmware_uf2_pca10056.zip
firmware_uf2_feather_nrf52840_express.zip

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 19, 2018

ok tested with a repl - works! do you want to start a PR?

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 20, 2018

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 30, 2018

ok so i did more testing tonite, and used the mu plotter to get a sense of the data and it seems really spikey, did you notice this too?

image

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Dec 30, 2018

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Dec 31, 2018

just using a wire, but it needs to be stable no matter what the method - did you add a way to test with an optional charge gpio pin? that should work more consistantly :)

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Jan 3, 2019

Hmmm. That's going to be hard whether there's a separate charge pin or not

  • touching the pin directly (without an insulating layer) introduces a bunch of electrical noise and also potentially some leakage currents ... I get very noisy results too when touching the non-insulated parts of the touchpad.

  • touching just an insulated wire is quite a small coupling area compared to a touchpad, and so while there's a capacitance signal there it's quite small.

We might be able to do some better signal processing, eg: a sliding window mean or median, I'll try a few things and see if I can pull a signal out of the noise.

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Jan 3, 2019

In general though, capacitive touch detection works better and will always work better on the Feathers than on the DevKit, because the devkit has ~3" of track zigzagging its way between MCU and pin header and the Feather doesn't.

Looking forward to that Feather '840 arriving :-)

urish pushed a commit to urish/circuitpython that referenced this issue Jan 5, 2019

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Jan 5, 2019

image

using the technique linked above in the "capacitivesensor" library, it seems to work quite well, you need a drive pin but not ADC required

capacitive_test.txt

@tannewt tannewt modified the milestones: 4.0 Beta, 4.x Jan 14, 2019

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Jan 25, 2019

(back from vacation) That's really interesting! That's using just time-to-threshold and averaging over 10 cycles where I'm trying to get all fancy and stuff ... i'll try that out!

@ladyada

This comment has been minimized.

Copy link
Member

ladyada commented Jan 25, 2019

ok! welcome back :)

@nickzoic

This comment has been minimized.

Copy link
Collaborator

nickzoic commented Jan 27, 2019

Well, that is a fraction of the code size, works with non-analogue-capable pins, gets better touch sensing results than my fancy-pants math version and still works fine with a single pin rather than a separate drive pin!

Not quite done yet but will make it into a PR this weekend ...

@tannewt

This comment has been minimized.

Copy link
Collaborator

tannewt commented Jan 28, 2019

Done in #1499. Thanks!

@tannewt tannewt closed this Jan 28, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment