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

FreeMODBUS na Attiny2313. Czy próbował ktoś uruchamiać?

31 Sie 2010 12:57 4020 9
  • Poziom 10  
    Witam

    Czy ktokolwiek próbował wykorzystać bibliotekę FreeMODBUS dostępną na http://freemodbus.berlios.de/ na procesorze ATTiny2313?
    I czy mógłby się podzielić informacjami o wynikach takiech eksperymentów?

    Pozdrawiam
  • SterControlSterControl
  • Poziom 20  
    Raczej marnie IMHO to wygląda ....

    Popatrz w wymagania dotyczące ilości pamięci Link.

    Teoretycznie bardzo uproszczoną obsługę modbusa da się na tym procku zrobić ... gdzieś widziałem komercyjne rozwiązanie dla bascoma, ale pojawia się pytanie w jakim celu, bo trzeba się jeszcze męczyć z upchaniem jakiejś funkcjonalności w i tak już bardzo zapchanej pamięci.

    IMHO lepiej już wziąć jakąś atmegę8.

    Jeszcze jedno .... najnowsza wersja dostępna jest pod adresem www.freemodbus.org
  • SterControlSterControl
  • Poziom 10  
    Tak myślałem. Do tej pory próbowałem sam napisać obsługę opartą na przerwaniach od UATR'u i liczniku, ale dojechałem do chyba martwego punktu - odpytywanie pojedyńczego modułu działa bez problemu, natomiast problem jest z kilkoma modułami, tzn. jak odpytuję kilka modułów odpowiada tylko jeden...
    Algorytm po krótce wyglądał tak, może ktoś zobaczy błąd w moim rozumowaniu:

    Code:
    #define F_CPU 11059000UL      // czestotliwosc taktowania CPU
    

    #define InterfaceDirection   2   // kierunek konwersji MAX485 (1 - transmit, 0 - receive)

    //ModBUS
    #define ErrorCommand      0x01
    #define ErrorRange         0x02
    #define ErrorParameters      0x03
    #define SendRegisters      0x04
    #define SendAck            0x10

    #define ModBUS_Timer_Bit_Distanse 180

    /******************** includes **************************/

    #include <avr/io.h>
    #include <util/delay.h>
    #include <avr/interrupt.h>

    /******************** variables *************************/

    volatile unsigned char ModBUS_Table[24];

    volatile union
    {
       unsigned int CRC16;
       unsigned char CRC8[2];
    } ModBUS_CRC;

    const unsigned char ModBUS_OwnAdress = 101;
    volatile unsigned char ModBUS_Timeout;
    volatile unsigned char ModBUS_Buf[11];
    volatile unsigned char ModBUS_Buf_Pointer = 0;
    volatile unsigned char ModBUS_ResponseStatus = 0;
    volatile unsigned char ModBUS_SendStatus = 0;

    /************ functions declaration *********************/

    void ModBUS_CRC16_Update(unsigned char Byte);


    /****************** main function ***********************/
    int main(void)
    {

    //Port_Init

       DDRD = 0b01111110;   // PD0 <- RxD
                      // PD1 -> TxD
                      // PD2 -> InterfaceDirection
       PORTD = 0b11111000;

    //USART_Init

       UBRRH = 0x00;                              //baud rate = 9600bps
       UBRRL = 71;

       UCSRB = (1<<RXEN | 1<<TXEN | 1<<RXCIE | 1<<TXCIE);    // nadawanie i odbieranie wlaczone
                                              // wlaczone przerwanie po odebraniu ramki
       UCSRC = (1<<UCSZ1 | 1<<UCSZ0);         // 8 n 1
       
    // Timer0_Init

       TCCR0B = (1<<CS02);         // F_CPU/256 ~0,02ms
       TIMSK|= (1<<TOIE0);         // przerwanie po przepełnieniu
       TCNT0 = ModBUS_Timer_Bit_Distanse;

       for (unsigned char i=0; i<24; i++)
       {
          ModBUS_Table[i]=0;            
       }   

    // Interrupt_Init

       sei();   


    //main loop

       while(1)
       {
       }
    }

    //----------------------------------------------------
    ISR(USART_RX_vect)
    {
       TCNT0 = ModBUS_Timer_Bit_Distanse;               // resetuje licznik odstępu między znakami

       if(ModBUS_Buf_Pointer < 9)                     // komenda adresowana do urzadzenia max 8 bajtów (komentda 0x04)
       {                                       // warunek zapobiega przepełnieniu się bufora
          ModBUS_Buf[ModBUS_Buf_Pointer] = UDR;         // jeśli w buforze znajdują się więcej
                                              // niż dwa znaki zaczynam liczyć CRC16
          if(ModBUS_Buf_Pointer > 1)                           // w ten sposób po odebraniu ramki wyliczona suma kontrolna nie zawiera
             ModBUS_CRC16_Update(ModBUS_Buf[ModBUS_Buf_Pointer - 2]);// bajtów przesłanej sumy kontrolnej
                                                       // zerowanie licznika CRC16 następuje przy przerwaniu od licznika czasu odstępu między znakami
          ModBUS_Buf_Pointer++;      
       }
    }

    //----------------------------------------------------
    ISR(TIMER0_OVF_vect)
    {                                       
       if ((ModBUS_Buf_Pointer != 0) && (ModBUS_CRC.CRC8[0] == ModBUS_Buf[ModBUS_Buf_Pointer - 2]) && (ModBUS_CRC.CRC8[1] == ModBUS_Buf[ModBUS_Buf_Pointer - 1]))
       {   
          if (ModBUS_Buf[0] == ModBUS_OwnAdress)
          {
             TIMSK&=~(1<<TOIE0);                     // blokuje przerwania timera
          
             if (ModBUS_Buf[1] == 0x04)               // odczytaj rejestry
             {
                if((ModBUS_Buf[3] > 11) || (ModBUS_Buf[2] != 0) || ((ModBUS_Buf[3] + ModBUS_Buf[5]) > 12))   
                {                                          
                   ModBUS_ResponseStatus = ErrorRange;                  // rejestr/rejestry do odczytania poza przestrzenią rejestrów urządzenia (błąd 0x02)
                }
                else if ((ModBUS_Buf[4] == 0 ) && (ModBUS_Buf[5] == 0))      // ilość rejestrów do odczytu = 0 (błąd 0x03)
                {
                   ModBUS_ResponseStatus = ErrorParameters;
                }
                else
                {
                   ModBUS_ResponseStatus = SendRegisters;
                }         
             }
             else
             {
                ModBUS_ResponseStatus = ErrorCommand;   
             }

             PORTD|= (1<<InterfaceDirection);      // przy komunikacji RS485 trzeba ręcznie zmieniać kierunek
                                           // komunikacji, zmieniam na nadawanie
             UDR = ModBUS_OwnAdress;               // wysyłam pierwszy bajt odpowiedzi - własny adres
             ModBUS_CRC.CRC16 = 0xffff;            // reset CRC
             ModBUS_CRC16_Update(ModBUS_OwnAdress);   // zaczynam liczyć CRC
          }   
          else
          {
             ModBUS_CRC.CRC16 = 0xffff;
             ModBUS_Buf_Pointer = 0;
          }
       }
       else
       {
          ModBUS_CRC.CRC16 = 0xffff;
          ModBUS_Buf_Pointer = 0;
       }   
    }

    //----------------------------------------------------
    ISR(USART_TX_vect)
    {
       switch (ModBUS_SendStatus)               // ModBUS_Send_Status - określa mi miejsce dalszego wykonywania
       {                                 // procedury odpowiedzi po kolejnym przerwaniu Tx Complete
          case 0:                                    
          {
             if(ModBUS_ResponseStatus > 0x03)
             {
                UDR = ModBUS_ResponseStatus;            
                ModBUS_CRC16_Update(ModBUS_ResponseStatus);
             }
             else                                 // przy wystąpieniu błędu OR z 0x80
             {
                UDR = ModBUS_ResponseStatus | 0x80;      
                ModBUS_CRC16_Update(ModBUS_ResponseStatus | 0x80);
             }
             ModBUS_SendStatus++;                     // przechodze przy następnym przerwaniu do następnego case'a
          }
          break;

          case 1:                                    
          {
             if(ModBUS_ResponseStatus == SendRegisters)      // wysyłam liczbę bajtów odpowiedzi
             {
                ModBUS_Buf[5] = ModBUS_Buf[5]<<1;         // jest 2x liczba rejestrów do odczytu
                UDR = ModBUS_Buf[5];                  // rejestry 16 bitowe, stąd liczba bajtów do wysłania
                ModBUS_CRC16_Update(ModBUS_Buf[5]);         // dodatkowo wykorzystuję ModBUS_Buf[5] do zliczania wysłanych bajtów
                ModBUS_Buf[3] = ModBUS_Buf[3]<<1;         // wskazuję pierwszy do odczytu rzeczywisty adres tabliecy ModBUS_Table
                ModBUS_Buf[5] = ModBUS_Buf[5] + ModBUS_Buf[3];   // określam ostatni adres do odczytu
                ModBUS_SendStatus++;                  // po przerwaniu przechodzę do następnego etapu
             }
             else                                 // przy wystąpieniu błędu zamiast liczby bajtów wysyłam
             {                                    // kod błędu i przechodzę do wyłania CRC
                UDR = ModBUS_ResponseStatus;         
                ModBUS_CRC16_Update(ModBUS_ResponseStatus);
                ModBUS_SendStatus = 3;
             }
          }
          break;

          case 2:                                    // wysyłam zawartość rejestrów      
          {   
             UDR = ModBUS_Table[ModBUS_Buf[3]];            // umieszczam bajt tablicy w buforze
             ModBUS_CRC16_Update(ModBUS_Table[ModBUS_Buf[3]]);
             ModBUS_Buf[3]++;                        // zwiększam wskaźnik następnego bajtu do wysłania
             if(ModBUS_Buf[3] == ModBUS_Buf[5])            // jeśli wskaźnik będzie równy ostatniej pozycji do odczytu
             {                                    // przechodze do wysyłania CRC
                ModBUS_SendStatus = 3;
             }
          }   
          break;
       
          case 3:                              // wysyłam młodszą część CRC
          {
             UDR = ModBUS_CRC.CRC8[0];      
             ModBUS_SendStatus++;
          }
          break;

          case 4:                           // wysyłam starszą część CRC
          {
             UDR = ModBUS_CRC.CRC8[1];         
             ModBUS_SendStatus++;
          }
          break;

          default:                           // zeruje wartości i zakańczam transakcję
          {
             PORTD&=~(1<<InterfaceDirection);   // przełączam układ MAX485 na odbieranie
             
             TCNT0 = ModBUS_Timer_Bit_Distanse;   // zeruję licznik odstępu mięsdzy znakami
             ModBUS_SendStatus = 0;            // zeruję status odpowiedzi urządzenia

             for(unsigned char i=0; i<ModBUS_Buf_Pointer; i++)
             {
                ModBUS_Buf[i] = 0;
             }

             ModBUS_Buf_Pointer = 0;            // zeruję wskaźnik bufora
             TIMSK|= (1<<TOIE0);               // odblokowuję przerwania licznika Timera
          }
          break;

       }// end switch
    }


    // ---------------------------------------------------------------------
    void ModBUS_CRC16_Update(unsigned char Byte)
    {
       ModBUS_CRC.CRC16^= Byte;         

       for (char Bit=0;Bit!=8;Bit++)                     
       {
          if (ModBUS_CRC.CRC16 & 0x0001)
          {
             ModBUS_CRC.CRC16 = ModBUS_CRC.CRC16>>1;
             ModBUS_CRC.CRC16^= 0xa001;
          }
          else
             ModBUS_CRC.CRC16 = ModBUS_CRC.CRC16>>1;
       }
    }


    Moduły testuje pod sterownikiem Backhoffa.
  • Poziom 27  
    Witam.

    Nie wnikałem za bardzo w kod ale.. zacznij od uproszczenia funkcji odpytywania -tj wysyłania ramek przez UART. Zrezygnuj na początku z przerwań i po prostu wykorzystując pooling wysyłaj w pętli ramki. (Jak dojdziesz co jest nie tak, wtedy zaimplementuj przerwania:)

    Np by odczytać wartość rejestru spod adresu 100 wysyłasz:


    Bajt1 (Adres modbus) - AdresUrzadzenia
    Bajt2 (Funkcja odczytu pojedynczego rejestru) - 0x04
    Bajt3 (Adres rejestru Hi) - 0x00
    Bajt4 (Adres rejestru Lo) - 0x64
    Bajt5 (CRC Hi) - CRCHI
    Bajt6 (CRC Lo) - CRCLO

    i zmieniając zmienną AdresUrzadzenia wysyłasz te komendy do innych urzadzen.

    Pamietaj, by po wyslaniu ramki przez master odczekac pewien czas, tzw timeout. W przeciwnym wypadku gdy pierwsze urządzenie slave będzie odpowiadać, a Ty będziesz chciał wysyłać kolejną ramkę dojdzie do kolizji na magistrali RS485(RS485 pracuje w trybie Half-Duplex)

    Jeszcze jedna ważna sprawa, gdy wyślesz ostatni bajt ramki nie możesz odrazu przełaczać się w tryb odbioru danych, gdyż AVR ma 1-bajtowy rejestr przesuwny na wychodzace dane i nalezy sprawdzać czy jest on pusty (flaga transmit Complete - TXC)
  • Poziom 10  
    Jeśli chodzi o funkcję odpytującą jest one realizowana w sterowniku. Używam do tego bloczka dostarczonego przez Beckhoff'a. Ja buduję natomiast slave'a, co powinienem chyba dodać na samym początku. Ramkę z rozkazem zatem generuje mi sterownik. Mam jednocześnie podgląd co jest wysyłane i co jest odbierane przez mastera, przez menagera TwinCAT'a. Bloczek zawiera w sobie mechanizm timeout'u. I jestem pewny że od strony sterownika jest wszystko ok, ponieważ działają na nim poprawnie już instalacje z wieloma slave'ami.
    Poza tym widzę że nie wnikałeś w kod :) ponieważ zmiana kierunku linii następuję dopiero w przerwaniu po opróżnieniu z bufora ostatniego bajtu.
    Próbowałem nie siekać odpowiadania i procedurę odpowiedzi zawrzeć w jednym ciągu (chociaż szkoda mi czasu procesora) i nie zmieniło to nic, przy czym jeśli już urządzenie odpowiada to nie ma z tym problemu.

    Ale dzięki za uwagi.
  • Poziom 20  
    Ja bym poszedł raczej w kierunku zgodnym z tym co napisał adamusx.

    W pętli głównej programu zrób procedurę która obsługuje interpretację ramki i tworzenie odpowiedzi ( ramka i odpowiedź w buforze. ) w przerwaniach tylko i wyłącznie pluj danymi z bufora i zbieraj dane do bufora. Dobrym nawykiem jest pisanie przerwań najkrótszych jak to tylko możliwe.

    Jeśli chcesz to poszukaj po moich postach .. znajdziesz projekt termohigrometru na HST11 - implementacja freemodbus na atmega8 lub 88.

    Serio uważam że attiny to pomyłka w takiej aplikacji, ale jeśli chcesz to podeśle Ci kawałek kodu częściowa implementacja rozwiązania o którym napisałem .. z tym że nie był to modbus, a wersja lite protokołu siemensa USS.
  • Poziom 16  
    Nie ma opcji, żeby odpalić FreeModbus na 2313, za mało pamięci RAM i FLASH. Ramka w trybie RTU może mieć do 256B i minimum tyle potrzeba na bufor. Wersja full skompilowana na mega8 zajmuje 5.7kB i 401B RAM, a minimalna (read input registers), RTU odpowiednio 3.3kB/355B RAM.
  • Poziom 26  
    Freemodbus jest uniwersalną biblioteką, którą można "portować" chyba na wszystkie procesory i to zarówno przez "serial line" jak i TCP/IP. W związku z tym zużycie RAM-u jest bardzo duże, bo przechowywane są w nim wskaźniki na funkcje wykonujące określone rzeczy. W implementacji specjalnej (uproszczonej) na jeden procesor w trybie "serial line" można odwołania poprzez wskaźniki na funkcje (przechowywane w RAM-ie) zastąpić odwołaniami poprzez nazwy funkcji (wtedy większość RAM-u się zwolni).
    A tak w ogóle, to jak zobaczyłem tą bibliotekę, to stwierdziłem, że jestem w stanie bez problemu napisać może mniej uniwersalną wersję, ale zdecydowanie bardziej wydajną i wymagającą mniej zasobów. Tak to już zresztą jest, że jak coś jest do wszystkiego, to najczęściej jest do niczego.
  • Poziom 27  
    @arturt134 zgadzam się z Tobą. Też kiedyś chciałem użyc tej biblioteki, ale stwierdziłem, że szybciej i łatwiej będzie napisać coś swojego. W końcu Modbus nie jest taki skomplikowany.. ale patrząc na tą bibliotekę może mieć wręcz odwrotne wrażenie. Stworzona przeze mnie biblioteka do tej pory pracuje w kilku przemysłowych urządzeniach i bez problemu komunikuje się z panelami HMI :)
  • Poziom 16  
    adamusx: Ja też jakiś czas temu stworzyłem własną implementację Modbusa slave i na AVR wyszło mi niecałe 1.5kB FLASH, a RAMu tyle co na bufor.