Как прочитать все данные, отправленные в USART с прерыванием?

В настоящее время я могу читать байт за байтом из USART с помощью этого кода

ISR(USART_RX_vect)
{
    cli();
    while(!(UCSR0A&(1<<RXC0))){};
    // clear the USART interrupt  
    received = UDR0;

    if(pinState == 0)
    {
        OCR2A = received;
        pinState = 1;
    }
    else if(pinState == 1)
    {
        OCR2B = received;
        pinState = 2;
    }
    else
    {
        OCR0A = received;
        pinState = 0;
    } 
    sei();
}

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

Чтобы было понятнее, я кратко объясню, что это pinStateтакое. Я отправляю 3 байта данных, и я хочу, чтобы 1-й байт передавался 2-му pin 3 pwmбайту pin 11 pwmи 3-му байту в pin6 pwm. Как вы видите, в каждом прерывании я получаю не 3 байта, а 1 байт. Так что эта вещь pinState предназначена только для этой цели.

Ответы (3)

Вы не упомянули, какой микроконтроллер вы используете, но это, вероятно, не имеет значения. Периферийные устройства USART обычно работают именно так, как вы обнаружили: по одному байту за раз. Однако это не является ограничением.

Основываясь на фрагменте кода, который вы разместили в своем вопросе, вы пытаетесь выполнить некоторые функции с каждым полученным байтом. Это ограничивает вас однобайтовыми операциями. Что, если вместо этого вы использовали ISR только для заполнения массива байтов? Затем, после поступления определенного количества байтов, только тогда вы считываете байты и интерпретируете их значение — желательно в основном цикле вашего кода, а не в ISR.

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

Кроме того, я не думаю, что вам нужен cli();ваш ISR, поскольку он автоматически выполняется MCU.
Я не пишу код для конкретного процессора. Таким образом, я хотел быть осторожным с различиями. Это все равно, что рассматривать, является ли int 32-битным или 16-битным в любом процессоре, так сказать.
@Zgrkpnr__ Обработчики прерываний — это область, в которой вы не можете писать переносимый код. Детали имеют значение, и явная очистка флага, который вы должны разрешить аппаратному обеспечению, может привести к диким неожиданным побочным эффектам. Аналогичным образом, если не сбросить флаг, который аппаратное обеспечение ожидает от вас, это также вызовет проблемы.
В любом случае это явно AVR. Этот код уже далек от портативности :)

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

Выходная сторона циклической очереди опрашивается на наличие доступного контента вашим основным кодом, который хочет использовать полученные данные. Когда вы начинаете работать с многобайтовыми потоками на последовательном интерфейсе, часто желательно создать «протокол», который позволит принимающей стороне отслеживать, с чего начинается каждый пакет данных. Есть много способов сделать это. Один из способов — выбрать уникальное значение байта для начала пакета. Другой способ — установить MSB первого байта пакета, а затем установить все остальные байты пакета с очищенным MSB.

** Некоторые комментарии о вашем ISR **

  1. Как правило, размещение циклов опроса внутри процедуры обслуживания прерываний является плохой практикой. Вы хотите, чтобы время выполнения ISR было как можно короче. Я не знаю, какова цель цикла опроса, но вам следует изучить возможность его удаления.

  2. Обычно нет необходимости отключать и снова включать прерывания внутри ISR. Большинство MCU автоматически отключают прерывания при входе в ISR, а затем восстанавливают предыдущее состояние при выполнении возврата из прерывания.

  3. Непонятно, что делает логика состояния вывода внутри ISR. Это совершенно не применимо к обслуживанию прерывания USART.

Обновил мой вопрос, добавив немного больше деталей.
Оба ответа привели меня к одному и тому же решению, которое оказалось полезным и решило мою проблему. Спасибо. Я проголосовал за ваш ответ, а также

Как раз сегодня я оказался в точно таком же положении и написал программу в соответствии с тем, что предлагает Майкл Карас в своем ответе, используя круговой буфер. Я использовал PIC18, поэтому некоторый код может не скомпилироваться, но он ясно показывает идею, и этот код должно быть легко портировать на AVR, ...

Я объявил некоторые глобальные переменные:

#define EUSART_BUFFER_SIZE 2048
char eusart_rx_buffer[EUSART_BUFFER_SIZE];   // the actual buffer, now 2048 bytes long
uint16_t eusart_rx_buffer_rd = 0;            // the current read position
uint16_t eusart_rx_buffer_wr = 0;            // the current write position

Предполагается stdint.h, что он включен для uint16_tтипа.

Идея такова:

  • В ISR, когда мы получаем байт, сохраняем его eusart_rx_buffer[eusart_rx_buffer_wr]и увеличиваем позицию записи.
  • Когда мы хотим прочитать данные, вы можете прочитать от eusart_rx_buffer_rdдо eusart_rx_buffer_wr.

Конечно, когда одновременно сохраняется более 2048 байт, буфер будет перезаписан, и вы потеряете данные. Однако есть некоторые приемы, которые вы можете использовать, чтобы обойти это. Вы можете изменить EUSART_BUFFER_SIZEв соответствии с вашими потребностями. Меньшее значение, конечно, требует меньше памяти для данных.

Теперь в моем ISR у меня есть:

if (PIR1bits.RCIF) {                                  // EUSART data received
    eusart_rx_buffer[eusart_rx_buffer_wr++] = RCREG;  // Store the received data
    if (eusart_rx_buffer_wr >= EUSART_BUFFER_SIZE)    // Increment write pointer
        eusart_rx_buffer_wr = 0;
    PIR1bits.RCIF = 0;                                // Clear interrupt flag
}

Конечно, на AVR этот код будет выглядеть немного иначе, но идея та же.

Затем, где вы хотите прочитать данные, вы можете сделать что-то вроде:

while (eusart_rx_buffer_rd != eusart_rx_buffer_wr) {   // While there's data in the buffer
    do_sth(eusart_rx_buffer[eusart_rx_buffer_rd++]);   // Do something with it
    if (eusart_rx_buffer_rd >= EUSART_BUFFER_SIZE)     // Increase read pointer
        eusart_rx_buffer_rd = 0;
}

Этот код был написан для PIC18 с использованием компилятора XC8, но большая часть его написана на стандартном C и может быть легко скопирована или перенесена.