Используем libc (newlib) для stm32

Многие тулчейны для ARM на GCC (Code Sourcery Lite в том числе) поставляются вместе с newlib — стандартной библиотекой языка C. С помощью неё можно использовать множество удобных стандартных функций (printf/scanf/malloc и т.д.).
Для того чтобы использовать эту библиотеку от нас требуется реализация некоторых системных вызовов.
Для примера я скопипастил и дописал мимнимальную реализацию для stm32. stdout/stderr отправляются в USART1, stdin — из USART1.

#include <errno.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/unistd.h>
#include <stm32f10x.h>

extern uint32_t __get_MSP(void);

#define STDOUT_USART USART1
#define STDERR_USART USART1
#define STDIN_USART USART1

// errno
#undef errno
extern int errno;

/*
 Переменные среды - пустой список.
 */
char *__env[1] = { 0 };
char **environ = __env;

int _write(int file, char *ptr, int len);

// exit - экстренный выход. В качестве выхода - зацикливаемся.
void _exit(int status)
{
    while (1);
}

// close - закрытие файла - возвращаем ошибку 
int _close(int file)
{
    return -1;
}
/*
 execve - передача управления новому процессу - процессов нет -> возвращаем ошибку.
 */
int _execve(char *name, char **argv, char **env)
{
    errno = ENOMEM;
    return -1;
}

/*
 fork = создание нового процесса
 */
int _fork()
{
    errno = EAGAIN;
    return -1;
}

/*
 fstat - состояние открытого файла
 */
int _fstat(int file, struct stat *st)
{
    st->st_mode = S_IFCHR;
    return 0;
}

/*
 getpid - получить ID текущего процесса
 */

int _getpid()
{
    return 1;
}

/*
 isatty - является ли файл терминалом.
 */
int _isatty(int file)
{
    switch (file)
    {
    case STDOUT_FILENO:
    case STDERR_FILENO:
    case STDIN_FILENO:
        return 1;
    default:
        //errno = ENOTTY;
        errno = EBADF;
        return 0;
    }
}

/*
 kill - послать сигнал процессу
 */
int _kill(int pid, int sig)
{
    errno = EINVAL;
    return (-1);
}

/*
 link - устанвить новое имя для существующего файла.
 */

int _link(char *old, char *new)
{
    errno = EMLINK;
    return -1;
}

/*
 lseek - установить позицию в файле
 */
int _lseek(int file, int ptr, int dir)
{
    return 0;
}

/*
 sbrk - увеличить размер области данных, использутся для malloc
 */
caddr_t _sbrk(int incr)
{
    extern char _ebss; 
    static char *heap_end;
    char *prev_heap_end;

    if (heap_end == 0)
    {
        heap_end = &_ebss;
    }
    prev_heap_end = heap_end;

    char * stack = (char*) __get_MSP();
    if (heap_end + incr > stack)
    {
        _write(STDERR_FILENO, "Heap and stack collision\n", 25);
        errno = ENOMEM;
        return (caddr_t) -1;
        //abort ();
    }

    heap_end += incr;
    return (caddr_t) prev_heap_end;

}

/*
 read - чтение из файла, у нас пока для чтения есть только stdin
 */

int _read(int file, char *ptr, int len)
{
    int n;
    int num = 0;
    switch (file)
    {
    case STDIN_FILENO:
        for (n = 0; n < len; n++)
        {
            while (USART_GetFlagStatus(STDIN_USART, USART_FLAG_RXNE) == RESET);
            char c = (char) (USART_ReceiveData(STDIN_USART) & (uint16_t) 0x01FF);
            *ptr++ = c;
            num++;
        }
        break;
    default:
        errno = EBADF;
        return -1;
    }
    return num;
}

/*
 stat - состояние открытого файла.
 */

int _stat(const char *filepath, struct stat *st)
{
    st->st_mode = S_IFCHR;
    return 0;
}

/*
 times - временная информация о процессе (сколько тиков: системных, процессорных и т.д.)
 */

clock_t _times(struct tms *buf)
{
    return -1;
}

/*
 unlink - удалить имя файла.
 */
int _unlink(char *name)
{
    errno = ENOENT;
    return -1;
}

/*
 wait - ожидания дочерних процессов
 */
int _wait(int *status)
{
    errno = ECHILD;
    return -1;
}

/*
 write - запись в файл - у нас есть только stderr/stdout
 */
int _write(int file, char *ptr, int len)
{
    int n;
    switch (file)
    {
    case STDOUT_FILENO: /*stdout*/
        for (n = 0; n < len; n++)
        {
            while (USART_GetFlagStatus(STDOUT_USART, USART_FLAG_TC) == RESET);
            USART_SendData(STDOUT_USART, (uint8_t) * ptr++);
        }
        break;
    case STDERR_FILENO: /* stderr */
        for (n = 0; n < len; n++)
        {
            while (USART_GetFlagStatus(STDERR_USART, USART_FLAG_TC) == RESET);
            USART_SendData(STDERR_USART, (uint8_t) * ptr++);
        }
        break;
    default:
        errno = EBADF;
        return -1;
    }
    return len;
}

Для работы нужно скомпилировать этот файл вместе с исходниками программы, линкер подхватит исходники и будет использовать эти функции вместо заглушек.
Теоретически, можно прикрутить все функции работы с процессами к какой-нибудь RTOS, тогда все будет вообще зашибись. Или функции работы с файлами к SD карточке или внешнему флешу.
  • +4
  • 15 августа 2011, 14:32
  • Kosyak
  • 1
Файлы в топике: newlib_stubs.c.zip

Комментарии (2)

RSS свернуть / развернуть
Спасибо, очень полезная информация. Сейчас портирую Contiki на STM3210E-EVAL. Без этой статьи я бы еще долго все заглушки реализовывал.
0
Восхитительно! Не знал о такой возможности. Именно этого кода хотел пример термометра для STM32L-DISCOVERY. Но хочется узнать откуда брали реализацию _sbrk()? И вообще куда смотреть, чтоб узнать что еще не реализовано?

Помню в каких-то примерах для MSP430го (под IAR) надо было объявнить что-то типа putc() и тогда у тебя появлялся printf(), который гнал вывод в USART. Думаю, тут что-то вроде тоже есть. Хе-хе!

Другое наблюдение. Все выше перечисленное (особенно _fork(), _wait() и _kill()) используется в многозадачных ОС. И все это вызовы ядра либо libc, которая линкуется к процессам. Но тут же нет ни процессов, ни MMU, ни ОС! А судя по заглушкам — все возможно?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.