Wire communication during interrupt handling

Hello, everyone.
I am trying to do a small project using few i2c peripherials.
First of all, I am using I2C connected LCD display (16x2), works just fine.
Additionally, I am using MCP23017 for handling the input buttons and output pins.

My code follows:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define LCD_I2C_ADDR    0x27
#define MCP             0x20
#define MCPenable       4
#define BACKLIGHT_PIN   3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7
#define LED     13

LiquidCrystal_I2C lcd(LCD_I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

byte intValue = 0;
byte inputs = 1;
byte intState = 0;

void setup()
{
  Wire.begin();
  lcd.begin (16,2);
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.clear();
  Wire.begin();
  
  // first we reset MCP
  pinMode(MCPenable, OUTPUT);
  digitalWrite(MCPenable, LOW);
  delay(10);
  digitalWrite(MCPenable, HIGH);
  delay(10);
  I2Csend(MCP, 0x01, 0x00); // set all of bank B to OUTPUTS
  I2Csend(MCP, 0x00, 0xFF); // set all of bank A to INPUTS
  I2Csend(MCP, 0x0C, 0xFF); // set pullup ON for bank A
  I2Csend(MCP, 0x02, 0xFF); // invert inputs of bank A
  I2Csend(MCP, 0x04, 0xFF); // enable interrupt for all pins of bank A
  
  pinMode(LED, OUTPUT);

  attachInterrupt(1, intControl, RISING);
}

void loop()
{
  Wire.beginTransmission(MCP);
  Wire.write(0x12); // GPIOA
  Wire.endTransmission();
  Wire.requestFrom(MCP, 1); 
  inputs = Wire.read();
  
  //I2Csend(MCP, 0x13, inputs);

  lcd.setCursor(0,0);
  lcd.print("Inputs: ");
  lcd.print(inputs);
  lcd.print("        ");
  
  lcd.setCursor(0,1);
  lcd.print("Int: ");
  lcd.print(intValue / 2);
  lcd.print(" ");
  lcd.print(intState);
  lcd.print("             ");
  delay(50); // for debounce
}

void I2Csend(byte I2Caddress, byte memAddr, byte memValue) {
  Wire.beginTransmission(I2Caddress);
  Wire.write(memAddr); // IODIRA register
  Wire.write(memValue); // set all of bank A to INPUTS
  Wire.endTransmission();
}

void intControl() {
  detachInterrupt(1);
  // this works no problem
  intValue++;
  digitalWrite(LED, !digitalRead(LED));

  //problem code - trying to get the interrupt register value from MCP port A (reg. 0x10)
  Wire.begin();
  Wire.beginTransmission(MCP);
  Wire.write(0x10);
  Wire.endTransmission();
  Wire.requestFrom(MCP, 1); 
  intState = Wire.read();

  I2Csend(MCP, 0x13, intState);
  // end of problem code

  delay(100); // debounce?
  attachInterrupt(1, intControl, RISING);
}

The problem is, that without interrupt handling, all acts well.
As soon as I wired the interrupt pin to the Arduino pin 3, the code works, but only when I disable the problematic part within the interrupt handling.
I assume the timing interrupts, relevant for i2c communication, are not working when external interrupt occur, but that's just my guess.
Anyone have any example for me or an advice, what am I doing wrong?
Thanks.

That IRQ demonstrates that you don't understand a LOT about interrupt handlers.

  1. when an IRQ runs, interrupts are disabled

  2. an IRQ should be kept as short as possible

  3. any variables used in an IRQ that will be shared with the program must be declared volatile

You don't need the detach and attach because of (1). No IRQ, including the ones for millis(), delay(), Serial and Wire can run while any other IRQ is running.

The only things that your IRQ should be doing is to increment intValue, that should be volatile, and set an also volatile flag so that the rest of your code can do the slower parts of the process.

Throwing in the delay because debounce? That's like adding a 5 ton anchor to a rowboat.

void intControl() {
  detachInterrupt(1);
  // this works no problem
  intValue++;
  digitalWrite(LED, !digitalRead(LED));

  //problem code - trying to get the interrupt register value from MCP port A (reg. 0x10)
  Wire.begin();
  Wire.beginTransmission(MCP);
  Wire.write(0x10);
  Wire.endTransmission();
  Wire.requestFrom(MCP, 1); 
  intState = Wire.read();

  I2Csend(MCP, 0x13, intState);
  // end of problem code

  delay(100); // debounce?
  attachInterrupt(1, intControl, RISING);
}

Calling delay() in an ISR hangs the system completely as do Serial calls, because
they all wait for specific interrupts. Waiting for millis() doesn't work as no timer
interrupts get a look in.

I can't recall offhand if the Wire library uses interrupts itself, if so they you can't
call it while interrupts are disabled.

With care its possible to re-enable interrupts in an ISR before returning, but you
have to know what you are doing (ie understand re-entrancy).

GoForSmoke:
That IRQ demonstrates that you don't understand a LOT about interrupt handlers.

Not about to argue about that, that's why I am trying to learn something :slight_smile:

GoForSmoke:
Throwing in the delay because debounce? That's like adding a 5 ton anchor to a rowboat.

As a matter of fact, the debounce line including the comment has been copied from Adruino example here on the forum :slight_smile:

OK, I managed to read the documentation to MCP23017 and I found out, that I am wasting my time and resources of Arduino to focus on the interrupts.
I managed a very simple code and thanks to the ability to capture the state of the interrupt on MCP23017 and store it until read, I do not have to hurry to read it :slight_smile:
Final code:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define LCD_I2C_ADDR    0x27
#define MCP             0x20
#define MCPenable       4
#define MCPint          5
#define BACKLIGHT_PIN   3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7
#define LED     13

LiquidCrystal_I2C lcd(LCD_I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

byte inputs = 1;
int counter = 0;

void setup()
{
  Wire.begin();
  lcd.begin (16,2);
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.clear();
  Wire.begin();
  
  // first we reset MCP
  pinMode(MCPint, INPUT);
  pinMode(MCPenable, OUTPUT);
  digitalWrite(MCPenable, LOW);
  delay(10);
  digitalWrite(MCPenable, HIGH);
  delay(10);
  I2Csend(MCP, 0x01, 0x00); // set all of bank B to OUTPUTS
  I2Csend(MCP, 0x00, 0xFF); // set all of bank A to INPUTS
  I2Csend(MCP, 0x0C, 0xFF); // set pullup ON for bank A
  I2Csend(MCP, 0x02, 0xFF); // invert inputs of bank A
  I2Csend(MCP, 0x04, 0xFF); // enable interrupt for all pins of bank A
  
  pinMode(LED, OUTPUT);
}

void loop()
{
  if (!digitalRead(MCPint)) {
    Wire.beginTransmission(MCP);
    Wire.write(0x10); // Interrupt capture GPIO A
    Wire.endTransmission();
    Wire.requestFrom(MCP, 1); 
    inputs = Wire.read();
    
    if (inputs == 0) return;
    
    I2Csend(MCP, 0x13, inputs);
  
    lcd.setCursor(0,0);
    lcd.print("Inputs: ");
    lcd.print(inputs);
    lcd.print("        ");
    
    lcd.setCursor(0,1);
    lcd.print("Int: ");
    lcd.print("             ");
    delay(500);
  } else {
    lcd.setCursor(0,0);
    lcd.print("Nothing pressed ");
    lcd.setCursor(0,1);
    lcd.print(counter++);
    lcd.print(" ");
    lcd.print(digitalRead(MCPint));
    lcd.print("                ");
  }
  delay(100);
}

void I2Csend(byte I2Caddress, byte memAddr, byte memValue) {
  Wire.beginTransmission(I2Caddress);
  Wire.write(memAddr); // IODIRA register
  Wire.write(memValue); // set all of bank A to INPUTS
  Wire.endTransmission();
}

Now the interrupt is fully handled by the MCP, it sets the trigger LOW once any pin from GPIO A changes and waits until it is read. Once I read the register 0x10, I can get the value of each input.
Once the button is released, however, the interrupt is set again, therefore I read it again and when the value of the register 0x10 is 0 (nothing pressed), I ignore such input.
Works without problem.
Thanks for any advice on the interrupt management :slight_smile:

Would you like to get rid of those delay()?

It would let you have other processes run together with what you have without using.... interrupts.
The simplest is a blinking status light but buttons/leds/serial I/O are also not difficult.

GoForSmoke:
Would you like to get rid of those delay()?

Can you, please, post some code? I'd like to see something that would allow me to run some serial communication (especially using SoftwareSerial).

Here is a simple one that has a blinking status led (the Blink Without Delay sketch) showing how to make code run on time without blocking other code to do so.

The other code is a very simple user interface to make the led blink faster or slower or show the help message. The user text can get much more sophisticated (truly I should add number input to this example) with added code.

What is important is not what this does but how the parts work while allowing each other to work at the same time. Once that is understood it becomes possible to add MANY parts that work together.

Arduino at 16 MHz can do 20+ small things in a millisecond. This is something that even many non-beginners do not seem to consider when coding. One thing to do is copy the code and add parts to tell just how many times a second it does something, or what the shortest idle time might be. These will tell useful info about how much more it could be doing, or how much slower the clock could run and therefore the voltage be (at 8 MHz, 3.3V operation at lower power is possible -- longer life on batteries) and still do the job. This no-block-code method makes it easier to add and remove such tests.

Please ask about any part, even if it is just 1 letter, that you have any question about.
The code is not perfect or the only way but it is a way that works.

/* Blink without Delay -- with UL fixes and User Input
 
 Turns on and off a light emitting diode(LED) connected to a digital  
 pin, without using the delay() function.  This means that other code
 can run at the same time without being interrupted by the LED code.
 
 The circuit:
 * LED attached from pin 13 to ground.
 * Note: on most Arduinos, there is already an LED on the board
 that's attached to pin 13, so no hardware is needed for this example.
 
 
 created 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 modifed to add User-I/O as BWD2 1 Jan 2014 
 by GoForSmoke :-P
 
 This example code is in the public domain.
 
 
 http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
 */

// constants won't change. Used here to 
// set pin numbers:
const int ledPin =  13;      // the number of the LED pin

// Variables will change:
int ledState = LOW;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated

// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
unsigned long tWait = 1000UL;           // interval at which to blink (milliseconds)
unsigned long userFactor = 100UL;

void usageMsg( void )
{
  Serial.println ( F ( __FILE__ ) );
  Serial.println( F("\nUSER CONTROL CHARS\n") );
  Serial.println( F("H for Help to print this message.") );
  Serial.println( F("F to blink faster, less factor") );
  Serial.println( F("S to blink slower, more factor") );
  Serial.println( F("digit x factor = blink\n") );
}

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);

  Serial.begin( 57600 ); 
  Serial.println( F("\nBWD2 startup\n") );
  usageMsg(); 
}

void waitMsg( unsigned long wTime )
{
  Serial.print( F( "wait = " ));
  Serial.println( wTime );

}

void factorMsg( unsigned long uFactor )
{
  Serial.print( F( "User factor now = " ));
  Serial.println( uFactor );

}

void loop()
{
  // check to see if it's time to blink the LED; that is, if the 
  // difference between the current time and last time you blinked 
  // the LED is bigger than the interval at which you want to 
  // blink the LED.
  unsigned long currentMillis = millis();

  if ( currentMillis - previousMillis >= tWait ) 
  {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   

    // if the LED is off turn it on and vice-versa:
    ledState = !ledState; // ! is logical not, opposite

      // set the LED with the ledState of the variable:
    digitalWrite( ledPin, ledState );
  } // end of blink state change

    //    lowest priority is serial data because it is slow.
  if ( Serial.available() ) // user input section -- it reads from serial monitor
  {
    char  serChar = Serial.read();

    if ( serChar >= '0' && serChar <= '9' ) // digits only
    {
      if ( serChar > '0' )
      {
        tWait = (long)( serChar - '0' ) * userFactor;
      }
      else
      {
        tWait = 10UL * userFactor;
      }
      waitMsg( tWait );
    } 
    else if ((( serChar >= 'A' ) && ( serChar <= 'Z' )) || 
      (( serChar >= 'a' ) && ( serChar <= 'z' ))) // alphas only 
    {
      serChar &= 0xDF; // strips bit 5, makes lowercase alphas uppercase
      if ( serChar == 'F' ) // faster
      {
        if ( userFactor > 1UL )
        {
          userFactor /= 2UL; // increase the frequency
          tWait /= 2UL;
        }
        Serial.print( F("faster: " ) );
        factorMsg( userFactor );
        waitMsg( tWait );
      }
      else if ( serChar == 'S' )  // slower
      {
        if ( userFactor < 1000UL )
        {
          userFactor *= 2UL;  // decrease the frequency
          tWait *= 2UL;
        }
        Serial.print( F("slower: ") );
        factorMsg( userFactor );
        waitMsg( tWait );
      }
      else if ( serChar == 'H' )  // help
      {
        usageMsg();
      }
    }
    // all other serial chars are ignored with no error messages
  } // end of serial available

  //    finished all tasks  
}

Hello, GoForSmoke.

Thanks for pointing me to the direction I need.
The example seems to be very helpful, but to use it, I would have to re-write and re-think the whole programming approach and algorithm. Anyhow, I like it and I will do it, at least for the fun of learning new stuff.
Anyhow, I do not understand few things, mostly semantics:

unsigned long tWait = 1000UL;

What does this UL after the number means?

Serial.println( F("H for Help to print this message.") );

And again, what does the F here means?

Anyhow, I like this approach, I'll test it - it would probably help me to solve the issue of navigating menu while communicating using SoftwareSerial. I'll also consider keeping the interrupt counter code as small as possible.

U means unsigned, L means long, integer constants default to type int otherwise (on
Arduino 16 bit signed).

The F macro allows use of strings in flash memory (hiding the use of pgm_read_byte()),
which saves putting strings in the very constrained amount of available RAM.

sancho_sk:
Hello, GoForSmoke.

Thanks for pointing me to the direction I need.
The example seems to be very helpful, but to use it, I would have to re-write and re-think the whole programming approach and algorithm. Anyhow, I like it and I will do it, at least for the fun of learning new stuff.

Suppose I tell you that I will help you fit your code in to that and change out from that what is not needed?

Because your code is very close already, I saw that and then is when I asked how you would like to lose the delay().
The delay()'s you have will come out and

if (!digitalRead(MCPint))
{
}
else
{
}

will go in, inside of a time check that will do what the delay()'s you have now do.

We will also need to pull over setup() code, define's and variable declarations but practically all of it will be copy and paste then Autoformat to make it clean for Gene.

How does that hit you?

Anyhow, I do not understand few things, mostly semantics:

unsigned long tWait = 1000UL;

What does this UL after the number means?

1000UL is the way that we ensure the compiler turns the constant 1000 into a 32-bit unsigned long.

Not always does it make a difference but when it does, it is a real pain to debug. Learning to code for me, in the first years I think I spent more time debugging than planning and writing put together, maybe 2x to 3x more. You can bet I spent time making ways to reduce that!

Variables have type. Types are like int, char, long, and also type can be programmer invented. The variable is declared to be a type.

Constants like 0, 1, 100, 20000, without type information get used by the compiler however and at times wrongly.
Putting type information like U for unsigned and/or L for long pins the conversion down and keeps me mindful of what type I am working with, to not mix types in math that even more results in bugs.

So the UL is for the compiler and also for myself.

Serial.println( F("H for Help to print this message.") );

And again, what does the F here means?

The F macro allows use of strings in flash memory (hiding the use of pgm_read_byte()),
which saves putting strings in the very constrained amount of available RAM.

Anyhow, I like this approach, I'll test it - it would probably help me to solve the issue of navigating menu while communicating using SoftwareSerial. I'll also consider keeping the interrupt counter code as small as possible.

The only part of your code that needs to use interrupt may be I2C, serial and millis(). Throwing in another can easily mess with those and the timing they need. As it is, you should know that too much serial printing will affect when other parts of your code run. Just knowing that may keep you from "fixing" something else if you do get a problem.

PS - by the way,

I thought for sure you would have question about why this line is the way it is, with the subtraction,

  if ( currentMillis - previousMillis >= tWait )

and not

  if ( currentMillis >= calculatedEndOfWaitMillis )

because most people do not get that on the first time, or even later. Many even learn ideas counter to it that they resist changing and insist on fixing "the rollover" that does not need fixing.

GoForSmoke, you have NO idea how thankful I am for the tip of yours.
I changed the approach to my code and now it is unbelievable, what I can achieve without interrupts and without any special treatment.
Right now the code ca simultaneously read temperature from 5 1wire sensors, read light level from i2c sensor, handle button press from 6 inputs using MCP23017 (do not have more buttons :D) and communicate seamlessly.
I have to just finish the serial communication and RS485 communication, but that's something I have already done before.

Thanks a LOT!
For anyone interested, here is the code:

#include <RS485_protocol.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <dht.h>

// basic I2C config (addresses)
#define LCD_I2C_ADDR    0x27
#define Light_I2C_ADDR  0x23
#define MCP             0x20

// specific configuration for MCP23017 
#define MCPenable       4  // reset pin
#define MCPint          5  // reading interrupt

#define DHT22_PIN 9
#define DHTpower  8
#define DHTground 10

// LCD configuration
#define BACKLIGHT_PIN   3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

#define REC_PIN 11
#define XMIT_PIN 12
// LCD
LiquidCrystal_I2C lcd(LCD_I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);
// OneWire temp sensors
OneWire ds(10);  // on pin 10
SoftwareSerial softSerial (REC_PIN, XMIT_PIN);  // receive pin, transmit pin

// runtime variables
unsigned long tRunLast = 0UL;         // Last time temperature measurement started (for 1wire parasite interval)
unsigned long tInt = 22000UL;         // Interval for temperature measurement (22 sec)
unsigned long tRunLast1w = 0UL;       // Last measurement of 1wire sensor
unsigned long tInt1w = 1000UL;        // Interval of measurement using 1wire sensor
unsigned long tWait = 1000UL;         // Interval for 1-wire parasite power stabilization (1 sec)
byte tRunning = false;                // check if temperature measurement over 1wire is running
unsigned long lRunLast = 0UL;         // Last time light measurement started (for 1wire parasite interval)
unsigned long lInt = 60000UL;         // Interval for light measurement (60 sec)
unsigned long mRunLast = 0UL;         // Last time MCP read started
unsigned long mInt = 50UL;            // Interval for MCP read (interrupt clear)
unsigned long currentMillis = 0UL;    // main time-counting var


byte poradie1w = 0;                   // Storing the last measured temperature sensor ID

volatile unsigned long flowTicks1 = 0UL;  // tracking of number of ticks for water flow 1
volatile unsigned long flowTicks2 = 0UL;  // tracking of number of ticks for water flow 2

byte i;
byte data[12];
byte addr[8];

void setup() {
  // setting serial
  Serial.begin(115200);
  Serial.println("Init...");
  // setting interrupts
  attachInterrupt(0, flowMeter1, RISING);
  attachInterrupt(1, flowMeter2, RISING);
  
  Wire.begin();  // setting I2C
  lcdInit();     // setting LCD
  lightInit();   // setting light sensor
  setupMCP();    // setting up MCP and interrupt
  
  Serial.println("Init done. Starting...");
}

void loop() {
  currentMillis = millis();
  if (tRunning == false && currentMillis - tRunLast >= tInt) {
    // start of temperature measurement (no measurement is running now)
    getTemp1();
    tRunLast = currentMillis;
  }
  if (tRunning == true && currentMillis - tRunLast >= tInt1w) {
    getTemp2();
    tRunLast1w = currentMillis;
    tRunLast = currentMillis; // posuniem interval merania teploty na cas od ukoncenia posledneho merania
  }
  if (currentMillis - lRunLast >= lInt) {
    // start of light measurement
    measureLight();
    lRunLast = currentMillis;
  }
  if (!digitalRead(MCPint)) {
    // read what happened on MCP
    getMCP();
  }
  if (Serial.available()) {
    // read serial data (this takes long!!!)
  }
  if (softSerial.available()) {
    // read serial data (this takes even longer!!!)
  }
}

void flowMeter1() { flowTicks1++; }
void flowMeter2() { flowTicks2++; }

void measureLight() {
  // tu budem merat svetlo
  uint16_t Light_Intensity;
  Wire.beginTransmission(Light_I2C_ADDR);
  Wire.write(0x10);
  Wire.requestFrom(Light_I2C_ADDR, 2);
  Light_Intensity = Wire.read();
  Light_Intensity <<= 8;
  Light_Intensity |= Wire.read();
  Wire.endTransmission();
  Light_Intensity /= 1.2;
  lcd.setCursor (0,1);        // go to start of 2nd line
  lcd.print(Light_Intensity);
  lcd.print(" Lux            ");
  Serial.print("Light ");
  Serial.println(Light_Intensity);
}

void getTemp1() {
  if(!ds.search(addr)) {
    ds.reset_search();
    poradie1w = 0;
    return;
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);
  tRunning = true;
  tRunLast1w = millis();
}

void getTemp2() {
  ds.reset();
  ds.select(addr);
  ds.write(0xBE);
  for (int i = 0; i < 12; i++) data[i] = ds.read();
  Serial.print("Temp ");
  for (int i = 0; i < 9; i++) Serial.print(addr[i], HEX);
  Serial.print(" ");
  Serial.println((( (data[1] << 8) + data[0] )*0.0625));
  tRunning = false;
  tRunLast1w = millis();
  poradie1w++;
}

void setupMCP() {
  // first we reset MCP
  pinMode(MCPint, INPUT);
  pinMode(MCPenable, OUTPUT);
  digitalWrite(MCPenable, LOW);
  delay(10);
  digitalWrite(MCPenable, HIGH);
  delay(10);
  I2Csend(MCP, 0x01, 0x00); // set all of bank B to OUTPUTS
  I2Csend(MCP, 0x00, 0xFF); // set all of bank A to INPUTS
  I2Csend(MCP, 0x0C, 0xFF); // set pullup ON for bank A
  I2Csend(MCP, 0x02, 0xFF); // invert inputs of bank A
  I2Csend(MCP, 0x04, 0xFF); // enable interrupt for all pins of bank A
}

void I2Csend(byte I2Caddress, byte memAddr, byte memValue) {
  Wire.beginTransmission(I2Caddress);
  Wire.write(memAddr); // IODIRA register
  Wire.write(memValue); // set all of bank A to INPUTS
  Wire.endTransmission();
}

void I2CsendSingle(byte I2Caddress, byte memValue) {
  Wire.beginTransmission(I2Caddress);
  Wire.write(memValue); // set all of bank A to INPUTS
  Wire.endTransmission();
}

void getMCP() {
  Wire.beginTransmission(MCP);
  Wire.write(0x10); // Interrupt capture GPIO A
  Wire.endTransmission();
  Wire.requestFrom(MCP, 1); 
  byte inputs = Wire.read();
  
  if (inputs > 0 && currentMillis - mRunLast >= mInt) {
    Serial.print("Keyboard ");
    Serial.println(inputs);
  }
  mRunLast = millis();
}

void lcdInit() {
  lcd.begin (16,2);
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.clear();
}

void lightInit() {
  I2CsendSingle(Light_I2C_ADDR, 0x01);
  delay(10);
  I2CsendSingle(Light_I2C_ADDR, 0x10);
  delay(2000);
}

sancho_sk:
I changed the approach to my code and now it is unbelievable, what I can achieve without interrupts and without any special treatment.
Right now the code ca simultaneously read temperature from 5 1wire sensors, read light level from i2c sensor, handle button press from 6 inputs using MCP23017 (do not have more buttons :D) and communicate seamlessly.
I have to just finish the serial communication and RS485 communication, but that's something I have already done before.

Does the different approach require any special genius or is it a matter of just being different from what you knew?

When I do multiple buttons or leds in an array, I only process one per pass through loop().
Keeping loop() short is key to the most responsive code so I break even for-next and while loops to keep that.

I did not invent this approach. It was there before 1980. I had my own version and then got shown different chip and system docs with more than I had imagined. The chips were slower then, about like an AVR at 1 MHz, Necessity is the Mother of Invention, we did not have speed or memory to waste.

Not blocking is the story of how the Tortoise beat the Hare. Hare waits. Tortoise just keeps going.

When you do text, think about processing 1 char at a time. If it's numbers, use a variable to build the final value and with each new digit multiply the old total (starts at zero) by ten then add the new after checking it is a digit. Sign and decimal are just steps in that dance.

if (( dataChar >= '0' ) && ( dataChar <= '9' )) // must be a digit
....
value = dataChar - '0'; // changes the ASCII text in dataChar to the value the text represents

It works for matching exact text sequences (words in text) but that gets deeper and trickier, too much for here.

Well now you can fly. Tell others who might be ready?

GoForSmoke, just so you know what your guidance enabled me to do.
Now, my garden control unit can:

  • read and display time and date from I2C RTC module
  • control 8 valves connected using I2C
  • read 8 push-buttons connected using I2C (with interrupt handling!!!)
  • measure humidity and temperature using DHT22 sensor
  • measure as much 1-wire DS1820 sensors as I want
  • receive commands using serial line from MR3020 running OpenWRT
  • automatically change the display content every 5 seconds (every second when displaying time)
  • switch the display to relevant part as soon as some interaction happens (push of a button, serial command, ...)

It works exceptionally great! I do not believe how much is possible to achieve using this uC - I was planning to use 2-3 to achieve this and run them using USB HUB from the MR3020, now it works as standalone unit using just 1 arduino pro mini and even that one is wasting 99% of the time on wait loop :wink:

THANKS A LOT!

Even more promising, you were able to figure it all out in about 1 month.
I can only wonder at what you may accomplish in coming years. :smiley:

PS - may I suggest that you reduce the clock speed to run on lower voltage?

At 3.4V you could connect directly to SD and many other devices.
A stand-alone 328P would not need an external clock at 8 MHz or less.

I am already experimenting this setup for the RS485. It is not usable for the main board, as it has to communicate with 5V i2c devices and lowering voltage reduced the reliability of such communication. And, for the main board I am using mini pro, soldered to break-out board, so that would be unnecesarry hastle.
However, the miniDevices on RS485 can now be made using ATmega8 or even ATtiny with almost none power consumption - not only I can lower the clock, but using your approach I can put the device to sleep for most of the time. Lowering the voltage is also relevant - have to check the specs for the 485 chip, but from my perspective, this should work as I am only using distance up to 150 meters.

I've got a new way to un-delay (yes, un-delay makes andale!) a function, tried and tested.

It takes 1 state and 2 time variables and yes, I should make a base class but I'm so rusty I'm not ready to do it proper yet.

pseudocode but yes I have this working for someone somewhere already

This is what a fictional function that had 2 delays turned into a non-blocking state machine looks like.

byte demoState;

attribute ((packed)) enum demoStates // makes bytes
{
demoOne, demoTwo, demoThree // yeah, yeah, NO imagination!
};

unsigned long demoStart, demoWait;

void demoFn( void )
{
// this takes care of waits for ALL cases
if ( demoWait ) // if == 0 then skip to switch-case
{
if ( millis() - demoStart < demoWait )
{
return; // if it's not time to run a case, return
}
else
{
demoWait = 0; // always when the timer runs out it is set to zero
}
}

switch ( demoState )
{

case demoOne :
if ( start )
{
// do something -- initialize values for case demoTwo right up to wait, if any....
demoStart = millis();
demoWait = some value
demoState = demoTwo;
}
else
{
start = 0;
}
break;
case demoTwo :
// do something
if ( !finished )
{
// do something every time demoFn() runs until finished, 1 no-delay step per pass
}
else
{
demoStart = millis();
demoWait = some value
demoState = demoThree;
}
break;
case demoThree :
// do something
demoState = demoOne; // function is finished running until started again
break;
}