У меня есть ситуация, в которой микроконтроллер должен выполнять большое количество преобразований АЦП и форматировать результаты в команды (или пакеты данных) и отправлять их на ПК с помощью 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, но и в следующих ситуациях:
В первой ситуации пакет никогда не передается, так как после помещения пакета в очередь не получен ACK.
Во второй ситуации пакеты никогда не извлекаются из очереди по той же причине, что и в первой ситуации. (Предположим, что первый пакет получил подтверждение, и обработчик UART хочет передать следующий, но в этот момент очередь пуста). В этом случае очередь становится все больше и больше, а пакеты не передаются.
Я могу решить первую, передав первый пакет вручную (без использования очереди) и поставив оставшиеся в очередь. Хотя решение не очень красивое, но оно работает... Мне все еще нужно решить вторую проблему.
Итак, я думаю, что обработчик UART не подходит для извлечения из очереди, поскольку он не может учитывать две ситуации, которые я только что упомянул. Но я не могу поместить его в основной цикл, так как он имеет самый низкий приоритет, и тогда проблемы с параллелизмом становятся проблемой.
Что было бы хорошим способом реализовать это?
Заранее спасибо :-)
Кстати 1: ACK передается на микроконтроллер, если ПК принял переданный пакет. Я сделал специальную кодировку пакетов, чтобы можно было определить их длину. Это прекрасно работает, и cmd_tx_pop_cmd() извлечет только первый пакет, доступный в очереди. Я проверял все это много-много раз, и все работает отлично, поэтому, пожалуйста, не зацикливайтесь на этой части, так как проблема не в ней.
Кстати 2: Микроконтроллер Tiva C Series TM4C123GH6 находится на панели запуска Tiva C Series. Я использую gcc-arm-none-eabi.
Как правило, протокол передачи, управляемый прерыванием, всегда должен запускаться откуда-то вне прерывания.
Но действительно ли вам нужны прерывания? Это звучит как очень простое приложение: основной цикл проверяет несколько возможных ситуаций:
Часть передачи может быть очищена, если выражена в STD.
Немного подумав, я сам придумал решение. Идея состоит в том, что только обработчик 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 и немного подчистить код.
Если вы считаете, что это не лучшее решение, или я упустил из виду что-то важное, сообщите мне об этом. На данный момент, похоже, работает как надо - и, я думаю, довольно простое решение :-)
Джон
pvh1987