Software capacitive touch is possible on one pin without any external components

Go To Last Post
36 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Everybody,

 

I want to contribute back to the community and released Arduino based library for very easy capacitive sense (MIT license).

 

I do not need 2 pins or any external components, 1 digital pin is enough (by combining an internal pull-up and VERY fast sampling).

 

See the 1 minute getting started video (sorry the comments on the video got disabled the moment I enabled that the video is kids friendly, but feel free to comment here on github):

https://www.youtube.com/watch?v=KwPeKHTvGJs

 

And read the Readme or download the project and try it yourself:

https://github.com/truhlikfredy/SinglePinCapacitiveSense

 

It's fast and pretty sensitive (the video is from the very first release which was not that fast and not that sensitive). Can do up to 4080 of continuous sampling at a rate of ~1.3 clocks per 1 sample at one go!

 

The first major advantage over generic libraries is that it's not runtime generic (only compile-time generic) and the compiler was able to optimize the instructions much better (mentioned it in the readme), thanks to C++ and templates. However the releases 2.0 and above are using handcrafted assembly, where I as able to use registers as a buffer and not having to test them for the majority of the time, which gave me such high sampling rate.

 

So even if you are not interested in the capacitive sense I would recommend to give it a look. I used plenty of comments in the code/assembly if anybody wants to have a look. Special fun is to make C++ template which can take arguments and feed them directly into the assembly as immediate values or addresses (even doing some offset calculation before). Links to my references are bundled in the code as well. Another very nice thing is that the inline assembly is C friendly so all inputs/outputs clobbers are defined and the C is much more safer and more aware what actually the assembly is doing to the CPU state (links are in readme in the reference and in the code as well).

 

And it has Travis CI builds done in the docker for each commit as well.

 

I hope some of this will be useful to somebody in some shape or form.

 

 

 

Last Edited: Sun. Feb 9, 2020 - 08:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

 

'The fact that whit TTL 5V the input '

 

whit - the correct spelling is 'with'. This is a blot in an otherwise well written piece of English.

 

Where does TTL factor into this? The AVR is CMOS.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you very much, I usually get mistakes when I'm in rush or when I write different parts at different times, I wouldn't be surprised when there would be more in the code.

Added the changes to develop branch, will tweak a few things and add it to the master. I want to test it on more boards and thinking about adding a debouncing feature into the library itself.

 

The video could have better-phrased text as well, might redo it at a certain point (and to showcase the speed/sensitivity increase from release 2.0)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
// This is to work around "because it is not the address of a variable" error
// where template argument is refused because it's not a pointer to a variable.
// https://stackoverflow.com/questions/37303968
constexpr uintptr_t PortD() {
  return (uintptr_t)&PIND;
}

 

You are in violation of Standard. You are not allowed to use reinterpret_cast in constexpr. The macro PIND has a reinterpret_cast inside, therefore it can't be used inside constexpr, EVER! angry<- picture rabid C++ standard code lawyer here.

The C++ Standard inquisitors are on to you.

Those people, who when you ask certain questions at stackoverflow give you that moronic answer: "Why would you ever need to do that?"

Those people, who have an encyclopedic knowledge of the C++ standard, and in their hubris believe that is all the knowledge there is.

Those people, who can barely grasp the peculiarities of MCU programming.

Yes, we all know them well...

 

And so, even though your workaround works on avr-gcc 7.x.x currently used by the Arduino IDE, it's a dangerous bug </sarc> already fixed in more recent versions.

The dreadfully permissive version 5.x.x currently used by Atmel Studio doesn't even need this workaround. <- kill it with fire!!!!! angryno

 

That's right, you could even put the address directly as template argument like this <(uintptr_t) &PIND> on v. 5.x.x, can you imagine the dreadful Pandora's box that opens? I tremble just to think about it.

V. 7.x.x already fixed that hole, but left the exploit you are using wide open!!!!

 

"Fortunately", it has now been fixed in more recent versions.

 

I, El Tangas, have devised a new workaround for more recent versions. It's secret, of course. I dare not share it publicly, lest the Code Inquisitors get wind of itwink

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Sorry for the rant above.

 

Regarding the actual work, I adjusted contrast on your trace

 

 

Yeah, I see the problem, the rise time is very fast and the logic state of the input goes from 0->1 in just a few CPU cycles, so you need maximum possible sampling rate.

Naturally you had to use assembly to achieve it even if using so many registers. I can't see a better way either, TBH.

 

On a more modern AVR of the AVR-0/1 series maybe you could use the analog comparator + DAC + event system to set a threshold higher, let's say, 90% of VDD, giving you much more time. Maybe I'll try it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In case you are not aware (sounds like a new discovery), this has been a thing for many years. Back in the early PIC days (80's or something),  I had a paper clip as my touch sensor on some pic16 and I'm quite sure it was an old idea at the time also.

 

That is not meant to detract from what you have, and if you discovered it to some degree on your own then that is a good thing. There are plenty of discoveries where many people end up in the same place from different routes. It is sometimes a little sad when you end up at the north pole and discover you are number 10,001.

 

 

Trying to relive my early pic days (although pic was in asm)- I have a bare wire hanging from a pin and turning an led on by touching it (just a crude example, no pullup used at all)-

 

//tiny817 Xplained
//default 3.33MHz
#include <avr/io.h>
#include <stdbool.h>
#define F_CPU 3333333ul
#include <util/delay.h>

 

static void led_on(){ VPORTC.DIR |= PIN0_bm; VPORTC.OUT |= PIN0_bm; }
static void led_off(){ VPORTC.DIR |= PIN0_bm; VPORTC.OUT &= ~PIN0_bm; }

 

bool touch(){
    static uint8_t tmin = 255, tmax = 0;
    VPORTA.DIR |= PIN2_bm; //PA2 = output
    VPORTA.OUT |= PIN2_bm; //high
    VPORTA.DIR &= ~PIN2_bm; //PA2 = input
    uint8_t v = 0;
    while( (VPORTA.IN & PIN2_bm) && v < 255 ) v++;
    if( v < tmin ) tmin = v;
    if( v > tmax ) tmax = v;
    return v > ((tmax - tmin)/2 + tmin); //if v > 1/2 difference = on
}

int main(){
    for(;;){
        if( touch() ){
            led_on();
            _delay_ms(50); //'debounce'
        } else {
            led_off();
        }
    }
}

 

 

I'll have to add that to my avr0/1 c++ library since atmelchip does not want anyone to see the secret touch peripheral-

 

//Touch.hpp

#pragma once
#include "Port.hpp"

 

template<PINS::PIN Pin_>
struct Touch {
    static bool isOn(){
        static uint8_t tmin = 255, tmax = 0;
        Port<Pin_, PINS::OUTL>::init(); //init output, high
        Port<Pin_, PINS::INH> p; //back to input
        uint8_t v = 0;
        while( p.isHigh() and v < 255 ) v++;
        if( v < tmin ) tmin = v;
        if( v > tmax ) tmax = v;
        return v > ((tmax - tmin)/2 + tmin);
    }
};

 

Touch<PINS::PA0> touch1;

if( touch1.isOn() ){ /*do something*/ }

Last Edited: Sun. Feb 9, 2020 - 01:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I haven't experimented much with touch sensing, but after looking at your code I think I may try making a simple touch library.  Inline functions may be a more readable  way of implementing it instead of resorting to templates.

https://gcc.gnu.org/onlinedocs/g...

 

I also think I could do it in straight C.  A loop like the following takes 4 cycles per itteration:

    uint8_t time = 0; do {
        time++;
    } while (PINB & (1<<TOUCH_GPIO));

I'd do the loop 16 times, and sum the time.  That would give an effective resolution of 1/4 cycle, and provide noise immunity/debounce.  I'd also try to report an intensity instead of on/off.  It shouldn't take me long to whip something up once I'm finished with a couple other projects.

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Originally I had it as <(uintptr_t) &PIND> but then when I was changing toolchain version I was getting the errors. I would prefer if I would access to the addresses as just uint16_t and I wouldn't have to do the mess. What you propose I should do so it will compile well in various versions of Arduino, Atmel studio and a generic distribution avr gcc toolchain?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:

Regarding the actual work, I adjusted contrast on your trace

 

 

Yeah, I see the problem, the rise time is very fast and the logic state of the input goes from 0->1 in just a few CPU cycles, so you need maximum possible sampling rate.

Naturally you had to use assembly to achieve it even if using so many registers. I can't see a better way either, TBH.

 

On a more modern AVR of the AVR-0/1 series maybe you could use the analog comparator + DAC + event system to set a threshold higher, let's say, 90% of VDD, giving you much more time. Maybe I'll try it.

 

Is the original contrast wrong? To be specific unload pin will raise in about 2000ns, while at 16MHz (62.5ns per clock) that is 32 clocks, that's not that little, and yes I kept repeating that the high sampling rate is the point of the library, 1 sample for each 1.3 clock. So in around I still should have around 24 samples even for unloaded pin going high. No assembly was not needed, revision 1.0 (the one in the video) was able to do it in the C++/template, but I had maybe 3-4 steps of sensitivity for pressure, now switching to assembly have pretty high resolution in the pressure. No the point was to avoid extra HW and extra setups, wanted something which will work on point-blank digital input which are more plentiful and the other more special pins are free for the rest of the application. If I wanted to use dedicated HW pins, I would use the qtouch dedicated hw.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
Trying to relive my early pic days (although pic was in asm)- I have a bare wire hanging from a pin and turning an led on by touching it (just a crude example, no pullup used at all)-

 

Yes that is similar to the 1.x I had (before going for assembly). What is the point to setup the pin to output high before switching to input?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ralphd wrote:

I haven't experimented much with touch sensing, but after looking at your code I think I may try making a simple touch library.  Inline functions may be a more readable  way of implementing it instead of resorting to templates.

https://gcc.gnu.org/onlinedocs/g...

 

I also think I could do it in straight C.  A loop like the following takes 4 cycles per itteration:

    uint8_t time = 0; do {
        time++;
    } while (PINB & (1<<TOUCH_GPIO));

I'd do the loop 16 times, and sum the time.  That would give an effective resolution of 1/4 cycle, and provide noise immunity/debounce.  I'd also try to report an intensity instead of on/off.  It shouldn't take me long to whip something up once I'm finished with a couple other projects.

 

 

I wanted something newbie-friendly and Arduino kids like their classes. Will making the function inlining be able to treat the arguments as immediates? I seen inlining as a way to save a jump and to reuse code, but not sure about the argument handling. My code does report intensity (see the serial report in the loop, has calibrated and raw outputs.

 

Having light touch could take about 4000us to trigger, 50ns per clock (at 20MHz), is 80 clocks, with 4 cycles per iteration you should count about 20 samples, so 'time' is far from overflowing, but I would feel unease let it to overflow and 20 samples is not that much resolution. I still feel better with my solution with 1.3 cycles per sample and with overflow check. Imagine doing stronger press, having device with weaker pull-up and overflowing it consistently without realizing it. Mine and your wouldn't be very immune to the noise, and I would say 16 samples wouldn't be enough for debounce, 64uS is not much for debouncing, I would guess it would be better if it would be in mS range

 

Doing it 16 times

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>What is the point to setup the pin to output high before switching to input?

 

There are many variations, but I just chose to start 'high' (output/high), then back to input and let the pin drift downward until it trips the low point (inc a var while waiting). If my finger is on the wire, it takes longer to discharge of course. The pullup could also be used instead of output/high, but output/high would charge anything attached faster and make sure I'm starting as high as possible (since it is really unknown where exactly the starting point is- high just above the high level or high close to Vdd are both high). Didn't put a ton of thought into it, just was trying to show minimal code that shows a mostly reliable simple on/off. Not much resolution either (3.3Mhz), but I probably wouldn't try for more than on/off anyway as I doubt anything more would be reliable (there is a reason they created specific touch peripherals).

 

I'm not Arduino man, but I do have it on the pc somewhere.

I would try to make the template parameter something that is common/already created-

https://godbolt.org/z/zvmMTa

they already have this contraption built to deal with pins so I'm not sure I would want to now force a user to figure out port letters and numbers in addition to all the other translating taking place (probably not as bad as I imagine), plus you can then eliminate your problem parameter and do static_assert at the same time. I'm not sure I pieced together the Arduino pin rube goldberg machine correctly, but I think its close enough for the example.

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ralphd wrote:

I haven't experimented much with touch sensing, but after looking at your code I think I may try making a simple touch library.  Inline functions may be a more readable  way of implementing it instead of resorting to templates.

https://gcc.gnu.org/onlinedocs/g...

 

I also think I could do it in straight C.  A loop like the following takes 4 cycles per itteration:

    uint8_t time = 0; do {
        time++;
    } while (PINB & (1<<TOUCH_GPIO));

I'd do the loop 16 times, and sum the time.  That would give an effective resolution of 1/4 cycle, and provide noise immunity/debounce.  I'd also try to report an intensity instead of on/off.  It shouldn't take me long to whip something up once I'm finished with a couple other projects.

 

 

What about the case when somebody will ground the pin, you have no timeout and will keep looping indefinitely. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

>What is the point to setup the pin to output high before switching to input?

 

There are many variations, but I just chose to start 'high' (output/high), then back to input and let the pin drift downward until it trips the low point (inc a var while waiting). If my finger is on the wire, it takes longer to discharge of course. The pullup could also be used instead of output/high, but output/high would charge anything attached faster and make sure I'm starting as high as possible (since it is really unknown where exactly the starting point is- high just above the high level or high close to Vdd are both high). Didn't put a ton of thought into it, just was trying to show minimal code that shows a mostly reliable simple on/off. Not much resolution either (3.3Mhz), but I probably wouldn't try for more than on/off anyway as I doubt anything more would be reliable (there is a reason they created specific touch peripherals).

 

I'm not Arduino man, but I do have it on the pc somewhere.

I would try to make the template parameter something that is common/already created-

https://godbolt.org/z/zvmMTa

they already have this contraption built to deal with pins so I'm not sure I would want to now force a user to figure out port letters and numbers in addition to all the other translating taking place (probably not as bad as I imagine), plus you can then eliminate your problem parameter and do static_assert at the same time. I'm not sure I pieced together the Arduino pin rube goldberg machine correctly, but I think its close enough for the example.

 

 

And if the capacitance is high, you think 1 clock will be always enough to get it HIGH enough? Drifting down isn't fairly slow for an 8-bit counter? What if the sensor is large (and slow) and you run at 20MHz (or maybe even higher) clocks.

 

For a output this will will set the pin HIGH, then switching it to input will not go to high-z to drift down as you described, but leave it the pull-up configuration so the input will start high and then go higher? Or do I miss something?

 

VPORTA.OUT |= PIN2_bm; //high

 

Isn't the tmin/tmax sensitive to one-off noise to mess up the measurements for the whole power cycle with no way to reset them?

 

Thanks for the link, but that code is what I was trying to avoid from the very beginning, code being generic runtime (while I wanted code generic only at compile time), that load from the 16-bit Z register takes 2 cycles, then ANDing it is another 1 cycle, loop jump another 2 cycles. The revision 1.x I did in C already was able to take advantage of SBIS/SBIC instructions and do in 1 cycle (and two cycles when sampling finished and has to do jump out) + 2 cycles for loop jump. 5 vs 3 clocks where almost every single clock counts is not a negligible difference. And that is not doing any timeout/count checking which would slow it down either more. While the assembly approach can do 1.3 cycles per sample while doing the count and timeout checking.

 

Another issue with the code is that the compiler preloaded the R30, R31 and R25 with the port address and that's good, but does it give you guarantee that the moment you will do the timeout/count checking and do you will setup the port to pullup that these things will happen instantly after the pullup. What if it will decide to populate all these registers after setting up the pullup on the port and wasting even more cycles? It's tricky to do optimization for speed, when the Arduino is hardcoded to optimize for size.

 

I'm not Arduino man either, dusted off some old arduino and installed IDE just for this. I wanted something easy to use for any user and in a library form which will work well for 1 pin or for 10 and from slow clocks to fast clocks as well so I gave this Arduino approach a shot.

Personally I like the gcc shipped with my Debian or when using IDE then many years ago I really enjoyed CodeVision and back then was producing faster/smaller results than gcc and then for runtime JTAG debugging using the AVR studio (as it was able to open the symbols and everything).

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:

What you propose I should do so it will compile well in various versions of Arduino, Atmel studio and a generic distribution avr gcc toolchain?

 

I don't have the actual code here right now, but it works like this:

The objective is to obtain the address of an SFR like PORTB, for example, but you can't do any actual C++ operation on PORTB itself in any place that requires a constant expression, like inside a template argument or a constexpr function.

 

So what I did is:

  1. precompiler expands SFR macro (like PORTB)
  2. precompiler stringifies expanded SFR macro
  3. inside that string there will be the address somewhere, as ASCII, like "0x10" for example.
  4. That string is fed to a constexpr function that will extract and return the address as a uint16_t, accounting for cases where the SFR address is in I/O or memory, if required.
  5. The whole thing is wrapped in a macro like ADDRESS(x), so if you want the address of PORTB you do ADDRESS(PORTB)

 

It's quite ugly, but should work with any version of avr-gcc. The whole thing happens at compile time, so it doesn't generate any actual code, just a uint16_t with the address.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:

they already have this contraption built to deal with pins so I'm not sure I would want to now force a user to figure out port letters and numbers in addition to all the other translating taking place (probably not as bad as I imagine), plus you can then eliminate your problem parameter and do static_assert at the same time. I'm not sure I pieced together the Arduino pin rube goldberg machine correctly, but I think its close enough for the example.

 

The port translation is annoying, it's not great and it creates overhead (footprint and runtime) and trains people to accept it as ok. So I'm not a fan of it either.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:

truhlik_fredy wrote:

What you propose I should do so it will compile well in various versions of Arduino, Atmel studio and a generic distribution avr gcc toolchain?

 

I don't have the actual code here right now, but it works like this:

The objective is to obtain the address of an SFR like PORTB, for example, but you can't do any actual C++ operation on PORTB itself in any place that requires a constant expression, like inside a template argument or a constexpr function.

 

So what I did is:

  1. precompiler expands SFR macro (like PORTB)
  2. precompiler stringifies expanded SFR macro
  3. inside that string there will be the address somewhere, as ASCII, like "0x10" for example.
  4. That string is fed to a constexpr function that will extract and return the address as a uint16_t, accounting for cases where the SFR address is in I/O or memory, if required.
  5. The whole thing is wrapped in a macro like ADDRESS(x), so if you want the address of PORTB you do ADDRESS(PORTB)

 

It's quite ugly, but should work with any version of avr-gcc. The whole thing happens at compile time, so it doesn't generate any actual code, just a uint16_t with the address.

 

To be more specific, for example the board I selected will include the iom328p.h header, there I have PORT defined:

iom328p.h
#define PORTD _SFR_IO8(0x0B)

 

That then goes to sfr_defs.h where it will add to it offset (in my case 0x20 after the registers finished, but probably not all AVRs have the IOs at that offset)
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))

 

So the PORTD define ends up as (*(volatile uint8_t *)( 0x0B + 0x20))

You mean to use this string feature in macros?

https://gcc.gnu.org/onlinedocs/gcc-4.8.5/cpp/Stringification.html

 

Where will be gurantee that it will not break the moment somebody will update slightly the headers and it will be the slightly different syntax? What would happen they updated the macro to this:

#define _SFR_IO8(io_addr) _MMIO_BYTE(__SFR_OFFSET + (io_addr) )

 

Isn't it depending too much on the styling of code (which I have no control over) to not change at all?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:
Isn't it depending too much on the styling of code (which I have no control over) to not change at all?

 

Indeed it is. Still beats not working at all.

 

edit: in fact I never actually finished the code because I decided to use a different approach. The draft I have is very basic and works only for addresses of SFRs that are in the memory space, not I/O.

 

edit2:

These are the macros for stringification and a test:

#define STRINGIFY(content) #content
#define EXPAND_AND_STRINGIFY(input) STRINGIFY(input)

static_assert ( false, EXPAND_AND_STRINGIFY(PORTA) );

 

the part I never did get quit right is parsing the strings at compile time. I know it should be possible because of this:  https://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/

 

edit3: actually I found the rest of my draft code, so here it is a full example, for the ATmega4809:

#include <avr/io.h>

#define STRINGIFY(content) #content
#define EXPAND_AND_STRINGIFY(input) STRINGIFY(input)

constexpr uint8_t char2val(const uint8_t val){
    return 
        ( ( val >= '0' ) && ( val <= '9' ) ) ?
            ( val - '0' ) : (
        ( ( val >= 'A' ) && ( val <= 'F' ) ) ?
            ( val - 'A' + 10 ) : 0xFF );
}

constexpr uint16_t val(const char * const str, const uint16_t previous = 0){
    return ( char2val(str[0]) != 0xFF ) ? ( val(str+1, 16 * previous + char2val(str[0])) ) : ( previous );
}

constexpr uint16_t str2val(const char * const str){
    return
        ( (str[0] == '0') && ( ( str[1]=='x' ) || ( str[1]=='X' ) ) ) ?
            val(str + 2) : (
        ( (str[0] == '\0') || ( str[1]=='\0' ) ) ?
            0 :	str2val(str + 1) );
}

int main(void)
{
    PORT_t & portA = *(PORT_t*) str2val( EXPAND_AND_STRINGIFY( PORTA ) );
    portA.DIR = 0;
    return str2val( EXPAND_AND_STRINGIFY( PORTA ) );
}

Sorry for the lack of comments... This is meant for C++11 so constexpr functions can only have the return statement in their body. That's why they are somewhat weird and recursive.

The asm output is:

000000b2 <main>:
  b2:	10 92 00 04 	sts	0x0400, r1	; 0x800400 <__TEXT_REGION_LENGTH__+0x700400>
  b6:	80 e0       	ldi	r24, 0x00	; 0
  b8:	94 e0       	ldi	r25, 0x04	; 4
  ba:	08 95       	ret

 

I don't remember if I actually tested it as template argument, maybe it's lost somewhere in my drive.

Last Edited: Mon. Feb 10, 2020 - 04:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you very much. Once I had to even use the ##:

https://gcc.gnu.org/onlinedocs/gcc-7.5.0/cpp/Argument-Prescan.html#Argument-Prescan

 

These macros are sometimes like black magic, very toolchain vendor dependent (which probably the code now is anyway with a gcc specific asm inline), but sometimes unnecessary.

 

I will play with it in few days and see how the original library could be improved/fixed.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:
Indeed it is. Still beats not working at all.

 

With which toolchain version you can't build or run the original code I posted?

 

Which set of toolchain versions you would recommend to keep using/testing (to make sure mostly all quirks are covered)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:

ralphd wrote:

I haven't experimented much with touch sensing, but after looking at your code I think I may try making a simple touch library.  Inline functions may be a more readable  way of implementing it instead of resorting to templates.

https://gcc.gnu.org/onlinedocs/g...

 

I also think I could do it in straight C.  A loop like the following takes 4 cycles per itteration:

    uint8_t time = 0; do {
        time++;
    } while (PINB & (1<<TOUCH_GPIO));

I'd do the loop 16 times, and sum the time.  That would give an effective resolution of 1/4 cycle, and provide noise immunity/debounce.  I'd also try to report an intensity instead of on/off.  It shouldn't take me long to whip something up once I'm finished with a couple other projects.

 

 

What about the case when somebody will ground the pin, you have no timeout and will keep looping indefinitely. 

 

I use solder mask over my PCB touch pads.  How could someone ground the pin?

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ralphd wrote:
I use solder mask over my PCB touch pads.  How could someone ground the pin?

 

The library I linked can work with exposed wires and with isolated pads, so there are some extra checks bundled, can be scaled to dozen sensors without doing copy/paste and still it's faster.

 

Using the solder mask will protect the touchpad, but what about:

https://en.wikipedia.org/wiki/Whisker_(metallurgy)

 

And the counter left unprotected to overflow is itching me as well :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>Or do I miss something?

 

//tiny817 Xplained

 

avr0/1 pins are different.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:

With which toolchain version you can't build or run the original code I posted?

 

I use Atmel Studio as the IDE. The toolchains I currently have installed are

 

This is a simple test code:


#include <avr/io.h>

constexpr uintptr_t PortB() {
    return (uintptr_t)&PORTB;
}

template <uintptr_t data>
uint8_t example() {
    return data;
}

int main(void)
{
    return example<PortB()>();
}

 

I compiled for both ATmega328P and ATmega4809. They both have the PORTB macro. So here is what happens:

 

  • 5.4.0 compiles for both targets
  • 7.3.0 compiles for ATmega328P, fails for ATmega4809. This is because the PORTB macro is defined in a different way for each, for m328P is a volatile uint8_t, but for m4809 is a structure containing several volatile uint8_t
  • 10.0.0 fails for both, this is the behaviour required by standard compliance

 

The error reported by v. 10.0.0 goes like this for m328P:

 error: no matching function for call to 'example<PortB()>()'
           21 |  return example<PortB()>();
              |                          ^
 info: candidate: 'template<unsigned int data> uint8_t example()'
           15 | uint8_t example() {
              |         ^~~~~~~
 info:   template argument deduction/substitution failed:
 error:   in 'constexpr' expansion of 'PortB()'
 error: 'reinterpret_cast<volatile uint8_t* {aka volatile unsigned char*}>(37)' is not a constant expression
           11 |  return (uintptr_t)&PORTB;

the important part is the one I highlighted in red, that results from the macro expansion. In terms of C++, the normal cast from C is actually a reinterpret_cast which can't occur in a constexpr function according to the standard.

 

So if you want your code to be compliant, you'll have to manage for it to compile with v. 10.0.0 without errors. I think it's possible to install a different toolchain in the Arduino IDE manually, so if you feel adventurous give it a try.

Last Edited: Mon. Feb 10, 2020 - 11:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
m4809 is a structure containing several volatile uint8_t

 

That could be handled by different ifdefs, was not aware of this, not sure if any Arduino uses this cpu but it wouldn't harm to have it supported, by any chance do you know if any other CPUs have atypical port definitions?

 

It would be so much easier if they would just give me the offset as defined literal and let me do stuff with it on my own :/

 

The gcc 10 sounds pretty new, I was in the mindset the avr was behind and might stay behind as the 8-bit not really benefit from the c++ fanciness and that is usually the significant change. Funny enough with riscv I use gcc 9 and that architecture is considered bleeding-edge :)

 

I will leave the Arduino vanilla as it is to test that expierence, but I can make myself makefiles and Docker containers with other gcc versions so I will be able to do batch builds without touching that ugly UI :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:
not sure if any Arduino uses this cpu

 

https://store.arduino.cc/arduino-uno-wifi-rev2

https://store.arduino.cc/arduino-nano-every

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

edit-

Never mind. Wrong compiler version.

Last Edited: Tue. Feb 11, 2020 - 09:46 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

edit-

Never mind. Wrong compiler version.

 

You didn't have to delete it, yes it gave me hopes and whole day I was waiting to try it out. And I was doing a similar enum approach when I was struggling to get the templates parameter into the assembly. I really like the progression, slowly from not possible, to possible, from fast to even faster and doing improvements over time. I will do the bit static assertion you mentioned, that is good addition. Will keep messing around, if I will find some improvement I will update the thread.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think this was it-

 

https://godbolt.org/z/R8EfZE

 

then realized I had 5.4 set as the compiler, and figured the code didn't help the cause.

 

 

I am not a language lawyer (or a law abiding programmer), so normally just go by who has the final say- the compiler authors. For my own c++ code, I have chosen enums to be the placeholders for register values and use for many function arguments (I don't use manufacturer's headers for c++). The main reason was I never liked dealing with pointer addition and all the rest, and usually dislike the manufactures headers because of the overly verbose/all caps/many underscore nature which I find hard to read. Sorta like my posts minus the all caps/many underscore. The benefit of enums is they convert to other things when needed without a fight, and can carry a type with them so are useful as function arguments where the type takes care of the compile time checking and then have no need to assert valid values. 

Last Edited: Wed. Feb 12, 2020 - 12:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, it doesn't matter what you throw at it, neither constexpr functions, nor enums, no form of constant expression can penetrate the impervious anti reinterpret_cast shield implemented in recent avr-gcc versions.

And even if you could find something that can, it would be considered a bug and fixed later.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
And even if you could find something that can, it would be considered a bug and fixed later.

 

I'm kinda annoyed, but that is the reality. And when thinking about it it's good, we need to trust the compiler and it shouldn't break, and depending on something broken is not great.

It just would be easier when the avr defines were made there would be one without the cast, just a literal number offset. Probably they were not expecting it would cause these problems, or that they would be used in this scenario. Maybe if the defines would get updated and contain redundant macros to allow these shenanigans.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's what I came up with after a few hours of work.  The touch sense part takes only 40 bytes, so there's lots of room left even on an ATtiny13.

https://github.com/nerdralph/ner...

I tested it with a short 24AWG wire (stripped of insulation at the end), and also with a 5cm x 5cm insulated metal plate.

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

Last Edited: Thu. Feb 13, 2020 - 06:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ralphd wrote:

Here's what I came up with after a few hours of work.  The touch sense part takes only 40 bytes, so there's lots of room left even on an ATtiny13.

https://github.com/nerdralph/ner...

I tested it with a short 24AWG wire (stripped of insulation at the end), and also with a 5cm x 5cm insulated metal plate.

 

 

Why you used  goto forever; instead of a while loop?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:

ralphd wrote:

Here's what I came up with after a few hours of work.  The touch sense part takes only 40 bytes, so there's lots of room left even on an ATtiny13.

https://github.com/nerdralph/ner...

I tested it with a short 24AWG wire (stripped of insulation at the end), and also with a 5cm x 5cm insulated metal plate.

 

 

Why you used  goto forever; instead of a while loop?

 

I like goto.  Maybe because the first high-level language I learned was BASIC.

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

truhlik_fredy wrote:

ralphd wrote:

Here's what I came up with after a few hours of work.  The touch sense part takes only 40 bytes, so there's lots of room left even on an ATtiny13.

https://github.com/nerdralph/ner...

I tested it with a short 24AWG wire (stripped of insulation at the end), and also with a 5cm x 5cm insulated metal plate.

 

 

Why you used  goto forever; instead of a while loop?

 

I just made a big improvement to the timing resolution.  It now calculates the rise time to within 1 cycle.  And the code is smaller by 6 bytes.

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ralphd wrote:
It now calculates the rise time to within 1 cycle.

 

getting rid of 'magic' code to force something into assembly (i would say when you have to resort to such things, then asm is better). So now it looks cleaner.

 

Interesting, switching between high-z and pull-up, but doing it so often looks odd to me a bit. And the moment you will want 4 of these sensors, you will copy/paste the code and redo the defines? Yes the size is small, but that was for me the smallest priority, mine library was not intended to go into a tiny, more like mega8 and bigger. I rather spend more footprint and have higher resolution counter and overflow/timeout checks etc..