Я пытаюсь понять, как использовать функцию printf для печати на последовательный порт.
Моя текущая установка — это сгенерированный код STM32CubeMX и SystemWorkbench32 с платой обнаружения STM32F407 .
Я вижу в stdio.h, что прототип printf определяется как:
int _EXFUN(printf, (const char *__restrict, ...)
_ATTRIBUTE ((__format__ (__printf__, 1, 2))));
Что это означает? Где находится точное местоположение определения этой функции? Каков был бы общий смысл выяснения того, как использовать эту функцию для вывода?
Я получил первый метод с этой страницы, работающий на моем STM32F072.
http://www.openstm32.org/forumthread1055
Как там сказано,
Я заставил работать printf (и все другие консольно-ориентированные функции stdio) путем создания пользовательских реализаций низкоуровневых функций ввода-вывода, таких как
_read()
и_write()
.Библиотека GCC C вызывает следующие функции для выполнения низкоуровневого ввода-вывода:
int _read(int file, char *data, int len) int _write(int file, char *data, int len) int _close(int file) int _lseek(int file, int ptr, int dir) int _fstat(int file, struct stat *st) int _isatty(int file)
Эти функции реализованы в библиотеке GCC C как подпрограммы-заглушки со "слабой" связью. Если объявление какой-либо из вышеперечисленных функций появляется в вашем собственном коде, ваша подпрограмма-заменитель переопределит объявление в библиотеке и будет использоваться вместо стандартной (нефункциональной) процедуры.
Я использовал STM32CubeMX для настройки USART1 ( huart1
) в качестве последовательного порта. Поскольку я хотел только printf()
, мне нужно было только заполнить _write()
функцию, что я сделал следующим образом. Это условно содержится в syscalls.c
.
#include <errno.h>
#include <sys/unistd.h> // STDOUT_FILENO, STDERR_FILENO
int _write(int file, char *data, int len)
{
if ((file != STDOUT_FILENO) && (file != STDERR_FILENO))
{
errno = EBADF;
return -1;
}
// arbitrary timeout 1000
HAL_StatusTypeDef status =
HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);
// return # of bytes written - as best we can tell
return (status == HAL_OK ? len : 0);
}
Ответ @AdamHaun - это все, что вам нужно, sprintf()
так как легко создать строку, а затем отправить ее. Но если вам действительно нужна собственная printf()
функция, тогда вам подойдут функции с переменными аргументами (va_list) .
С va_list
пользовательской функцией печати выглядит следующим образом:
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void vprint(const char *fmt, va_list argp)
{
char string[200];
if(0 < vsprintf(string,fmt,argp)) // build string
{
HAL_UART_Transmit(&huart1, (uint8_t*)string, strlen(string), 0xffffff); // send message via UART
}
}
void my_printf(const char *fmt, ...) // custom printf() function
{
va_list argp;
va_start(argp, fmt);
vprint(fmt, argp);
va_end(argp);
}
Пример использования:
uint16_t year = 2015;
uint8_t month = 12;
uint8_t day = 18;
char* date = "date";
// "Today's date: 2015-12-18"
my_printf("Today's %s: %d-%d-%d\r\n", date, year, month, day);
Обратите внимание, что хотя это решение дает вам удобную функцию для использования, но оно медленнее, чем отправка необработанных данных или использование даже файлов sprintf()
. С высокой скоростью передачи данных, я думаю, этого будет недостаточно.
Другой вариант, и, вероятно, лучший вариант — использовать отладчик ST-Link, SWD вместе с утилитой ST-Link. И используйте Printf через программу просмотра SWO , вот руководство по утилите ST-Link , соответствующая часть начинается на странице 31.
Printf через SWO Viewer отображает данные printf, отправленные от цели через SWO. Он позволяет отображать некоторую полезную информацию о работающей прошивке.
_EXFUN — это макрос, вероятно, содержащий некоторые интересные директивы, которые сообщают компилятору, что он должен проверить строку формата на совместимость с printf и убедиться, что аргументы для printf соответствуют строке формата.
Чтобы узнать, как использовать printf, я предлагаю справочную страницу и еще немного поиска в Google. Напишите несколько простых программ на языке C, использующих printf, и запустите их на своем компьютере, прежде чем пытаться перейти на их использование на микропроцессоре.
Интересным будет вопрос «куда девается печатный текст?». В unix-подобной системе он переходит в «стандартный выход», но в микроконтроллере такого нет. Библиотеки отладки CMSIS могут отправлять текст printf в порт отладки полухостинга arm, то есть в ваш сеанс gdb или openocd, но я понятия не имею, что будет делать SystemWorkbench32.
Если вы не работаете в отладчике, может иметь смысл использовать sprintf для форматирования строк, которые вы хотите напечатать, а затем отправить эти строки через последовательный порт или на любой другой дисплей, который вы могли подключить.
Осторожно: printf и связанный с ним код очень велики. Это, вероятно, не имеет большого значения для 32F407, но это реальная проблема для устройств с небольшой флэш-памятью.
__cdecl
чтобы избежать ошибок. По крайней мере, в newlib/cygwin _EXFUN используется только для установки __cdecl
.Если вам нужен другой подход и вы не хотите перезаписывать функции или объявлять свои собственные, вы можете просто использовать snprintf
для достижения той же цели. Но не так элегантно.
char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
printf() (обычно) является частью стандартной библиотеки C. Если ваша версия библиотеки поставляется с исходным кодом, вы можете найти там реализацию.
Вероятно, было бы проще использовать sprintf() для генерации строки, а затем использовать другую функцию для отправки строки через последовательный порт. Таким образом, все сложное форматирование выполняется за вас, и вам не нужно ломать стандартную библиотеку.
Для тех, кто испытывает затруднения, добавьте в syscalls.c следующее:
extern UART_HandleTypeDef huart1; // access huart1 instance
//extern int __io_putchar(int ch) __attribute__((weak)); // comment this out
__attribute__((weak)) int __io_putchar(int ch)
{
HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return (status == HAL_OK ? ch : 0);
}
Подключитесь к вашему COM-порту через шпатлевку, и все будет хорошо.
Тут
Даниэль
cp.engr
_write()
функция - это то, что я сделал. Подробности в моем ответе ниже.Джозеф Пигетти