Parallel actions with Arduino

Hey guys,

My project is a basic home temperature monitor with a NodeMCU, a BME280 sensor, and an OLED screen. For a while I was happy to log the data to Thingspeak and update the OLED at the same time. However, having the OLED permanently on started to disturb me, so I added a button to the project, so the OLED would only show the latest data on demand, and only for a few seconds.

The problem is that the NodeMCU sends the data every 5 minutes. A millis() function with a 60 second delay assures it doesn’t send the data more than once at each period. However, during this delay, the code for the button is blocked and I cannot use it. Is there a way to have the two functions (Thingspeak update and screen) going on at the same time?

Code below:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <BME280I2C.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include "arduino_secrets.h"
#include "ThingSpeak.h" // always include thingspeak header file after other header files and custom macros

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     LED_BUILTIN // OLED reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // OLED setup

BME280I2C bme;

char ssid[] = SECRET_SSID;   // your network SSID (name) 
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;

unsigned long myChannelNumber = SECRET_CH_ID;
const char * myWriteAPIKey = SECRET_WRITE_APIKEY;

// Initialize our values
int number1 = 0;
int number2 = 0;
int statuscode = 0;
String myStatus = "";
float tmp = 0;
float hmd = 0;
int buttonPin = 10;  
int buttonState = 0;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0 ,60000);

void setup() {
 
  // Start everything  
  pinMode(buttonPin, INPUT_PULLUP);    // declare pushbutton as input
  Serial.begin(115200);
  Wire.begin();

  while(!bme.begin()) {
    Serial.println("Could not find BME280 sensor!");
    delay(1000);
  }
  ThingSpeak.begin(client);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  // Get NTP
  timeClient.begin();

  // Awake display
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.println("Konichiwa!");
  display.display();

  // Ok to Serial
  Serial.println("Setup completed.");
  delay(5000);
  display.clearDisplay();
  display.display();
}

void loop() {

  tmp = bme.temp();
  hmd = bme.hum();
 
  // Connect or reconnect to WiFi
  if(WiFi.status() != WL_CONNECTED){
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(SECRET_SSID);
    while(WiFi.status() != WL_CONNECTED){
      WiFi.begin(ssid, pass);  // Connect to WPA/WPA2 network. Change this line if using open or WEP network
      Serial.print(".");
      delay(5000);     
    }
    Serial.println("\nWifi connected.");
  }

  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW) {
    Serial.println("clicked");
    display.clearDisplay();
    display.setCursor(0,0);
    display.println("Now:");
    display.print(tmp);
    display.print(char(167));
    display.println("C");
    display.print(hmd);
    display.println("%H");
    display.display();
    delay(5000);
    display.clearDisplay();
    display.display();
  }
  
  // Get the time and update if the time is right
  timeClient.update();
  int mm = timeClient.getMinutes();

  if (mm % 5 == 0) {
    
    // Set Thingspeak fields
    ThingSpeak.setField(1, tmp);
    ThingSpeak.setField(2, hmd);
    // Set Thingspeak status
    ThingSpeak.setStatus(myStatus);
    
    // Write to ThingSpeak channelw
    statuscode = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  
    // Get status
    if(statuscode == 200){
      Serial.println("Channel update successful.");
    }
    else{
      Serial.println("Problem updating channel. HTTP error code " + String(statuscode));
    }
    delay(60000);
  }
}

Take a look at the blink without delay example in the IDE

Get rid of this:

    delay(60000);

Use millis() to schedule the sending of data.

  static unsigned long lastTime = 0;
  if (millis() - lastTime >= 60000ul)
  {
    lastTime = millis();

    // PUT THE SENDING OF THE DATA HERE
  }

Thank you both.

To correct what I said above, I'm not using the millis() here anymore, but an NTP call. I needed the 5-minute interval to be exact — not only in regular intervals, but also to start at minute 0, 5, 10, etc on the dot.

If I use the millis() example, it really depends on the time of the setup, so I could end up sending data at 14:27, 14:32, 14:37, etc.

I thought I could lower the delay by making the code run at exactly 00 seconds of the given minute, so it wouldn't repeat itself later on if the given minute hadn't ended. However, this required an NTP call every second. I tried and it seemed too taxing for the board, though it didn't really produce any error.

Just wondering how you handle the varying propagation delays in getting the NPT time over the internet.
Paul

I did not account for that. Do you think that will lead to significant deviations?

That is likely the reason the time standard program for my ham radio digital communications program makes several NPT calls to determine an average delay before using a time for communications basis.
Paul

I question your entire timing scheme with constant refreshing of NTP time values.

The esp8266 system timer is based on an 40MHz crystal oscillator and had specs for accuracy of +/- 10ppm.

In my projects, I have used the familiar Arduino Time Library with a synch provider update from ntp at periodic interval. All timing with in the sketch is based on time library calls. You can easily find your minute changes. This is similar to the architecture when using an RTC which synchronizes the system time and the time libary.

There is an esp8266 example sketch called TimeNTP_ESP8266WiFi which demonstrates the integration of the NTP synchronization with the time libary.

Can you point out what's wrong with the scheme I'm using? Am I paging the server too often? Or is it the fact that it's not a regular interval? Newbie here. I looked at the sketch you mentioned and from my understanding it updates every third of a second. Is that right?

Hello East,

These are relevant to what you are trying to do:

Using millis for timing
Demonstration for several things at the same time

Thank you. Here's the updated code for the NTP migrated to millis():

  if (millis() - lastTime >= 300000ul) {

    lastTime = millis();
    
    // Set Thingspeak fields
    ThingSpeak.setField(1, tmp);
    ThingSpeak.setField(2, hmd);
    // Set Thingspeak status
    ThingSpeak.setStatus(myStatus);
    
    // Write to ThingSpeak channelw
    statuscode = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  
    // Get status
    if(statuscode == 200){
      Serial.println("Channel update successful.");
    }
    else{
      Serial.println("Problem updating channel. HTTP error code " + String(statuscode));
    }
  }

It has been working rather well and doesn't seem to interfere that much with the button. I would say the remote update takes about 15, 20s. What's problematic here is making sure that the count starts at min. 0, 5, 10, etc. at 00 seconds. Does it make sense to use the NTP only during setup, to force the program to wait until that specific threshold is reached and then start the main loop?

I looked at the sketch you mentioned and from my understanding it updates every third of a second. Is that right?

No. The interval is in seconds, so the demo sketch is set for 5 minutes (300 seconds).

You can test for the accuracy of the onboard timer and change based on that. I would think that once a day would be fine. Maybe once every six hours at most.

setSyncProvider(getNtpTime);
  setSyncInterval(300);

Am I paging the server too often? Or is it the fact that it's not a regular interval?

It looks like you are paging every pass through loop with lots of calls to delay() controlling the frequency. If you really want to do something with data at regular intervals referenced to clock time I would suggest the alternative approach of timing on the ESP with less frequent connections to the server. In my experience with both RTC's and WiFi the synchronized approach with the Time Library is simple and reliable.

You’re right. But I’m not entirely sure I need another library for this.

I had another look at the NTP library and realised I didn’t really need updates at every loop. I did need to have the NTP running and to be constantly asking for the current minute in order to trigger my main block of code, which was messy.

So I made another version (see below), using millis() in the loop and the NTP in the setup. During setup, it gets the time from the NTP and then waits for the right time window to sync the millis() and turn off the NTP.

There’s probably an easier way to do this, like, calculating how many millis() until there are until the next checkpoint and just adding the difference right away.

Just to stress this again, I needed to have postings every 5m on the clock, but also have the Arduino realign itself to this interval during power failures or the like, without constantly using the NTP. This version is missing the button but I think it’ll run alright since the millis() block takes only about 3 seconds to execute.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <BME280I2C.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include "arduino_secrets.h"
#include "ThingSpeak.h" // always include thingspeak header file after other header files and custom macros

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     LED_BUILTIN // OLED reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // OLED setup

BME280I2C bme;

char ssid[] = SECRET_SSID;   // your network SSID (name) 
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;

unsigned long myChannelNumber = SECRET_CH_ID;
const char * myWriteAPIKey = SECRET_WRITE_APIKEY;

// Initialize our values
int number1 = 0;
int number2 = 0;
int statuscode = 0;
String myStatus = "";
float tmp = 0;
float hmd = 0;
static unsigned long lastTime = 0;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0 ,30000);

void setup() {
 
  // Start basics
  Serial.begin(115200);
  Wire.begin();

  while(!bme.begin()) {
    Serial.println("Could not find BME280 sensor!");
    delay(1000);
  }
  ThingSpeak.begin(client);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  timeClient.begin();

  // Start display
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.println("Konichiwa!");
  display.display();

  Serial.println("Setup completed.");
  delay(5000);
  display.clearDisplay();
  display.setCursor(0,0);
  display.println("Starting..");
  display.display();

  // Start Wifi
  while(WiFi.status() != WL_CONNECTED){
      WiFi.begin(ssid, pass);  // Connect to WPA/WPA2 network. Change this line if using open or WEP network
      Serial.print(".");
      delay(5000);     
  }

  // Get current time
  timeClient.update();
  Serial.print("Current time is.. ");
  Serial.println(timeClient.getFormattedTime());
  Serial.println("Aligning clocks..");
  while(timeClient.getMinutes() % 5 != 0) {
    Serial.print("..");
    delay(5000); 
  }
  lastTime = millis();
  Serial.println("Done");
  timeClient.end();
  display.clearDisplay();
  display.display();
  
}

void loop() {

  // Wake up every 5 minutes
  if (millis() - lastTime >= 300000ul) {

    lastTime = millis();

    // Reconnect to Wifi if needed

    if(WiFi.status() != WL_CONNECTED){
      Serial.print("Attempting to connect to SSID: ");
      Serial.println(SECRET_SSID);
      while(WiFi.status() != WL_CONNECTED){
        WiFi.begin(ssid, pass);
        Serial.print(".");
        delay(5000);     
      }
      Serial.println("\nWifi connected.");
    }

    // Set values from sensors
    tmp = bme.temp();
    hmd = bme.hum();

    // Set Thingspeak fields
    ThingSpeak.setField(1, tmp);
    ThingSpeak.setField(2, hmd);
    
    // Set Thingspeak status
    ThingSpeak.setStatus(myStatus);
    
    // Write to ThingSpeak channel
    statuscode = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  
    // Update display with results
    if(statuscode == 200){
        Serial.println("Channel update successful.");
        display.clearDisplay();
        display.setCursor(0,0);
        display.println("Now:");
        display.print(tmp);
        display.print(char(167));
        display.println("C");
        display.print(hmd);
        display.println("%H");
        display.display();
        Serial.println("Display updated.");    
    }
    else{
        Serial.println("Problem updating channel. HTTP error code " + String(statuscode));
        display.clearDisplay();
        display.setCursor(0,0);
        display.println("Error!");
        display.display();
    }
   } 
    
}

For some reason the millis() block above can sometimes produce tiny variations. Just checking my overnight logs, I find that every now and then, a log entry will be one minute late, like at 5:01, or 8:31.

I suppose that a) setup could've started at, say, 23:05:39, with the 39 seconds mark being the actual mark where the code executes on 5 minute intervals. Thus sometimes 20 seconds isn't enough and the timestamp marks minute 1 or minute 31. Also, b) could be the Wifi reconnect taking longer than usual, since it only runs every 5m.

I changed this line to the setup:

lastTime = (millis() - (timeClient.getSeconds() * 1000) + 2000);

So the timer will be set closer to second 0.

I've also moved the Wifi reconnect out of the millis() block so the Wifi situation is already taken care of when the log is activated.

I would suggest you read the seconds value of the time, and set your millis() delay to a value which will cause the "seconds" value of the next read to be 30.

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