T4.0 ISR Rising Edge Voltage Trigger Level?

AshPowers

Well-known member
Automotive digital tachometer signal - 0-5v square wave - rising edge at each cylinder TDC on ignition stroke. 6 cylinder engine will produce 3 rising edges per crankshaft rotation (each 4-cycle cylinder only fires every 720 degrees).

8000RPM Max, 400Hz tach signal peak.

(Yes, I am level shifting this signal down to a 0-2v signal)

On the scope the signal going into the T4 on pin A5 is clean. The tach signal idles at 0V with ~37mV of noise. This noise does NOT trigger the ISR. The pulsewidth is ~3ms @ 2V.

In my ISR, the only ISR being used in this setup: (I have simplified the code below) RPMPulse1, RPMPulse2, and RPM are all declared as volatile float.
Code:
SETUP:
attachInterrupt(digitalPinToInterrupt(AuxIn), REFRISE, RISING);

void REFRISE() {
  RPMPulse1 = micros() - RPMPulse2;
  RPMPulse2 = micros();
  RPM = 20000000 / RPMPulse1;
  }

In my main loop I am performing other tasks such as reading A/D inputs for two other sensors, controlling a PWM output to a solenoid, and driving an ILI9341 TFT with XPT2046 touchscreen to display data, etc. This process loop cycles ~43Hz.

Here is a short stream result of the data I am getting from this:

inf
1775.57
1775.57
inf
1816.86
1775.57
inf
1775.57
1775.57
inf
inf

Obviously divide by zero is occuring which I have confirmed that the RPMPulse1 is sometimes returning a value of 0. I suspect this is because the ISR is getting triggered nearly back to back by noise or??

I could write in a small bit of patch code to prevent this /0 from happening but I'd rather figure out what is really going on here.

After looking more closely at the signal while it is high, there is a bit of noise in it with occasional small ~40mv peaks at the end just before dropping low. The occurrence of these little peaks seems to be similar to how often I get this /0...

However, the micros() timebase should still report a difference in time, albeit somewhere in the 1-2ms range, but it is not, so I am not inclined to think that this small ripple has anything to do with it.

What is the criteria for the signal to be registered as a rising edge?
 
Last edited by a moderator:
Does it change anything if you add this line?
Code:
void REFRISE() {
  RPMPulse1 = micros() - RPMPulse2;
  RPMPulse2 = micros();
  RPM = 20000000 / RPMPulse1;
  [B][COLOR=#ffa500]asm("dsb");[/COLOR][/B]
  }

also, make sure that the variables are declared volatile
 
Automotive digital tachometer signal - 0-5v square wave - rising edge at each cylinder TDC on ignition stroke. 6 cylinder engine will produce 3 rising edges per crankshaft rotation (each 4-cycle cylinder only fires every 720 degrees).

8000RPM Max, 400Hz tach signal peak.

(Yes, I am level shifting this signal down to a 0-2v signal)


The input voltages required are < 0.99V for a logic LOW and > 2.31V for a logic HIGH, as the datasheet
gives these as 0.3 and 0.7 times the supply.

2V is not enough. Try level shifting down to 3.3V, not 2.0V
 
I'm very interested in this thread...I have a similar tach setup where I'm reading a IR LED sensor to measure reflective tape on a flywheel. Generally the system works well but every once in a while I'll get a multiple pulses (false reads) when according to the scope, I see no extra pulses for each read (no bouncing). I'm using a teensy 3.2 connected to a fairly stable power source.

@FrankB, what does asm("dsb"); do?

To mimimize my problems with getting false reads i have done the following....

1. added hardware debouncing (a 0.1 uf Cap from signal to 3v3 (my rpm is <600 so this value seems to work)
2. added software debouncing (if last read is > some value) consider it a legitimate pulse
3. DONT LAUGH but i also added a line in the ISR to check if digitalRead(pin) == LOW, then it's a pulse (my sensor goes low for a pulse)

#3 seemed to be the idea that cleaned out most of the false reads, but i'll look at asm("dsb"); to see if it helps.

Keeping an eye on this thread, maybe my "fixes" will help others....however i'd really like to know why the false reads.
 
"dsb" makes sure the interrupt code doesn't exit only to re-enter because it exited before the response to it was recorded.

Here is a tech'ish description I saw recently posted here ?
The DSB instruction is a memory barrier, ensuring that the following code will not be executed before the previous memory operations complete (apparently it can be pretty common to finish the execution of the interrupt handler, before that clear actually getting through, due to differences in the clock rates between different systems)
 
OK Guys, I FINALLY figured out how to completely eradicate the false triggers on the ISRs... You MUST declare the pinmode with either a PULLUP or PULLDOWN!!

<code>
pinMode(REF1, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(REF1), REFRISE, RISING);
</code>

As soon as I did this, I am getting ZERO false hits.

This should be put in the documentation somewhere. Nothing I read regarding this stated that you must use a pull in either direction.. :-(!!!
 
Good that you've figured out a fix.

I wouldn't point the finger at the documentation when really this is a fix for your specific setup. Input_pullup adds, as you probably know, a pullup resistor to the input to 3v3, and has changed the processing of your signal. What this suggests to me is that the signal was being driven badly - as markT suggested. The voltages between a logic high and a logic low are called the uncertainty region because you can't quite be sure which logic they're going to be read as. It's quite possible that you could trigger the interrupt repeatedly.

If you want to look at it further, why don't you pop a scope on the teensy pins and see how INPUT_PULLUP has affected the signal? You could also try changing your level shifting to hit 3V3.
 
I changed the resistor divider on my circuit to level shift the signal from 0-3.3V. That didn't fix the problem nor did it change the "shape" of the signal other than it was peaking at 3.3V rather than 2.0v. (checked on my scope). I wasn't having an issue with it NOT triggering the ISR even at 0-2v. It was falsely reporting the trigger with a zero time elapsed.

Good that you've figured out a fix.

I wouldn't point the finger at the documentation when really this is a fix for your specific setup. Input_pullup adds, as you probably know, a pullup resistor to the input to 3v3, and has changed the processing of your signal. What this suggests to me is that the signal was being driven badly - as markT suggested. The voltages between a logic high and a logic low are called the uncertainty region because you can't quite be sure which logic they're going to be read as. It's quite possible that you could trigger the interrupt repeatedly.

If you want to look at it further, why don't you pop a scope on the teensy pins and see how INPUT_PULLUP has affected the signal? You could also try changing your level shifting to hit 3V3.
 
Well clearly the pullup resistor is changing something, what are the resistor values you're using for the divider compared to the internal pullup? Have you looked at the signal with and without this pull-up?

It was falsely reporting the trigger with a zero time elapsed.

is inaccurate. What you mean to say is "the ISR was producing an output in a manner that I did not want it to". The difference is important because the ISR cannot falsely be triggered, but we might not want it to be triggered when it is (or the converse).
The difference between INPUT and INPUT_PULLUP, is one bit in one register, that in turn closes one gate to connect a resistive path between the input and the 3v3 power rail. It doesn't change the hardware beyond that, nor does it affect how the interrupt is handled from hardware, into software, and then to your output. Going down the rabbit hole of why that one change is getting rid of the behaviour you don't want, if you have the time and inclination, might reveal something about your setup that's useful later on.
 
Heh, you must not have much experience with ISRs then... I'm not the only one having the issue with false ISR triggers.

I have spent 4 days doing nothing other than studying this problem. Trying EVERYTHING you can possibly imagine to correct the issue.

I implore you to setup a T4.0 with an ISR and you can generate as clean of a signal as you want to feed into an input and tell me you don't get false triggers if you aren't using the pullup/pulldown....

It....just....will....not....work....without....it....

Well clearly the pullup resistor is changing something, what are the resistor values you're using for the divider compared to the internal pullup? Have you looked at the signal with and without this pull-up?



is inaccurate. What you mean to say is "the ISR was producing an output in a manner that I did not want it to". The difference is important because the ISR cannot falsely be triggered, but we might not want it to be triggered when it is (or the converse).
The difference between INPUT and INPUT_PULLUP, is one bit in one register, that in turn closes one gate to connect a resistive path between the input and the 3v3 power rail. It doesn't change the hardware beyond that, nor does it affect how the interrupt is handled from hardware, into software, and then to your output. Going down the rabbit hole of why that one change is getting rid of the behaviour you don't want, if you have the time and inclination, might reveal something about your setup that's useful later on.
 
It is more than one bit:
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
    const struct digital_pin_bitband_and_config_table_struct *p;

    if (pin >= CORE_NUM_DIGITAL) return;
    p = digital_pin_to_info_PGM + pin;
    if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
        *(p->reg + 1) |= p->mask; // TODO: atomic
        if (mode == OUTPUT) {
            *(p->pad) = IOMUXC_PAD_DSE(7);
        } else { // OUTPUT_OPENDRAIN
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_ODE;
        }
    } else {
        *(p->reg + 1) &= ~(p->mask); // TODO: atomic
        if (mode == INPUT) {
            *(p->pad) = [COLOR=#0000ff]IOMUXC_PAD_DSE(7);[/COLOR]
        } else if (mode == INPUT_PULLUP) {
            *(p->pad) = [COLOR=#0000ff]IOMUXC_PAD_DSE(7)[/COLOR] [COLOR=#008000]| IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | [/COLOR][COLOR=#008000]IOMUXC_PAD_HYS[/COLOR];
        } else if (mode == INPUT_PULLDOWN) {
            *(p->pad) = [COLOR=#0000ff]IOMUXC_PAD_DSE(7)[/COLOR] [COLOR=#008000]| IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(0) | [/COLOR][COLOR=#008000]IOMUXC_PAD_HYS;[/COLOR]
        } else { // INPUT_DISABLE
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_HYS;
        }
    }
    *(p->mux) = 5 | 0x10;
}

...but 2V are still on the edge. I bet with 3V the additional settings play no big role anymore.

Can you post the crcuit? Including the part before your voltage divider.
 
Frank,
I figured it would be something along those lines as to what the difference is... I just wasn't sure where to look for it.. :)
 
Oh wow, the answer's right there!
I confess I've yet to use a T4, which is why I've never seen this before. If you crack open pins_teensy.c in the T3 core files and compare it to pins_digital.c (as frank has shown above) the difference shows itself.
Yes my changing a single bit statement is an oversimplification, the T4 is a bit more complicated, so getting a pullup resistor enabled is 4 bits:

PAD_PKE, Pull/Keep Enable feild, sets whether the pin will have a pull resistor or keep function enabled. When this is 0 the next registers have no effect
PAD_PUE, Pull/Keep select feild, sets whether the keeper will be used (0 value) or the pull (1 value)
PAD_PUS, pull up/down config feild, are two bits that set whether the pull is up or down, and 100k or 47k

For comparison, the difference on a T3 is one bit to enable the pull, and one to direct it.
the final register is the kicker!

PAD_HYS, Hyst. Enable Field, changes whether the input is CMOS, or Schmitt triggered.
Hysteresis, for anyone who doesn't know, is a method that gets rid of the noise by changing a threshold voltage depending on the state. Effectively, once the signal goes high, it must drop by more than X mV before it will be considered low again. Values from the manual suggest this is 2.5mV or 5 depending on the setting. This stops multiple triggers as a signal crosses a threshold (exactly as it's done for you) as if the receiver is fast enough and sensitive enough it can be difficult to produce a clean result.
It's also in my opinion, very difficult to observe using a scope noise below a few 10s of mV as my scope has that much noise as it's floor, so unless someone has high-grade equipment (or at least higher than mine) the difference between a signal that does and doesn't cause multiple triggers might be un-observable.

So it's not the pullup resistor having an effect, but rather there's an extra register being set to enable the Schmitt when a pull-up is set. I don't think this is the right behaviour, I actually think the Schmitt should be enabled by default as I can't really see a scenario where a user would want not to have this, but I can see cases where someone would not want to enable a pull resistor. Ergo, I think pins_digital.c should be changed to
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
    const struct digital_pin_bitband_and_config_table_struct *p;

    if (pin >= CORE_NUM_DIGITAL) return;
    p = digital_pin_to_info_PGM + pin;
    if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
        *(p->reg + 1) |= p->mask; // TODO: atomic
        if (mode == OUTPUT) {
            *(p->pad) = IOMUXC_PAD_DSE(7);
        } else { // OUTPUT_OPENDRAIN
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_ODE;
        }
    } else {
        *(p->reg + 1) &= ~(p->mask); // TODO: atomic
        if (mode == INPUT) {
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_HYS; // change is here!!
        } else if (mode == INPUT_PULLUP) {
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
        } else if (mode == INPUT_PULLDOWN) {
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(0) | IOMUXC_PAD_HYS;
        } else { // INPUT_DISABLE
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_HYS;
        }
    }
    *(p->mux) = 5 | 0x10;
}

if you'd like to make that change and see if it works without the pullup now, I think that confirms what the problem was and maybe someone can decide if it should be amended.
 
Last edited:
It's also in my opinion, very difficult to observe using a scope noise below a few 10s of mV as my scope has that much noise as it's floor, so unless someone has high-grade equipment (or at least higher than mine) the difference between a signal that does and doesn't cause multiple triggers might be un-observable.

The 'scope probe's capacitance would probably remove most of the noise anyway, even on x10....
 
1/400Hz is 2.5ms, 2500us or 1500000 cycles on a 600MHz machine.

You could do some software debouncing by ignoring too-fast changes:

Code:
void REFRISE() {
  RPMPulse1 = micros() - RPMPulse2;
  if (RPMPulse1 >10  ){  // debounce for 10uS 
    RPMPulse2 = micros();
    RPM = 20000000 / RPMPulse1;
  }
}

I was getting spurious counts with a reed switch and added some software debouncing in the tach code here: https://forum.pjrc.com/threads/6702...ce-vs-Uno-pinmode()-detaches-attachInterrupt()
 
Coming back around to this.. I have not been able to locate a file by the name you state... pins_digital.c does not exist on my system whatsoever. I've been dealing with the hysteresis issue by using pullups and pulldowns but now I have a setup that I need neither! Please fill me in on what the file is named. Thanks!!

Oh wow, the answer's right there!
I confess I've yet to use a T4, which is why I've never seen this before. If you crack open pins_teensy.c in the T3 core files and compare it to pins_digital.c (as frank has shown above) the difference shows itself.
Yes my changing a single bit statement is an oversimplification, the T4 is a bit more complicated, so getting a pullup resistor enabled is 4 bits:

PAD_PKE, Pull/Keep Enable feild, sets whether the pin will have a pull resistor or keep function enabled. When this is 0 the next registers have no effect
PAD_PUE, Pull/Keep select feild, sets whether the keeper will be used (0 value) or the pull (1 value)
PAD_PUS, pull up/down config feild, are two bits that set whether the pull is up or down, and 100k or 47k

For comparison, the difference on a T3 is one bit to enable the pull, and one to direct it.
the final register is the kicker!

PAD_HYS, Hyst. Enable Field, changes whether the input is CMOS, or Schmitt triggered.
Hysteresis, for anyone who doesn't know, is a method that gets rid of the noise by changing a threshold voltage depending on the state. Effectively, once the signal goes high, it must drop by more than X mV before it will be considered low again. Values from the manual suggest this is 2.5mV or 5 depending on the setting. This stops multiple triggers as a signal crosses a threshold (exactly as it's done for you) as if the receiver is fast enough and sensitive enough it can be difficult to produce a clean result.
It's also in my opinion, very difficult to observe using a scope noise below a few 10s of mV as my scope has that much noise as it's floor, so unless someone has high-grade equipment (or at least higher than mine) the difference between a signal that does and doesn't cause multiple triggers might be un-observable.

So it's not the pullup resistor having an effect, but rather there's an extra register being set to enable the Schmitt when a pull-up is set. I don't think this is the right behaviour, I actually think the Schmitt should be enabled by default as I can't really see a scenario where a user would want not to have this, but I can see cases where someone would not want to enable a pull resistor. Ergo, I think pins_digital.c should be changed to
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
    const struct digital_pin_bitband_and_config_table_struct *p;

    if (pin >= CORE_NUM_DIGITAL) return;
    p = digital_pin_to_info_PGM + pin;
    if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
        *(p->reg + 1) |= p->mask; // TODO: atomic
        if (mode == OUTPUT) {
            *(p->pad) = IOMUXC_PAD_DSE(7);
        } else { // OUTPUT_OPENDRAIN
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_ODE;
        }
    } else {
        *(p->reg + 1) &= ~(p->mask); // TODO: atomic
        if (mode == INPUT) {
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_HYS; // change is here!!
        } else if (mode == INPUT_PULLUP) {
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
        } else if (mode == INPUT_PULLDOWN) {
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(0) | IOMUXC_PAD_HYS;
        } else { // INPUT_DISABLE
            *(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_HYS;
        }
    }
    *(p->mux) = 5 | 0x10;
}

if you'd like to make that change and see if it works without the pullup now, I think that confirms what the problem was and maybe someone can decide if it should be amended.
 
Coming back around to this.. I have not been able to locate a file by the name you state... pins_digital.c does not exist on my system whatsoever. I've been dealing with the hysteresis issue by using pullups and pulldowns but now I have a setup that I need neither! Please fill me in on what the file is named. Thanks!!

Teensy3 has pinMode in : cores\teensy3\pins_teensy.c

Teensy4 has pinMode in : cores\teensy4\digital.c
 
This thread helped my particular problem a great deal. Now on to the "other fish to fry"!

MANY thanks to all!
 
Back
Top