2

Is it possible to delay a task inside a function that is called only once (meaning, it's not inside the loop) without using delay()?

This library I'm using, for reading sensor measurements via serial has a delay() in it. I want to get rid of it, but I don't how to do it, because the library's function is not looped.

I guess the simple solution would be to move the library functions inside the loop, so I can do a simple check with millis() and run the delayed part as soon as 250 ms have passed. I would like to improve this library though and do a simple, single call for sensor data that doesn't not hold up the entire program.

This is the function with the delay:

uint16_t COZIR::Request(char* s)
{
  Command(s);
  // empty buffer
  buffer[0] = '\0';
  // read answer; there may be a 100ms delay!
  // TODO: PROPER TIMEOUT CODE.
  delay(250);  
  int idx = 0;
  while(CZR_Serial.available())
  {
    buffer[idx++] = CZR_Serial.read();
  }
  buffer[idx] = '\0';
  uint16_t rv = 0;

  switch(buffer[1])
  {
    case 'T' :
            rv = atoi(&buffer[5]);
            if (buffer[4] == 1) rv += 1000;
            break;
    default :
            rv = atoi(&buffer[2]);
            break;
  }
  return rv;
}

I've had a look at the SoftTimer library, but it requires sketches to be written very differently (without a loop). I'm also not sure if I can even pass arguments to delayed tasks with that library.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
kslstn
  • 123
  • 4

2 Answers2

3

You could split Request() into two separate functions: You call sendRequest(), then go do other stuff, then, some time later, you call getResponse(). If you get RESPONSE_NOT_READY, then you know you have to call it later:

#define REQ_WAIT_DELAY     250  // Wait for 250 ms
#define RESPONSE_NOT_READY ((uint16_t) -1)

uint32_t time_request_sent;

void COZIR::sendRequest(char* s)
{
  Command(s);
  time_request_sent = millis();
}

uint16_t COZIR::getResponse()
{
  if (millis() - time_request_sent < REQ_WAIT_DELAY)
      return RESPONSE_NOT_READY;
  buffer[0] = '\0';  // empty buffer
  int idx = 0;
  // From here, same as original COZIR::Request()
}

This assumes that RESPONSE_NOT_READY (0xffff) is not an otherwise valid response. If all 2^16 values of an uint16_t may be valid, then you need another way for the library to signal that the response is not ready. Also, make sure you never compare timestamps, because of the millis() rollover problem. Taking the difference between two timestamps and comparing this with a specified delay (as in the example above) is rollover-safe.

Update: Here is an example of how this could be used:

COZIR cozir(some_serial_connection);

void loop()
{
    static bool waiting_for_a_cozir_response = false;

    if (time_to_get_another_cozir_reading()) {
        if (waiting_for_a_cozir_response) {
            report_something_is_wrong("This COZIR is too slow!");
        }
        else {
            cozir.sendRequest("Hi Cozir! Pliz send me another reading.");
            waiting_for_a_cozir_response = true;
            /* We won't get the response on this iteration. *
             * We will ask next time.                       */
        }
    }
    else if (waiting_for_a_cozir_response) {
        uint16_t response = cozir.getResponse();
        if (response == RESPONSE_NOT_READY) {
            /* Nothing to do now, really. We will just try again *
             * at the next loop() iteration.                     */
        }
        else {
            waiting_for_a_cozir_response = false;
            do_something_with_the_cozir_response(response);
        }
    }
    do_whatever_else_has_to_be_done_in_the_loop();
}
Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
0

You can look at the BlinkWithoutDelay example sketch (under Digital). Basically you store the current millis as a variable, then have an if statement to check that it has been more than x milliseconds.

Nathan
  • 181
  • 3