Применение printf в stm32f4

PRE SCRIPT:
Те, для кого английский не проблема, могут прочесть статью здесь
Там все по теме принтф гораздо лучше чем у меня. Жаль, я поздно ее обнаружил.Рекомендую!

Так долго боролся с printf на stm32f4 discovery, что решил оставить заметку на память. От печки начинать не буду — предполагается что среда разработки уже функционирует.
Итак, у меня имеется плата stm32f4 discovery, воткнутая в usb, Archlinux с пакетами stlink-git,gcc-arm-none-eabi. Библиотека STM32F4xx_DSP_StdPeriph_Lib_V1.0.1 скачана, makefile взят здесь же — we.easyelectronics.ru/storm_ua/esche-odin-shablon-proekta-pod-stm32-na-gcc.html(спасибо коллеге storm_ua) и слегка подкорректирован. Для пробы написан helloword — лампочки мигают. Дальше собственно борьба с printf. Просто добавить include <stdio.h> и sprintf ( о printf пока не говорим — куда выводить? но sprintf вроде должен работать) не получилось. Масса сообщений о ненайденных функциях _sbrk_r, _exit и тому подобных. Добавляем, как рекомендуется файл syscalls.c. В нем должны быть прописаны реализации системных функций применительно к конкретному процессору. Либо просто пустые заглушки к этим фукциям.

Итак добавлен следующий syscalls.c (из проекта freertos):

/****************************************************************************
*  Copyright (C) 2009 by Michael Fischer. All rights reserved.
****************************************************************************
*  History:
*
*  28.03.09  mifi   First Version, based on the original syscall.c from
*                   newlib version 1.17.0
****************************************************************************/

#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
/***************************************************************************/
int _read_r (struct _reent *r, int file, char * ptr, int len)
{
  r = r;
  file = file;
  ptr = ptr;
  len = len;
  errno = EINVAL;
  return -1;
}
/***************************************************************************/
int _lseek_r (struct _reent *r, int file, int ptr, int dir)
{
  r = r;
  file = file;
  ptr = ptr;
  dir = dir;
  return 0;
}
/***************************************************************************/
int _write_r (struct _reent *r, int file, char * ptr, int len)
{  
  r = r;
  file = file;
  ptr = ptr;
#if 0
  int index;
  /* For example, output string by UART */
  for(index=0; index<len; index++)
  {
    if (ptr[index] == '\n')
    {
      uart_putc('\r');
    }  
    uart_putc(ptr[index]);
  }  
#endif   
  return len;
}
/***************************************************************************/
int _close_r (struct _reent *r, int file)
{
  return 0;
}
/***************************************************************************/
/* Register name faking - works in collusion with the linker.  */
register char * stack_ptr asm ("sp");
caddr_t _sbrk_r (struct _reent *r, int incr)
{
  extern char   end asm ("end"); /* Defined by the linker.  */
  static char * heap_end;
  char *        prev_heap_end;
  if (heap_end == NULL)
    heap_end = & end;
  prev_heap_end = heap_end;
  if (heap_end + incr > stack_ptr)
  {
      /* Some of the libstdc++-v3 tests rely upon detecting
        out of memory errors, so do not abort here.  */
#if 0
      extern void abort (void);
      _write (1, "_sbrk: Heap and stack collision\n", 32);
      abort ();
#else
      errno = ENOMEM;
      return (caddr_t) -1;
#endif
  }
  heap_end += incr;
  return (caddr_t) prev_heap_end;
}
/***************************************************************************/
int _fstat_r (struct _reent *r, int file, struct stat * st)
{
  r = r; 
  file = file;
  memset (st, 0, sizeof (* st));
  st->st_mode = S_IFCHR;
  return 0;
}
/***************************************************************************/
int _isatty_r(struct _reent *r, int fd)
{
  r = r;
  fd = fd;
  return 1;
}
/*** EOF ***/

Здесь кое-чего не хватает, копипастим заглушки, уж не помню откуда:

int _getpid()
{
        return 1;
}

void _exit(int i)
{
//      printf("Program exit with code %d", i);
        i = i;
        while (1);
}

int _kill(int pid, int sig)
{
        (void)pid;
        (void)sig;
        errno = EINVAL;
        return (-1);
}

Запускаем make — ура, все откомпилировалось. Шьем — не работает. Доходит до принтф и зависает. После долгих поисков выясняется, что не хватает ключа линковщика в макефайле:
LDFLAGS += -mcpu=cortex-m4 -mthumb
Именно линковщика, для компилятора эти ключи стояли!
После этого зависания прекратились. Осталось научить printf выводить на USART.
Есть готовый пример вывода в вышеупомянутой библиотеке перефирии, папка USART_Printf. Прекрасно компилируется, но не работает. Посмотрим внимательно на код в этом примере. Что там делается? Ну, инициализация USART3, это понятно. Кроме того, вводится фунция PUTCHAR_PROTOTYPE int __io_putchar(int ch):

PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART */
  USART_SendData(EVAL_COM1, (uint8_t) ch);
  /* Loop until the end of transmission */
  while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TC) == RESET)
  {}
  return ch;
}

И все… Почему после этого должна заработать printf, я не понял.
Побродив по файлам /usr/arm-none-eabi/include я не нашел там вызова __io_putchar. Вообще не упоминается. Зато упоминается _write_r из файла syscalls.c. Т.е. принтф будет испльзовать для вывода _write_r, а что там у нас написано? Напоминаю:

int _write_r (struct _reent *r, int file, char * ptr, int len)
{  
  r = r;
  file = file;
  ptr = ptr;
#if 0
  int index;
  /* For example, output string by UART */
  for(index=0; index<len; index++)
  {
    if (ptr[index] == '\n')
    {
      uart_putc('\r');
    }  
    uart_putc(ptr[index]);
  }  
#endif   
  return len;
}

Т.е. реально выводом должна заниматься uart_putc, которая не написана, а вызов ее закомментирован!
Дальнейший путь стал ясен. Пишем свою функцию uart_putc, и раскомментируем ее вызов в _write_r. Все. printf теперь умеет выводить на USART.
Я использовал вот такую функцию (спасибо angel5a):

int uart_putc( const char ch)
{
        while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
        {}
        USART_SendData(USART2, (uint8_t) ch);
return 0;
}

Естественно, перед этим нужно инициализировать USART2. Работающий проект выкладываю. Компилировать — просто make. Шить можно например так st-flash write template.bin 0x8000000
Интересно, это только я шел таким длинным окольным путем? Как у других с printf?
Если есть вопросы-замечания буду рад.
P.S. Перечитал исправленную на основе комментов статью и понял, с чего, собственно происходили мои проблемы. В официальном примере ведь используется та же(по смыслу и содержанию) функция, что и у меня. Но они ее назвали PUTCHAR_PROTOTYPE int __io_putchar(int ch). А товарисч Michael Fischer в своем syscalls.c назвал ту же функци uart_putc. Хотя он честно написал, что «к примеру». Просто я не разобрался — сам дурак.
Вообщем, тему для себя вроде прояснил, слава богу, всем спасибо за советы!
  • +3
  • 12 декабря 2012, 14:43
  • s_levkov
  • 1
Файлы в топике: stm32f4-printf-shabl.tar.gz

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

RSS свернуть / развернуть
воспользуйтесь пожалуйста тегами
<code> </code>
для оформления кода
0
и CUT так же было бы неплохо
0
Известно, что в полная реализация printf для контролеров слишком тяжеловесна. Многие пишут свои легковесные реализации. Могу порекомендовать одну из реализаций, для отладки более чем достаточно, легко разобраться.
+1
Спасибо за ссылку. Хотя для STM32F407VGT6 c 196k ОЗУ можно и не экономить ;-). Меня интересовала сама возможность использования стандартной библиотеки и удивило что никто толком не может обьяснить, как начать ее использовать. Возможно, для настоящих спецов в моей заметке все очевидно, но я разобрался не сразу.
0
Сплошные костыли. Как работать с несколькими портами?
+2
Ну прям таки сплошные! Кстати, другого способа я просто не смог найти — подобным образом делают все (и в офицальном примере идея та же, только не работающая). Если скажете как по-другому, буду только рад.
Вывод на разные порты — я так понял это не вопрос, а замечание? Согласен. Это не предусмотрено и это плохо. Если мне придется одновременно выводить на разные порты, буду думать ;-)
0
Дескрипторы устройств ввода-вывода лучше оставить. Рекомендую реализовать stdout и stderr. Также добавить fopen, который будет возвращять дескриптор устройства в зависимости от названия. Почему нет для него заглушки?
0
Напишите статью, а то одни критики вокруг.
0
Так было уже, не?
0
Рано еще.
0
В Кейле, по-моему, всё гораздо проще (скорее, вся сложность спрятана в библиотеке) и требуется лишь написать самому вывод символа в порт (реализацию putc, грубо говоря).
0
Чтоб заработал printf() в Keil, нужно всего-навсего определить функцию fputc(int ch, FILE *f). В либах объявлен только прототип, Вам нужно просто сделать саму функцию. С f* можно ничего не делать, а ch — нужно выпхнуть в UART или на LCD — это уже куда захочется. printf() зовет fputc() для вывода каждого символа.
0
Функция putc предназначена для вывода ОДНОГО символа. Аналогично, функция uart_putc должна выводить в последовательный канал тоже ОДИН символ.

У Вас же функция реализована как int uart_putc(const char* str), то есть осуществляет вывод ASCIIZ-строки.

По моему, это не просто грязный код, это — бомба. Когда-нибудь да она рванет.

Пожалуйста, прокомментируйте.
0
Я эту функцию взял готовую где-то на просторах инета. У нее просто неудачное название, надо бы uart_puts(const char* str). А по содержанию все правильно, ее аргумент строка — const char* str, а не символ — int ch. Чувствуете разницу? Там звездочка.
0
Не совсем удачное (а точнее — вводящее в заблуждение) имя функции — это как бы не очень хорошо. Это еще не проблема. Проблема, однако, в том, что функция uart_putc (uart_puts) выводит строку, а не символ.

Посмотрите на код функции _write_r — откуда, собственно, осуществляется вызов uart_putc. Так вот, в этом вызове в качестве аргумента передается символ, а не указатель:

<<<<<<<<<<<<<<<<<<
uart_putc('\r');
}
uart_putc(ptr[index]);
>>>>>>>>>>>>>>>>>>

Чувствуете разницу? (с)
0
Сорри еще раз. Что интересно, этот, неправильный вариант работает, а напрашивающееся исправление:

int uart_putc( char ch)
{
    do
      {
        USART_SendData(USART2, (uint8_t) ch);
      }
        while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
return 0;
} 

не работает! Точнее, строка выводистя и программа зависает.
Буду ковырять дальше. Спасибо за замечание!
0
Ыыы (простите, не удержался).
А че бы и не повиснуть? я бы тоже повис. Пока уарт занят, пхать в него сиволы. И что, что уже через край лезет.
int uart_putc( char ch)
{
        USART_SendData(USART2, (uint8_t) ch);
        while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
        return 0;
} 
Чуете разницу? :)
0
да, цикл бы поставить перед отправкой, то будет «фоновая отправка» — чуть меньше тормозов программе.
0
Спасибо, осознал.
0
У этой функции просто неудачное название, надо бы int uart_puts(const char* str). Название не мое ;-) См. syscalls.c — Copyright (C) 2009 by Michael Fischer.
0
Извините, еще не проснулся, поторопился с ответом. Замечание справедливое, буду думать и исправлять.
0
Прежде, чем писать статью, надо было-бы на форуме это все обсудить. На самом деле, чтоб заработал printf нужно 5 строк кода написать, а здесь мы видим беспредметное обсуждение на целый том энциклопедии. Если хотите кого-то удивить, сделайте вывод в UART с буферизацией и прерываниями, чтоб не ждать, пока UART освободится. Кстати, у ST есть пример c LWIP, в нем есть файл serial_debug.c, где все, что Вы здесь обсуждаете, ДАВНО СДЕЛАНО. Займитесь чем-нибудь полезным.
-2
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.