LINUX.ORG.RU

stm32, перенаправление stdio в USB CDC, посоветуйте наилучший вариант

 , ,


0

2

Я в курсе, что большинство просто пишет _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К под какую-то хрень. Не то чтобы мне критично… так… слегка обидно :)
★★★★★

На SPL много примеров под CDC, куб вроде и хорошая штука, да и хал, но проблему можно на ровном месте поймать.

AUX ★★★ ()

Чтобы сделать чтение без блокировок, придётся сделать буферизацию. Типа, читать пришедшие символы в прерывании, складывать их в буфер. А в _read() - возвращать столько, сколько есть в буфере.

Beewek ()
Ответ на: комментарий от AUX

Куб отличная штука. Мне во время разработки приходится несколько раз пины перетусовывать, чтобы плата лучше развелась. Без куба усрался бы.

Насчет SPL можешь конкретных ссылок накидать по моему вопросу? Упарился искать уже.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Так я конкретно и отвечаю (на вопрос номер 3): куб внутри себя приём USART-а не буферизует, поэтому без буферизации узнать, есть ли принятый символ нельзя.

Из реализаций USB есть ещё libopencm3. Я пробовал, работает хорошо, жрёт сильно меньше, чем куб.

Вот мой пример USB-CDC, использующий libopencm3 и scmRTOS.

Beewek ()
Ответ на: комментарий от Beewek

Большое спасибо за ссылки, очень интересно.

(на вопрос номер 3): куб внутри себя приём USART-а не буферизует, поэтому без буферизации узнать, есть ли принятый символ нельзя.

По-моему кто-то из нас что-то путает. В кубе можно как минимум в настройках указать размер serial tx/rx буферов для cdc. Но я чтение пока не копал.

Давай переспрошу иначе. Допустим, на самом низком уровне у меня есть буферы и возможность узнать поступили данные или нет. Как мне уже на верхнем уровне приложения узнать об этом стандартными средствами сишечки, не сваливаясь в драйвер? Чтобы дергать только стандартные методы типа getchar(). Например, чтобы вместо подвисания возвращалась -1 когда данных нет. Это просто пример, я не знаю как правильно, но смысл вроде понятен. Не хочу без нужды в драйверной прослойке дополнительные методы плодить.

Vit ★★★★★ ()
Последнее исправление: Vit (всего исправлений: 1)
Ответ на: комментарий от Vit

В кубе можно как минимум в настройках указать размер serial tx/rx буферов для cdc.

Я там про USART писал, не про CDC. В CDC без буфера уже не обойтись, конечно.

Как мне уже на верхнем уровне приложения узнать об этом стандартными средствами сишечки, не сваливаясь в драйвер?

Насколько я знаю, стандартными - никак. Нет такой стандартной функции. При программировании на ПК приходится использовать платформо-специфичные функции (kbhit() в винде, разные хитрые ioctl() в линуксе).

Beewek ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.