5

For a school project we're working on a wearable device to track people's movement and heart rate. We use an Arduino Pro Mini with a heartbeat sensor and an accelerometer. The wearable device uses Bluetooth to send data, but only once or twice a day to save battery.

The problem we currently face is having the Arduino keep track of the accelerometer data while also reading the heartbeat sensor on a select interval. See the pseudo-code underneath. In what way (preferably without implementing too many libraries) could we merge these two loops so the accelerometer is read non-stop while keeping an interval (of about 30 minutes) between the heart beat readings? Any input would be greatly appreciated and if needed I can provide additional information.

void loop() {
// Heartbeat sensor loop
  while (array is not full) {
    delay(30 minutes);
    heartbeatArray[index++] = readHeartbeat();
  }
  sendData_Bluetooth(heartbeatArray);
  emptyArray();
// End of heartbeat sensor loop

// Accelerometer loop
  while (true)
    if (readAccelerometerData > limit)
      sendAlert();
// End of accelerometer loop
}
  • How long does readHeartbeat() take to execute? What is the accelerometer sampling rate and must it stay consistent? – JRobert Jun 11 '15 at 11:21
  • Reading the heartbeat takes about 10 seconds but may take longer as we plan to read multiple samples to check for inconsistent data (the sensor sometimes doesn't work or gives odd data). The accelerometer samples every 100 ms and should continue like that, preferably also while reading the heartbeat. – OlivierV Jun 12 '15 at 10:36
4

What you want to do is rather easy task, you just need to forget about delay() function. When writing some more complex programs using it is generally a bad idea, as it blocks code execution, so you won't get any sensor readings, communication, anything. Instead try something like this:

unsigned long delay1 = 100;
unsigned long delay2 = 1800000; //30 minutes in miliseconds
unsigned long t1, t2;

void setup() {
  t1 = t2 = millis();
}

void loop() {
  if(millis() - t1 > delay1) { //this will be executed every delay1 ms
    // do one part of code
    t1 = millis();
  }

  if(millis() - t2 > delay2) { //this will be executed every delay2 ms
    // do another part of code
    t2 = millis();
  }

  // here insert the code that should be executed as frequently as possible
  // (every loop passage)
}

There is also a nice Arduino library called TaskScheduler, that basically does the same thing: periodically executes different functions with different time intervals.

  • I figured delay() wouldn't be a good idea as I was aware of it blocking execution; I just put it in to indicate the wait before the next reading. Thanks for the millis() suggestion though, think I'll give that a try. Will it however block the accelerometer reading if the heartbeat sensor reading takes a few seconds? – OlivierV Jun 12 '15 at 10:39
  • @OlivierV accelerometer can be read every time main loop executes. So if you are stuck at some inner loop of reading heartbeat, other sensors won't be checked. Try to divide this long process to smaller pieces. Perform the first piece, read accelerometers, perform the second etc. State machines concept can come useful here. – mactro Jun 12 '15 at 10:57
1

millis() is your great friend here.

millis() is function that returns the value of a 32 bit counter that is incremented once per millisecond. By adding the current value returned by millis() to a value representing the number of milliseconds that you want to 'delay' for you get a value that can be compared with the ongoing value returned by millis() to see if the time period has elapsed.

You can have as many variables as you wish which hold future timeout values for millis(0 - you perform the related action when millis() returns >= the saved value and reinitialise the value to some new future time.

Variables used to hold values from millis() MUST be type unsigned long.
The millis() timer rolls over every 49.7 days.
( = 2^32/1000 ms/s / 3600 s/hr / 24 h/day)

  • If your system may run for longer than that without being reset or powered down then handling millis()'s timer rollover must be known about.

    It's MUCH easier than many people realise but best not dealt with here EXCEPT to know that if you add a constant to millis() and the value exceeds 2^32 and is truncated the comparisons with and subtractions wrt millis() still work correctly if the variable is type unsigned long. ("Why" is for elsewhere)

micros() is like unto millis() but returns the time since reset in microseconds. Depending on the processor used micros() may have a granularity of 4 uS rather than one uS. ie it steps by 4 counts every 4 uS rather than 1 count per uS.
micros() rolls over every 1.19 hours
( = 2^32 / 10^6/3600s/h)


// Example - not tested:

unsigned long DoitNow;  

void setup(){  
  DoitNow = millis() + 1800000; // Add half an hour of milliseconds 
 }  
void loop(){  
   if(millis() > DoitNow)  
   { Do heartbeat stuff;  
      DoitNow = millis() + 1800000; // Add another half hour of ms
   }  
 Do accelerometer stuff;  
}    
  • Your test millis() > DoitNow does not work if the computation of DoitNow = millis() + 1800000 has rolled over. You should never compare timestamps like you have done here, it does not make sense. Comparing delays (as in mactro's answer), on the other hand, is rollover-safe. – Edgar Bonet Jun 12 '15 at 9:39
  • I just finished a tutorial on the millis() rollover covering precisely this issue. You may want to take a look. – Edgar Bonet Jun 12 '15 at 11:20
0

To keep the 10Hz accelerometer sample rate consistent, you'll need an 10Hz interrupt routine to do the sampling, and probably queue them somewhere, as they will accumulate while you're off measuring the heart rate.

Your main loop can collect any accelerometer samples out of the queue periodically, and at appropriate intervals, read the heart beat. This organization will keep the accelerometer sample rate stead whether your main loop has anything else to do or not.

Make the queue more than long enough to hold all the samples that could collect during a heartbeat measurement, which you should probably make one at a time, even though you might do it in rapid succession, so the main loop gets to drain the accelerometer queue as often as possible.

Otherwise you'll need a queue as large as (10 Hz * 10 sec * maxPossibleConsecutiveHeartBeatMeasurements * factorOfSafety * sizeof(oneSample) bytes; and you don't have a whole lot of memory to play with in an Atmega 328.

Where will you be storing the measurements between Bluetooth connects? If it's an SD card, you'll need some space for its write-buffer, and the main loop will have to manage the card writing.

0

See https://electronics.stackexchange.com/a/67091/10371 (Since this is another stack exchange site, I won't copy the info)

The short answer is to use timers that trigger interrupts. Examples are shown in the link.

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.