Uno project, Pin alwasy reads high -- WARNING: This one is long.

I'll post my sketch and electronics, because that's the best way to get answers, right? Well, that's gonna make this a long post.

THE PROBLEM IN A NUTSHELL: Pin 4 always reads high, and a multimeter always reads it at exactly 5V. I'I'm virtually certain my circuit is not damaging that pin, because I've used several UNO boards, different manufacturers and get the same results in every case. I even bought a new one, and immediately got the same result.

I'm at a loss. Don't know if it's the circuit or code, but both look right to me. Overall, it looks as if the 5V is coming from the Uno itself.

THE PROJECT: The UNO is in a car, which is used by driving instructors whom I employ in my driving school. (Yes, it has a clean 9V power supply.) The vehicle is equipped with several cameras and a DVR to record video. The UNO's job is to control power to the DVR under certain circumstances. Specifically, via two relays, it is powered up immediately when either a door is opened, or the car's key is turned. Almost the first thing it does is activate a relay (which I call "HoldRelay" in code) that holds power to itself. As such, if the key is removed and all doors are closed, it will still have power until it chooses to deactivate that relay, thereby cutting its own power.

Further, the project includes a small 128x64 display and a corresponding button. When the button is pushed, it allows an instructor to select 30,60,90, or 120 minutes of NOT recording video, but it is supposed to automatically reenable after the specified time. (I did not want to give employees the ability to just turn it off, because they will conveniently "forget" to turn it back on.)

Since I intend to do this for 6 cars, I have had a custom PCB manufactured, (essentially a custom shield). I've linked a screen shot of its layout here:

The board includes:

  • a 9v power supply which takes vehicle power and "cleans it up". It's adjustable so I could lower its output voltage if needed.
  • two relays, which are activated when the key is turned (ACC) and when the doors are opened. Either closes the circuit to power the Uno from the aforementioned 9v supply.
  • two more relays on a relay module, controlled by the Uno. One of them "holds" power, the other controls power to the external DVR and cameras.
  • two voltage dividers (i.e. four resistors) so that the Uno SHOULD see half the 9V, or just ground, to indicate if the key is turned or any door is open. (Although that only works for the ACC relay, which is my problem.)
  • an I2C real time clock module which has a battery (for a variety of reasons, I use this for elapsed time instead of millis because I may want to prevent the recording from being disabled too often per day, and to remember how much time has passed even when unpowere. The actual time and date is irrelevant.)

Although not on the board as depicted, the system also includes the I2C display and button of course.

Due to the length of this post exceeding 9000 characters otherwise, I will put my code in a follow-up.

Here's the code

#include <EEPROM.h> //Library for EEPROM
#include <DS3231.h> //Library for temp sensor in clock
#include <Wire.h>  //Library for communication to RTC and display.
#include <WireRtcLib.h>  //Library for the real time clock.
#include <Adafruit_SSD1306.h>  // Display driver.
Adafruit_SSD1306 display(4); // NO CLUE what this does.
WireRtcLib::tm* t;  // I think this desclares the structure "t" to contain all the elements of time.
WireRtcLib rtc; // CLUE, anyone? Oh well who cares, it's necessary for the realtime clock.

const bool TestFastMode = true;  // If TRUE, a "minute" will elapse in 1 second for testing.
const int Timeout = 120;  // Minutes until unit allows itself to shut down, which also kills video.
const int buttonPin = 7;
const int DVRRelay = 6;
const int HoldRelay = 5;
const int DoorPin = 4;
const int ACCPin = 3;

int LastShownScreen = 0;
unsigned long minuteselapsed;

bool BatteryDead = false;
unsigned long TargetMinutes;
unsigned long TimeOfLastDoororACC = 0;

void setup()   {
  //Hold self-power on, in case doors shut.
  pinMode(HoldRelay, OUTPUT);
  digitalWrite(HoldRelay, LOW); //LOW = On. HIGH = Off (ON HERE)

  //Set up the button.
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH); // Activates the internal resistor so no external resistor needed.  LOW shall represent a pushed state.

  //set up the DVR pin.
  pinMode(DVRRelay, OUTPUT);

  //And the two inputs.
  pinMode(ACCPin, INPUT);
  pinMode(DoorPin, INPUT);

  Serial.begin(9600);
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x64)
  display.clearDisplay();
  rtc.begin();
  t = rtc.getTime();
  if (int(t->wday) == 1) {
    BatteryDead = true;
    TargetMinutes = 0;
    EEPROM.put(0, TargetMinutes);
  }
  EEPROM.get(0, TargetMinutes);
  Serial.print("TargetMinutes=");
  Serial.print(TargetMinutes);
}


void loop() {
  checktoreset(); // May set the clock way back if a lot of time has passed.
  int minutesleft = 0;
  if (TestFastMode == true) { // Uses seconds for minutes, and minutes for hours, for testing purposes.
    minuteselapsed = int(t->sec) + (int(t->min) * 60);
  }
  else {
    minuteselapsed = int(t->min) + (int(t->hour) * 60);
  }
  if (TimeOfLastDoororACC == 0) {
    TimeOfLastDoororACC = minuteselapsed;
  }
  if (digitalRead(ACCPin) == LOW || digitalRead(DoorPin) == LOW) {
    //Note that LOW = Active.
    TimeOfLastDoororACC = minuteselapsed;
  }
  else if (minuteselapsed > TimeOfLastDoororACC + Timeout) {
    Serial.print("Turning off self and DVR.");
    digitalWrite(DVRRelay, HIGH); //LOW = On. HIGH = Off (OFF HERE)
    digitalWrite(HoldRelay, HIGH); //LOW = On. HIGH = Off (OFF HERE)
  }

  if (TargetMinutes > 0) {
    t = rtc.getTime();
    if (minuteselapsed >= TargetMinutes) {
      //TIME WITHOUT RECORDING IS NOW EXPIRING.  Recording will be re-enabled.
      TargetMinutes = 0;
      resettime();
      //Write zero to eeprom as TargetMinutes. Recording enabled.
      EEPROM.put(0, TargetMinutes);
    }
  }

  if (minuteselapsed <= TargetMinutes) {
    minutesleft = TargetMinutes - minuteselapsed;
  }
  if (buttonbefore(50) == true) {
    bool changed = false;
    if (minutesleft == 0) {
      minutesleft = 30;
      changed = true;
    }
    showstatus(minutesleft);
    while (buttonbefore(3000) == true) { //Does nothing unless a button is pushed within 3 seconds.
      Serial.print("button pushed");
      if (minutesleft < 30) {
        minutesleft = 30;
      }
      else if (minutesleft < 60) {
        minutesleft = 60;
      }
      else if (minutesleft < 90) {
        minutesleft = 90;
      }
      else if (minutesleft < 120) {
        minutesleft = 120;
      }
      else {
        minutesleft = 0;
      }
      changed = true;
      TargetMinutes = minutesleft;
      showstatus(minutesleft);
    }
    if (changed == true) {
      //reset the clock
      resettime();
      TargetMinutes = minutesleft;
      //write TargetMinutes to eeprom.
      EEPROM.put(0, TargetMinutes);
    }
  }
  if (minuteselapsed < TargetMinutes) {
    if (LastShownScreen != 3) {
      LastShownScreen = 3;
      Serial.println("Showing Video OFF");
      display.clearDisplay();
      //display.drawBitmap(0, 0, NowOff_bits, 128, 64, 1); //Commented out for brevity in a forum post.
      display.display();
      digitalWrite(DVRRelay, HIGH); //LOW = On. HIGH = Off (OFF HERE)
    }
  }
  else {
    if (LastShownScreen != 2) {
      LastShownScreen = 2;
      Serial.println("Blanking Screen");
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(WHITE);
      if (BatteryDead) {
        display.setCursor(0, 9);
        display.println("    REPLACE CLOCK");
        display.setCursor(0, 24);
        display.println("       BATTERY");
        BatteryDead = false; // This is a lie, but it prevents the message from appearing more than once per powerup.
      }
      display.display();
      digitalWrite(DVRRelay, LOW);  //LOW = On. HIGH = Off (ON HERE)
    }
  }
  serialshowstuff();
  //Test whether it has timed out.
}
void serialshowstuff() {
  //Removed for brevity in a forum post.
  
}

void checktoreset() {
  //Not super critical, but sets the clock back to January 1, 2000 if it's not already Jan 1.  This way we're only dealing with hours and minutes.
  static long counter = 50000;
  //Occurs once upon reset, then every 41 minutes or so.
  if (counter >= 50000) {
    counter = 0;
    if (t->mday > 1 || t->mon > 1 || t->year > 0) {
      //More than 24 hours have elapsed. FAR EXPIRED.
      Serial.print("Resetting Clock");
      resettime();
      t = rtc.getTime();
      TargetMinutes = 0;
      //Write zero to eeprom as TargetMinutes.  Recording enabled.
      EEPROM.put(0, TargetMinutes);
    }
  }
  counter = counter + 1;
}
bool buttonbefore(int ms) {
  static bool WasPushed = false;
  //ms = at least 50 milliseconds.
  //Returns true if a button is pushed or false if milliseconds elapses.
  //To qualify, the button must be "unpushed" the moment before.
  int count = ms / 50;
  for (int x = 0; x < count; x++) {
    delay(50);
    if (digitalRead(buttonPin) == LOW) {
      //Note that LOW = Active
      if (WasPushed == false) {
        WasPushed = true;
        return true;
      }
    }
    else if (WasPushed == true) {
      WasPushed = false;
    }
  }
  return false;
}

void showstatus(int minutesleft) {
  //This is either "Recording is OFF until..." or "Recording is now ON".
  LastShownScreen = 4;
  display.clearDisplay();
  if (minutesleft == 0) {
    // display.drawBitmap(0, 0, NowOn_bits, 128, 64, 1); //Commented out for brevity in a forum post.
  }
  else {
    //display.drawBitmap(0, 0, OffUntil_bits, 128, 64, 1);  //Commented out for brevity in a forum post.
    showMinutes(minutesleft);
  }
  display.display();
  //wait for input or timeout.

}
void showMinutes (int i) {
  display.fillRect(0, 15, 128, 20, 0);
  int x = 39; //Reference point
  if (i < 100 && i >= 10) {
    x = x - 7;
  }
  if (i > 99) {
    showDigit((i % 1000) / 100, x);
  }
  if  (i > 9) {
    x = x + 15;
    showDigit((i % 100) / 10, x);
  }
  x = x + 15;
  showDigit((i % 10), x);
}

void showDigit (int i, int x)
{
  // Code removed for brevity in a forum post.
  
}


void resettime() { //Resets the time to Jan 1, 2000, which was a Saturday.  Actual time doesn't matter to this app, but elapsed time does.
  t = rtc.getTime();
  t->sec = 0;
  t->min = 0;
  t->hour = 0;
  t->mday = 1;
  t->mon = 1;
  t->year = 0;
  t->wday = 2;
  rtc.setTime(t);
}

Not a coder, but I think pin 4 is used for "chip select" of your display

Adafruit_SSD1306 display(4); // NO CLUE what this does.

Leo..

I didn't think that was a reference to a pin. Even though I don't know what it does, it wouldn't seem to make sense that it's a pin reference since the display doesn't use pin 4 and it's working fine.

However, if it is a pin reference, that could be related to the issue... well, perhaps I'll wait to see what others post on this topic.

I found a pdf on the display. It says if you are using an I2C interface then:

Finally, connect the pins to your Arduino
GND goes to ground
Vin goes to 5V
Data to I2C SDA (on the Uno, this is A4 on the Mega it is 20 and on the Leonardo digital 2)
Clk to I2C SCL (on the Uno, this is A5 on the Mega it is 21 and on the Leonardo digital 3)
RST to digital 4 (you can change this pin in the code, later)

Here is a link to the full pdf:
https://learn.adafruit.com/downloads/pdf/monochrome-oled-breakouts.pdf

nathancamp:
I found a pdf on the display. It says if you are using an I2C interface then:

That's actually a different display. The one I'm using has only four pins: VCC, GND, SCL and SCA. But unless that line of my code is affecting pin 4 somehow, (which would be weird since the display doesn't even use that pin) I don't think that's related to the issue. As is probably obvious, much of my code is snippets from examples.

Adafruit_SSD1306 display(4);

Then look up what it does!

Mark

Oh wow, you mean LOOKING for answers is an effective way to find them? You seem to be under the impression I chose to make things hard on myself.

Even though you may be better versed in where to find such answers than I am... I genuinely don't know where to look. I don't know where to find documentation on that driver, and believe it or not I HAVE looked through the .h file, and found it difficult to decipher given that my first experience coding in C is a couple weeks ago.

Believe it or not, I have tried and not succeeded at finding documentation on that driver. (Yes, even on the adafruit website.) I admit: It's possible this is because I'm an idiot. (I don't think I am an idiot, but that's exactly how an idiot would see it, right?) Regardless: The fact is, I have tried in good faith./\

If I knew where to find the answer, it would have been easier to look it up than to posting my code, modify my code so that it would fit in 9000 characters, modifying a graphic of my circuit board, and describing the situation in depth on this forum, would it not?

Even now, I have no idea where to "look up" what that function does. If you can provide a little pointer where to go, I'd appreciate it.

Even now, I have no idea where to "look up" what that function does. If you can provide a little pointer where to go, I'd appreciate it.

Start with the libs .h file then move on to the .cpp file

Mark

holmes4:
Start with the libs .h file then move on to the .cpp file

Mark

Since you RTFM'd me, I'm going to "RTFP" you. My post says very clearly: "believe it or not I HAVE looked through the .h file, and found it difficult to decipher given that my first experience coding in C is a couple weeks ago." (And yes I had looked through the .cpp as well.)

Nonetheless, I looked again and I've found a line in the .cpp which says "void Adafruit_SSD1306::display(void) {" (which MIGHT be that function, but I'm unfamiliar with what the double colon does.) This still gives me no clue, even looking through the code below that because since it says "display(void)" it looks to me as if it's not receiving any parameters at all (the 4)... and since the coders didn't elect to add meaningful comments, I don't know where to find a description of that function.

You're on a forum of mostly hobbyists, many of whom, like me, can tinker with sketches for Arduino and get result, but are not full C programmers. I'm a driving school owner, not a software engineer and if I attempt to become one my business will suffer. But I promise you: I didn't get this project as far as it is without reading my share of documentation.

If you feel I'm asking for spoon feeding, please click another post. Frankly it still seems unlikely the display(4) is the cause of the problem but I will experiment with changing the 4 to other values. If it's a pin reference, then changing it to 2 should alleviate the problem since that pin is unused. (And if so, it will mean I walk away with a solved problem in the most efficient manner possible.)

If you have to assign the pin number in your sketch, and the pin isn't actually used, try another (unused) pin number there.
Leo..

Adafruit_SSD1306 display(4);

Nope. This is a variable declaration.

It declares a variable named "display", of type Adafruit_SSD1306, and it's being initialized with 4.

Now referring to the constructor:

Adafruit_SSD1306(int8_t RST = -1);

Track it back a little, and you see that this sets the reset property of the device to that number.

And what do we do with reset? Oh, here in begin() :

  if ((reset) && (rst >= 0)) {
    // Setup reset pin direction (used by both SPI and I2C)
    pinMode(rst, OUTPUT);
    digitalWrite(rst, HIGH);
    // VDD (3.3V) goes high at start, lets just chill for a ms
    delay(1);
    // bring reset low
    digitalWrite(rst, LOW);
    // wait 10ms
    delay(10);
    // bring out of reset
    digitalWrite(rst, HIGH);
    // turn on VCC (9V?)
  }

Changing that line to

Adafruit_SSD1306 display(); should work, if I'm understanding the code right.

What is EEPROM.h.
Is one connected/used.
Isn't pin4 there used for chipselect.
Leo..

Wawa:
If you have to assign the pin number in your sketch, and the pin isn't actually used, try another (unused) pin number there.
Leo..

Thank you Wawa and DrAzzy, per my post just above yours, that's exactly what I did, and yes, it worked. Apparently it's a pin reference, although I still have no idea why a device would work fine, and yet need reference to a pin that it isn't even connected to.

To Mark (holmes4): Although your post may have pulled my attention toward the fact that the line in question might be the cause, (without actually saying so) you essentially pointed me to non-existent documentation and poorly-commented code that a typical Arduino hobbyist wouldn't understand... and I still don't. All the while, you apparently knew the solution without providing it. That's exceedingly irritating.

pburto - don't attack people offering help, even if you are not pleased with the tone - we are helping you for free....

Wawa:
What is EEPROM.h.
Is one connected/used.
Isn't pin4 there used for chipselect.
Leo..

EEPROM.h is a built-in library used to access the on-chip EEPROM on AVR chips, man...

DrAzzy:
pburto - don't attack people offering help, even if you are not pleased with the tone - we are helping you for free....

EEPROM.h is a built-in library used to access the on-chip EEPROM on AVR chips, man...

Well said. I agree, and I appreciate your help very much. But if "RTFM" is help, then we should post that in response to every question... to be helpful. If forum posters would just "R" enough "M"'s, they'd never have need to post. :wink:

All the while, you apparently knew the solution without providing it.

I'll teach you to learn I won't do it for you.

Post number 2 gave you the answer! Kama to Wawa.

Mark

holmes4:
I'll teach you to learn I won't do it for you.

Is that what you think occurred? Okay. Enjoy that notion.

That avatar you've chosen for yourself is a depiction of a person choosing to hurt his own forehead. If you're doing that, I suggest you stop. If you don't know how to stop, then look it up.