8 years, 6 months ago.

Equivalent to Arduino millis()

Hi,

Arduino has the fairly convenient `millis()` function which returns the number of milliseconds since boot. What is the situation for that on mBed. As far as I can tell mBed has these:

  • clock() - Returns number of CPU ticks, as defined by CLOCKS_PER_SEC. Unfortunately that is a) not constant, and b) not very fine-grained. For example on nRF51822 CLOCKS_PER_SEC is 100.
  • time_t time(...) - Standard POSIX time call, but unfortunately this just returns 0 on boards without a real RTC.
  • us_ticker_read() - This always returns microseconds since boot, and is required for mBed, but it has an ugly name and is an internal API.
  • As mentioned in the answer below, you can make an instance of Timer, but a) you have to manually start it, b) it isn't global, c) the name of the instance isn't fixed, d) you have to put it in your own Globals.h header (or similar).

This seems like a sorry situation for mBed's otherwise great API. I would suggest adding some non-internal functions like this:

float clock_s() { return us_ticker_read() / 1000000.0f; }
uint64_t clock_ms() { return us_ticker_read() / 1000; }
uint64_t clock_us() { return us_ticker_read(); }

Who's with me? (By the way the name is a bit unfortunate - it could cause confusion with clock() - but I couldn't think of anything better.)

Solution

Ok I decided to implement those functions, and while I was at it I made them not overflow after an hour like all the existing mBed time functions do (us_ticker_read(), Timer, Ticker, etc.). This code should last thousands of years without overflowing.

It assumes that the internal microsecond ticker wraps at 32 bits, which I'm *fairly* sure is always the case.

Clock.h

#pragma once

#include <stdint.h>

// Return the number of seconds since boot.
float clock_s();

// Return the number of milliseconds since boot.
uint64_t clock_ms();

// Return the number of microseconds since boot.
uint64_t clock_us();

Clock.cpp

#include <mbed.h>

// The us ticker is a wrapping uint32_t. We insert interrupts at
// 0, 0x40000000, 0x8000000, and 0xC0000000, rather than just 0 or just 0xFFFFFFFF because there is
// code that calls interrupts that are "very soon" immediately and we don't
// want that. Also because if we only use 0 and 0x80000000 then there is a chance it would
// be considered to be in the past and executed immediately.

class ExtendedClock : public TimerEvent
{
public:
	ExtendedClock()
	{
		// This also starts the us ticker.
		insert(0x40000000);
	}

	float read()
	{
		return read_us() / 1000000.0f;
	}

	uint64_t read_ms()
	{
		return read_us() / 1000;
	}

	uint64_t read_us()
	{
		return mTriggers * 0x40000000ull + (ticker_read(_ticker_data) & 0x3FFFFFFF);
	}

private:
	void handler() override
	{
		++mTriggers;
		// If this is the first time we've been called (at 0x4...)
		// then mTriggers now equals 1 and we want to insert at 0x80000000.
		insert((mTriggers+1) * 0x40000000);
	}

	// The number of times the us_ticker has rolled over.
	uint32_t mTriggers = 0;
};

static ExtendedClock _GlobalClock;

// Return the number of seconds since boot.
float clock_s()
{
	return _GlobalClock.read();
}

// Return the number of milliseconds since boot.
uint64_t clock_ms()
{
	return _GlobalClock.read_ms();
}

// Return the number of microseconds since boot.
uint64_t clock_us()
{
	return _GlobalClock.read_us();
}

Edit: I updated it to use 4 timer inserts instead of 2 - some implementations (the good ones) execute events that are 'in the past' immediately, so there is a chance that insert(0x80000000) when we are at 0 will happen immediately. This fixes that.

Also, if you are using nRF51 there are some bugs in the us ticker implementation that cause this to crash. See this bug and the pull requests on it: https://github.com/ARMmbed/mbed-hal-nrf51822-mcu/issues/53

3 Answers

7 years, 10 months ago.

Hi, I need solution to ported millis() to mbe but problem is, how to make in class. Timer t should started when first object will be created then rest of objects will use same timer.

Yes there is a static instance of ExtendedClock which is created when the program starts.

posted by Tim H 02 Jun 2016
7 years, 10 months ago.

Hello,

I'm using the following implementation of Arduino millis() when really needed:

millis.h

#ifndef MILLIS_H
#define MILLIS_H

void           millisStart(void);
unsigned long  millis(void);

#endif

millis.cpp

#include "mbed.h"
#include "millis.h"

volatile unsigned long  _millis;

void millisStart(void) {
    SysTick_Config(SystemCoreClock / 1000);
}

extern "C" void SysTick_Handler(void) {
    _millis++;
}

unsigned long millis(void) {
    return _millis;
}


When using the online compiler the above functions are also available in the millis library.

Make sure you call millisStart() before using:

main.cpp

#include "mbed.h"
#include "millis.h"

Serial  pc(USBTX, USBRX);

int main() {
    millisStart();
    while(1) {
        pc.printf("millis = %d\r\n", millis());        
        wait(1.0);
    }
}

Not all mBed devices have a SysTick though.

posted by Tim H 02 Jun 2016

What a shame! Anyway, thank you Tim for the info. Nevertheless, I confirm that it works with some NUCLEO boards and, since it's a global function, it can be used inside class implementation.

posted by Zoltan Hudak 02 Jun 2016

When i am trying to compile with mbed-rtos, it give an error: Symbol SysTick_Handler multiply defined

posted by Arnas MasRodjie 20 Apr 2018
8 years, 6 months ago.

Hi,

there's abolutely no need for another such function, as mbed already hast a much better implementation for such things than Arduino. You can setup a timer in your program and then start it at the beginning of main(). Then you can read the timer at any time you wish as seconds, milliseconds or microseconds. Actually the timer functions are internally based on the us_ticker. Look at Handbook -> Timer for more information.

#include "mbed.h"
...
Timer t;

int main() {
    t.start();
    ...
    float f = t.read();
    uint32_t m = t.read_ms();
    uint32_t u = t.read_us();
    ...
    t.stop();
}

Best regards
Neni

Yes I'm aware of `Timer`, but it isn't as convenient as `us_ticker_read()` because it isn't global, and its instance doesn't have a standard name (makes it awkward to use in libraries and examples). Perhaps if the mbed.h header had an `extern Timer deviceTime;` or something like that...

posted by Tim H 28 Sep 2015

That would be a waste in setups which don't need this global timer. Also that you think the us_ticker_read is an ugly name is a bit irrelevant, you can redefine it as any name you want in your own code. A more significant problem could be that it overflows after an hour or so.

And agreeing with Nenad: One of the things I really miss in Arduino are the very poor Timer functions compared to mbed. What do you need this for in the first place?

posted by Erik - 28 Sep 2015

Timer also overflows after 30 minutes. I need it to augment an RTC to get sub-second accuracy and so I can sleep the RTC after startup.

posted by Tim H 15 Oct 2015