Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[Mega32][Mega8][RS485] - kolizje - jakiś pomysł na rozwiązan

Citek 04 Mar 2009 22:04 2402 9
  • #1 04 Mar 2009 22:04
    Citek
    Poziom 22  

    Witam
    Borykam się z problemem komunikacji kilku procków po sieci RS485 (wykorzystałem układ SN75176. Wszystko jest ok, aż do czasu kiedy dwa procki ze chcą gadać. Wtedy kolejność ramki mi się rozsypuje.
    Ramkę mam stworzona w taki sposób:
    1 bajt - adres, 1 bajt - dane/rozkaz, 1 bajt suma kontrolna wyliczana z dodania poprzednich dwóch bajtów.
    Myślałem nawet wykorzystaniem jakoś timera, aby w razie błędu zaczynał zliczać ramkę od nowa. Ale jak to zapisać gdy korzystam z przerwań na Rx UARTa (program zatrzymuje się na np. raddr = UART_odbierz(); i czeka na znak, a gdy taki nie nadejdzie będzie czekał i czekał, jak temu zaradzić).
    Poniżej fragmenty kodów moich programów.
    Odbiornik:

    Code:
    #include <avr/io.h>                 // dostęp do rejestrów
    
    #include <inttypes.h>            //
    #include <util/delay.h>
    #include <avr/interrupt.h>      //obsługa przerwań
    #include <uart.h>
    #include "definicje.h"


    #define addr 41          // adres ustalony jest wg schematu, młodsze bity oznaczają adres, starsze rodzaj - dokładny opis w dokumentacji

    volatile int odbior=0;
    volatile uint8_t raddr, data, chk;

    void piny(void)
    {
       sbi(DDRC,0);
       sbi(PORTC,0);
       sbi(DDRC,1);
       sbi(PORTC,1);
       sbi(DDRD,4);
       cbi(PORTD,4);         //włączenie odbioru

       sbi(DDRC,3);
       cbi(PORTC,3);         //ustawienie przekaźnika w stanie wyłączony
    }

    SIGNAL(SIG_UART_RECV)
    {
       raddr = UART_odbierz();
       data = UART_odbierz();
       chk = UART_odbierz();

       if ((raddr==0x01)&&(data==open))
       {
       PORTC ^= (1<<LED2);//cbi(PORTC,LED2);
       odbior=1;//sbi(PORTC,RELAY);
       UART_pakiet(raddr,OK);
       }

    }

    int main(void)                  // program główny
    {
       UART_Init(2400);              // inicjalizacja portu szeregowego
       piny();
       sei();
       cbi(PORTC,LED1);

       while(1)                      // pętla nieskończona
       {




          _delay_ms(100);
          if (odbior==1)
          {
          PORTC ^= (1<<RELAY);
          odbior=0;
          }   
       }
    }


    Nadajnik:
    Code:
    #include <avr/io.h>                 // dostęp do rejestrów
    
    #include <inttypes.h>            //
    #include <util/delay.h>

    #include <uart.h>

    #include "definicje.h"


    #define addr 0x01      // adres ustalony jest wg schematu, młodsze bity oznaczają adres, starsze rodzaj - dokładny opis w dokumentacji



    int main(void)                  // program główny
    {
       UART_Init();              // inicjalizacja portu szeregowego

       DDRC = 0b11110111;         // ustawiamy prawie cały port C jako wyjścia
       PORTC = 0b11111100;         // ustalamy wartości początkowe pinów/podciągamy port
     
       sbi(DDRD,SN);            //port D4 jako wyjście
       cbi(PORTD,SN);            //stan niski (SN ustawiony na odbiór)
       unsigned int i = 0;
       while(1)                      // pętla nieskończona
       {
           while ((bit_is_clear(PINC, PIR))&& (i==0)) // Jeśli na wejściu 3 portu C (PC3) jest stan niski to,
          {
          //sbi(PORTD,SN);
          i = 1;
          sbi(PORTC,LED1);
          _delay_ms(100);
          UART_pakiet(addr,open);

          }

          if ((!bit_is_clear(PINC, PIR)&&(i==1)))
          {
          i = 0;
          //cbi(PORTC,LED1);
          }
       }
    }


    Biblioteka uart.c:
    Code:
    #include <avr/io.h> 
    
    #include <inttypes.h>
    #include <util/delay.h>
    #define cbi(sfr, b) (sfr &= ~(1<<b)) // bit 0 na pin portu
    #define sbi(sfr, b) (sfr |= (1<<b))  // bit 1 na pin portu

    #define GOOD 0xDD
    #define ERROR   0x21
    #define SN 4

    // Definicje funkcji odpowiedzialnych za komunikację po UART-cie

    void UART_Init(void)
    {
       /* Set baud rate */
       UBRRH = 0;
       UBRRL = 51;
       /* Enable receiver and transmitter */
       UCSRB = (1<<RXCIE)|(1<<TXEN)|(1<<RXEN);
          /* Set frame format: 8data, 1stop bit */
       UCSRC = (1<<URSEL)|(3<<UCSZ0);
    }

    // wysyła znak podany jako parametr na port szeregowy
    void UART_wyslij (unsigned char c)
    {
         /* Wait for empty transmit buffer */
       while ( !( UCSRA & _BV(UDRE)) )
       ;

       /* Put data into buffer, sends the data */
       UDR = c;
    }

    // odbiera znak z portu szeregowego i zwraca go jako wartość funkcji
    unsigned char UART_odbierz (void)
    {
         /* Wait for data to be received */
       while ( !(UCSRA & (1<<RXC)) )
       ;
       /* Get and return received data from buffer */
       return UDR;
    }

    void UART_pakiet(uint8_t addr, uint8_t data)
    {
       sbi(PORTD,SN);
       _delay_ms(1);
       //UART_putchar(SYNC); // Sync
       UART_wyslij(addr); // Receiver address
       UART_wyslij(data); // Button status
       UART_wyslij((addr+data)); // Checksum
       _delay_ms(1);
       cbi(PORTD,SN);
       //_delay_ms(50);
    }

    void UART_OK(uint8_t addr)
    {
       UART_pakiet(addr,GOOD);
    }

    void UART_ERROR(uint8_t addr)
    {
       UART_pakiet(addr,ERROR);
    }


    Dodano po 18 [minuty]:

    Myślałem też nad zastosowaniem Watchdoga, ale przy resecie poprzez ten układ wracałbym do początku programu, co nie wchodzi w grę ponieważ steruję zmiennymi a ich stanu nie zapisuję w pamięci EEPROM, więc ten sposób odpada

    0 9
  • #2 04 Mar 2009 23:02
    gmp
    Poziom 19  

    Citek napisał:
    Witam

    Ramkę mam stworzona w taki sposób:
    1 bajt - adres, 1 bajt - dane/rozkaz, 1 bajt suma kontrolna wyliczana z dodania poprzednich dwóch bajtów.
    Myślałem nawet wykorzystaniem jakoś timera, aby w razie błędu zaczynał zliczać ramkę od nowa. Ale jak to zapisać gdy korzystam z przerwań na Rx UARTa (program zatrzymuje się na np. raddr = UART_odbierz(); i czeka na znak, a gdy taki nie nadejdzie będzie czekał i czekał, jak temu zaradzić).


    Myślałem też nad zastosowaniem Watchdoga, ale przy resecie poprzez ten układ wracałbym do początku programu, co nie wchodzi w grę ponieważ steruję zmiennymi a ich stanu nie zapisuję w pamięci EEPROM, więc ten sposób odpada

    Analizowac kodu to mi sie nie chce,
    Ale co masz za problem z timerm i funkcja UART-Odbierz() ?
    w tej funkcja wlaczaj jaks flage, timer odliczy cza , przekaze ci to przez inna flage, a Ty w w tej petli sprawdzaj czy przyszedl znak i czy timer nie skonczyl odmierzac czasu.
    Gdy czas odmierzony, to jest Timeout error i robisz wtedy jakas obsluge bledow.

    0
  • #3 04 Mar 2009 23:51
    Citek
    Poziom 22  

    Chyba już wiem jak to zrobić, przecież to nie jest aż takie trudne. Za bardzo zasugerowałem się tym, że program się zatrzymuje, myślałem, że jest to spowodowane przez jakiś wewnętrzny proces w uP, a przecież to jest zwykła funkcja.
    Myślę, że teraz dam już sobie radę.
    Jak coś to będę pisał

    0
  • #4 05 Mar 2009 15:37
    Citek
    Poziom 22  

    Jednak coś nie mogę dać sobie rady, skonfigurowałem sobie timer w trybie CTC:

    Code:
       TCCR1B |= (1 << WGM12); // Ustawia timer w tryb CTC
    
       OCR1A = 125; // Ustawia wartość pożądaną na 500Hz dla preskalera 256
       TCCR1B |= ((1 << CS12));

    ustawiłem go na odliczanie 50 ms.
    Zastanawiam się jak teraz zapisać sprawdzanie czy jest odbierany znak i nie przekroczono czasu. Myślałem nad czym takim (pseudokod)
    Code:
    unsigned char UART_odbierz (void)
    
    {
       (  /* Wait for data to be received */
       while (!(UCSRA & (1<<RXC)) | (TIFR & (1 << OCF1A));
       if (TIFR & (1 << OCF1A))
       {
            czas=0;
       }
       else
       {
       return UDR;
       }
    }


    Niestety taki typ myślenia coś nie działa.
    Przy pisaniu tego kodu nasunęło mi się jeszcze jedno pytanie, jak z funkcji zwrócić dwie zmienne, czyli w moim przypadku chciałbym jeszcze zwrócić wartość zmiennej czas, potem aby w mojej funkcji UART_pakiet zapisać coś takiego:
    Code:
    void UART_pakiet(uint8_t addr, uint8_t data)
    
    {
       int czas=1;
       sbi(PORTD,SN);
       _delay_ms(1);
       //UART_putchar(SYNC); // Sync
       if (czas==1)
       {
       UART_wyslij(addr); // Receiver address
       }
       if (czas==1)
       {
       UART_wyslij(data); // Button status
       }
       if (czas==1)
       {
       UART_wyslij((addr+data)); // Checksum
       }
       _delay_ms(1);
       cbi(PORTD,SN);
       //_delay_ms(50);
    }

    Czekam na pomoc

    0
  • #6 05 Mar 2009 19:31
    Citek
    Poziom 22  

    Dzięki wszystkim za sugestie, na szczęście metodą prób i błędów oraz po ogromnej liczbie testów udało mi się rozwiązać mój problem.

    0
  • #7 05 Mar 2009 21:23
    bolek
    Specjalista - oświetlenie sceniczne

    To napisz jak to zrobiłeś. CAN "gada" na podobnej zasadzie co twoja. Tylko tam jest o tyle fajnie że procek ma sprzężenie z wysłanym sygnałem. Jeśli procek wystawi na linie 1, a odczyta z niej 0 to wypada z gry. Taka kolizja nie zakłóca przesyłanego sygnału

    0
  • #8 05 Mar 2009 22:01
    Citek
    Poziom 22  

    Rozwiązałem to w dosyć prosty sposób, wykorzystałem timery.
    Jak na razie kod działa, jeżeli wykryje błąd to po chwili się poprawia i dalej rozpoznaje poprawnie.
    Poniżej moje dzieło: :)

    Code:
    // odbiera znak z portu szeregowego i zwraca go jako wartość funkcji
    
    unsigned char UART_odbierz (void)
    {
       
       TCNT1 = 0;
       while (!(UCSRA & (1<<RXC)) && (TCNT1 <= 125));
       if (TCNT1 >= 125)
       {
       //LCD_WriteText("T");
       TCNT1=0;
       }
       else
       {
       return UDR;
       }
       //return 0;
    }

    0
  • #9 06 Mar 2009 12:14
    bartul_x
    Poziom 10  

    Witam
    Napisz cos wiecej ile sterownikow będzie na lini jak często sterowniki będą wysyłać telegram. Niestety przy MultiMaster na rs485 nie da się w stu procentach wyeliminować kolizji, może w twoim przypadku będzie lepiej zastosować układ przekazujący żeton albo dodatkowy sterownik sterujący przepływem danych.
    Za bardzo nie znam się na C wiec tutaj nie pomogę .
    Na pewno musisz w module nadawczym dołożyć wykrywanie zajętości lini poprzez testowanie koncówki RXD i dobrze by było końcówkę ”RE/” SN75176 podpiac do masy.
    W tej postaci programu będzie mnóstwo kolizji.

    0
  • #10 06 Mar 2009 23:43
    Citek
    Poziom 22  

    Jak na razie wszystko działa, a jest podłączone w sumie 5 urządzeń (1 serwer + 4 moduły). Od czasu do czasu zdarzają się błędy, na szczęście mogę je zignorować. Jedyne co planuję to dodanie jeszcze ramek potwierdzających otrzymanie poprawnej ramki.
    Miałoby to działać w taki sposób:
    1. Urządzenie A (nadawcze) wysyła ramkę do urządzenia B (odbiorcze).
    2. Urządzenie B odbiera ramkę, sprawdza sumę kontrolną i jeżeli wszystko jest ok, wysyła ramkę (adres A, kod OK)
    2a. Jeżeli ramka została źle odebrana nic się nie dzieje
    3. Urządzenia A czeka na ramkę kontrolną, jeżeli jej nie dostanie w x czasu to wysyła jeszcze raz ramkę.
    Jeżeli odbierze ramkę kontrolną to zakończa transmisję.

    Wadami tego systemu jest generowanie dodatkowego ruchu na RSie, ale myślę, że przy małej ilości urządzeń sieć będzie działała normalnie.
    No problemy powinny powstać chyba dopiero przy 20-50 urządzeniach.

    0