I2C -- DS3231 + LCD + PCF8574 not working

I got into a strange issue. I made an application for activating a relay at 2 given times per day for filling my pellet heating bunker. Sketch is based on a Wemos mini clone. In the current version there is the DS3231 and a LCD 16x2. The LCD shows status messages and the current time. Once per day the RTC is updated by NTP.
The DS3231 (3,3V) is directly connected to I2C (SCL = D1, SDA = D2). The LCD is connected to I2C over a Mosfet based level shifter. Both DS3231 and LCD have the original pull-up resistors (4,7kOhm) on the modules. In this set-up all works well.

I want to extend the setup with a 4x4 keypad through a PCF8574 (no pull-ups) and include a menu for setting the (currently) hardcoded filling times and filling duration
After connecting the PCF8574 to I2C a ran the I2C scanner and the RTC, LCD and PCF8574 show up and all addresses are as expected and different.
However when I connect the PCF8574 to the I2C pins the Wemos is not anymore willing to react to the reset button and the LCD only shows white blocks.
What might be the problem here?

That's a good description of your setup and your problem -- but -- without a copy of your code we can't offer much help. A photo of the setup is frequently helpful as well.

The fact that the LCD shows only white blocks indicates that it is no longer being properly initialized meaning that the problem is likely to be in setup().

Are you SURE that all I2C addresses are as expected? Since the LCD probably also uses a PCF8574 you must make sure that one of them has an (A) suffix or that the jumpers are set so that the two have different addresses.

By the way, you don't really need the DS3231. The ESP8266 will keep very good time by itself if updated periodically by NTP.

Don

The reason for using the RTC is that both mains power and internet are not very stable. Lots of outings. With the RTC at least the time is kept and immediately available when power comes back.
In the code time is retrieved from the RTC which is updated each day. Also the timezone and summertime are adjusted.
Start times and filling time are in the sketch hard coded. This I want to change to input via 4x4 keypad with PCF8574.
I checked once again the I2C addresses and the PCF8574 is 0x20 whereas the LCD backpack has 0x57 and 0x64.
The LED stands for a Solid state relay.

//#include <Arduino.h>

// ESP WIFI Udp NTP Client
#include <millisDelay.h>  // For pellets filling
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <RTClib.h>
#include <SPI.h>
#include <Timezone.h>    // https://github.com/JChristensen/Timezone
#include <TimeLib.h>     // Not used, only required for Timezone
#include <hd44780.h>     // for LCD
#include <hd44780ioClass/hd44780_I2Cexp.h> // include i/o class header

// Initiate RTC
RTC_DS3231 rtc;  

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;

// Initiate LCD and define rows/columns
hd44780_I2Cexp lcd; // declare lcd object: auto locate & config display for hd44780 chip
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#define LCD_COLS 16
#define LCD_ROWS 2

// Initiate millisDelay
millisDelay fillDelay; // the delay object for filling pellets
millisDelay syncDelay; // delay object for rtcSyncing

// Timezone rules need to before set-up in order to store with CE.writeRules(100); in void(setup).
// Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     // Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       // Central European Standard Time
Timezone CE(CEST, CET);
TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev

#ifndef STASSID
#define STASSID "xxx"
#define STAPSK  "yyy"
#endif

const char * ssid = STASSID;        // network SSID (name)
const char * pass = STAPSK;         // network password
unsigned int localPort = 2390;      // local port to listen for UDP packets
IPAddress timeServerIP;             // pool.ntp.org NTP server address
const char* ntpServerName = "pool.ntp.org";
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

char daysOfTheWeek[7][5] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

time_t epoch;

// HARDCODED Start times according LOCAL time !!!!! =======
//=============================================================================
int     startH1      = 9  ;   // Start time Hours (24 clock) as H not HH
int     startM1      = 0  ;   // Start time Minutes as M not MM
int     startH2      = 19 ;
int     startM2      = 0  ;
const int fillPeriod = 15 ;   //fillPeriod (input) in minutes as 
//=============================================================================

// Define DELAY_TIME for millisDelay function
const unsigned long fillDelayTime = fillPeriod*1000*60L; // Running time pellet filling in milliseconds
const unsigned long syncDelayTime = 60*60*1000L;         // sync RTC every 60 minutes

int startH1utc = 0;           // Start time converted from LOCAL to utc (=RTC time)
int startH2utc = 0;
long diffTz = 0;              // Difference in seconds UTC - LOCAL
time_t local = 0;

const int relayPin = D6;      // Relay pin op WEMOS D6

// lcdClockDisplay=============================================
// ================================================================
void lcdClockDisplay(unsigned long local) {
  
  char bufDate[17];
  char bufTime[17];
  sprintf(bufDate, "Date: %.4d/%.2d/%.2d", year(local), month(local), day(local));
  lcd.clear(); lcd.setCursor(0, 0); lcd.print(bufDate);
  sprintf(bufTime, "Time: %.2d:%.2d:%.2d", hour(local), minute(local), second(local));
  lcd.setCursor(0, 1); lcd.print(bufTime);
}
// lcdPrintDigits=============================================
// ================================================================
void lcdPrintDigits(int digits) {
  // utility function for lcd clock display: prints preceding colon and leading 0
  lcd.print(":");
  if (digits < 10)
    lcd.print('0');
  lcd.print(digits);
}
// FUNCTION fillPellets ==============================================
// ===================================================================
void F_fillPellets (int startHour, int startMinute) {
  DateTime now = rtc.now();
  
  if (now.hour() == startHour && now.minute() == startMinute && now.second() == 0) {
    fillDelay.start(fillDelayTime);
    digitalWrite(relayPin,HIGH);
    Serial.print("Filling ON!");
    lcd.setCursor(0,1);
    lcd.print("Filling Pellets ");
    }
  if (fillDelay.justFinished()) { // don't combine this test with any other condition
    // delay timed out
    digitalWrite(relayPin, LOW); // turn led off
    Serial.println("Filling finished.");
    lcd.clear();  
  }
 }
// FUNCTION startUtc adjust to utc ===============================
//=================================================================
int F_startUtc(int hr) {
  
  int result;
  // Calculate difference Local - utc
  time_t utc = epoch;
  time_t local = CE.toLocal(utc, &tcr);
  if ((local - utc) != diffTz) {
    diffTz = (local - utc);
  }
  // Calculate local hour to utc hour
  // diffTz follows timezone (GMT + diffTz)
  hr = hr - diffTz / 3600L;
  if (hr > 23) {
    hr = hr - 24;
  }
  else {
    if (hr < 0) {
      hr = hr + 24;
    }
  }
  return result = hr;
}
// FUNCTION F_rtcPrint===============================
// =====================================================
void F_rtcPrint(){
DateTime now = rtc.now();
Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
}

 // FUNCTION obtainUTC ====================================
// ========================================================== 
  time_t obtainUTC() {
  //get a random server from the pool
  WiFi.hostByName(ntpServerName, timeServerIP);
  sendNTPpacket(timeServerIP); // send an NTP packet to a time server
  // wait to see if a reply is available
  delay(1000);
  int cb = udp.parsePacket();
  if (!cb) {
    //Serial.println("no packet yet");
  } 
  else {
    //Serial.print("packet received, length=");
    //Serial.println(cb);
    udp.read(packetBuffer, NTP_PACKET_SIZE); 
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    epoch = secsSince1900 - seventyYears;
  }
  return epoch;
 }
// FUNCTION sendNTPpacket====================================
// ==========================================================
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress& address) {
  //Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

//================================================================
void setup() {
  Serial.begin(115200);
  lcd.begin(LCD_COLS, LCD_ROWS); // Initiate LCD, ROWS and Columns
  pinMode(relayPin,OUTPUT);
  digitalWrite(relayPin,LOW);
  
  // Connecting to a WiFi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  // LCD message
  lcd.clear(); lcd.setCursor(0,0);
  lcd.print("Connecting WIFI");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    // LCD message
    lcd.setCursor(0,1);lcd.print("............");
  }
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  // LCD message
  lcd.clear(); 
  lcd.setCursor(0,0);lcd.print("WIFI connected");
  lcd.setCursor(0,1);lcd.print(WiFi.localIP());
  delay(3000);
  
  Serial.println("Starting UDP");
  udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(udp.localPort());

// Check RTC connection
 if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    // LCD message
    lcd.clear(); 
    lcd.setCursor(0,0);lcd.print("Not found RTC");
    lcd.setCursor(0,1);lcd.print("Check battery?");
    Serial.flush();
    while (1) delay(10);
  }
if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    epoch = obtainUTC();
    rtc.adjust(epoch);
    F_rtcPrint();
  }
Serial.println("Adjust RTC to NTP");
epoch = obtainUTC();
rtc.adjust(epoch);
F_rtcPrint();

// Start syncDelay
syncDelay.start(syncDelayTime);
 
}

//====================================================================
void loop() {

// Repeating timer delay for rtc syncing
if (syncDelay.justFinished()){
  syncDelay.repeat();
  epoch = obtainUTC();
  rtc.adjust(epoch);
  Serial.println("RTC synced");
}
// Display LOCAL time on LCD
lcdClockDisplay(rtc.now().unixtime() + diffTz);

// Display RTC time on Serial port
F_rtcPrint();

// Functions for pellet filling
startH1utc = F_startUtc(startH1);
F_fillPellets(startH1utc,startM1);
startH2utc = F_startUtc(startH2);
F_fillPellets(startH2utc,startM2);

delay(1000);
}

Those addresses are unusual. Did you set the addresses on the PCF modules by jumpering some pads? The default address for the PCF8574 is 0x27 (0x20-0x27) and the PCF8574A is 0x3f (0x38-0x3f). I am not aware of any PCF8574 I2C backpacks with 2 I2C addresses.

When using the hd44780 library that automatically determines the I2C address with more than one I2C device on the bus, you may have to specify the address of the LCD backpack in the LCD constructor since the library can use the first address that it sees.
hd44780_I2Cexp lcd(LCD_address);

// Initiate LCD and define rows/columns
hd44780_I2Cexp lcd; // declare lcd object: auto locate & config display for hd44780 chip
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#define LCD_COLS 16
#define LCD_ROWS 2

Why include hd44780ioClass/hd44780_I2Cexp.h twice?

I messed up, sorry. The backpack is 0x27, the RTC3231 has two addresses (0x57 and 0x68).
The address of the PCF8574 for the 4x4 keypad is 0x20.

I have taken that out.

I now put hd44780_I2Cexp lcd(0x27); and now it works!!
Great, thanks for your advice.

Now I can go on at least.
So far SOLVED.

Great that you got it to work. I am happy that I could help.

I found out about putting the address in the constructor when I used a MCP23008 at I2C address 0x20 with a keypad and an LCD at address 0x027. The LCD would not work till I put the address in the constructor.

I think you will find that it is the RTC module that has two I2C addresses. One is for the DS3231 and the other is for the memory chip that is typically on the module.

Don

Sure!

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