Проблемы передачи данных, связанные с FIFO, между микроконтроллером и ПК

У меня есть ситуация, в которой микроконтроллер должен выполнять большое количество преобразований АЦП и форматировать результаты в команды (или пакеты данных) и отправлять их на ПК с помощью UART. Чтобы измерять непрерывно при отправке данных, я создал циклический буфер (очередь/FIFO-буфер) для хранения ожидающих пакетов, и затем микроконтроллер (в идеале) будет очищать эту очередь так быстро, как позволит ПК.

Микроконтроллер автоматически отправляет следующую команду (если есть) после получения ACK-символа (в данном случае '!'). Обработчик прерываний UART выглядит следующим образом:

if (ch == '!') // ch is the received character
{
    cmd_rx_ack = '!';
    cmd_tx_pop_cmd();
}

Функция cmd_tx_pop_cmd() берет один пакет из циклического буфера и помещает байты в массив cmd_tx[] и длину пакета в cmd_tx_length.

В основном цикле:

if (cmd_rx_ack == ‘!’)
{
    cmd_rx_ack = 0; // Reset ack status

    // Transmit the command
    if (cmd_tx_length > 0)
    {
        cmd_tx_transmit();
    }
}

Функция cmd_tx_transmit() просто передает байты в cmd_tx[] один за другим (это делается в основном цикле, поскольку в обработчике UART это занимает слишком много времени).

Это прекрасно работает, если потребитель очереди (в данном случае UART-обработчик) имеет более высокий приоритет, чем производитель (таймер, который периодически заставляет АЦП преобразовывать и помещать пакет данных с результатом в очередь). Раньше у меня были проблемы с параллелизмом (см. Проблемы параллелизма с циклическим буфером во встроенной программе ), но теперь у меня другая проблема:

Я хочу, чтобы микроконтроллер отправлял следующий пакет в очереди не только при получении ACK, но и в следующих ситуациях:

  1. Первый пакет для передачи
  2. Если измерения выполняются со скоростью меньшей, чем время, необходимое для отправки посылки.

В первой ситуации пакет никогда не передается, так как после помещения пакета в очередь не получен ACK.

Во второй ситуации пакеты никогда не извлекаются из очереди по той же причине, что и в первой ситуации. (Предположим, что первый пакет получил подтверждение, и обработчик UART хочет передать следующий, но в этот момент очередь пуста). В этом случае очередь становится все больше и больше, а пакеты не передаются.

Я могу решить первую, передав первый пакет вручную (без использования очереди) и поставив оставшиеся в очередь. Хотя решение не очень красивое, но оно работает... Мне все еще нужно решить вторую проблему.

Итак, я думаю, что обработчик UART не подходит для извлечения из очереди, поскольку он не может учитывать две ситуации, которые я только что упомянул. Но я не могу поместить его в основной цикл, так как он имеет самый низкий приоритет, и тогда проблемы с параллелизмом становятся проблемой.

Что было бы хорошим способом реализовать это?

Заранее спасибо :-)

Кстати 1: ACK передается на микроконтроллер, если ПК принял переданный пакет. Я сделал специальную кодировку пакетов, чтобы можно было определить их длину. Это прекрасно работает, и cmd_tx_pop_cmd() извлечет только первый пакет, доступный в очереди. Я проверял все это много-много раз, и все работает отлично, поэтому, пожалуйста, не зацикливайтесь на этой части, так как проблема не в ней.

Кстати 2: Микроконтроллер Tiva C Series TM4C123GH6 находится на панели запуска Tiva C Series. Я использую gcc-arm-none-eabi.

Несколько вопросов: (1) что произойдет, если ПК не опрашивает ваше устройство и fifo переполняется? (2) Если ПК не отправляет ACK (возможно, передача теряется), что делает ваше устройство?
(1) FIFO не будет переполняться, поскольку измерения будут приостановлены, если в буфере нет места для нового пакета. Таймер измерения снова запустится, как только очередь опустеет. Это работает нормально, пока FIFO все еще содержит пакеты при получении ACK. Если нет, я сталкиваюсь с проблемой, описанной в моем вопросе. (2) Вероятнее всего, потребуется перезагрузить устройство, чтобы оно снова заработало. Я думаю, что я реализую функцию «сброса связи», которая очищает все буферы TX и RX после одной секунды бездействия UART. Это должно быть легко реализовать, как я делал это раньше.

Ответы (2)

Как правило, протокол передачи, управляемый прерыванием, всегда должен запускаться откуда-то вне прерывания.

Но действительно ли вам нужны прерывания? Это звучит как очень простое приложение: основной цикл проверяет несколько возможных ситуаций:

  • таймер истек: начать аналого-цифровое преобразование
  • АЦП завершено: поместить результат в очередь
  • ! получено: разрешена передача
  • передача разрешена, а передача запрещена: если очередь не пуста: всплывающий результат; разрешается чистый транзит; настроить передачу
  • Буфер uart пуст: передать следующий символ или очистить передачу, если он не доступен

Часть передачи может быть очищена, если выражена в STD.

Благодарю за ваш ответ. Я попытался отправить первый пакет, установив логическую переменную, которую опрашивает мой основной цикл. Затем он вставит в очередь и передаст. Это может решить первую ситуацию. Во второй ситуации обработчик UART и основной цикл плохо работают вместе (обработчик UART может извлекать байты из очереди, в то время как основной цикл также делает это). Кроме того, таймер измерения тем временем будет вставлять пакеты в очередь. Имейте ввиду, что мой вопрос упрощен - реальное устройство сложнее. Спасибо, в любом случае :-)
Решением может быть создание программного прерывания, которое запускается обработчиком UART (при получении ACK), а также после помещения каждого пакета в очередь. Тогда этот ISR будет иметь более высокий приоритет, чем UART-обработчик и таймер измерения (и, конечно, основной цикл) и единственное место, где выталкивается очередь. Основной цикл по-прежнему занимается фактической передачей байтов. Я не знаю об этом (и не знаю многого о программных прерываниях). Идея состоит в том, что очередь выталкивается в любом случае, а не только после ACK. Что вы думаете об этой идее?

Немного подумав, я сам придумал решение. Идея состоит в том, что только обработчик UART (потребитель) будет извлекать очередь, поскольку он имеет более высокий приоритет, чем основной цикл, а также более высокий приоритет, чем обработчик таймера, который выполняет измерения. Однако в двух ситуациях, которые я перечислил в своем вопросе выше, обработчик UART не будет извлекать очередь, потому что очередь пуста в то время, когда обработчик UART получил ACK.

Решение, которое я придумал, состоит в том, чтобы сообщить производителю (таймеру измерения), что очередь была пуста на момент последнего ACK и что следующий пакет нужно передать вручную (без использования очереди). Это решило вторую ситуацию. Первая ситуация решается всегда передачей первого пакета вручную.

Я изменил обработчик UART на

if (ch == '!')
{
    if (cmd_tx_queue.size == 0)
    {
        cmd_transmit_manually = 1;
        cmd_rx_ack = 0;
    }
    else
    {
        cmd_transmit_manually = 0;
        cmd_tx_pop_cmd();
        cmd_rx_ack = '!';
    }
}

Основной цикл остается прежним:

if (cmd_rx_ack == ‘!’)
{
   cmd_rx_ack = 0; // Reset ack status

   // Transmit the command
   if (cmd_tx_length > 0)
   {
       cmd_tx_transmit();
   }
}

В Timer ISR я просто проверяю, установлен ли cmd_transmit_manually. Если это так, я очищаю cmd_transmit_manually и помещаю пакет непосредственно в массив cmd_tx[] и устанавливаю cmd_rx_ack = '!'. Затем основной цикл передаст этот пакет при возвращении ISR таймера.

Если cmd_transmit_manually равно 0, вместо этого пакет помещается в очередь.

Через некоторое время обработчик UART получит ACK. Если очередь не пуста, следующая команда будет помещена в массив cmd_tx[] для передачи в основной цикл. Если он пуст, cmd_transmit_manually устанавливается равным 1. Никакой пакет не может быть помещен в очередь, пока выполняется эта проверка, поскольку обработчик UART имеет более высокий приоритет, чем обработчик таймера.

Я тестировал этот код много раз с разными периодами времени для измерения. Я еще не видел, чтобы это потерпело неудачу. Я также проверил, что в передаче нет отсутствующих пакетов.

Следующим шагом может быть создание функции cmd_tx_push_cmd(), которая автоматически передает вручную, если установлена ​​cmd_transmit_manually, а если нет, помещает пакет в очередь. Таким образом, можно упростить Timer ISR и немного подчистить код.

Если вы считаете, что это не лучшее решение, или я упустил из виду что-то важное, сообщите мне об этом. На данный момент, похоже, работает как надо - и, я думаю, довольно простое решение :-)