Serial теоретический вопрос

Тема в разделе "Проводная и беспроводная связь", создана пользователем issaom, 29 сен 2017.

  1. issaom

    issaom Гуру

    Код (C++):

    char test = 'A';

    //Обрабатываем прерывание по поступлению байта
    ISR(USART0_RX_vect)
    {
    //берем любую переменную и присваиваем ей значение
    char test = 'B';
    }
    Теперь бы еще понять как здесь сохранять переменные которые можно бы было читать в основном скетче.... ? После отправки данных значение test ,будет равно значению которое было присвоено при инициализации.... т.е. А
     
  2. rkit

    rkit Гуру

    Так вы объявили в прерывании новую локальную переменную с тем же именем.
    И не забываем, что утекающие за предел прерываний переменные следует объявлять как volatile.
     
    issaom нравится это.
  3. Limoney

    Limoney Гик

    Код (C++):
    volatile char inByte;

    inByte = UDR0;
     
     
  4. issaom

    issaom Гуру

    Вообще так тоже пробовал
    Код (C++):
    volatile char test = 'A';
    пока принимаю данные - в основном скетче "В" как только начинаю писать чего-нибудь в регистр UDR в основном скетче "А"
     
  5. issaom

    issaom Гуру

    Код (C++):
    #include <avr/interrupt.h> //библиотека прерываний
    #include <LiquidCrystal.h>
    LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
    volatile char test = 'A';

    char s[2];//Массив для приема байтов
    int i=0;//счетчик принятых байтов

    void USART_Init(int baudrate ) //Функция инициализации USART
    {
       /* Set baud rate */
       UBRR0H = baudrate>>8;
       UBRR0L = baudrate;
       //Разрешение на прием и на передачу через USART, прерывания по поступлению и по опустошению
       UCSR0B = (1<<RXCIE0)|(1<<TXCIE0)|(1<<RXEN0)|(1<<TXEN0);
       UCSR0C = (1<<UCSZ01)|(1<<UCSZ00); //размер слова 8 разрядов
       sei();
     
    } // USART_Init


    // the setup routine runs once when you press reset:
    void setup() {            
      // initialize the digital pin as an output.
      pinMode(13, OUTPUT);
      pinMode(18, OUTPUT);
      USART_Init(103);// число 103 соответствует baudrate 9600 при 16MHz. смотреть datasheet нa ATMega 2560
      lcd.begin(20, 4);      //инициализируем дисплей
    }


    void USART_Transmit( unsigned char data )//Функция отправки данных
    {
      /* Wait for empty transmit buffer */
      while ( !( UCSR0A & (1<<UDRE0)) );
      /* Put data into buffer, sends the data */
      UDR0 = data;
    }

    unsigned char USART_Receive( void )//Функция приема данных
    {
      /* Wait for data to be received */
      while ( !(UCSR0A & (1<<RXC0)) );
      /* Get and return received data from buffer */
      return UDR0;
    }

    //Обрабатываем прерывание по поступлению байта
    ISR(USART0_RX_vect)
    {
      s[i]=UDR0;//принимаем байт в массив char
      test = 'B';
      i++;
      if (i == 2)//если приняли 2 байта
       {
         if (s[0] == 'o' and s[1] == 'n')//проверяем что приняли, если команду "on"
          {
            digitalWrite(18, HIGH);//зажигаем светодиод
          /* отправляем обратно  в порт команду "on" (типо все ок команда принята) */
            USART_Transmit('o');
            delay(1);
            USART_Transmit('n');    
          }
       
         if (s[0] == 'o' and s[1] == 'f')//проверяем что приняли, если команду "of"
          {
            digitalWrite(18, LOW);//гасим светодиод
          /* отправляем обратно  в порт команду "of" (типо все ок команда принята) */
            USART_Transmit('o');
            delay(1);
            USART_Transmit('f');          
          }
         // сбрасываем все
         i=0;
         s[0]='0';
         s[1]='0';
       }
    }

    void loop() {
           lcd.clear();
           lcd.setCursor(0, 0);
           lcd.print(test);
           delay(5);          
    }
    вот это безобразие целиком
    при отправке первого символа 'o' - на экране "B"
    при отправке второго символа 'f" - на экране снова "А"
     
  6. rkit

    rkit Гуру

    Дуня крашится и перезапускается. Свести к минимальному примеру и перепроверить.
    i не volatile
    s не volatile
     
  7. issaom

    issaom Гуру

    Код (C++):
           lcd.clear();
           lcd.setCursor(0, 0);
           lcd.print(test);
           lcd.setCursor(0, 1);
           lcd.print(millis()/1000);
           delay(5);
    Добавил вот енто - пример действительно "плохо" рабочий millis сбрасывается - не понятно правда почему состояние 18 ножки сохраняется после перезагрузки....
     
  8. rkit

    rkit Гуру

    Потому вы его в сетапе не указываете. А перезагрузка по крешу сохраняет состояние регистров.
     
    issaom нравится это.
  9. issaom

    issaom Гуру

    а ларчик просто открывался - лишний бит был установлен на прерывание, а обработчика этого прерывания не было - из-за этого Arduino видимо и вешалась /перед этим изобразив видимость безглючной работы/ (автор поста скопипастил откуда то код - написал пример, а протестировать как следует поленился....

    Код (C++):
       //UCSR0B = (1<<RXCIE0)|(1<<TXCIE0)|(1<<RXEN0)|(1<<TXEN0); БЫЛО

       // UCSR0B.RXCIE0 - обработчик прерывания по наличию в буфере приема данных 1
       // UCSR0B.RXEN0 - разрешение работы приемника, сюда нужно записать 1
       // UCSR0B.TXEN0 - разрешение работы передатчика, сюда нужно записать 1
       // UCSR0B.TXCIE0 - разрешение прерывания при завершении передачи  (выкинуть)
     
       UCSR0B = (1 << RXCIE0)|(1 << RXEN0)|(1 << TXEN0);
    rkit большое спасибо за помощь )))
    Попробую сейчас рабочий пример написать
     
  10. issaom

    issaom Гуру

    Вот такая штука получилась ))) - отправляем через терминал любые 4 байта и наблюдаем их на экране + в ответ в терминал приходят значения которые можно установить в цикле loop() при этом код в loop даже не подозревает что кто-то там что-то подглядывает и меняет в его массивах по запросу )))
    (кстати как и в случае с аппаратным SPI все прекрасно работает без volatile)

    Код (C++):
    #include <avr/interrupt.h> //библиотека прерываний
    #include <LiquidCrystal.h>
    LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

    uint8_t IncomArr[4];   //Входящий массив
    uint8_t SendArr[4];    //Отправляемый массив
    uint8_t CountArr;   //Счетчик принятых байтов

    void setup() {
      // открываем последовательный порт 0, задаем скорость передачи данных 9600 бод
      UBRR0H = 0;
      UBRR0L = 103; // число 103 соответствует baudrate 9600 при 16MHz. смотреть datasheet нa ATMega 2560
      // UCSR0B.RXCIE0 - обработчик прерывания по наличию в буфере приема данных 1
      // UCSR0B.RXEN0 - разрешение работы приемника, сюда нужно записать 1
      // UCSR0B.TXEN0 - разрешение работы передатчика, сюда нужно записать 1
      UCSR0B = (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0);
      UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); //размер слова 8 разрядов
      sei();
      lcd.begin(20, 4);      //инициализируем дисплей
    }

    ISR(USART0_RX_vect) //Обрабатываем прерывание по поступлению байта
    {
      IncomArr[CountArr] = UDR0; //принимаем байт во "Входящий массив"
      CountArr++;
      if (CountArr == sizeof(IncomArr)) { // если приняли 4 байта
        CountArr = 0;
        int i;
        for (i = 0; i < sizeof(SendArr); i++) {
          while ( !( UCSR0A & (1 << UDRE0)) );       // Wait for empty transmit buffer
          UDR0 = (SendArr[i]);
        }
      }
    }

    // the loop routine runs over and over again forever:
    void loop() {
      SendArr[0] = 97; //присваиваем значения элементам массива который хотим отправить
      SendArr[1] = 98;
      SendArr[2] = 99;
      SendArr[3] = 100;
      lcd.setCursor(0, 0);    //печатаем на экране значения которые получили
      lcd.print(IncomArr[0]);
      lcd.setCursor(0, 1);
      lcd.print(IncomArr[1]);
      lcd.setCursor(0, 2);
      lcd.print(IncomArr[2]);
      lcd.setCursor(0, 3);
      lcd.print(IncomArr[3]);
    }
    Осталось как-то эту штуку научить по тайм-ауту отваливаться на случай потери связи, чтобы получить полноценный аналог функции Serial.readBytes()
     
  11. rkit

    rkit Гуру

    Это правило придумано не просто так. Даже если сработало сейчас, то нет никакой гарантии, что в неправильную фазу луны все не сломается.
     
  12. Unixon

    Unixon Оракул Модератор

    volatile же сделано чтобы переменная в памяти сидела, а не в регистре, и оптимизациями не сокращалась.
     
  13. issaom

    issaom Гуру

    ну... я так понял что если код скомпилирован, залит, тщательно оттестирован и работает как нужно - то фазы луны уже не повлияют на конечную работоспособность изделия....
     
  14. rkit

    rkit Гуру

    А потом понадобилось изменить одну строчку, собрать на другой версии компилятора - и опять тестируй. Зато не надо писать целых восемь лишних букв и пробел.
     
    b707 нравится это.
  15. b707

    b707 Гуру

    issaom - я бы поставил volatile
    Озвучте хоть одну причину, почему этого не нужно делать?
    Ваши отговорки в стиле "я несколько раз переходил реку по тонкому льду и все нормально"
     
  16. issaom

    issaom Гуру

    Уже поставил volatile! Расскажите лучше какой механизм можно использовать чтобы отмерить 0.5 секунды от последней посылки байта и известить об этом loop() /например обнулив данные входящего массива/
     
  17. Unixon

    Unixon Оракул Модератор

    0.5сек с какой точность?
    Самое простое - опрос таймера в loop(), наихудшая точность - время выполнения loop().
    Если нужно точнее, встраиваете обратный отсчет в обработчик прерывания таймера.
     
  18. issaom

    issaom Гуру

    Пока есть идея использовать таймер 1 с одноименной библиотекой. Выставить его на 0.6 секунды. Пришло 4 байта таймер сбросился и снова отсчитывает 0.6 если проходит больше 0.6 - прерывание по таймеру поднимает какую-то булеву переменную которую потом и обнаружит loop и покажет на экране....
     
  19. issaom

    issaom Гуру

    Запилил таймер на "Timer/Counter5" для Arduino Mega - вроде даже работает как нужно - осталось объединить 2 программы для получения аналога Serial.readBytes() /правда в отличии от оригинальной функции она не будет подвешивать плату в ожидании поступления всего пакета. Пока очередной пример блика на этот раз на таймере ))) может кому пригодится....
    Код (C++):
    #include <LiquidCrystal.h> //библиотека дисплея
    LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
    volatile int sekund = 0;

    void setup()
    {
      pinMode(13, OUTPUT);

      // (1/16000000)/256 = 62500 (прескалер настроен на коэффициент деления 256)
      // 1mls = 62.5 тика

      // initialize timer5
      noInterrupts();           // disable all interrupts
      TCCR5A = 0;               // сброс битов WGM51, WGM50
      TCCR5B = 0;
      TCNT5  = 0;               // сброс регистр счета тиков
      OCR5A = 62500;            // на этом значении счетчика он будет автоматически очищаться (CTC)
      TCCR5B |= (1 << WGM52);   // CTC mode
      TCCR5B |= (1 << CS52);    // 256 prescaler
      TIMSK5 |= (1 << OCIE5A);  // Разрешить прерывание для TIMER5_COMPA_vect
      interrupts();               // enable all interrupts
      lcd.begin(20, 4);           // инициализируем дисплей

    }

    ISR(TIMER5_COMPA_vect)          // timer compare interrupt service routine
    {
      //мигаем светодиодом на PIN 13 1 раз в 2 секунды
      if (PORTB & (1 << PB7))
      {
        PORTB &= ~(1 << PB7);
      }
      else
      {
        PORTB |= (1 << PB7);
      }
      sekund ++; //увеличиваем счетчик секунд
    }

    void loop()
    {
      lcd.setCursor(0, 0);             //печатаем на экране значения которые получили
      lcd.print(sekund, DEC);          //через прерывание по таймеру
      lcd.setCursor(0, 1);             //печатаем на экране значения которые получили
      lcd.print(millis() / 1000, DEC); //через mills
      delay(5);
    }
     
  20. Arduiner001

    Arduiner001 Гик

    Предложу свой ЖУТКИЙ костыль
    Можно настроить таймер (например, первый) на прерывание при достижении какого-то значения. Вот пример:
    Код (C++):

      TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
      TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
      TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
      OCR1A = F_CPU / 2 / 123; //123 - количество вызовов прерывания в секунду. Стоит отметить, почему я делю F_CPU на два: потому что в предыдущих строчках таймер настроен на работу от внутреннего генератора на частоте ~8МГц, что в два раза меньше внешней частоты - 16 МГц
      TIMSK1 |= _BV(OCIE1A);
      sei();
    А затем написать обработчик прерывания:
    Код (C++):
    ISR(TIMER1_COMPA_vect) {
      if(Serial.available()){
        //Делаем что-то
      }
    }