I2C RTC works with Pro Mini but not Nano

I made a couple of electronic clocks, one based on ATMega 328P Pro Mini board and another based on LGT8F328P SSOP20 "Pro Mini-style" miniEVB board. Both items work perfectly. Previously I'd posted full code here.

As a next step I decided to make a similar Nano based clock. But I can't make I2C run. RTC output simply doesn't show up in serial monitor. I've already done extensive research and can assure you that:

  1. RTC, which is DS3231, has 4.7 kOhm pullup resistors for both SDA and SCL.
  2. I2C check sketch works properly and returns typical addresses: 0x57 and 0x68.
  3. I tried connecting RTC's VCC pin to both 3V3 and 5V pins on Nano.
  4. It's the same RTC that worked normally with Pro Mini and miniEVB SSOP20 boards, so it's obviously not broken.

What should I check and try next?

P. S. MCU is ATMega 328PB if that matters.

That’s weird that works and the RTC sketch doesn’t.


Show us wiring of the setup that does not work.


Here. Pro Mini works. Nano does not.

what else have you connected to the nano?
could you upload the nano code?

Initially I had hooked up shift register and 4 BJTs in order to drive 7-segment displays, but then I found out there's an issue with I2C and disconnected everything except RTC.

// This sketch reads RTC output and drives multiplexed 7-segment 4-digit LED display with 4 common cathodes.
// Between GND and each common cathode a small-signal NPN BJT is required.

#include <Adafruit_BusIO_Register.h>
#include <Adafruit_I2CDevice.h>
#include <Adafruit_I2CRegister.h>
#include <RTClib.h>


// GENERIC GLOBAL VARIABLES

// This sketch works with both DS3231 and DS1307 RTCs. Applicable variant must be uncommented to create an object.
// RTC_DS3231 rtc;
RTC_DS1307 rtc;

// For storing numbers from RTC output.
uint8_t RTC_hours;
uint8_t RTC_minutes;
uint8_t RTC_seconds;

// For storing digits to be displayed.
uint8_t hours_first_digit;
uint8_t hours_second_digit;
uint8_t minutes_first_digit;
uint8_t minutes_second_digit;
uint8_t seconds_first_digit;
uint8_t seconds_second_digit;


// GLOBAL VARIABLES RELATED TO USER-DEFINED FUNCTIONS

// dispay_digit()
  // Pins which drive transistors connecting common cathodes to GND
  // and thus switch the active display section ("D" is for "digit").
  const uint8_t D1_PIN = 12;
  const uint8_t D2_PIN = 11;
  const uint8_t D3_PIN = 10;
  const uint8_t D4_PIN = 9;

  // Pins which interact with 74HC595 (known as simply "595") latched shift register IC.
  // The 595 is used to create a set of 8 parallel output signals ("byte mask") that lights up only necessary segments at any given time.
  const uint8_t DATA_PIN = 8;
  const uint8_t LATCH_PIN = 4;
  const uint8_t CLOCK_PIN = 7;

  uint8_t dot_byte;                                 // A byte which, being added to a byte mask, turns decimal point ("dot") bit into 1.
  bool dot_state;                                   // Makes decimal point blink.

// serial_output()
  uint8_t previous_RTC_seconds;                     // Reference point for finding out if seconds output has been changed, which entails sending data via UART.
  bool initial_RTC_seconds_was_stored = 0;          // Makes the code which finds out initial previous_RTC_seconds run only once.

// compensate()
  const int COMPENSATION_VALUE = 0;                 // Number of seconds added to or subtracted from RTC_seconds in order to keep up with the actual time. Zero value means no compensation.
                                                    // Value should be negative if RTC runs ahead of time, positive otherwise. Valid value ranges from -59 to 59.
  const uint8_t COMPENSATION_THRESHOLD = 12;        // How many hours will pass until compensation occurs.
  uint8_t compensation_counter = 0;                 // Hours counter.
  uint8_t previous_RTC_hours;                       // Reference point for finding out if hours output has been changed, which entails raising hours counter.
  bool initial_RTC_hours_was_stored = 0;            // Makes the code which finds out initial previous_RTC_hours run only once.
  bool compensation_loaded = 0;                     // Enables compensation and puts it "on hold" until RTC_seconds takes appropriate value.
  bool compensation_all_clear;                      // Confirms that RTC_seconds holds appropriate value. It prevents driving RTC_seconds below 0 or above 59.

// manual_settime()
  bool settime_mode = 0;                            // Used to begin and end while() loops during which time may be manually set.

  // Pins for active-low manual input buttons.
  const uint8_t SETTIME_HOURS_PIN = 2;
  const uint8_t SETTIME_MINUTES_PIN = 3;

  // For button debounce.
  bool settime_hours_button_is_pressed;
  bool settime_hours_button_wasnt_pressed;
  bool settime_minutes_button_is_pressed;
  bool settime_minutes_button_wasnt_pressed;
  unsigned long previous_millis;
  bool holding_toggle_available = 0;
  int holding_toggle_timing = 500;

  // For storing temporary numbers to be loaded into RTC when settime mode is turned off.
  uint8_t settime_hours;
  uint8_t settime_minutes;
  uint8_t settime_seconds;


void setup() {
  rtc.begin();

  // If necessary, uncomment following line to set RTC time, upload the sketch, comment the following line once again and then re-upload the sketch.
  // rtc.adjust(DateTime(2022, 3, 14, 1, 53, 0));  // year, month, date, hours, minutes, seconds

  pinMode(D1_PIN, OUTPUT);
  pinMode(D2_PIN, OUTPUT);
  pinMode(D3_PIN, OUTPUT);
  pinMode(D4_PIN, OUTPUT);
  
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
  
  pinMode(SETTIME_HOURS_PIN, INPUT_PULLUP);
  pinMode(SETTIME_MINUTES_PIN, INPUT_PULLUP);

  // Prevents artifacts from being displayed immediately after boot.
  digitalWrite(D1_PIN, 0);
  digitalWrite(D2_PIN, 0);
  digitalWrite(D3_PIN, 0);
  digitalWrite(D4_PIN, 0);

  // May be handy for debugging purposes etc.
  Serial.begin(9600);
}


void loop() {

  if(millis() - previous_millis >= 1000) {
    previous_millis = millis();
    Serial.println("test!");
  }
  
  DateTime now = rtc.now();

  RTC_hours = now.month(), DEC;
  RTC_minutes = now.month(), DEC;
  RTC_seconds = now.month(), DEC;

  // Division and modulo operators are used
  // to separate numbers into first and second digit.
  hours_first_digit = RTC_hours % 10;
  hours_second_digit = RTC_hours / 10;
  minutes_first_digit = RTC_minutes % 10;
  minutes_second_digit = RTC_minutes / 10;
  seconds_first_digit = RTC_seconds % 10;
  seconds_second_digit = RTC_seconds / 10;

  // Display function callers.
  display_digit(D1_PIN, hours_first_digit, 0);
  display_digit(D2_PIN, hours_second_digit, 1);    // Decimal point is used to visually separate hours from minutes and indicate that the clock is running.
  display_digit(D3_PIN, minutes_first_digit, 0);
  display_digit(D4_PIN, minutes_second_digit, 0);

  // Serial output function caller.
  serial_output();

  // Time drift compensation function caller.
  compensate();

  // Turn on settime mode.
  settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
  if (settime_hours_button_is_pressed && settime_hours_button_wasnt_pressed) {
    delay(10);

    settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
    if (settime_hours_button_is_pressed) {
      holding_toggle_available = true;
      previous_millis = millis();
      
      while (holding_toggle_available) {
        settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
        
        if (!settime_hours_button_is_pressed) {
            holding_toggle_available = false;
        }

        if (millis() - previous_millis >= holding_toggle_timing) {
          holding_toggle_available = false;        // Not really necessary, but it feels right to drop this flag to zero befor settime sequence begins.
          manual_settime();                        // Settime function caller.
        }
      }
    }
  }
  settime_hours_button_wasnt_pressed = !settime_hours_button_is_pressed;
}


void display_digit(int current_cathode, int digit_to_display, bool whether_dot_is_used) {  // User-defined function dedicated to actually displaying digits.

  // Defines which display section (digit) is to be turned on right now.
  digitalWrite(D1_PIN, current_cathode == D1_PIN ? 1 : 0);
  digitalWrite(D2_PIN, current_cathode == D2_PIN ? 1 : 0);
  digitalWrite(D3_PIN, current_cathode == D3_PIN ? 1 : 0);
  digitalWrite(D4_PIN, current_cathode == D4_PIN ? 1 : 0);

  // Byte masks for digits from 0 to 9. May vary depending on the order
  // in which 595 output pins are wired to the display input pins.
  uint8_t output_matrix[] = {
    0b11101110,  // 0
    0b00000110,  // 1
    0b11100011,  // 2
    0b01100111,  // 3
    0b00001111,  // 4
    0b01101101,  // 5
    0b11101101,  // 6
    0b00100110,  // 7
    0b11101111,  // 8
    0b01101111   // 9
  };

  // If third argument of function is 1, decimal point will blink once per second.
  dot_byte = 0;
  if (whether_dot_is_used) {
    dot_state = seconds_second_digit % 2;  // Every other second the dot blinks.
      if (dot_state) {
        dot_byte = 0b00010000;
      } else dot_byte = 0;
  }

  // Displaying.
  digitalWrite(LATCH_PIN, 0);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, output_matrix[digit_to_display] + dot_byte);
  digitalWrite(LATCH_PIN, 1);

  // Anti-ghosting sequence.
  delay(4);  // Anti-ghosting delay
  digitalWrite(LATCH_PIN, 0);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, 0b00000000); // Turns all display sections off for one clock period.
  digitalWrite(LATCH_PIN, 1);
}


void serial_output() {  // Optional user-defined function. Used for sending RTC output via UART for debugging purposes etc.
  if (!initial_RTC_seconds_was_stored) {
    previous_RTC_seconds = RTC_seconds;
    initial_RTC_seconds_was_stored = 1;
  }

  if (RTC_seconds != previous_RTC_seconds) {
    Serial.print(RTC_hours);
    Serial.print(":");
    Serial.print(RTC_minutes);
    Serial.print(":");
    Serial.println(RTC_seconds);
    previous_RTC_seconds = RTC_seconds;
  }
}


void compensate() {  // User-defined function dedicated to software compensation for time drift.
  if (!initial_RTC_hours_was_stored) {                   // Begin counting hours.
    previous_RTC_hours = RTC_hours;
    initial_RTC_hours_was_stored = 1;
  }
  
  if (RTC_hours != previous_RTC_hours) {
    ++compensation_counter;
    previous_RTC_hours = RTC_hours;
  }
    
  if (compensation_counter >= COMPENSATION_THRESHOLD) {
    compensation_loaded = 1;                             // When a set number of hours has passed, a compensation is ready to take place.
    compensation_counter = 0;
  }

  if (COMPENSATION_VALUE > 0 || RTC_seconds >= abs(COMPENSATION_VALUE))  // Prevents RTC_seconds from being driven below 0 or above 59
    compensation_all_clear = 1;
    else compensation_all_clear = 0;
  
  if (compensation_loaded && compensation_all_clear && COMPENSATION_VALUE >= -59 && COMPENSATION_VALUE <= 59) {         
    rtc.adjust(DateTime(2022, 3, 14, RTC_hours, RTC_minutes, RTC_seconds + COMPENSATION_VALUE)); // year, month, date, hours, minutes, seconds with compensation value added.
    compensation_loaded = 0;
  }
}


void manual_settime() {  // Settime mode allows setting time manually without neither using IDE nor resetting the MCU.
  // This stuff is executed just once, after that function gets caught into a while() loop.
  settime_mode = true;
  settime_hours = 10;
  settime_minutes = 0;
  settime_seconds = 0;
  Serial.print(settime_hours);
  Serial.print(":");
  Serial.print(settime_minutes);
  Serial.print(":");
  Serial.println(settime_seconds);
  
  settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
  settime_hours_button_wasnt_pressed = !settime_hours_button_is_pressed;
      
  // While settime mode is on.
  while (settime_mode) {
    // Set hours or turn off settime mode.
    settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
    if (settime_hours_button_is_pressed && settime_hours_button_wasnt_pressed) {
      delay(10);

      settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
      if (settime_hours_button_is_pressed) {
        holding_toggle_available = true;
        previous_millis = millis();

        while (holding_toggle_available) {
          settime_hours_button_is_pressed = !digitalRead(SETTIME_HOURS_PIN);
                    
          if (!settime_hours_button_is_pressed) {
            holding_toggle_available = false;
            ++settime_hours;
            if (settime_hours > 23) {
              settime_hours = 0;
            }
            Serial.print(settime_hours);
            Serial.print(":");
            Serial.print(settime_minutes);
            Serial.print(":");
            Serial.println(settime_seconds);
          }

          if (millis() - previous_millis >= holding_toggle_timing) {
            holding_toggle_available = false;
            settime_mode = false;
            rtc.adjust(DateTime(2022, 3, 14, settime_hours, settime_minutes, settime_seconds));  // year, month, date, hours, minutes, seconds
          }
        }
      }
    }
    settime_hours_button_wasnt_pressed = !settime_hours_button_is_pressed; 

    // Set minutes.
    settime_minutes_button_is_pressed = !digitalRead(SETTIME_MINUTES_PIN);
    if (settime_minutes_button_is_pressed && settime_minutes_button_wasnt_pressed) {
      delay(10);
      settime_minutes_button_is_pressed = !digitalRead(SETTIME_MINUTES_PIN);
      if (settime_minutes_button_is_pressed) {
        ++settime_minutes;
        if (settime_minutes > 59) {
          settime_minutes = 0;
        }
        Serial.print(settime_hours);
        Serial.print(":");
        Serial.print(settime_minutes);
        Serial.print(":");
        Serial.println(settime_seconds);
      }
    }
    settime_minutes_button_wasnt_pressed = !settime_minutes_button_is_pressed;

    // Same as outside settime while() loop, but calculated from temporary settime numbers, not actual RTC output.
    hours_first_digit = settime_hours / 10;
    hours_second_digit = settime_hours % 10;
    minutes_first_digit = settime_minutes / 10;
    minutes_second_digit = settime_minutes % 10;
    seconds_first_digit = settime_seconds / 10;
    seconds_second_digit = settime_seconds % 10;

    // Same as outside settime while() loop.
    display_digit(D1_PIN, hours_first_digit, 0);
    display_digit(D2_PIN, hours_second_digit, 0);    // Decimal point is off to indicate that settime mode is on.
    display_digit(D3_PIN, minutes_first_digit, 0);
    display_digit(D4_PIN, minutes_second_digit, 0);
  }
}

The ATmega328PB has a multiplexer for the pin mapping for the I2C bus.
I don't know what that means, I just read it somewhere.

The ATmega328PB has a multiplexer for the pin mapping for the I2C bus.
I don't know what that means, I just read it somewhere.

Thanks, I'll read that.

BTW I've just tried cutting the code to bare bones, keeped only RTC output refresh and serial print. Nothing changed.

Section 2.8

Is there a official Arduino board with a ATmega328PB ?

I guess not, but Mini Core has a configuration for it.

Page number? Couldn't find such section.

There is a PDF document "AT15007" (link to document at Pololu website), then it is at page 7.

It is also online: https://onlinedocs.microchip.com/pr/GUID-CBDC1838-0100-4F26-A45A-134958193C3B-en-US-4/index.html

I still don't know what it means. Is the first I2C interface the same, and the additional features are only for the second I2C interface ?

I believe so.

Meanwhile this whole situation is driving me barking mad. I swapped ATmega328PB for my older ATmega168... For the same result! Therefore issue well may have nothing in common with "PB" MCU. I did some additional testing, played with pullup resistor values, changed bus speed with Wire.setClock(10000), tried to jumpstart RTC with Wire.begin(0x57) and Wire.begin(0x68), tried changing battery, tried another RTC (DS1307), all without any success.

But as soon as I plug it in my Pro Mini board, it works like a charm.

I do need help :frowning:

just connected up a DS3231_RTC to Nano ATmega328P

// GND - GND
// VCC - 5V
// SDA - A4
// SCL - A5

works OK

0:12:00 1/1/0 Day of week: Sunday
0:12:01 1/1/0 Day of week: Sunday
0:12:02 1/1/0 Day of week: Sunday
0:12:03 1/1/0 Day of week: Sunday
0:12:04 1/1/0 Day of week: Sunday
0:12:05 1/1/0 Day of week: Sunday

I’ve seen a Nano with the analog pins reversed.

i.e. A0 was A7, A6 was A1 . . .

I can clearly see A4 connected to pin 27 and A5 to pin 28.

Can you humour us and see if a simple blink sketch can toggle these pins ?


Did you say a second Nano does the same thing ? :thinking:


Was it Nick Gammons scanner that you used to check the I2C ?


// I2C Scanner
// Written by Nick Gammon
// Date: 20th April 2011

#include <Wire.h>

void setup() {
  Serial.begin (115200);

  // Leonardo: wait for serial port to connect
  while (!Serial) 
    {
    }

  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}


  • Please check all the SMD soldering with a magnifying device.

which RTC are you looking at DS3231 or DS1307 ? I see referencs to both in your posts
I assume the code compiles, links, uploads and starts OK?
is any output displayed on the serial monitor?

Have you ever been in a situation when you finally get things to work but still suffer because now you can't find out for sure what was wrong in first place? Well, that's how I feel now. After a sleepless night all my Nano boards began to interface with RTCs normally. I can output time both in serial monitor and 7-segment displays.

Overall I tested three Nano boards:

  1. Unofficial w/ ATmega168.
  2. Unofficial w/ ATmega328pb.
  3. LGT8F328P LQFP32 miniEVB.

Initially I was able to run sketch and read RTC output from serial monitor with ATmega boards (but not with Logic Green board), but after a while they stopped working. At some point of my research I had a guess that the latter board somehow "corrupts" RTC, perhaps by messing with I2C bus (although I'm not sure that can happen with a slave device). When RTC got "corrupted", it stopped working with all Nano boards (but not Pro Mini-style boards). Furthermore, any board which interacted with such "corrupted" board stopped working with any RTC. I suggested it can be a widely known (not to me though) "stuck I2C bus" problem and researched this issue thorougly, among other things I ran diagnostic sketch by Forward Computing and followed hints from here. I did receive "SCL is held low" message repeatedly and started tinkering about removing it, which included sending from 8 to 10 pulses to SCL every 10 microseconds (100 KHz) and turning MCU on and off rapidly.

Maybe it really was the case and my tinkering actually helped, but when I was ready for the next test I noticed that a switch on my breadbored which connected two VCC buses (I was using both) was turned off. Perhaps I accidentally toggled it when I was switching boards on the breadboard (thus my guess about LGT8F328P board being the cause of the issue). I turned on the switch, ran next test and from then on all my boards work correctly. Did SCL line actually got stuck or I just messed with the switch? I don't know. I think the latter looks much more probable. Or maybe there were both problems.

Suggestion about breadboard switch position error is reinforced by the fact that my unofficial Pro Mini ATmega328P and LGT8F328P SSOP20 miniEVB clock boards worked infallibly no matter what, and they're assembled as finished, soldered boards, not breadboard prototypes. Therefore possibility of absent connection was null for them, and maybe it's the reason they always worked properly.

Also initially I used a smaller breadboard with no switches and managed to run ATmega328PB Nano board with RTC properly (Logic Green board ostensibly didn't work, but see below). Perhaps migration to a bigger breadboard could cause the problem due the aforementioned switch.

Both. Furthermore, the library I use, Adafruit RTClib, works well with both chips even without toggling DS1307/DS3231 in the code (as long as you use only basic functions).

No, I used scanner sketch from here.

Blink worked OK with A4 and A5. I even had a theory that blinking these pins as GPIO "unlocked" one of my Nano's I2C bus, because after some blinking the board started reading RTC again (but not for long, because I kept on testing, switching boards and RTC, and eventually ran into a stoppage again). Again, I'm not sure whether I2C bus was actually stuck or I just messed with a switch.

One last hint, in case someone reads this thread in the future – lools like LGT8F328P in 32-pin package requires main clock to be set to 16 MHz for proper reading from RTC. When I set clock to 32 MHz data stream eventually stops. Maybe it can be accounted for in Wire.h functions instead of changing main clock, but the latter variant works OK for me. Aforementioned requirement somehow was not the case for the same chip in SSOP20 package. Lack of this information made me believe that my LGT8F328P Nano-style board doesn't read RTC properly, which added to my confusion.

Thanks for the input, everyone :slight_smile:

UPD: DS3231 works OK with 32 MHz MCU's, it's just not every LGT8F328P can consistently run at this clock (some begin to crash and reboot shortly after turning on, while others don't). Therefore I just set some LGT8F328P-based boards to 16 MHz clock.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.