Internal pull up and pull down resistors on teensy 3

cmason

Well-known member
I'm simply trying to drive buttons off digital pins. I have the buttons wired to 3.3V through a 1k resistor and then each button leads to a different pin. I'm then trying to use the internal pulldown to pull the pin down to ground when the button is open (to avoid artifacts as described here). I'm basically doing:

Code:
static const uint8_t buttonPins[MAX_BUTTONS] =  {2, 3, 4, 16, 17, 18};
for (int i = 0; i < MAX_BUTTONS; ++i) {
    uint8_t pin = buttonPins[i];
    pinMode(pin, INPUT);
    *portConfigRegister(pin) = PORT_PCR_MUX(1) | PORT_PCR_PE;
    attachInterrupt(pin, CHANGE);
}

Pins 4,16,17,18 work as I expect. Pins 2 and 3 do not. When I attach a meter between pin 2 and ground with the button open, I see ~ 1V. Between 4 and ground, I see low millivolts. With the buttons closed I always see 3.3 V. I've tried a number of different combinations of pins, and always see the same behavior: a few of the lower number pins don't work as expected.

My understanding is that enabling the internal pulldown should act exactly like connecting a ~20k resistor between that pin and ground. Is this correct?

I'm not at all an electronics person so it's quite possible something else is going on. I know it's not the buttons because I've tested them with a multimeter and they read no connection open and ~ 1 ohm closed.

The complete code is here (it's more complex, involving analog read on a slider pot), etc.

I'm sure this is something really stupid I'm doing. Makes me feel dumb. Any help would be most appreciated.

-c
 
I don't think there is an internal pulldown on the I/Os on Teensy 3, but there is an internal pull-up. This is in fact easier to use because one end of each button can be grounded.

So, do in setup() {...
pinMode(15, INPUT_PULLUP);
digitalWrite(15, HIGH);

and connect the button from 15 (in this example) to ground. You don't need a 1 k resistor, but if the buttons go off board it may be useful as protection for the CPU -- just put the R in series with the pin15 connection. You don't need a pullup (the pin provides it).

The internal pullup is about 33 kohm (100 uA).
 
Yeah, this had nothing to do with pull up or pull down. Just a stupid bug.

For posterity: the teensy 3 does indeed have both internal pull up and pull down resistors. Both seem to work ok. Put another way: you can either

  • connect the button between ground and the pin and use the internal pull up
    Code:
    pinMode(n, INPUT_PULLUP);
    or
  • connect the button between 3.3V and the pin and use the internal pull down
    Code:
    *portConfigRegister(pin) = PORT_PCR_MUX(1) | PORT_PCR_PE;.

This latter felt more natural to me (why would an input pin source voltage?) but I understand that they're basically equivalent.

-c
 
Paul,

Is there any plan to have INPUT_PULLDOWN functionality similar to the INPUT_PULLUP?

Keith
 
You can just use the code I posted above:

Code:
*portConfigRegister(pin) = PORT_PCR_MUX(1) | PORT_PCR_PE;

-c
 
I was trying to get that set up last night but I was running into issues with Windows saying the USB device couldn't be recognized anymore.

It still seems to recognize it in that I can reflash Blink back to it and it works but a very simple app with just your code above builds but Windows doesn't like it after my Teensy 3.0 reboots. Any ideas there?

Also, do I need this bit?
Code:
pinMode(pin, INPUT);

Also, this part didn't compile...
Code:
attachInterrupt(pin, CHANGE);

I changed it to:
Code:
attachInterrupt(pin, changed, CHANGE);
// ...
void changed() {
    // TODO: Implement this. How to know which pin changed though?
}

For reference, this is what I see the moment I build and upload with that portConfigRegister snippet:
N6WdVj7.png
USB device not recognized
The last USB device you connected to this computer malfunctioned, and Windows does not recognize it.
 
Last edited:
You can just use the code I posted above:

Code:
*portConfigRegister(pin) = PORT_PCR_MUX(1) | PORT_PCR_PE;

-c

FWIW, the above is bad code, as it will overwrite other bits in the register.
The proper way is
Code:
    *portConfigRegister(pin) |= PORT_PCR_PE; //pull enable
    *portConfigRegister(pin) &= ~PORT_PCR_PS; //pull down

This is an old thread, but I do not see INPUT_PULLDOWN implemented (at least in my 1.20 teensyduino install).
To implement pulldown
add this define to core_pins.h, around line 43
Code:
#define INPUT_PULLDOWN 3
change pinMode function in pins_teensy.c to
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
	volatile uint32_t *config;

	if (pin >= CORE_NUM_DIGITAL) return;
	config = portConfigRegister(pin);

	if (mode == OUTPUT) {
		*portModeRegister(pin) = 1;
		*config = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);
	} else {
		*portModeRegister(pin) = 0;
		if (mode == INPUT) {
			*config = PORT_PCR_MUX(1);
		} else if (mode == INPUT_PULLUP) {
			*config = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; // pullup
		} else {
			*config = PORT_PCR_MUX(1) | PORT_PCR_PE; // pulldown
		}
	}
}
 
Last edited:
another variation that does not override pullup and open drain setting.

change in core_pins.h
Code:
#define INPUT            0
#define OUTPUT           1
#define INPUT_PULLUP	 2
#define INPUT_PULLDOWN   3
#define OUTPUT_OPENDRAIN 4

change in pinmode in pins_teensy.c
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
	volatile uint32_t *config;

	if (pin >= CORE_NUM_DIGITAL) return;
	config = portConfigRegister(pin);

        if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
		*portModeRegister(pin) = 1;
		__disable_irq();
		*config &= ~PORT_PCR_MUX_MASK;
		*config |= (PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1));
                if (mode == OUTPUT_OPENDRAIN) {
		    *config |= PORT_PCR_ODE;
		} else {
		    *config &= ~PORT_PCR_ODE;
                }
		__enable_irq();
	} else if (mode == INPUT || mode == INPUT_PULLUP || mode == INPUT_PULLDOWN){
		*portModeRegister(pin) = 0;
		__disable_irq();
		*config &= ~PORT_PCR_MUX_MASK;
                *config |= PORT_PCR_MUX(1);
		if (mode == INPUT_PULLUP) {
		    *config |= (PORT_PCR_PE | PORT_PCR_PS); // pullup
		} else if (mode == INPUT_PULLDOWN) {
		    *config |= (PORT_PCR_PE); // pulldown
		    *config &= ~(PORT_PCR_PS);
		}
                __enable_irq()
	}
}
 
Last edited:
Would be very nice to have in Teensyduino

I'll probably add INPUT_PULLDOWN. Right now I'm focused on compatibility, fixing bugs, and porting libraries.

another variation that does not override pullup and open drain setting.

change in core_pins.h
Code:
#define INPUT            0
#define OUTPUT           1
#define INPUT_PULLUP	 2
#define INPUT_PULLDOWN   3
#define OUTPUT_OPENDRAIN 4

change in pinmode in pins_teensy.c
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
	volatile uint32_t *config;

	if (pin >= CORE_NUM_DIGITAL) return;
	config = portConfigRegister(pin);

        if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
		*portModeRegister(pin) = 1;
		__disable_irq();
		*config &= ~PORT_PCR_MUX_MASK;
		*config |= (PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1));
                if (mode == OUTPUT_OPENDRAIN) {
		    *config |= PORT_PCR_ODE;
		} else {
		    *config &= ~PORT_PCR_ODE;
                }
		__enable_irq();
	} else if (mode == INPUT || mode == INPUT_PULLUP || mode == INPUT_PULLDOWN){
		*portModeRegister(pin) = 0;
		__disable_irq();
		*config &= ~PORT_PCR_MUX_MASK;
                *config |= PORT_PCR_MUX(1);
		if (mode == INPUT_PULLUP) {
		    *config |= (PORT_PCR_PE | PORT_PCR_PS); // pullup
		} else if (mode == INPUT_PULLDOWN) {
		    *config |= (PORT_PCR_PE); // pulldown
		    *config &= ~(PORT_PCR_PS);
		}
                __enable_irq()
	}
}

Something like that would be very nice to have in Teensyduino!

_Markk
 
Anything new on this functionality? The pins_teensy.c has become a bit more complicated in the last year and I am unsure of how to implement this.. Can Paul add this? Is there a reason it is not added?

Keithg
 
It's basically not changed.

Do Markk's change to the core_pins.h

And change pinMode() in pins_teensy.c to:
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
	volatile uint32_t *config;

	if (pin >= CORE_NUM_DIGITAL) return;
	config = portConfigRegister(pin);

	if (mode == OUTPUT) {
#ifdef KINETISK
		*portModeRegister(pin) = 1;
#else
		*portModeRegister(pin) |= digitalPinToBitMask(pin); // TODO: atomic
#endif
		*config = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);
	} else {
#ifdef KINETISK
		*portModeRegister(pin) = 0;
#else
		*portModeRegister(pin) &= ~digitalPinToBitMask(pin);
#endif
		if (mode == INPUT || mode == INPUT_PULLUP || mode == INPUT_PULLDOWN) {
			*config = PORT_PCR_MUX(1);
			if (mode == INPUT_PULLUP) {
		    	*config |= (PORT_PCR_PE | PORT_PCR_PS); // pullup
			} else if (mode == INPUT_PULLDOWN) {
			    *config |= (PORT_PCR_PE); // pulldown
			    *config &= ~(PORT_PCR_PS);
			}
		} else {
			*config = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; // pullup
		}
	}
}

Perhaps one of us should make a pull request so Paul can update this easily?

EDIT -
I have done so
 
Last edited:
Back
Top