Форматированная печать в кольцевой буфер

Я пишу встроенный код для STM32F3 mc (STM32F3-Discovery). Мне нужно вывести некоторые данные на UART, и я использую для этого DMA, так как это позволяет мне сосредоточиться на считывании датчиков и обработке данных, а не на ожидании завершения передачи байта. Однако проблема в том, что я должен объединить:

  1. Отформатированный вывод (т.е. некоторый из printf)
  2. Количество последовательных отпечатков (которые происходят до завершения предыдущей печати)

Так что я думаю о круговом буфере. Но я не думаю, что знаю, как заставить sprintf учитывать конец буфера и продолжать запись в начало буфера. Я, конечно, могу создать еще один временный буфер, печатать туда и копировать побайтно, но мне это не кажется элегантным.


person dccharacter    schedule 17.02.2013    source источник
comment
Интересное требование, но я не думаю, что вы добьетесь большего успеха, чем «форматировать во временный буфер с последующим копированием в кольцевой буфер с помощью соответствующей функции копирования».   -  person Jonathan Leffler    schedule 18.02.2013


Ответы (2)


Одним из решений может быть реализация собственного sprintf, который может работать с кольцевыми буферами. К сожалению, это не поможет вам решить более простую проблему: что вы будете делать, если ваш кольцевой буфер заполнен и вы вызовете sprintf?

Если ваша ситуация с памятью может себе это позволить, я бы предложил другое решение этой проблемы:

Идея основана на двух связанных списках буферов (один список для свободных буферов, один список как очередь передачи). Буферы имеют одинаковый размер, поэтому они могут хранить строку длины наихудшего случая. Буферы создают простую кучу, где выделение/освобождение — это только удаление из очереди/постановка в очередь элемента либо из списка свободных, либо из списка передачи.

Наличие буферов одинакового размера гарантирует, что вы не будете страдать от внешних эффектов фрагментации, таких как «шахматная доска», при динамическом выделении памяти. Создание собственной кучи для этого задания также даст вам полный контроль над общим размером буфера, доступным для задач передачи.

Я мог бы представить этот запуск следующим образом:

  1. Вы выделяете буфер из свободного списка для рендеринга данных.
  2. Используйте свои функции рендеринга (например, sprintf) для рендеринга данных в буфере.
  3. Добавьте данные для отправки в очередь передачи (и при необходимости инициируйте передачу)

Для передачи DMA вы обрабатываете IRQ завершения передачи. Там вы перемещаете только что переданный буфер в «свободный список» и настраиваете передачу для следующего буфера в очереди.

Это решение не будет самым эффективным с точки зрения использования памяти, но эффективность времени выполнения будет хорошей, поскольку вы записываете в память только один раз, а выделение/освобождение — это только выборка/сохранение указателя где-то. Конечно, вы должны убедиться, что у вас нет условий гонки между вашим приложением и IRQ для выделения/освобождения.

Возможно, эта идея вдохновит вас на решение ваших задач.

person junix    schedule 17.02.2013

Один из способов приблизиться к этому - выделить буфер printf в 2 раза больше размера вашего кольцевого буфера, а затем использовать первую половину в качестве кольцевого буфера. Обнаружив переполнение, скопируйте переполнение во второй половине назад на передний план (кольцевая часть). Что-то вроде этого:

void xprintf(const char *format, ...)
{
  int written;
  va_list args;
  va_start(args, format);
  written = vsnprintf(buffer, HALF_BUFFER_SIZE, format, args);
  va_end(args);
  if (buffer + written > bufferStart + HALF_BUFFER_SIZE)
  { // time to wrap
    int overflow = (buffer + written) - (bufferStart + HALF_BUFFER_SIZE); 
    memmove(bufferStart, bufferStart + HALF_BUFFER_SIZE, overflow;
    buffer = bufferStart + overflow;
  }
}
person Travis Griggs    schedule 19.02.2013