logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

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

Postman5 31 Sie 2010 12:57 4308 9
  • #1 8459768
    Postman5
    Poziom 11  
    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
  • #2 8459928
    __Maciek__
    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
  • #3 8460140
    Postman5
    Poziom 11  
    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:

    #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.
  • #4 8460463
    adamusx
    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)
  • #5 8460634
    Postman5
    Poziom 11  
    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.
  • #6 8460802
    __Maciek__
    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.
  • #7 8474754
    victoriii
    Poziom 19  
    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.
  • #8 8480705
    arturt134
    Poziom 27  
    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.
  • #9 8481736
    adamusx
    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 :)
  • #10 8482276
    victoriii
    Poziom 19  
    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.
REKLAMA