Я в курсе, что большинство просто пишет _write()
, и успокаивается что «printf работает». Хотелось бы более полный вариант, с чтением и без блокировок.
Тут нашел такой вариант:
// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/src/retarget/retarget.c
#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <../Inc/retarget.h>
#include <stdint.h>
#include <stdio.h>
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
UART_HandleTypeDef *gHuart;
void RetargetInit(UART_HandleTypeDef *huart) {
gHuart = huart;
/* Disable I/O buffering for STDOUT stream, so that
* chars are sent out as soon as they are printed. */
setvbuf(stdout, NULL, _IONBF, 0);
}
int _isatty(int fd) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
return 1;
errno = EBADF;
return 0;
}
int _write(int fd, char* ptr, int len) {
HAL_StatusTypeDef hstatus;
if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
if (hstatus == HAL_OK)
return len;
else
return EIO;
}
errno = EBADF;
return -1;
}
int _close(int fd) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
return 0;
errno = EBADF;
return -1;
}
int _lseek(int fd, int ptr, int dir) {
(void) fd;
(void) ptr;
(void) dir;
errno = EBADF;
return -1;
}
int _read(int fd, char* ptr, int len) {
HAL_StatusTypeDef hstatus;
if (fd == STDIN_FILENO) {
hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
if (hstatus == HAL_OK)
return 1;
else
return EIO;
}
errno = EBADF;
return -1;
}
int _fstat(int fd, struct stat* st) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
st->st_mode = S_IFCHR;
return 0;
}
errno = EBADF;
return 0;
}
#endif //#if !defined(OS_USE_SEMIHOSTING)
В нем все замечательно, кроме пары маленьких нюансов:
- Он не работает :). Там везде проверки, чтобы отличать дескрипторы 0, 1, 2 от реальных файлов, а на практике в функции прилетают другие значения (ну то есть без проверки ок, а с ней не пашет). Мне конечно файлы без надобности, но просто интересно понять, почему так.
- Тот пример для UART, а мне надо для USB CDC Middleware из stm32cube. Особенно интересует, как делать не блокирующее чтение с автоматическим echo в терминале.
Минимальный рабочий код, только для printf, выглядит так:
int _write(int fd, char* ptr, int len)
{
CDC_Transmit_FS((uint8_t*)ptr, len);
return len;
}
Интересуют такие вещи:
- Рабочий код
_read()
, конкретно для stm32, того кода что делает куб. - Насколько вообще есть смысл перекрывать что-то кроме
_read()
/_write()
или на чахлых эмбедах с newlib больше все равно ничего не дергается? Тот же_is_atty()
например. - Для
_read()
, как потом ПРОСТЫМИ способами проверить, что в stdin есть символ (чтобы вызовgetchar()
не зависал)? - Есть ли альтернативные реализации USB CDC поверх HAL/LL? Та что в кубе - не нравится, жрет како-то дикое количество памяти под конфигурацию ендпоинтов. Может я чего-то не понимаю, но по-моему выделять память под 15 параметров для каждого EP обычного COM-порта это перебор. В итоге при буфере 512 байт оно жрет еще 2К под какую-то хрень. Не то чтобы мне критично… так… слегка обидно :)