TL;DR
If you are just interested in looking at the working code, see the full source code example at the end of this article.
- In order to make
std::chronowork with Newlib Nano, you have to implement the system call_gettimeofday. - The implementation of
_gettimeofdayhas to return the current time since the epoch (in this case start of the system) in the structtimevaland return 0. - To obtain the current time, you can set up a timer interrupt that increments a counter variable, which then is used to fill the struct
timevalin the_gettimeofdayimplementation.
Was this article helpful?
If you like this article and want to support me, you can do so by buying me a coffee, pizza or other developer essentials by clicking this link: Support me with PayPal
Environment
- ARM GNU Toolchain (arm-none-eabi) (I am using version 13.2)
- Git repository: https://github.com/devzeb/embedded_template
- Git branch:
gettimeofday-stm32g4
The compiler and linker flags used can be found at this section or at the CMake files of the repository.
What is the problem with using std::chrono with Newlib Nano?
Given this trivial example code:
1
2
3
4
5
6
7
8
#include <chrono>
int main()
{
auto now_steady = std::chrono::steady_clock::now();
return 0;
}
Trying to compile this code yields the following linker error:
1
2
3
4
5
/opt/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: /opt/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/lib/thumb/nofp/libg_nano.a(libc_a-gettimeofdayr.o): in function `_gettimeofday_r':
gettimeofdayr.c:(.text._gettimeofday_r+0xe): undefined reference to `_gettimeofday'
collect2: error: ld returned 1 exit status
The linker is missing the function _gettimeofday, therefore the linking process fails. The error message hints, that the call is made from the function _gettimeofday_r of the library libg_nano.a (see What is libg_nano.a?).
This means that the C standard library implementation is trying to make a call to _gettimeofday, but this function is neither implemented in the library itself nor in our user code (main.cpp).
What is the dependency between std::chrono::steady_clock and gettimeofday?
Looking at the disassembly of the code, we can extract the following information:
1
2
3
4
std::chrono::_V2::steady_clock::now() calls std::chrono::_V2::system_clock::now()
std::chrono::_V2::system_clock::now() calls gettimeofday
gettimeofday calls _gettimeofday_r
_gettimeofday_r calls _gettimeofday
What is gettimeofday, _gettimeofday and _gettimeofday_r, and why isn’t it available?
As the error originates from the Newlib Nano library, we can find a hint to the function gettimeofday in this section of its documentation:
Supporting OS subroutine required: Some implementations require gettimeofday.
This means that gettimeofday is an OS subroutine, which is another name for system call. So gettimeofday is a function that is supposed to interact with the operating system.
On many other platforms, this system call is implemented and interacts with the OS, e.g. the Linux kernel. The function is also part of the POSIX operating system standard. This means that it is supposed to be available on all POSIX compliant operating systems.
However, on the Cortex-M platform, we do not have an operating system kernel that is able to provide the current time. Therefore, this function is nowhere to be found in the platform specific version of the Newlib Nano C standard library.
If you are wondering why the linker did not complain about the function gettimeofday, but _gettimeofday, you can find the answer in the section Why did the linker not complain about gettimeofday, but _gettimeofday?.
Concept to make std::chrono work
Now that we know that _gettimeofday is missing to make std::chrono::steady_clock::now() work, we have to implement it ourselves.
From the documentation of gettimeofday in the POSIX standard, we can find out what the function is supposed to do:
DESCRIPTION
The gettimeofday() function shall obtain the current time, expressed as seconds and microseconds since the Epoch, and store it in the timeval structure pointed to by tp. The resolution of the system clock is unspecified.
RETURN VALUE
The gettimeofday() function shall return 0 and no value shall be reserved to indicate an error.
This means we have to do two things in our implementation of _gettimeofday:
- modify the pointer struct timeval to contain the current time since the epoch
- return 0 to indicate success
The timezone struct is defined in the include file sys/_timeval.h (in the Newlib repository: newlib/libc/include/sys/_timeval.h):
1
2
3
4
5
6
7
/*
* Structure returned by gettimeofday(2) system call, and used in other calls.
*/
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* and microseconds */
};
The struct timeval contains both seconds and microseconds since the epoch. The epoch is usually defined as January 1st, 1970, 00:00:00 UTC. In our case though, we have no way of knowing the current date, as this information is not available on the microcontroller. Therefore, we define the epoch ourselves as the start of the system.
So, in order to properly fill the struct timeval, we have to obtain the number of seconds and microseconds since the start of the system.
Obtaining the number of seconds and microseconds since the start of the system
There are multiple possible ways of getting this information. Two possibilities are to:
- use a timer peripheral together with an interrupt to increment a time counter variable
- use a real time clock (RTC) peripheral
As most Cortex-M microcontrollers include a timer peripheral, I chose to use the interrupt based approach for this example.
Implementation
To make std::chrono work on our bare metal system, we have to:
- Implement a time counter function that increments a time counter variable
- Implement
_gettimeofdayto return the content of the time counter variable - Initialize a timer to issue an interrupt with a fixed interval (e.g. 1ms)
- Implement the timer interrupt handler to call the time counter function
When your system uses a RTOS, a periodically invoked tick function is usually already available. In this case, you can just call the time counter function from within the tick function.
Implementing the time counter function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace
{
// use the type of timeval::tv_usec to make sure that the type is compatible with the struct timeval
using TMicroseconds = decltype(std::declval<timeval>().tv_usec);
// not using TSeconds = decltype(std::declval<timeval>().tv_sec) because it's internally a long long (int64_t), which is not compatible with std::atomic on the Cortex-M platform
using TSeconds = std::uint32_t; // 4294967295 seconds = 136,1 years -> max uptime of 136 years is enough
// updated from the timer interrupt, so the variables need to be atomic
std::atomic<TSeconds> seconds_passed{0};
std::atomic<TMicroseconds> microseconds_passed{0};
// make sure that the atomics can be used without support for locks
static_assert(decltype(seconds_passed)::is_always_lock_free, "seconds_passed is not lock free");
static_assert(decltype(microseconds_passed)::is_always_lock_free, "microseconds_passed is not lock free");
}
void on_microseconds_passed(const TMicroseconds microseconds)
{
static constexpr auto microseconds_per_second = 1000 * 1000;
// use a non atomic variable to store the result, instead of re-reading the atomic, which is more expensive
const auto result = microseconds_passed += microseconds;
// if a second has passed, increment the seconds counter
if (result >= microseconds_per_second)
{
seconds_passed += 1;
// adapt the microseconds counter to make sure that seconds_passed + microseconds_passed is always the current time
microseconds_passed -= microseconds_per_second;
}
}
I chose to use two counter variables for seconds and microseconds, because it is required by the struct timeval. This enables me to express a wider range of time values than if I would use only one variable as a microseconds or milliseconds time counter.
The variables are updated from the timer interrupt, so they must be updated atomically to prevent data races (= undefined behavior), according to the C++ language standard.
The static_assert expressions make sure that the atomics can be used without support for locks, because the Newlib Nano C standard library does not support locks out of the box. As a 64 bit variable would require a lock to be updated atomically on the Cortex-M platform, I use an unsigned 32-bit integer for TSeconds instead. This has the side effect, that the highest value expressible by the variable is 4294967295 seconds, which is equal to 136,1 years. This is enough for my particular use case, as I do not expect the system to run for more than this timespan.
Implementing the _gettimeofday function
With this code in place, we can now implement the _gettimeofday function:
1
2
3
4
5
6
7
8
9
extern "C" {
int _gettimeofday(timeval* ptimeval,
[[maybe_unused]] void* ptimezone)
{
ptimeval->tv_sec = seconds_passed;
ptimeval->tv_usec = microseconds_passed;
return 0;
}
}
This implementation is trivial. We just fill the required variables of the struct timeval with our counter variables and return 0 to indicate success.
Implementing the timer interrupt
In order to implement the timer interrupt, I use the stm32g4xx_hal library from STMicroelectronics. The HAL provides an example implementation of a timer interrupt, that is executed every 1ms.
We just have to call the on_microseconds_passed function from the interrupt handler:
1
2
3
4
5
6
7
8
9
10
11
12
// note: even tough the example implementation is written in C, I am using C++
extern "C" {
void TIM6_DAC_IRQHandler()
{
HAL_TIM_IRQHandler(&TimHandle);
static constexpr auto microseconds_per_millisecond = 1000;
// call the function that increments the "time counter" variable
on_microseconds_passed(microseconds_per_millisecond);
}
}
I am using the
on_microseconds_passedfunction, even though the timer interrupt only has millisecond resolution. This is so you can adapt this example more easily to your needs, e.g. if you have a sub-millisecond resolution for your particular use case.
Full source code example
As I mentioned before, you can find the full source code example in my embedded_template repository at the branch named gettimeofday-stm32g4. Look at the files gettimeofday.cpp and stm32g4xx_hal_timebase_tim.cpp.
Conclusion
With everything implemented, the adaption is complete. I can now use std::chrono::steady_clock::now() on my Cortex-M microcontroller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "stm32g4xx_hal_timebase_tim.h"
#include <chrono>
int main() {
// initialize the timer to update every 1ms and then call on_microseconds_passed(1000) in the interrupt handler
if (embedded_template_HAL_InitTick(0) != HAL_OK) {
return 1;
}
while (true) {
auto now_steady = std::chrono::steady_clock::now();
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now_steady.time_since_epoch()).count();
if (seconds >= 5) {
// exit the program after 5 seconds
break;
}
}
return 0;
}
Additional information
What is libg_nano.a?
libcis the C standard library.libgis a debugging-enabled libc, according to this comment.- The suffix
_nanoindicates that the Newlib Nano library is used instead of the bigger Newlib library.
Therefore, libg_nano.a is a debugging-enabled libc from the Newlib Nano project.
The Newlib official webpage explains that:
Newlib is a C library intended for use on embedded systems.
The Newlib Nano project is a subset of the Newlib project, providing the C standard library for embedded systems.
Why did the linker not complain about gettimeofday, but _gettimeofday?
This has to do with the fact, that Newlib provides a reentrant version of the C standard library functions. From this section of the Newlib documentation.
Reentrancy is a characteristic of library functions which allows multiple processes to use the same address space with assurance that the values stored in those spaces will remain constant between calls. The Red Hat newlib implementation of the library functions ensures that whenever possible, these library functions are reentrant. However, there are some functions that can not be trivially made reentrant. Hooks have been provided to allow you to use these functions in a fully reentrant fashion.
These hooks use the structure reent defined in reent.h. A variable defined as ‘struct _reent’ is called a reentrancy structure. All functions which must manipulate global information are available in two versions. The first version has the usual name, and uses a single global instance of the reentrancy structure. The second has a different name, normally formed by prepending ‘’ and appending ‘_r’, and takes a pointer to the particular reentrancy structure to use.
This is the reason why the system call gettimeofday is actually implemented as a trampoline function to the reentrant version _gettimeofday_r, which then calls the actual system call implementation _gettimeofday.
To sum it up: Newlib renamed the actual system call implementation from gettimeofday to _gettimeofday to enable the function to be reentrant. Both gettimeofday and _gettimeofday have the same signature and the documentation of gettimeofday is also valid for _gettimeofday.
If you want more information about this, look at the section Proof that the _gettimeofday is the actual system call function.
Proof that the _gettimeofday is the actual system call function
The following comments are based on this revision of Newlib:
- git repo: https://sourceware.org/git/newlib-cygwin.git
- git revision:
1a177610d8e181d09206a5a8ce2d873822751657
We can extract the following hints:
- in
newlib/libc/include/sys/time.hwe find the declaration ofgettimeofdayand_gettimeofday:
1
2
3
4
5
6
int gettimeofday (struct timeval *__restrict __p,
void *__restrict __tz);
...
#ifdef _LIBC
int _gettimeofday (struct timeval *__p, void *__tz);
#endif
We can also find a stub implementation of _gettimeofday in libgloss/libnosys/gettod.c: If you look closely at the file path, you can see that this implementation is not part of Newlibs libc, but part of libgloss. Libgloss is a library that provides the low level functions for a specific target. In this case, the target is nosys, which means that the target does not provide any system calls. Therefore, the implementation of _gettimeofday is a stub that returns an error.
1
2
3
4
5
6
7
int
_gettimeofday (struct timeval *ptimeval,
void *ptimezone)
{
errno = ENOSYS;
return -1;
}
- in
newlib/libc/syscalls/sysgettod.cwe can find the implementation ofgettimeofday:
1
2
3
4
5
6
int
gettimeofday (struct timeval *ptimeval,
void *ptimezone)
{
return _gettimeofday_r (_REENT, ptimeval, ptimezone);
}
- in
newlib/libc/reent/gettimeofdayr.cwe can find the implementation of_gettimeofday_r:
1
2
3
4
5
6
7
8
9
10
11
12
int
_gettimeofday_r (struct _reent *ptr,
struct timeval *ptimeval,
void *ptimezone)
{
int ret;
errno = 0;
if ((ret = _gettimeofday (ptimeval, ptimezone)) == -1 && errno != 0)
_REENT_ERRNO(ptr) = errno;
return ret;
}
And there is the call to _gettimeofday!
Compiler and linker flags used
Compiler flags: arm-none-eabi-g++:
-std=gnu++20-fdiagnostics-color=always--specs=nano.specs-mthumb-mcpu=cortex-m4-mfloat-abi=hard-ffunction-sections-fdata-sections-fno-rtti-fno-exceptions-fno-common-fno-non-call-exceptions-fno-use-cxa-atexit
Linker flags: arm-none-eabi-g++:
--specs=nano.specs-mthumb-mcpu=cortex-m4-mfloat-abi=hard-Wl,--gc-sections-Wl,--print-memory-usage-Wl,-T/home/seb/code/embedded_template/device/stm32g474/STM32G474RETX_FLASH.ld