Multitasking and interrupts with adafruit RGB shield.

I am using Arduino mega.

I have this RGB lcd shield with buttons: RGB LCD Shield Kit w/ 16x2 Character Display - Only 2 pins used! [NEGATIVE DISPLAY] : ID 714 : $24.95 : Adafruit Industries, Unique & fun DIY electronics and kits

It has an MCP23017 i2c parallel port expander chip, so it sends the button state to the arduino presumably when readButtons() is called.

I have been working on a menu system which uses the lcd shield and it works fine. Now I want to start adding a variety of sensors to the mega such as the DHT22 sensor.

Multitasking

The first issue I have is writing the multitasking and interrupt side of things. Obviously I have to use interrupts for the buttons, or some kind of quick multitasking polling to check if readButtons() is true. Assuming the former is the way to go I have done some research and it seems that connecting pin 20 of the MCP23017 to pin 2 or 3 or the arduino mega will allow me to detect a button press as an interrupt. Can I just do this off the bat or do I need to rewrite some of the mcp/lcd libraries to accomodate for this?

Sensor polling

That's the button/interrupt side of things, now I am curious about the sensors. In the case of the DHT22, it takes about 2 seconds to read the temperature and humidity.

I am somewhat confused about how this works though because it seems like you can call it as many times as you want, but it will only give you an updated reading every 2 seconds. I guess it has some on-board chip which handles everything and just returns a new value when you ask for it and after it's ready?
If not (or in the case of another sensor), will it cause trouble to interrupt a sensor in the middle of its execution (with a button interrupt) and then return to its execution after the interrupt has been handled? That is, could register values(in the sensor) have changed when the sensor execution returns to where it left off?

So although button interrupts are a lot faster than button polling (generally speaking), in the case of sensors it seems as though the multitasking is required. Should I use interrupts to detect the button press, and then multitask between current sensor and button handler? Or finish current sensor first before handling button interrupt? Or will it be fine to just interrupt the sensor processing until until after the button press has been handled?

Can I just do this off the bat or do I need to rewrite some of the mcp/lcd libraries to accomodate for this?

Without seeing the specific library code that is an impossible question to answer. I would expect that the library has methods to set up the MCP interrupt control registers. You will have to code the Arduino side. I don't think an interrupt on the Arduino side is necessary. Treat the MCP interrupt out as a switch signal that you poll for. If your loop() execution time is so long that you miss switches, your code is not right. Upon interrupt the MCP interrupt output will persist until the interrupt capture (INTCAP) or GPIO register is read, so you won't miss it.

If you forget about interrupts (on the Arduino), then most of the other questions become moot. Save your Arduino interrupts for something more appropriate.

Why read a sensor that can update every 2 seconds any faster than every 2 seconds? How fast can humidity and temperature change?

groundfungus:
Why read a sensor that can update every 2 seconds any faster than every 2 seconds? How fast can humidity and temperature change?

Right. Keep your loop() tight (don’t use delay() calls) and only poll the sensor data every 2-3 seconds (at most) based on millis(). Can’t imagine that it would matter if the senor data is a few seconds old.

You don’t need interrupts for the buttons. With a tight loop() function, just poll readButtons() on every iteration and check for a change in button state.

syphex:
The first issue I have is writing the multitasking and interrupt side of things. Obviously I have to use interrupts for the buttons, or some kind of quick multitasking polling to check if readButtons() is true.

It's by no means obvious. Human fingers are slow. Unless you are doing some operation that blocks for more than (say) 250ms, just poll the state of the buttons.

Have a look at Several Things at a Time - no interrupts are used.

...R

Ok I have done some assembly on a similar prototype board so I know the basic concepts of multitasking and have written a multitasking kernel. I just don't know how long it takes the DHT22(for example) to actually complete its execution. I don't know where the 2 second rule comes from or why it is suggested. Just for example's sake assume it takes 2 seconds to actually get a reading (has to wait for stabilization or something). If it is halfway through (1 second) and I switch to polling buttons and come back after 1 second(I know it wont be this long), could I get bogus readings or something?

Also I thought interrupts are always much preferred to polling in the case of buttons. If the time slices are small, it constantly leaves the sensor function to poll the buttons and thus slows down sensor readings (maybe get bad data). If the time slices are big it doesn't poll the buttons enough and hence slows down button response time.

With interrupts however it won't affect the execution of sensor readings UNTIL a button is pressed, and it seems easier to implement since I won't have to write a multitasking component (unless it is bad to exit sensor execution prematurely, a problem which is only more exasperated with polling).

I wired up a DHT22 and measured how long that it takes to get a reading from the sensor. 5 milliseconds, approximately. You are not supposed to read it more often than 2 seconds, but the actual reading takes 5 milliseconds. I tried to find an explanation of the 2 seconds between samples and other than the specification in the data sheet could not find any. So the specification says 2 seconds, good enough for me.
So that is 5 milliseconds every 2 seconds that you spend reading the sensor. I don't think that you will miss a button press.

If it is halfway through (1 second) and I switch to polling buttons

The sensor reading function blocks for the 5 milliseconds that it is executing so you can't just go off and poll the switch and it does not take 2 seconds to read the sensor.

Also I thought interrupts are always much preferred to polling in the case of buttons.

When working with a PC, maybe, but microcontrollers are different. Interrupts are for things that must be handled NOW. A few millisecond delay between pushing a button and a reaction will not be seen by a human so an interrupt is not required.

Read the post linked by Robin2. Run some examples from the dht library of your choice.

groundfungus:
but the actual reading takes 5 milliseconds.

I haven’t used one those DHT sensors myself but I think I read in another Thread that you don’t even have to wait the 5 millisecs. You can start a reading and come back later (after 5 msecs) and pick up the data.

…R

I just recorded micros() , called the read method and subtracted the recorded micros from micros() on return .
So the library that I use (DHT22.h from the playground I think) blocks that long. Other libraries may be different. My main point was that it does not take 2 seconds to read the sensor.

Ok thanks guys for testing the DHT22 for me. However I was just using it as an example, because as I said I have multiple sensors to read. I also have PH, EC (from atlast scientific), and a CO2 sensor. Those are the only digital ones I can think of at the moment. Would it be safe to assume that those sensors can also function 100% if left during mid execution and then returned to? BTW I am also logging the average of these sensor values to an SD card every few minutes or so.

Also my button handler is quite large, as it needs to navigate my menu system.

 if (buttons) {
      current_index_menu=current_menu->menulist[current_index[Y]][current_index[X]];
      waitforinput=4;
      if (holdtimer<20) holdtimer+=1;
      releasetimer=0;
      //Serial.println("Holdtimer: "); Serial.println(holdtimer);
     // Serial.println("Menu selected: "); Serial.println(menu_selected);
      lcd.clear();
      
      if (buttons & BUTTON_UP) {
        UP=true;
        if (current_index[Y]==0)
        {
          for (i=0; current_menu->menulist[i+1]!=0; i++)
          {
           current_index[Y]=i+1;; //replace with find 0 index
           Serial.println(i);
           index_offset=i;
          }
        }
        else {
          if (index_offset-current_index[Y]==1) index_offset--; 
          current_index[Y]--;
        }
      }
      
      
      if (buttons & BUTTON_DOWN) {
        DOWN=true;
        if (current_menu->menulist[current_index[Y]+1]==0)
        { 
          Serial.println("zero");
          //index_offset=1;
          current_index[Y]=0;
        }
        else {
          Serial.println("not zero");
          //if (current_index[Y]==index_offset) index_offset++; 
          current_index[Y]++;
          //Serial.println(current_index[Y]);
        }
        
      }
      
      
      if (buttons & BUTTON_LEFT) {
        LEFT=true;
        if (menu_selected==true)
        change_value(current_index_menu->value,current_index_menu->type,-1);
        else
        if (current_index[X]==0)
        {
          for (i=1; current_menu->menulist[current_index[Y]][i]!=0; i++)
            current_index[X]=i; 
        }
        else current_index[X]--;
      }
      
      
      if (buttons & BUTTON_RIGHT) {
        RIGHT=true;
        if (menu_selected==true)
        change_value(current_index_menu->value, current_index_menu->type,1);
        else
        if (current_menu->menulist[current_index[Y]][current_index[X]+1]==0)
          current_index[X]=0;
        else
          current_index[X]+=1;
        
      }
      
      
      if (buttons & BUTTON_SELECT) {
          if (current_index_menu->menulist!=0) //if selected menu has other menu items
          { 
   
        waitforinput=3;
        if (current_index_menu->previous_menu==0) //if selected menu has no previous menu, set its previous menu to the current menu
        {
          current_index_menu->previous_menu=current_menu;
          current_index_menu->menulist[0][0]=current_menu;
        }
        else if (current_index_menu==current_menu->previous_menu)
        {
          Serial.println("previous menu selected");
          if (current_menu->exitfunction!=0)
          {
            Serial.println("calling exit function");
            (*(current_menu->exitfunction)) ();
          }
          else Serial.println("Function=0");
        }
        else 
        {
        Serial.println("not previous menu");
                 Serial.println("calling entry function");
            if (current_menu->entryfunction!=0)
            (*(current_menu->entryfunction)) ();
        }
        if (current_index_menu->color==0)
          current_index_menu->color=current_menu->color;
        current_menu=current_index_menu;
        if (current_index_menu!=&MAIN) main_selected=0;
        else main_selected=1;
       
        lcd.setBacklight(current_index_menu->color);
        current_index[X]=0;
        current_index[Y]=0;
        Serial.println("setting index to 0");
        //Serial.println(current_menu->previous_menu->name);
        
      }
      else if (current_index_menu->value!=0) //if selected menu has a value select or deselct it
        {
          if (menu_selected)
          menu_selected=false;
          else menu_selected=true;
         
        }
        

      }
      
      //Serial.println(current_index[Y]);
     // Serial.println(current_index[Y]);
     
     if (current_menu==&MAIN) //if in control of the main menu quickly display the contents of the current sub menu
     {
      
      current_menu=current_menu->menulist[current_index[Y]][current_index[X]];
      lcd.setBacklight(current_menu->color);
      display_menu();
      current_menu=&MAIN;
      
     }
     else display_menu();
     
      Serial.println(current_menu->name);
      Serial.print("previous_menu: ");
      Serial.println(current_menu->previous_menu->name);
    }
  else 
  {
  holdtimer=0;
  if (releasetimer <60) releasetimer+=1;
  else 
    {
      //if (current_index[Y]<2) current_index[Y]+=1;
        // else current_index[Y]=0;
    }
  }

If I am not using interrupts, how often should I check (buttons)? I.e. What kind of time slices would seem appropriate? Because it seems almost that just navigating the menu is enough to introduce some lag between button presses.

"Would it be safe to assume that those sensors can also function 100% if left during mid execution and then returned to? "

It is never safe to assume. Assumption is the mother of all screw ups. Go look at library code or data sheets and find out for sure.

Delta_G:
"Would it be safe to assume that those sensors can also function 100% if left during mid execution and then returned to? "

It is never safe to assume. Assumption is the mother of all screw ups. Go look at library code or data sheets and find out for sure.

Yes I agree, that's why I want to assume that it does matter. How can I code with this assumption in mind instead of looking up the datasheets of every sensor? Keep track of which sensor is currently being read, and if a button is pressed instead of continuing where it left off just start the sensor function again?

"Yes I agree, that's why I want to assume that it does matter. How can I code with this assumption in mind instead of looking up the datasheets of every sensor? "

You don't. That's the point. If you assume then you leave yourself open to screw it up. You figure it out for sure. Why would you even think of using a sensor without having a datasheet for it? I would look before I ever bought the sensor in case it said something that didn't jive with my plan. Just blindly using things without knowing their specifications is just plain stupid if you ask me. Sure it takes a little time and effort but it's a lot easier than troubleshooting something later that you have no info on. And for sure easier than having to refactor the code because you didn't know some esoteric fact about the sensor. Don't be lazy now and cause yourself headache in the future.

Delta_G:
"Yes I agree, that’s why I want to assume that it does matter. How can I code with this assumption in mind instead of looking up the datasheets of every sensor? "

You don’t. That’s the point. If you assume then you leave yourself open to screw it up. You figure it out for sure. Why would you even think of using a sensor without having a datasheet for it? I would look before I ever bought the sensor in case it said something that didn’t jive with my plan. Just blindly using things without knowing their specifications is just plain stupid if you ask me. Sure it takes a little time and effort but it’s a lot easier than troubleshooting something later that you have no info on. And for sure easier than having to refactor the code because you didn’t know some esoteric fact about the sensor. Don’t be lazy now and cause yourself headache in the future.

Ok then, I thought it would be safer to assume that sensor’s can’t be interrupted. Anyway once I read up about the sensor’s what approach should I use to cater for them? Find the maximum time it could take to read each sensor and delegate time slices appropriately?

"Ok then, I thought it would be safer to assume "

There you go wanting to assume again. It really is true. Assumption is the mother of all screw ups. If you'd really rather guess and assume than know I guess that is your business. Just don't bother us with problems that arise only because you're too lazy to read. It isn't fair to ask us to do your work if you won't.

groundfungus:
I just recorded micros() , called the read method and subtracted the recorded micros from micros() on return .
So the library that I use (DHT22.h from the playground I think) blocks that long.

I have an idea that what I had been reading was a non-library solution - but I could well be completely wrong.

…R

Delta_G:
"Ok then, I thought it would be safer to assume "

There you go wanting to assume again. It really is true. Assumption is the mother of all screw ups. If you'd really rather guess and assume than know I guess that is your business. Just don't bother us with problems that arise only because you're too lazy to read. It isn't fair to ask us to do your work if you won't.

Excuse me, I was simply trying to suggest that it may be wise to assume the worst case scenario in the layout of my program beforehand in order to better account for the problems that may arise in the future which you are talking about. Maybe you're the one too lazy to read, and I request that you stop bashing me for wanting to play it safe (because that is what you're trying to say in the first place, which I said I agree with) and conduct yourself appropriately.

Now in regards to this comment:

syphex:
Yes I agree, that's why I want to assume that it does matter. How can I code with this assumption in mind instead of looking up the datasheets of every sensor? Keep track of which sensor is currently being read, and if a button is pressed instead of continuing where it left off just start the sensor function again?

I was simply trying to ask for a suggestion to accommodate for a larger variance in sensor read time. I realize it comes across as me not wanting to bother looking at the sensor datasheets, but that's not what I was trying to convey.