Tuesday, June 21, 2011

Tutorial: Arduino timing methods with millis()

This is chapter thirty-seven of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – in what feels like an endless series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.

Welcome back fellow arduidans!

In this article we introduce the millis(); function and put it to use to create various timing examples. Please ensure you are running version 13 or upwards of the Arduino IDE.

Millis? Nothing to do with lip-syncers… hopefully you recognised milli as being the numerical prefix for one-thousandths; that is multiplying a unit of measure by 0.001 (or ten to the power of negative 3). Interestingly our Arduino systems will count the number ofmilliseconds (thousands of a second) from the start of a sketch running until the count reaches the maximum number capable of being stored in the variable type unsigned long (a 32-bit [four byte] integer – that ranges from zero to (2^32)-1.

(2^32)-1, or 4294967295 milliseconds converts to 49.71027-odd days. The counter resets when the Arduino is reset, it reaches the maximum value or a new sketch is uploaded. To get the value of the counter at a particular juncture, just call the function – for example:

start=millis();

Where start is an unsigned long variable. Here is a very simple example (download sketch) to show you millis() in action:

Example 37.1

/* Example 37.1 - millis() demonstration http://tronixstuff.wordpress.com/tutorials > chapter 37 John Boxall | CC by-sa-nc */  unsigned long start, finished, elapsed;  void setup() { Serial.begin(9600); }  void loop() { Serial.println("Start..."); start=millis(); delay(1000); finished=millis(); Serial.println("Finished"); elapsed=finished-start; Serial.print(elapsed); Serial.println(" milliseconds elapsed"); Serial.println(); delay(500); }

The sketch stores the current millis count in start, then waits one second, then stores the value of millis again in finished. Finally it calculates the elapsed time of the delay.  In the following screen dump of the serial monitor, you can see that the duration was not always exactly 1000 milliseconds:

To put it simply, the millis function makes use of an internal counter within the ATmega microcontroller at the heart of your Arduino. This counter increments every clock cycle – which happens (in standard Arduino and compatibles) at a clock speed of 16 Mhz. This speed is controlled by the crystal on the Arduino board (the silver thing with T16.000 stamped on it):

Crystal accuracy can vary depending on external temperature, and the tolerance of the crystal itself. This in turn will affect the accuracy of your millis result. Anecdotal experience has reported the drift in timing accuracy can be around three or four seconds per twenty-four hour period. If you are using a board or your own version that is using a ceramic resonator instead of a crystal, note that they are not as accurate and will introduce the possibility of higher drift levels. If you need a much higher level of timing accuracy, consider specific timer ICs such as the Maxim DS3231.

Now we can make use of the millis  for various timing functions. As demonstrated in the previous example sketch, we can calculate elapsed time. To take this idea forward, let’s make a simple stopwatch. Doing so can be as simple or as complex as necessary, but for this case we will veer towards simple. On the hardware perspective, we will have two buttons – Start and Stop - with the 10k ohm pull-down resistors connected to digital pins 2 and 3 respectively.

When the user presses start the sketch will note the value for millis – then after stop is pressed, the sketch will again note the value formillis, calculate and display the elapsed time. The user can then press start to repeat the process, or stop for updated data. Here is the sketch (download):

Example 37.2

/* Example 37.2 – Super-basic stopwatch using millis(); http://tronixstuff.wordpress.com/tutorials > chapter 37 John Boxall | CC by-sa-nc */  unsigned long start, finished, elapsed;  void setup() { Serial.begin(9600); pinMode(2, INPUT); // start button pinMode(3, INPUT); // stop button Serial.println("Press 1 for Start/reset, 2 for elapsed time"); }  void displayResult() { float h,m,s,ms; unsigned long over; elapsed=finished-start; h=int(elapsed/3600000); over=elapsed%3600000; m=int(over/60000); over=over%60000; s=int(over/1000); ms=over%1000; Serial.print("Raw elapsed time: "); Serial.println(elapsed); Serial.print("Elapsed time: "); Serial.print(h,0); Serial.print("h "); Serial.print(m,0); Serial.print("m "); Serial.print(s,0); Serial.print("s "); Serial.print(ms,0); Serial.println("ms"); Serial.println(); }  void loop() { if (digitalRead(2)==HIGH) { start=millis(); delay(200); // for debounce Serial.println("Started..."); } if (digitalRead(3)==HIGH) { finished=millis(); delay(200); // for debounce displayResult(); } }

The calls to delay() are used to debounce the switches – these are optional and their use will depend on your hardware. Below is an example of the sketch’s serial monitor output – the stopwatch has started, and then button two pressed six times across periods of time:

If you had a sensor at the start and end of a fixed distance, speed could be calculated: speed = distance ÷ time.

You can also make a speedometer for a wheeled form of motion, for example a bicycle. At the present time I do not have a bicycle to mess about with, however we can describe the process to do so – it is quite simple. (Disclaimer – do so at your own risk etc.)  First of all, let’s review the necessary maths. You will need to know the circumference of the wheel taking into account the location of the sensor. (Sensor? For example – a reed switch and magnet. Consider the reed switch to be a normally-open button, and connect as usual with a 10k ohm pull-down resistor. Others may use a hall-effect sensor – each to their own). Remember from maths class:

(image licence)

To calculate the circumference – use the formula:

circumference = 2πr 

where r is the radius of the circle. However be careful when measuring your value for the radius. It should be the distance from the centre of the wheel to the sensor/magnet – not the edge of the wheel. Now that you have the wheel circumference, this value can be considered as our ‘fixed distance’, and therefore the speed can be calculated by measuring the elapsed time between of a full rotation.

Your sensor – once fitted – should act in the same method as a normally-open button that is pushed every rotation. Our sketch will measure the time elapsed between every pulse from the sensor. To do this, our example will have the sensor output connected to digital pin 2 – as it will trigger an interrupt to calculate the speed. (Interrupts? See chapter three). The sketch will otherwise be displaying the speed on a normal I2C-interface LCD module. The I2C interface is suggested as this requires only 4 wires from the Arduino board to the LCD – the less wires the better.

Here is the sketch for your perusal (download):

Example 37.3

/* Example 37.3 – Basic speedometer using millis(); http://tronixstuff.wordpress.com/tutorials > chapter 37 John Boxall | CC by-sa-nc */  #include "Wire.h" // for I2C bus LCD #include "LiquidCrystal_I2C.h" // for I2C bus LCD module - http://bit.ly/m7K5wt LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display  float start, finished; float elapsed, time; float circMetric=1.2; // wheel circumference relative to sensor position (in meters) float circImperial; // using 1 kilometer = 0.621371192 miles float speedk, speedm;    // holds calculated speed vales in metric and imperial  void setup() { attachInterrupt(0, speedCalc, HIGH); // interrupt called when sensors sends digital 2 high (every wheel rotation) start=millis(); // setup LCD lcd.init();      // initialize the lcd lcd.backlight(); // turn on LCD backlight lcd.clear(); lcd.println(" Wear a helmet! "); delay(3000); lcd.clear(); Serial.begin(115200); circImperial=circMetric*.62137; // convert metric to imperial for MPH calculations }  void speedCalc() { elapsed=millis()-start; start=millis(); speedk=(3600*circMetric)/elapsed; // km/h speedm=(3600*circImperial)/elapsed; // Miles per hour }  void loop() { lcd.setCursor(0,0); lcd.print(int(speedk)); lcd.print(" km/h "); lcd.print(int(speedm)); lcd.print(" MPH   "); lcd.setCursor(0,1); lcd.print(int(elapsed)); lcd.print(" ms/rev      "); delay(1000); // adjust for personal preference to minimise flicker }

There isn’t that much going on – every time the wheel completes one revolution the signal from the sensor will go from low to high – triggering an interrupt which calls the function speedCalc(). This takes a reading of millis() and then calculates the difference between the current reading and the previous reading – this value becomes the time to cover the distance (which is the circumference of the wheel relative to the sensor – stored in

float circMetric=1.2;

and is measured in metres). It finally calculates the speed in km/h and MPH. Between interrupts the sketch displays the updated speed data on the LCD as well as the raw time value for each revolution for curiosity’s sake. In real life I don’t think anyone would mount an LCD on a bicycle, perhaps an LED display would be more relevant. If you didn’t feel like making something from scratch, you could use a Digit Shield or something similar. In the meanwhile, you can see how this example works in the following short video clip. Instead of a bike wheel and reed switch/magnet combination, I have connected the square-wave output from a function generator to the interrupt pin to simulate the pulses from the sensor, so you can get an idea of how it works:

That just about sums up the use of millis() for the time being. There is also the micros(); function which counts microseconds.

So there you have it – another practical function that can allow more problems to be solved via the world of Arduino. As always, now it is up to you and your imagination to find something to control or get up to other shenanigans. If you have any suggestions with regards to parts for the next article, leave a comment below and we’ll look into it.  As always I hope you enjoyed reading this as much as I did writing it for you.

If you have any questions about the processes or details in this article, please ask in our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, there is the odd competition or give-away –  and we can all learn something. Or follow tronixstuff on twitter and facebook. High resolution images available on flickr.

Otherwise, have fun, stay safe, be good to each other – and make something!

No comments:

Post a Comment