2

I'm currently attempting to replace delay() in my project so that i may perform other tasks while waiting on a particular event to fire.

The issue I'm having, is every example i've found using millis / elapsedmillis has everything inside the loop, where I'm trying to use it inside void buttons(). needless to say, either it's not going to work outside of loop, or I'm doing something ridiculous, and too tired, or too ignorant to see what i'm doing wrong..

The end goal is to have the button draw over the top of the existing one, wait 1000ms then draw the next button. (time interval will be changed later to sync with hardware spin down etc)

Here is the complete sketch, perhaps someone can show me a reliable way to replace delay in the buttons funciton:

#include <Adafruit_GFX.h>
#include <TouchScreen.h>
#include <stdint.h>
#include <SPI.h>
#include <SD.h>
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
#include <elapsedMillis.h>

elapsedMillis timer0;
elapsedMillis sinceTime1;
elapsedMillis sinceTime2;
elapsedMillis sinceTime3;

#define debug = false;
//#define debug = true;

#if defined(__SAM3X8E__)
#undef __FlashStringHelper::F(string_literal)
#define F(string_literal) string_literal
#endif

// most mcufriend shields use these pins and Portrait mode:
uint8_t YP = A1;  // must be an analog pin, use "An" notation!
uint8_t XM = A2;  // must be an analog pin, use "An" notation!
uint8_t YM = 6;   // can be a digital pin
uint8_t XP = 7;   // can be a digital pin
uint8_t Landscape = 0;

uint16_t TS_LEFT = 120;
uint16_t TS_RT = 940;
uint16_t TS_TOP = 940;
uint16_t TS_BOT = 180;
#define MINPRESSURE 10
#define MAXPRESSURE 1000

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 250 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0

// Assign human-readable names to some common 16-bit color values:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

int16_t BOXSIZE;
int16_t PENRADIUS = 3;
uint16_t oldcolor, currentcolor;

#define SD_SCK 13
#define SD_MISO 12
#define SD_MOSI 11
#define SD_CS 10
// In the SD card, place 24 bit color BMP files (be sure they are 24-bit!)



void setup() {
  // Init TouchScreen
    ts = TouchScreen(XP, YP, XM, YM, 300);     //call the constructor AGAIN with new values.

    BOXSIZE = tft.width() / 6;
    tft.fillScreen(BLACK);

  // put your setup code here, to run once:
    Serial.begin(9600);

  tft.reset();

  uint16_t identifier = tft.readID();
if (identifier == 0x7789) {
        //Serial.println(F("Found ST7789V LCD driver"));
        TS_LEFT = 120; TS_RT = 940; TS_TOP = 940; TS_BOT = 180;
        Landscape = 1;
    } else {
        //Serial.print(F("Unknown LCD driver chip: "));
        //Serial.println(identifier, HEX);
        return;
    }

    //Serial.print(F("Found LCD driver chip ID: "));
    //Serial.println(identifier, HEX);

  tft.begin(identifier);

  //Serial.print(F("Initializing SD card..."));
  if (!SD.begin(SD_CS, SD_MOSI, SD_MISO, SD_SCK)) {
    Serial.println(F("failed!"));
    return;
  }
  //Serial.println(F("OK!"));
  tft.begin(identifier);
  tft.setRotation(3);
  BOXSIZE = tft.width() / 6;
  tft.fillScreen(BLACK);
  bmpDraw("splash.bmp", 80, 20);
  delay(5000);
  loadhome();

}

void loop() {
//DO STUFF HERE

//Testing Text stuff here
  tft.setTextColor(BLACK);
  tft.setTextSize(2); 
  tft.setCursor(15, 33);
  tft.print("123.456");
  tft.setTextColor(BLACK);
  tft.setCursor(115, 33);
  tft.print("543.210");
  tft.setTextColor(BLACK);
  tft.setCursor(215, 33);
  tft.print("789.654");
  buttons();
}

void loadhome(){
  tft.setRotation(3);  
  BOXSIZE = tft.width() / 6;
  tft.fillScreen(BLACK);
  bmpDraw("cell.bmp", 10, 25);
  bmpDraw("cell.bmp", 110, 25);
  bmpDraw("cell.bmp", 210, 25);
  bmpDraw("stop.bmp", 15, 175);
  bmpDraw("locked.bmp", 90, 175);
  bmpDraw("play.bmp", 165, 175);
  bmpDraw("home.bmp", 240, 175);
  bmpDraw("coolstp.bmp", 15, 105);
  bmpDraw("spinstp.bmp", 240, 105);

  tft.setTextColor(RED);
  tft.setTextSize(3); 
  tft.setCursor(50, 3);
  tft.print("X");
  tft.setTextColor(RED);
  tft.setCursor(150, 3);
  tft.print("Y");
  tft.setTextColor(RED);
  tft.setCursor(250, 3);
  tft.print("Z");

}

int homer = 0;
int play = 1;
int lock = 0;
int cool = 0;
int spin = 0;


const int interval = 1000;
unsigned long curMillis; //Current millis

void buttons(){
  //Touchscreen stuff
    TSPoint p = ts.getPoint();

    // if sharing pins, you'll need to fix the directions of the touchscreen pins
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);

    p.x = map(p.x, TS_LEFT, TS_RT, 0, tft.width());
    p.y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
/*
//Use to map buttons
//Be sure to mark out button code before use!!
 if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
    Serial.print(F("p.y ="));
    Serial.print(p.y);
    Serial.println("");
    Serial.print(F("p.x ="));
    Serial.print(p.x);
    Serial.println("");
 }
*/    
// Begin Buttons    
//Coolant Button
if (p.y > 110 && p.y < 170 && p.x > 250 && p.x < 300) { // if stop area is pressed
  if(cool == 0){
   bmpDraw("coolen.bmp", 15, 105);
   //Serial.println(F("Coolant Button Pressed!"));
   cool = 1;
  } else {
   bmpDraw("coolerr.bmp", 15, 105);
   sinceTime1 = 0;
   //delay(500); //replacing with millis()
   if (sinceTime1 >= 1000) {
   bmpDraw("coolstp.bmp", 15, 105);
   //Serial.println(F("Coolant Button Pressed!"));
   cool = 0;
   }
  }
}

//Stop Button
if (p.y > 185 && p.y < 235 && p.x > 250 && p.x < 300) { // if stop area is pressed
   //Serial.println(F("Stop Button Pressed!"));
   Serial.write(0x18);
   Serial.print("\n");
}

//Lock Button
if (p.y > 185 && p.y < 235 && p.x > 182 && p.x < 234) { // if stop area is pressed
  if(lock == 0){
    Serial.print("$X\n");
   bmpDraw("unlocked.bmp", 90, 175);
   //Serial.println(F("Lock/Unlock Button Pressed!"));
   lock = 1;
  } else {
    Serial.print("$X\n");
   bmpDraw("locked.bmp", 90, 175);
   //Serial.println(F("Lock/Unlock Button Pressed!"));
   lock = 0;  
  }
}

//Play-Pause Button
if (p.y > 185 && p.y < 235 && p.x > 110 && p.x < 160) { // if stop area is pressed
  if(play == 0){
   Serial.print("~\n");
   bmpDraw("play.bmp", 165, 175);
   //Serial.println(F("Play/Pause Button Pressed!"));
   play = 1;
  } else {
   Serial.print("!\n");
   bmpDraw("pause.bmp", 165, 175);
   //Serial.println(F("Play/Pause Button Pressed!"));
   play = 0;    
  }
}

//Home Button
if (p.y > 185 && p.y < 235 && p.x > 35 && p.x < 90) { // if stop area is pressed
  if(homer == 0){
   bmpDraw("homeg.bmp", 240, 175);
   //Serial.println(F("Home Button Pressed!"));
   homer = 1;
  } else {
   Serial.print("$H\n");
   bmpDraw("homey.bmp", 240, 175);
   sinceTime2 = 0;
   //delay(500); //replacing with millis()
   if (sinceTime2 >= 1000) {
   bmpDraw("homer.bmp", 240, 175);
   }
   //delay(500); //replacing with millis()
   sinceTime2 = 0;
   if (sinceTime2 >= 1000) {
   //delay(750);
   bmpDraw("homeg.bmp", 240, 175);
   //Serial.println(F("Home Button Pressed!"));
   }
   homer = 1;
  }
}

//Spindle Button
if (p.y > 110 && p.y < 170 && p.x > 35 && p.x < 90) { // if stop area is pressed
  if(spin == 0){
   bmpDraw("spinen.bmp", 240, 105);
   //Serial.println(F("Spindle Button Pressed!"));
   spin = 1;
  } else {
   bmpDraw("spinerr.bmp", 240, 105);
   sinceTime3 = 0;
   //delay(500); //replacing with millis()
   if (sinceTime3 >= 1000) {
   bmpDraw("spinstp.bmp", 240, 105);
   //Serial.println(F("Spindle Button Pressed!"));
   }
   spin = 0;   
  }
}

}


#define BUFFPIXEL 15

void bmpDraw(char *filename, int x, int y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;

  if((x >= tft.width()) || (y >= tft.height())) return;

  //Serial.println();
  //Serial.print(F("Loading image '"));
  //Serial.print(filename);
  //Serial.println('\'');
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.println(F("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    read32(bmpFile);
    //Serial.println(F("File size: ")); 
    //Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    //Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    read32(bmpFile);
    //Serial.print(F("Header size: ")); 
    //Serial.println(read32(bmpFile));

    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      //Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
       // Serial.print(F("Image size: "));
       // Serial.print(bmpWidth);
       // Serial.print('x');
       // Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each column...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              // Push LCD buffer to the display first
              if(lcdidx > 0) {
                tft.pushColors(lcdbuffer, lcdidx, first);
                lcdidx = 0;
                first  = false;
              }
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx++] = tft.color565(r,g,b);
          } // end pixel
        } // end scanline
        // Write any remaining data to LCD
        if(lcdidx > 0) {
          tft.pushColors(lcdbuffer, lcdidx, first);
        } 
       // Serial.print(F("Loaded in "));
        //Serial.print(millis() - startTime);
        //Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}
  • 5
    Please edit your code down to the minimum required to demonstrate and debug the problem. – Transistor Jan 13 '16 at 18:36
  • Do you want the buttons and processes to act independently from each other, or do you want some kind of blocking? Like "Press Coolant" runs does the coolant for 1000ms, but does or does not allow "Stop" to be pressed? do the buttons block each other? – Dave X Jan 13 '16 at 21:34
  • For "Spindle Button" should what should happen if you hold your finger on it for 10 seconds? Start up spin, make the button be 'spinen.bmp' for one loop(), then display 'spinerr.bmp' for 1000ms, then display 'spinstp.bmp', shut off the spin, and repeat? I'd keep a state variable for the screen handling a touch so you don't cycle/race, and add it to the if(p...) condition. I think I'd move the button-de-animation out of the if(p....) condifional. – Dave X Jan 13 '16 at 22:04
  • The whole idea is to give a visual representation of the device doing "something". My thinking was to time how long it takes the spindle to spin down and stop, use that time for the timer.. So an example would be, the spinde takes 5 seconds to slow down and stop, when user push the button, the command to stop the spindle is sent, the button is redrawn from green(on) to yellow(slowing) then to red(stopped) after 5secs. same for coolant, and homing. I'm trying to use something other than delay, as i need serial to update the X, Y and Z positions. and have access to stop in case of emergency – ArcAiN6 Jan 14 '16 at 0:08
2

The usual technique at this scale of technology is to keep loop() pretty short, mostly making frequent calls to a timer function that keeps a list of things to be done (implemented with callback-functions) and the times at which to do them, and which calls those functions at the appropriate times. The upper level(s) of your sketch would schedule tasks by registering one or more callback-functions to be run at a given time or periodically.

When the timer function is called, if there is nothing to do, or when a callback-function returns, the timer function returns immediately to its caller (probably loop()). loop() can do other work, as long as it can still call the timer function as frequently as your task dispatching requires.

The SimpleTimer library at Arduino Playground is an implementation of such a timer-function.

0

Taking JRobert's advice, i checked out the simpletimer library.. the final solution was to set up three timers and change the way the buttons functioned.

as per documentation of the simpletimer library, added the following global:

#include <SimpleTimer.h>
SimpleTimer timer;
...

Then to setup, i added my three timers

timer.setInterval(7000, spindle); //times based on hardware reality
timer.setInterval(2000, coolant);
timer.setInterval(21500, homer);
...

Then started the timers in loop by adding:

timer.run();
...

Finally, i edited the buttons and added variables to denote state of button. i.e.

/*
0 == on, 
1 == transitional1 
2 == transitional2 
3 == stopped
*/
int homeb = 0;
int cool = 0;
int spin = 0;

Changed the touch screen code for each button to kick off the timers...

//Coolant Button
if (p.y > 110 && p.y < 170 && p.x > 250 && p.x < 300) { // if stop area is pressed
  if(cool == 3){
   bmpDraw("coolen.bmp", 15, 105);
   //PWM the spindle pin on
   cool = 0;
  } else {
    if(cool == 0){
      //PWM the spindle pin off
      cool = 1; //Start the timer sequence for stopping coolant
    }
  }
}

then i added a void for each timer, this is where i redraw the buttons

void coolant() {
  if (cool == 1){
   bmpDraw("coolerr.bmp", 15, 105);
   cool = 2;
  } else {
    if (cool = 2){
   bmpDraw("coolstp.bmp", 15, 105);
   cool = 3;
    }
  }
}

Hope this helps someone else in the future, as it was a real pickle for me to figure out at first.. Thanks again, and credit goes to JRobert, his advice pointed me directly to the solution.

  • Glad to have helped - and thanks for posting your implementation. – JRobert Jan 14 '16 at 19:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.