AVR: обмен данными по uart c использованием прерываний и fifo

У функций для  приёма/передачи данных по uart/usart в avr atmega, описанных в предыдущей заметке есть существенный недостаток, они работают по опросу статусного бита, а это значит, что программа микроконтроллера часто будет крутиться в цикле опроса, вместо того чтобы выполнять какие-нибудь другие полезные действия.

Что бы освободить программу контроллера от этих рутинных действий будем использовать циклический буфер и прерывания. Для реализации циклического буфера возьмем код из заметки простое FIFO.


В контроллере uart/usart avr atmega реализованы три вектора прерывания:

  • по окончанию передачи байта
  • по окончанию приёма байта
  • по освобождению регистра данных передатчика

Будем использовать последние два. По прерыванию приёма будем складывать принятый байт в один циклический буфер (rx_fifo), а по освобождению регистра данных передатчика передавать байт, извлеченный из другого циклического буфера (tx_fifo). Основная программа будет считывать данные из rx_fifo а записывать в tx_fifo.

При настройке uart/usart в avr atmega разрешаем прерывание по приёму байта:

  //разрешить прием, передачу данных и прерывание по приёму байта
  UCSRB = ( 1 << TXEN ) | ( 1 << RXEN ) | (1 << RXCIE );

 

Пример обработчика прерывания по приёму байта в uart/usart avr atmega:

ISR( USART_RXC_vect )
{
  unsigned char rxbyte = UDR;
  if( !FIFO_IS_FULL( uart_rx_fifo ) ) {
    FIFO_PUSH( uart_rx_fifo, rxbyte );
  }
}

 

Пример обработчика прерывания по освобождению регистра данных передатчика в uart/usart avr atmega:

ISR( USART_UDRE_vect )
{
  if( FIFO_IS_EMPTY( uart_tx_fifo ) ) {
    //если данных в fifo больше нет то запрещаем это прерывание
    UCSRB &= ~( 1 << UDRIE );
  }
  else {
    //иначе передаем следующий байт
    char txbyte = FIFO_FRONT( uart_tx_fifo );
    FIFO_POP( uart_tx_fifo );
    UDR = txbyte;
  }
}

Функции записывающие и считывающие данные в fifo, оформим так, что бы их можно было использовать для создания стандартного потока данных. Это позволит использовать стандартные функции, такие как putchar, getchar, gets, puts, printf, scanf. Так как c fifo мы работаем и в прерываниях, что бы не было сбоев, будем запрещать прерывания при записи и извлечении данных из циклических буферов.

int uart_putc(  char c, FILE *file )
{
  int ret;
  cli(); //запрещаем прерывания
  if( !FIFO_IS_FULL( uart_tx_fifo ) ) {
    //если в буфере есть место, то добавляем туда байт
    FIFO_PUSH( uart_tx_fifo, c );
    //и разрешаем прерывание по освобождению передатчика
    UCSRB |= ( 1 << UDRIE );
    ret = 0;
  }
  else {
    ret = -1; //буфер переполнен
  }
  sei(); //разрешаем прерывания
  return ret;
}
int uart_getc( FILE* file )
{
  int ret;
  cli(); //запрещаем прерывания
  if( !FIFO_IS_EMPTY( uart_rx_fifo ) ) {
    //если в буфере есть данные, то извлекаем их
    ret = FIFO_FRONT( uart_rx_fifo );
    FIFO_POP( uart_rx_fifo );
  }
  else {
    ret = _FDEV_EOF; //данных нет
  }
  sei(); //разрешаем прерывания
  return ret;
}

Ну и под конец пример программы:

FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, uart_getc, _FDEV_SETUP_RW);
 
int main( void )
{
  stdout = stdin = &uart_stream;
  uart_init();
  sei();
  puts( "Hello world\r\n" );
  while( 1 ) {
    int c = getchar();
    if( c != EOF ) {
      putchar( c );
    }
  }
  return 0;
}

Исходники примера работы с uart для avr-gcc можно скачать тут

Запись опубликована в рубрике Микроконтроллеры avr с метками , , , . Добавьте в закладки постоянную ссылку.

12 комментариев: AVR: обмен данными по uart c использованием прерываний и fifo

  1. Артём Двинин говорит:

    Отправил вам письмо.
    Пишите, буду рад помочь :)

  2. валерий говорит:

    к сожалению на сайте нет ваших контактов, мне нужна консультация по програмированию atmega 1280 и возможно заказ программы,не могли бы вы связаться со мной или оставить свои координаты для связи

  3. Андрей говорит:

    Я немного изменил для себя функцию uart_getc. Теперь эта функция при отсутствии символов в буфере чтения uart_rx_fifo будет ждать, пока в буфере не появится хоть один байт. Однако микроконтроллер «зависает», как определил позже, не завершается цикл while( FIFO_IS_EMPTY( uart_rx_fifo ) ). текст модифицированной функции привожу ниже.

    int uart_getc_1( FILE* file )
    {
      int ret;
      while( FIFO_IS_EMPTY( uart_rx_fifo ) )
      {
      };
     
      cli(); //запрещаем прерывания
      if( !FIFO_IS_EMPTY( uart_rx_fifo ) ) {
        //если в буфере есть данные, то извлекаем их
        ret = FIFO_FRONT( uart_rx_fifo );
        FIFO_POP( uart_rx_fifo );
      }
      else {
        ret = _FDEV_EOF; //данных нет
      }
      sei(); //разрешаем прерывания
      return ret;
    }

    Вы не могли бы подсказать, почему нет выхода из цикла при приеме байта?

  4. Артём Двинин говорит:

    Попробуйте изменить описание структуры FIFO

    #define FIFO( size )
      struct {
        unsigned char buf[size];
        volatile unsigned char tail;
        volatile unsigned char head;
      }

    Про volatile можно почитать тут

  5. Сергей говорит:

    Цитата: «Функции записывающие и считывающие данные в fifo, оформим так, что бы их можно было использовать для создания стандартного потока данных. Это позволит использовать стандартные функции, такие как putchar, getchar, gets, puts, printf, scanf.»

    Вопрос по работе функции gets(s); Как она должна работать в данном случае? Ведь по UART передаётся информация байтами. И не обязательно последним байтом будет код конца строки. Во многих случая будет \n или \r. Или функция gets(s); не работает? Тогда нужно писать свой обработчик? Кстати, функция puts(s); вроде как работает и передаёт строку по UART.

  6. Артём Двинин говорит:

    Как точно будет работать gets сказать не могу, надо смотреть как именно она реализована в avr-libc.
    Но по стандарту gets обрабатывает ‘\n’ и признак конца файла, при этом заменяет их на ‘\0′

    В примере признак конца файла ( _FDEV_EOF ) возвращается функцией uart_getc когда в буфере кончились данные FIFO_IS_EMPTY( uart_rx_fifo ),
    поэтому gets будет работать если строка отправлена без пауз между байтами (как например при ручном вводе), т.е. пока обрабатываем один принятый байт, должен уже прийти следующий.

    Но лучше написать свой обработчик, т.к. в gets нет проверки выхода за пределы массива.

    Пример обработчика можно посмотреть в заметке про подключение gps приёмника http://mainloop.ru/avr-atmega/avr-gps.html

    #define BUF_SIZE 128
    char buf[BUF_SIZE];
    unsigned char buf_cnt;
     
    int main( void  )
    {
     
      uart_init( 9600 ); //настраиваем uart на 9600
      sei();  
     
     
     
      buf_cnt = 0;
      while( 1) {
         int c;
         //получаем байт из 
         if( (c = getchar()) != EOF ) {
     
           if( (c == 'r') || (c == 'n') ) { 
              //если приняли признак конца строки	  
              buf[buf_cnt] = '';
    	  //
              //  тут добавить код, который должен выполняться когда строка принята
              // 
     
    	  }
              buf_cnt = 0;
           } 
           else if( buf_cnt < (BUF_SIZE-1) ) {
    	  //пока есть место в буфере помещаем туда принятые байты
              buf[buf_cnt] = c;
              buf_cnt++;
           }      
         }
      }
      return 0;
    }
  7. Сергей говорит:

    Большое спасибо за развёрнутый ответ

  8. dl говорит:

    Посоветуйте как корректно реализовать функцию приема строки байт переменной длины?
    Если скажем наш буфер равен значению 128 бит (16 байт), а ответ устройства на команды может приходить разной длины, например как 16, так 8 так и 4 байт соответственно, как тогда корректно заполнять и читать буфер?

    Пример (HEX-формат). Причем команды запросов и ответов, состоят из двух байт, префикс + параметр.

    Запрос: BA 22
    Ответ: BD 2F F3 45 FA 17 2F F3 45 FA 17 2F F3 45 FA 17
    Запрос: BC 17
    Ответ: АD 22 FE 44 BB 55 FE 17

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

    Обработку необходимо производить после получения всей строки ответа.

  9. dl говорит:

    Не совсем понятно, что есть конструкция:
    FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, uart_getc, _FDEV_SETUP_RW);

    Для чего вообще использовать STREAM в случае с UART?

  10. Артём Двинин говорит:

    dl,
    Данная строка указывает, что при чтении из потока uart_stream использовать функцию uart_getc, а для записи uart_putc.

    После чего можно выполнить форматированный вывод текста в uart при помощи fptintf( uart_stream, «bla-bla-bla %u», 42 );
    А если указать что stdout равен uart_stream, то можно использовать просто printf.

  11. Андрей говорит:

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

    	while(1)
    	{
    		c = getchar();
     
    		if (c=='s'){
    c=getchar();
    		if(c=='+')
    		{
    			puts_P( PSTR( "Turn supply ONn" ) );
    				PORTB=PORTB|1;
    			}
    			else if (c=='-')
    			{
    				//printf("Turn supply OFFn");
    				puts_P( PSTR( "Turn supply OFFn" ) );
    				PORTB&amp;=(~0b00000001);
    			}
    		}
    		else if (c=='p')
    		{
    			scanf("%d.%d ", &amp;b, &amp;p);
    // ... ... ...
    		}
    }
  12. Andrew Ternity говорит:

    Строка

    FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, uart_getc, _FDEV_SETUP_RW);

    теперь ошибочна и нужно писать

    FILE * uart_stream = fdevopen(uart_putc, uart_getc);

Добавить комментарий

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>