Electronic – arduino – Direct port manipulation gives random result: Delay necessary

arduinoatmega328p

Setup (Arduino Pro Mini ATmega328p 16MHz, 5V, no pin connected):

Photo of the Arduino on a breadboard, with FTDI attached

When called from the setup(), this works:

pinMode(11, INPUT_PULLUP);
bool identifiesAsFast = digitalRead(11); // always true

This doesn't:

const uint8_t identificationPinBitNumber = PB3; // pin 11
const byte identificationPinBit = bit(identificationPinBitNumber);
DDRB &= ~identificationPinBit;
PORTB |= identificationPinBit;
bool identifiesAsFast = PINB & identificationPinBit; // occasionally false

Pin 11 is not connected, but – as you can see above – I have it pulled high. So I would expect it to always have a value of 1.

What am I missing when doing direct port manipulation?

I get the behavior with four different Arduino Pro Minis. All are original Pro Minis, designed by Sparkfun, made in the USA and bought from Digi-Key. One of them I pulled freshly out of the original bag. I may have to remove and insert the FTDI adapter around thirty times, but eventually I am able to reproduce the issue.

Adding a delay of 100 ms in between pulling pin 11 high and reading it seems to resolve the issue. However, I don't understand why that delay would be necessary.

PB3 for pin 11 is correct, as I verified by connecting an LED and making it blink. See also the pin mapping diagram which can be found elsewhere on the web:

Pin mapping

Full code:

// Felix E. Klee <felix.klee@inka.de>

const uint8_t ledPinBitNumber = PB5; // pin 13
const byte ledPinBit = bit(ledPinBitNumber);

const uint8_t identificationPinBitNumber = PB3; // pin 11
const byte identificationPinBit = bit(identificationPinBitNumber);

unsigned long delayDuration; // ms

void turnLedOn() {
  PORTB |= ledPinBit;
}

void turnLedOff() {
  PORTB &= ~ledPinBit;
}

void setDelayDuration() {
#if 0
  pinMode(11, INPUT_PULLUP);

  bool identifiesAsFast = digitalRead(11); // always true
#else
  // Input pullup:
  DDRB &= ~identificationPinBit;
  PORTB |= identificationPinBit;

  bool identifiesAsFast = PINB & identificationPinBit; // occasionally false
#endif

  delayDuration = identifiesAsFast ? 300 : 3000;
}

void setup() {
  DDRB |= ledPinBit; // Set up LED pin for output
  setDelayDuration();
}

void loop() {
  turnLedOn();
  delay(delayDuration);
  turnLedOff();
  delay(delayDuration);
}

Best Answer

As weird as this is going to sound, your code is too fast.

The pin (and the part of the breadboard that it's plugged into) have some capacitance. It takes time for the resistive pullup in the AVR to pull the pin high.

The Arduino pinMode() and digitalRead() functinons are slow enough that, by the time they get around to reading the pin, the pullup has done its job. Your code is accessing registers directly, which is considerably faster -- so it's possible for the pin to still be low when you read it.

The amount of delay you need is pretty minimal, though. 50 microseconds (not milliseconds!) should be sufficient.