Doing two things at once, can someone point me in the right direction?

Been chipping away at building an environmental data logger. Pretty standard stuff. Longtime listener, first time poster.

I2C 1602 LCD
DS1307 RTC
LC Studios SD card module
2x DS18b20 on one-wire
2x DHT11

I learn’t (and forgot) Java last year at uni, that has been a major help with the programming side of things. Adding an LCD has bumped up the game a bit from printing to serial and a data file. Below is my code, it should be straight forward. Up the top I have defines that I may set to true for where info is printed (Serial, LCD, Data logger).I have constructors calling my sensors and formatting the output, and are called in the main loop, timed by delays. My problem is now that I would like to have data displayed in “real-time” on the LCD, but printed to the data file every 15 minutes on the hour (0900,0915,0930,0945 etc). Any advice would be greatly appreciated, even if it is just giving me a tid-bit and telling me to dig for the rest!

Cheers everyone

#define ECHO_TO_SERIAL 1
#define ECHO_TO_LOGFILE 1
#define ECHO_TO_LCD 1

/*
>>DHT Temp & Humidity hardware configuration
 Connect pin 1 (on the left) of the sensor to +5V
 Connect pin 2 of the sensor to whatever your DHTPIN is
 Connect pin 4 (on the right) of the sensor to GROUND
 Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor
 
 >>DS18B20 Temp sensors on 1-wire
 1st sensor uses (flat facing from right) +5v, data (bridged with 4.7k resistor to +5v), ground
 data is pin three
 2nd sensor + only uses data and gnd
 
 >>Real Time Clock Module
 Wiring the RTC to Arduino
 The most useful pins are duplicated from P1 to P2. 
 The pin "BAT" can be fed into a ADC pin for monitoring the voltage of the battery. It is exposed for the convenience of measuring the voltage of the battery. 
 Connecting "VCC" to 5 V will trickle charge the onboard battery.
 Arduino Tiny RTC I2C Real Time Clock Pinout
 PIN 	Description 	Comment
 BAT 	Battery voltage 	To monitor the battery voltage, or not connected
 GND 	Ground 	Ground
 VCC 	5V supply 	Power the module and charge the battery
 SDA 	I2C data 	I2C data for the RTC
 SCL 	I2C clock 	I2C clock for the RTC
 DS 	DS18B20 Temp. Sensor output 	One wire inteface
 SQ 	Square wave output 	Normally not used
 The I2C wires "SDA" and "SCL" are the data line and clock line, they should be connected to the corresponding pins depending on the Arduino board.
 Board 	I2C / TWI pins
 Uno, 	A4 (SDA), A5 (SCL)
 
 >>SD card Config
 * SD card attached to SPI bus as follows:
 ** UNO:  MOSI - pin 11, MISO - pin 12, CLK - pin 13, CS - pin 9 (CS pin can be changed)
 and pin #10 (SS) must be an output
 
 
 >>Relay
 Center and bottom screw on relay is normal closed
 In1 is conntected to Arduino pin
 In1 is conntected to Arduino pin
 In1 is conntected to Arduino pin
 In1 is conntected to Arduino pin
 */

/* Includes for I2C TinyTRTC and 1602 LCD*/
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>

/* Includes for LCStudio SD Module*/
#include <SD.h>

#include <OneWire.h> //For DS18B20 Library
#include <DallasTemperature.h> //For DS18B20 Library

#include "DHT.h" //For DHT Library

#define ONE_WIRE_BUS 2 //For DS18B20 Library

#define DHT_A_PIN 3 //For DHT_A
#define DHT_A_TYPE DHT11 //For DHT_A

RTC_DS1307 RTC;
// create an lcd instance with correct constructor for how the lcd is wired to the I2C chip
LiquidCrystal_I2C lcd(0x20, 4, 5, 6, 0, 1, 2, 3, 7, NEGATIVE); // addr, EN, RW, RS, D4, D5, D6, D7, Backlight, POLARITY

// Creat a set of new characters
const uint8_t charBitmap[][8] = {
  { 
    0xc, 0x12, 0x12, 0xc, 0, 0, 0, 0   }
  ,
  { 
    0x6, 0x9, 0x9, 0x6, 0, 0, 0, 0   }
  ,
  { 
    0x0, 0x6, 0x9, 0x9, 0x6, 0, 0, 0x0   }
  ,
  { 
    0x0, 0xc, 0x12, 0x12, 0xc, 0, 0, 0x0   }
  ,
  { 
    0x0, 0x0, 0xc, 0x12, 0x12, 0xc, 0, 0x0   }
  ,
  { 
    0x0, 0x0, 0x6, 0x9, 0x9, 0x6, 0, 0x0   }
  ,
  { 
    0x0, 0x0, 0x0, 0x6, 0x9, 0x9, 0x6, 0x0   }
  ,
  { 
    0x0, 0x0, 0x0, 0xc, 0x12, 0x12, 0xc, 0x0   }

};

File logFile; //our data logging file

const int chipSelect = 10; //Chip select/SS for LC Studio SD module

DHT dht_A(DHT_A_PIN, DHT_A_TYPE); //For DHT_A

//For DS18B20 config.
OneWire oneWire(ONE_WIRE_BUS);// Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire);// Pass our oneWire reference to Dallas Temperature. 
DeviceAddress onBoardThermometer = { 
  0x28, 0xED, 0xE3, 0x83, 0x04, 0x00, 0x00, 0x26 };// Assign the addresses of your 1-Wire temp sensors.
DeviceAddress remoteThermometer = { 
  0x28, 0xCC, 0xB5, 0x84, 0x04, 0x00, 0x00, 0x28 };
//*

void setup(void)
{

  Serial.begin(9600);

  Wire.begin();
  RTC.begin();
  //int charBitmapSize = (sizeof(charBitmap ) / sizeof (charBitmap[0]));
  lcd.begin(16,2);  // initialize the lcd as 20x4 (16,2 for 16x2)
  lcd.setBacklight(1); // switch on the backlight

  sensors.begin(); //For DS18B20
  sensors.setResolution(onBoardThermometer, 10); //For DS18B20

    dht_A.begin(); //For DHT_A

  pinMode(SS, OUTPUT); //SS for sd card


  //this echo is for debugging to serial, hope to expland it
#if ECHO_TO_SERIAL
  Serial.println("Serial printing ON");
  Serial.println("Checking SD card...");
  if(!SD.begin(chipSelect)){
    Serial.println("initialization failed!");
    return;
  }
  else{
    Serial.println("SD is ok!");
  }
  //want to check the other sensors but not sure how yet      
#endif //ECHO_TO_SERIAL    

}//setup


void printDs18b20(DeviceAddress deviceAddress)
{
  float temp = 0;

  sensors.requestTemperatures();
  temp = sensors.getTempC(deviceAddress);

#if ECHO_TO_LOGFILE
  logFile.print(temp);
  logFile.print(",");
#endif//ECHO_TO_LOGFILE

#if ECHO_TO_SERIAL
  Serial.print(" temperature is : ");
  Serial.print(temp);
  Serial.print("*C ,");
#endif //ECHO_TO_SERIAL

#if ECHO_TO_LCD
  lcd.clear();
  lcd.home();
  lcd.print("  ");
  lcd.print(temp);
  lcd.print("*C ,");
#endif //ECHO_TO_LCD

}

void printDht(DHT dhtIdentifier)
{
  int dht_Temp = dhtIdentifier.readTemperature();
  int dht_Humidity = dhtIdentifier.readHumidity();

#if ECHO_TO_LOGFILE
  logFile.print(dht_Temp);
  logFile.print(",");
  logFile.print(dht_Humidity);
#endif //ECHO_TO_LOGFILE

#if ECHO_TO_SERIAL
  Serial.print(" Remote Temp & Humidity : ");
  Serial.print(dht_Temp);
  Serial.print("*C, ");
  Serial.print(dht_Humidity);
  Serial.print("% , "); 
#endif//ECHO_TO_SERIAL

#if ECHO_TO_LCD
  lcd.print(" Remote Temp & Humidity : ");
  lcd.print(dht_Temp);
  lcd.print("*C, ");
  lcd.print(dht_Humidity);
  lcd.print("% , "); 
#endif//ECHO_TO_SERIAL

}

void printTime()
{
  DateTime now = RTC.now();

#if ECHO_TO_LOGFILE
  logFile.print(now.year(), DEC);
  logFile.print('/');
  logFile.print(now.month(), DEC);
  logFile.print('/');
  logFile.print(now.day(), DEC);
  logFile.print(',');
  logFile.print(now.hour(), DEC);
  logFile.print(':');
  logFile.print(now.minute(), DEC);
  logFile.print(':');
  logFile.print(now.second(), DEC);
  logFile.print(",");
#endif//ECHO_TO_LOGFILE

#if ECHO_TO_SERIAL
  Serial.print("The time is: ");
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
#endif //ECHO_TO_SERIAL

#if ECHO_TO_LCD
  lcd.print("The time is: ");
  lcd.print(now.hour(), DEC);
  lcd.print(':');
  lcd.print(now.minute(), DEC);
  lcd.print(':');
  lcd.print(now.second(), DEC);
#endif //ECHO_TO_SERIAL
} 

void loop(void){

#if ECHO_TO_LOGFILE
  logFile = SD.open("log.txt", FILE_WRITE);

  printTime();
  printDs18b20(onBoardThermometer);
  printDht(dht_A);
  logFile.println();
  logFile.close();
  delay(300000);
#endif//ECHO_TO_LOGFILE


#if ECHO_TO_SERIAL
  printTime();
  Serial.println();
  Serial.print("Onboard");
  printDs18b20(onBoardThermometer);
  Serial.print("Remote");
  printDs18b20(remoteThermometer);
  printDht(dht_A);
  Serial.println();
#endif//ECHO_TO_SERIAL

#if ECHO_TO_LCD
  printTime();
  lcd.println();
  lcd.print("Onboard");
  printDs18b20(onBoardThermometer);
  lcd.print("Remote");
  printDs18b20(remoteThermometer);
  printDht(dht_A);
  lcd.println();
#endif//ECHO_TO_SERIAL



}

Take a look at the Blink Without Delay. http://arduino.cc/en/Tutorial/BlinkWithoutDelay

You should know that the millis() and micros() do rollover. It is possible to avoid a rollover. I wrote the second part of this page to use register arithmatic: http://playground.arduino.cc/Code/TimingRollover

I think there are libraries for timing/dispatching.

I have a sketch like this: See the loop() as a background task. A dispatcher function uses millis() to count seconds, minutes and hours. I use variables to count. For example every 15 minutes would need a variable in the 'minute' part that counts up to 15.

You could use flexitimer2 (http://playground.arduino.cc/Main/FlexiTimer2). I have used that quite often and it works great.

If you set it up like this :

FlexiTimer2::set(900, 1.0, logging);

Logging should be called once every 15 minutes. (900 = 15 * 60 seconds).

logging() would be the function where you actually do the writing of the log data. So you loop() will then concentrate on only the reading and displaying of the LCD data.

Thank you both, I will investigate both these methods further this afternoon and post my results!

I had a feeling the way I have written my program I would have to start over again, hopefully not!

Erdin: Take a look at the Blink Without Delay. http://arduino.cc/en/Tutorial/BlinkWithoutDelay

You should know that the millis() and micros() do rollover. It is possible to avoid a rollover. I wrote the second part of this page to use register arithmatic: http://playground.arduino.cc/Code/TimingRollover

I think there are libraries for timing/dispatching.

I have a sketch like this: See the loop() as a background task. A dispatcher function uses millis() to count seconds, minutes and hours. I use variables to count. For example every 15 minutes would need a variable in the 'minute' part that counts up to 15.

Oh dear.....

Both millis() and micros() return unsigned longs for a reason. It's because with -unsigned- variables you don't have to do anything about rollover. That works whether the variable is 8 bit, 16 bit, 32 bit or 64 bit (yes, Arduino does 64 bit integers) as long as it's unsigned.

Here's a sketch that demonstrates it, you can change the values as you like:

unsigned long a, b, c;

void setup() {
  Serial.begin( 9600 );
  a = 0xffffff00UL;
  b = 0x10UL;
  Serial.println( "\n unsigned math\n" );
  Serial.print( "a = ");
  Serial.print( a, DEC );
  Serial.print( " = 0x");
  Serial.print( a, HEX );
  Serial.print( " = 0b");
  Serial.println( a, BIN );
  Serial.print( "b = ");
  Serial.print( b, DEC );
  Serial.print( " = 0x");
  Serial.print( b, HEX );
  Serial.print( " = 0b");
  Serial.println( b, BIN );
  if ( b >= a )
  {
   Serial.println( "b >= a" );
  }
  else          
  {
    Serial.println( "a > b" );
  }
  c = a - b;
  Serial.print( "a - b = ");
  Serial.print( c, DEC );
  Serial.print( " = 0x");
  Serial.print( c, HEX );
  Serial.print( " = 0b");
  Serial.println( c, BIN );
  c = b - a;
  Serial.print( "b - a = ");
  Serial.print( c, DEC );
  Serial.print( " = 0x");
  Serial.print( c, HEX );
  Serial.print( " = 0b");
  Serial.println( c, BIN );
  c = b - (b + 1);
  Serial.print( "b - (b + 1) = ");
  Serial.print( c, DEC );
  Serial.print( " = 0x");
  Serial.print( c, HEX );
  Serial.print( " = 0b");
  Serial.println( c, BIN );
  
  while( 1 );
}

void loop() 
{
}

lazaah, have a look at this blog by Nick Gammon.

How to do multiple things at once … like cook bacon and eggs
http://www.gammon.com.au/forum/?id=11411

It’s a full, sensible, easy explanation with code included.

GoForSmoke: Here's a sketch that demonstrates it, you can change the values as you like:

GoForSmoke, if you look at the second part of the page I linked ( http://playground.arduino.cc/Code/TimingRollover ), you see that there is an example and test sketch that shows just that XD

I got as far as the exposition into signed ints and hit the X buzzer.

Might be just me but I find that if someone gets a wrong idea it's 3x harder to dig that out than to just show them the right thing in the first place. When you've gone through 3 or 4 pages of a problem thread and the OP still can't let go of "rollover" it ceases to be fun. When it happens again and again it turns into something like "why not Strings? they're easier!".

GoForSmoke, especially for you, I have added a link to the second part, so you don't have to scroll down. http://playground.arduino.cc//Code/TimingRollover#arithmetic

@Erdin, interestingly enough, your sketch examples also work with signed integers.

It's not the fact that they're unsigned than makes it work. It's the fact that you're always using differences between time stamps, and not the time stamps directly. Signed integers have a rollover point as well. It's just that it's on the opposite end of the number line. However, if you only use differences, they avoid the rollover issues just as easily.

This is what I mean about wrong ideas getting stuck. Just use unsigned numbers and there -is- no “rollover problem” to logic around, it’s the same one simple expression every time.

“Rollover problem” is like wondering where the sun goes at night. When you understand that the Earth turns continuously, it’s not a problem to be solved. Do you have a hard time knowing it’s been 10 minutes when a clock goes from 11:55 to 12:05? Clocks are unsigned, they just go around same as the Earth, for some strange reason.

PS - use either way and see what the longest interval you can time is.

GoForSmoke: This is what I mean about wrong ideas getting stuck. Just use unsigned numbers and there -is- no "rollover problem" to logic around, it's the same one simple expression every time.

"Rollover problem" is like wondering where the sun goes at night. When you understand that the Earth turns continuously, it's not a problem to be solved. Do you have a hard time knowing it's been 10 minutes when a clock goes from 11:55 to 12:05? Clocks are unsigned, they just go around same as the Earth, for some strange reason.

The example that he posted demonstrates he's aware of what's happening and knows that the solution to this "problem" is using unsigned variables and subtraction. I think the argument to be made is whether this is really a "problem" that needs such a lengthy explanation and demonstration.

Edited:

No, let's just see it be a problem again and again because it was planted as one up on the Playground when there really is no problem.

I can time intervals up to 49.71 days to the millisecond using unsigned longs and the same single if statement.

Adding: That's 49 days 17 hours 2 minutes 47 seconds and 295 milliseconds to be more precise for picky people, ie 1 ms less than 4294967296 ms.

I am building an data logger with an LCD to display data from sensors (DHTs, Ds1820b). I am currently having trouble piecing together an interactive menu which would display date/time, temp1, temp2, …, tempN, backlight on/off. Displaying each menu item, a function would be called keeping the sensor values as close to realtime as needed.

Ideally the menu would be 2-D like as below, with the ability to toggle the backlight using the button

23:59:59, 31/12/1999
|
|
temp1—>temp2—>temp3
|
|
backlight on/off

I have been attempting to implement the MenuBackend library by modifying this example http://www.coagula.org/content/pages/tutorial-manage-menu-and-lcd-display-arduino. But I feel this library is beyond what I require, and for its size I am unsure if its use is justifiable in my application.

Any tips on how to get started, or where to look would be great. I don’t want to just be given an answer, without working for it, but some guidance and structure would be appreciated. I am relatively new to programming (especially C), and am at the most challenging step of my project yet.

Cheers in advance !

Two months on, and the same project, huh?

Please do not cross-post. This wastes time and resources as people attempt to answer your question on multiple threads.

Threads merged.

  • Moderator

GoForSmoke: lazaah, have a look at this blog by Nick Gammon.

How to do multiple things at once ... like cook bacon and eggs http://www.gammon.com.au/forum/?id=11411

It's a full, sensible, easy explanation with code included.

Where to get started is right there: http://www.gammon.com.au/forum/?id=11411

[quote author=Nick Gammon link=topic=183625.msg1449492#msg1449492 date=1383209189] Two months on, and the same project, huh? Please do not cross-post. This wastes time and resources as people attempt to answer your question on multiple threads. Threads merged. - Moderator [/quote]

Apologies , usually a forum would scold for reviving a two month old topic.

GoForSmoke:

GoForSmoke: lazaah, have a look at this blog by Nick Gammon.

How to do multiple things at once ... like cook bacon and eggs http://www.gammon.com.au/forum/?id=11411 It's a full, sensible, easy explanation with code included.

Where to get started is right there: http://www.gammon.com.au/forum/?id=11411

I understand the concepts from above in regards to time keeping. I guess the concept i am having trouble with is how to propagate the actual menu structure. Ideally each object in the menu, would call a function such as "void printLcdTime()" or "void printLcdTemp", that is where I had trouble using previous examples. Would using a 2d array be a feasible option?

A simple state machine should do it. Don't get bogged down by the words "state machine". Basically when you press your button the code advances to a new "display mode".

I don't see why you can't be getting the temperate all the time, regardless of whether you display it or not.

In pseudo code it would be roughly:

* Find temperature
* Find time
* Update LCD based on current mode:
  - if time mode, show time
  - if date mode, show date
  - if temperature mode, show temperature
* If switch pressed, move to next mode
* Go back to first step (loop)

Yes, the next concept to marry in with the timers is the state machine, or finite state machine however you might name it.

The do many things at once (real time tasking) idea is to keep loop() running as fast as you can for quick response while doing the different many things one at a time on each pass through and make a control system to select which code to run on any certain pass through.

In Nick’s example everything is controlled by time. A state machine controls by what is going on, the process state. But there is nothing to say that you can mix the two or have a state machine within a state machine. These are techniques, not religions.

In a simple state machine I might print a menu and watch Serial for a single digit choice number then either take an action based on a valid choice or print an error message on invalid choice. In my example there are only 2 choices on the menu so that leaves me with at least 5 states.

state - action

0 - print menu
10 - get choice
100 - do choice 1
150 - do choice 2
200 - error

Inside loop() I put a switch-case statement with one case for every state.

switch (state)
{

case 0
print the menu and change the state value to 10
break

case 10
if ( Serial.available() )
{
read Serial
switch ( read_char )
{
case ‘1’
change state to 100
break

case ‘2’
change state to 200
break

default
change state to 200
}
}
break

case 100
do whatever menu choice 1 should do
break

case 150
do whatever menu choice 2 should do
break

case 200
print bad menu choice user error message
change state to 0

default
print special alert the programmer error in the code message, this should never be reached

}

And I use that breakdown as comments to help fill in the actual code.

Note that for the do menu choice cases that those may be broken up into more than one case and should be if there is any delay in time or waiting for a response (perhaps a sub-menu and user response but maybe waiting for a pin to go HIGH or LOW) then one case does everything up to that wait then changes to state to the next that waits for Serial or checks the pin and simply lets loop() run again until the response is detected.

ALWAYS keep whatever section of code is to run short. NEVER BLOCK.

One thing to do is to add a blinking led (like good old pin 13 led) as a running status indicator. If you can see that not blinking right (once per second) then you have a hangup in your code.

Thanks guys, I am a bit embarrassed how easy it actually was. I think I looking at other sources of information made it seem like quite a complex thing to do. I am sure it COULD be, but KISS right?!

Code for simply registering the joystick movement is below, feel free to pull me up on anything. I would obviously like to develop good habits early on. One thing I do not like is the name of the lastTimeMoved variable, but can’t think of a nicer name! Can anyone point me in the direction of comment etiquette?

//Joystick setup
int xAxisPin = A0;
int yAxisPin = A1;

unsigned long lastTimeMoved;

//

void setup()
{

  Serial.begin(9600);

  // initialise the last time joystick was moved
  // here so that it picks up the first press and works from there on
  lastTimeMoved = millis(); 


}

void loop()
{


  switch (readJoystick()){
  case 1:
Serial.println("Left");
    break;

  case 2:
 Serial.println("Right");
    break;

  case 3:
 Serial.println("Down");
    break;      

  case 4:
 Serial.println("Up");
    break;      
  }

}


int readJoystick(){  //reads the joystick

  const int menuSpeedLimit = 175; //Without this the joystick is sensitive and will read way too often!

  // The take the joysticks analog potentiometer readings and use treatValue() to turn 
  // them into numbers between 0-8 and saves them as reading values. They are the most recently
  //read values
  int readXAxis = treatValue(analogRead(xAxisPin));  
  int readYAxis = treatValue(analogRead(yAxisPin));

  // These are to store the up to date values and the previous value stored so
  // the program knows if the joystick has moved (based on previous) and if it
  // has where it has moved too
  int nowXAxis, nowYAxis, previousXAxis, previousYAxis;

  // this is what is returned for the readjoystick, I initialise it as zero kinda as insurance
  // from the function running through unexpectedly
  int joystickDirection = 0;


  // check to see if the joystick is being shifted:
  // do this by checking if it is sitting in its default axis state
  // since the last move, if it has:
  if ( (readXAxis != 4) || (readYAxis != 4)){ //4 is where the joystick rests

    nowXAxis = readXAxis;
    nowYAxis = readYAxis;

    previousXAxis = nowXAxis;
    previousYAxis = nowYAxis;

    // this acts as a speed control for the joystick/menu control
    // check to see if the time since the joystick last moved has 
    // exceeded the menu speed limit
    if ((millis() - lastTimeMoved) > menuSpeedLimit){

      // update the last time moved
      lastTimeMoved = millis();

      // now we figure out what the joystick has actually done 
      // and set the joystick direction variable to a number 
      // that identifies the direction
      if (nowXAxis < 1){ 
        joystickDirection = 1; //joystick has gone left
      }
      else if (nowXAxis > 7){
        joystickDirection = 2; //joystick has gone right
      }

      if (nowYAxis < 1){
        joystickDirection = 3; //joystick has gone down
      }
      else if (nowYAxis > 7){
        joystickDirection = 4; //joystick has gone up
      }
    }
  }

  return joystickDirection; // return the direction for whatever called it!
}

// turns joystick values into number between 0 and 8
int treatValue(int data) {
  return (data * 9 / 1024);
}