Частенько при разработке встраиваемых систем нужно воспользоваться функциями вроде rand, sprintf, malloc и т.д. Часть таких функций можно в той или оной степени реализовать самому, но на это не всегда находится время. Один из вариантов решения задачи - использовать специальную библиотеку Newlib, встроенную в тулчейн Sourcery CodeBench Lite. Библиотека newlib содержит реализацию функций стандартной библиотеки С с учетом особенностей применения во встраиваемых системах. 

Не могу сказать что библиотека оптимальная - некоторые функции могут занять десятки килобайт флеша, так что пользоваться этим делом можно только если у вас достаточно памяти. Если же память критично придется либо реализовать функции самому, либо использовать более урезанные реализации вроде baselibc.

Итак для использования Newlib нужно выполнить объявление системных вызовов, которые должны вызываться функциями из Newlib для всяческого низкоуровнего взаимодействия с ресурсами (память, вывод, ввод). В обычной стандартной библиотеке Си системные вызовы реализованы в самой операционной системе, то есть сама система управляет ресурсами. У нас же понятие "системный вызов" не совсем правильно названо, потому что никакой операционной системы нету, а функции требуемые для Newlib придется реализовать самому.

Для большинства функций нужен системный вызов _sbrk, динамически выделяющий память. Объявим этот вызов в файле syscalls.c:

#include <sys/types.h>
extern int __HEAP_START;
caddr_t _sbrk ( int incr )
{
  static unsigned char *heap = NULL;
  unsigned char *prev_heap;
  if (heap == NULL) {
    heap = (unsigned char *)&__HEAP_START;
  }
  prev_heap = heap;
  /* check removed to show basic approach */
  heap += incr;
  return (caddr_t) prev_heap;
}

В функции используется константа __HEAP_START, отвечающая за адрес начала "кучи" - области ОП, которая будет использована для функций вроде malloc. Контсанта должна быть предоставлена в линке-скрипте. Скорее всего она будет указывать на конец сегмента bss:

 /* Uninitialized data */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;    /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;    /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM
  PROVIDE(end = _ebss);
  PROVIDE(_end = _ebss);
  PROVIDE(__HEAP_START = _ebss);

Теперь можно просто подключать инклудники со стандартными функциями

#include <stdio.h>
#include <stdlib.h>
и использовать:
char * random_time_str = malloc(6);
uint8_t hh = rand() % 24;
uint8_t mm = rand() % 60;
sprintf(random_time_str, "%d:%2d", hh, mm);
Точно таким же образом можно заставить работать printf добавив еще пачку системных вызовов _close, _fstat, _isatty, _lseek, _read, _write. Какими именно они должны быть можно без труда посмотреть в Google, например тут https://sites.google.com/site/stm32discovery/open-source-development-with-the-stm32-discovery/getting-newlib-to-work-with-stm32-and-code-sourcery-lite-eabi