Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Komunikacja modbus w c++ rs232 -> rs485

12 May 2010 20:48 14919 46
  • Level 9  
    Witam !

    Muszę napisać program który będzie odczytywał parametr z licznika energii elektrycznej (LUMEL) za pomocą rs485 oraz protokołu MODBUS.

    Przewaliłem już całe forum jak i dużą część google ale nie znalazłem nic co mogło by rozwiązać mój problem.

    Komunikacja następuje poprzez konwerter usb -> rs232 -> rs485.

    Parametr jaki muszę odczytać kryje się pod rejestrem 4019, parametr MS word oraz mieści się w zakresie od 0 do 99999999.

    Nie maże nawet aby ktoś mógł podać mi kod na odczyt tego, ale proszę o ukierunkowanie jak Wy byście to zrobili. Znam podstawy c++ oraz kilku innych języków więc jakoś chyba dam rade :)

    Z góry dziękuje !
  • Level 19  
    Chyba chodzi ci o RS-485. Musisz znać protokół w jakim komunikuje się urządzenie. Po prostu musisz wysyłać co określony stały czas np. co 1 s zapytanie do urządzenia o odczyt rejestru 4019. Urządzenie powinno wysłać odpowiedź. Najlepiej wysyłać/odbierać dane w trybie ASCII. Musisz zapoznać się jak wygląda protokół modbus i po prostu wysyłać odpowiednią ramke zapytania w tym protokole.
  • Level 9  
    Oczywiście rs485, pomyłka - już poprawiłem.
    Powiedzmy, że mam już komunikacje po rs232, w jakiej firmie wysłać zapytanie ?
    Wiem, że najpierw ma być adres itd. lecz jak wygląda to w praktyce ?

    Z pewnego MODBUS Testera skopiowałem sobie o to taką informacje którą on próbował wysłać
    Code:
    [03][01][0F][B2][00][64][9F][30]
    czy właśnie to wysyłać do urządzenia ?
  • Helpful post
    Level 14  
    Quote:

    kolego usuń kropkę na końcu bo nie działa link

    zrobione

    Quote:

    [03][01][0F][B2][00][64][9F][30]

    Jak wysyłasz coś takiego od mastera to oznacza zapytanie do slave nr [03], funkcja [01] odczyt wyjść bitowych z watrość od [0F][B2](offset) = 4018 do [00][64](segment) = 100, ostatnie bity to suma kontrolna ;)

    Wniosek - do odczytu wartości 4019 skorzystaj z funkcji [04] - odczyt n rejestrów wejściowych bo wartość z zakresu 0 do 99999999 na pewno nie jest wejście bitowym.
  • Level 9  
    Dzięki chłopaki, dużo pomogliście :)

    Adres urządzenia to 3, czy 03 to to samo ? :)

    Ja potrzebuje odczytać tylko komórkę pamięci pod adresem 4019 czyli adres powinien wyglądać tak ? :

    Code:
    [03][04][00][12][00][12][25][ED]]


    I czy suma kontrolna może być zawsze taka sama ?
  • Level 19  
    tryb ASCII ramka wygląda tak:

    [znacznik początku(jeden znak = ':')] [adres(dwa znaki)] [funkcja(dwa znaki)] [dane(n znaków)] [suma kontrolna LRC(dwa znaki)] [znacznik końca(dwa znaki = CR + LF)]

    Code:
    char query[liczba_znakow] = { ':', '0', '1', .... nie wiem ..., 0x0D, 0x0A};


    takie query trzeba wysłać na COM'a do urządzenia

    z tym zapoznaj się http://www.eti.pg.gda.pl/katedry/kose/dydakty..._organizacja_systemow_elektronicznych/cw9.pdf

    Dodano po 2 [minuty]:

    Jak wysyłasz w ASCII to musisz wysłać tak dla urządzenia o numerze 3:

    Code:
    char query[liczba_znakow] = { ':', '0', '3', .... nie wiem ..., 0x0D, 0x0A};


    suma kontrolna jest różna w zalezności od zapytania ale oczywiście jak zapytanie jest cały czas to same to raz obliczona jest ta sama

    Dodano po 6 [minuty]:

    poczitamagoczi dopisz do mojego zapytania w C tak jak powinno być
  • Level 9  
    Mogę przełączyć licznik na ASCII wiec spróbuje. A liczbę (sumę?) kontrolną wylicza się z zapytania czy jest ona z góry ustalona ?
  • Level 19  
  • Level 14  
    No a w trybie RTU ja bym zrobił to tak: [03][04][0F][B2][00][64][suma][suma].
    Czyli zczytujesz z urządzenia o adresie 3 wartość z rejestrów od 4018 do 4118.
    W C w sumie tak samo się deklaruje tablice np: char query[liczba_znakow] = {'0', '3',, 0x0D, 0x0A};
  • Level 19  
    znalazłem coś takiego do obliczenia sumy kontrolnej ale najpierw musimy znać postać ramki dla tego konkretnego przykładu

    http://www.lammertbies.nl/comm/info/crc-calculation.html

    Dodano po 18 [minuty]:

    W ASCII pisze, że musi być znak początku ramki ':' oraz suma kontrolna LRC (nie CRC)

    Code:
    char query[17] = { ':', '0', '3', '0', '4', '0', 'F', 'B', '3', '0', '0', '0', '1', H_suma, L_suma, 0x0D, 0x0A};
  • Level 9  
    Ramke wysyłać przez rs z tymi nawiasami ? ;)

    Znalazłem coś takiego, zdaje się, że też między innymi do wyliczenia sumy kontrolnej, może zerkneli by koledzy okiem ? ;)
  • Level 9  
    Tak przewalam te pdf-y i wychodzi na to że przed wysłaniem ramki muszę rozpocząć transmisje, wiecie może jak to zrobić ? ;)

    Dzięki wielkie za pomoc ! :)
  • Helpful post
    Level 19  
    Sumę kontrolną liczy się b. prostym algorytmem nie ma tam jakiś tablic ze stałymi - to powinno być dosłownie kilka linijek kodu. Gdzieś mam program do oblicznia LRC dla PLC Fatek ale w ... LabVIEW i nie mam go zainstalowanego (kiedyś go napisałem bo mi był potrzebny)

    Dodano po 1 [minuty]:

    Z wysyłaniem nie ma żadnej filozofii - trzeba tylko ustawić parametry transmisji takie same w PC jak i w urządzeniu i wysłać znaki na COM
  • Level 9  
    A nie masz może jakiegoś programu co pracuje na modbus-ie przez rs-232 ? Jak bym zobaczył jakiś przykład to pewnie już bym wszystko załapał ;)
  • Level 19  
    Musisz normalnie otworzyć port COM (port szeregowy w domyslnie COM1) w PC z odpowiednio ustawionymi parametrami transmisji i wysłać ramkę z danymi (te dane co są powyżej w tablicy query tylko na razie jeszcze nie znam sumy kontrolnej)
  • Level 9  
    Doszukałem się pewnego kodu liczącego LCR, oto on:

    Code:
    /*Funkcja lrcgen wyliczająca bajt kontrolny LRC, parametry funkcji:*/
    
    /*fptr - wskaźnik do bufora wiadomości, nb - ilość bajtów w buforze*/
    char lrcgen (fptr,nb) /*funkcja zwraca jeden bajt*/
    unsigned char *fptr;
    unsigned nb;
    {
    unsigned char lrc, sum = 0;
    while (nb--)
    sum += *fptr++; /*sumowanie kolejnych bajtów bez przeniesień*/
    lrc = ~sum + 1; /*uzupełnienie do 2*/
    return (lrc); }


    Tylko nie wiem czy bufor wiadomości powinien zawierać znaki końca przy wyliczaniu. No i czy mam sam podać ilość bajtów w buforze.
  • Level 19  
    Pisze w dokumentacji pdf której link podałem

    Dodano po 37 [minuty]:

    Nie wiem czy dobra suma kontrolna:

    Code:
    char query[17] = { ':', '0', '3', '0', '4', '0', 'F', 'B', '3', '0', '0', '0', '1', '8', 'D', 0x0D, 0x0A};


    Dodano po 18 [minuty]:

    w sterowniku fatek też niby jest LRC ale jest liczona inaczej (bez uzupełnienia do 2)
  • Level 9  
    Wielkie dzięki za zaangażowanie ! :)
    Modziłem, modziłem i coś wymodziłem ;) Tylko jeszcze mi nie odpowiada, myślę że jakoś źle tablice podstawiam. W między czasie skopiowałem ramkę z testera wraz z sumą. Oto programik w którym gdzieś tkwi wciąż błąd:

    Code:
    #include <windows.h> 
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    int main()
    {
       HANDLE hCom;    //uchwyt portu
       DCB dcb;        //konfiguracja portu
       BOOL fSuccess;  //flaga pomocnicza
       DWORD RS_ile;   //ilosc bitow wyslanych
       int numer;
       
       //otwarcie portu COM1 z prawami RW
       
              printf ("Wpisz numer portu COM:");
    scanf("%s", numer);


       
       hCom = CreateFile( TEXT("COM4"), GENERIC_READ | GENERIC_WRITE,
            0,    // exclusive access
            NULL, // default security attributes
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
     
       if (hCom == INVALID_HANDLE_VALUE)
       {
            printf("Brak polaczenia z portem nr. %u !!   Blad: %d.\n", numer, GetLastError());
         system("PAUSE");
            return 1;
       }

       //pobranie aktualnych ustawien portu
        fSuccess = GetCommState(hCom, &dcb);


       //ustawienie naszej konfiguracji
       dcb.BaudRate = CBR_9600;     // predkosc transmisji
       dcb.ByteSize = 8;             // ilosc bitow danych
       dcb.Parity = NOPARITY;        // brak bitu parzystosci
       dcb.StopBits = TWOSTOPBITS;    // dwa bity stopu

       fSuccess = SetCommState(hCom, &dcb);

    char lrcgen (fptr,nb) /*funkcja zwraca jeden bajt*/
    unsigned char *fptr;
    unsigned nb;
    {
    unsigned char lrc, sum = 0;
    while (nb--)
    sum += *fptr++; /*sumowanie kolejnych bajtów bez przeniesień*/
    lrc = ~sum + 1; /*uzupełnienie do 2*/
    return (lrc); }   
       
    char query_buff[11] = {':', '0', '3', '0', '4', '0', 'F', 'B', '2', '0', '0'};
    char spr = lrcgen(query_buff,11);
    /*char rs_buff[14] = {':', '0', '3', '0', '4', '0', 'F', 'B', '2', '0', '0', spr, 0x0D, 0x0A};
    000012-Tx:3A 30 33 30 33 30 46 42 34 30 30 30 31 33 36 0D 0A -- wyciag z testera
    char rs_buff[17] = {':', '3A', '30', '33', '30', '33', '30', '46', '42', '34', '30', '30', '30', '31', '33', '36', '0D', '0A'};
    */
    char rs_buff[17] = { ':', '0', '5', '0', '4', '0', 'F', 'B', '3', '0', '0', '0', '1', '8', 'D', 0x0D, 0x0A};
    while (1 == 1){
        fSuccess = WriteFile(hCom, &rs_buff, 64, &RS_ile, 0);
        printf ("Wyslalem: %d \n", &rs_buff);

    time_t start;
    time_t current;

    time(&start);
    printf("wait 1s.\n");
    do{
    time(&current);
    }while(difftime(current,start) < 1.0);
    }
        if (!fSuccess)
        {
          printf ("Nie moge wyslac!! %d.\n", GetLastError());
          system("PAUSE");
          return 4;
        }
       system("PAUSE");
       return 0;
    }
  • Level 19  
    Jeżeli w takiej ramce zapytania jest jakiś błąd to z reguły urządzenia (przynajmniej sterowniki PLC) są tak skonstruowane, że nic nie wysyłają. Tyczy się to również sumy kontrolnej - jeżeli jest zła to sterownik też nic nie odpowie.

    Z tego co widzę zmieniłeś numer urządzenia na 5 tak więc suma kontrolna jest inna 8B a nie 8D.

    Widzę również, że masz inne parametry transmisji w trybie ASCII niż podane w dokumentacji o modbusie (patrz pkt. 2.6). Sprawdź to (działa bez problemu w C++ Borland):

    Code:
    lpFileName = "COM4";
    
    hCommDev = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hCommDev != INVALID_HANDLE_VALUE) // sprawdza, czy port jest otwarty prawidłowo
    {
      SetupComm(hCommDev, cbInQueue, cbOutQueue);
      dcb.DCBlength = sizeof(dcb);       
      GetCommState(hCommDev, &dcb);

      dcb.BaudRate = CBR_9600;

      dcb.Parity = EVENPARITY; // ustawienie parzystości
      dcb.StopBits = ONESTOPBIT; // bity stopu
      dcb.ByteSize = 7; // bity danych

      //-przykładowe ustawienia flag sterujących DCB-
      dcb.fParity = TRUE; // sprawdzanie parzystości
      dcb.fDtrControl = DTR_CONTROL_DISABLE;
      dcb.fRtsControl = RTS_CONTROL_DISABLE;
      dcb.fOutxCtsFlow = FALSE;
      dcb.fOutxDsrFlow = FALSE;
      dcb.fDsrSensitivity = FALSE;
      dcb.fAbortOnError = FALSE;
      dcb.fOutX = FALSE;
      dcb.fInX = FALSE;
      dcb.fErrorChar = FALSE;
      dcb.fNull = FALSE;

      SetCommState(hCommDev, &dcb);
      GetCommMask(hCommDev, &fdwEvtMask);
      SetCommMask(hCommDev, EV_TXEMPTY);
    }
    else
    {
      switch ((int)hCommDev)
      {
        case IE_BADID:
        printf("Niewłaściwa nazwa portu lub port jest aktywny\n");
        break;
      };
    }

    Pytałeś o przykładowy działający program ja mogę ci wysłać tylko, że mam go napisanego w CodeGear 2007 (C++ Borland) - jak chcesz to wyślij mi emaila to dołącze do odpowiedzi.

    Poniżej kod do liczenia sumy kontrolnej:

    Code:
    #include<stdio.h>
    
    #include<conio.h>

    char lrcgen (unsigned char *fptr, unsigned nb);

    void main()
    {
     unsigned char CMNDarray[12]= { '0', '5', '0', '4', '0', 'F', 'B', '3', '0', '0', '0', '1'};

     clrscr();

     printf(" LRC Calculated is %X ",lrcgen(CMNDarray, 12));
    }

    /*Funkcja lrcgen wyliczająca bajt kontrolny LRC, parametry funkcji:*/
    /*fptr - wskaźnik do bufora wiadomości, nb - ilość bajtów w buforze*/
    char lrcgen (unsigned char *fptr, unsigned nb) /*funkcja zwraca jeden bajt*/
    {
    unsigned char lrc, sum = 0;
    while (nb--)
    sum += *fptr++; /*sumowanie kolejnych bajtów bez przeniesień*/
    lrc = ~sum + 1; /*uzupełnienie do 2*/
    return (lrc);
    }

    Czytałem tego pdf o protokole modbus i z niego wynika, że nie bierze pod uwagę znaku ':' (znak początku ramki w protokole modbus w trybie ASCII). Nie wiem czy to nie jest błąd w tekście bo w protokole Fatek (sterownik PLC) znak początku ramki wliczany jest do sumy kontrolnej a jeżeli tak to na 100% zła jest nasza suma kontrolna. Nie mamy również pewności,, że ta część ramki:

    ..., '0', '5', '0', '4', '0', 'F', 'B', '3', '0', '0', '0', '1', ...

    jest poprawna.

    Ponadto jak wyślesz zapytanie to nie ma od razu odpowiedzi z urządzenia bo to chwilę trwa zanim odpowie. Z doświadczenia wiem, że wystarczy poczekać np. 100ms

    Code:
    ...
    
    //wysłanie zapytania
    ...
    Sleep(100);
    ...
    //odbiór danych
    ...

    Może ktoś pomoże kto zna się na modbusie żebyśmy mieli przynajmniej prawidłową ramkę zapytania ?
  • Automation specialist
    Witam

    Ja pisałem programu do komunikacji po ModBus'e z miernikiem LUMEL NA6
    i moim zdaniem łatwiej by było wykonać komunikacje w trybie RTU.
    Suma kontrolna CRC16 jest dostępna w dokumentacji do ModBus'a
    w języku C. Ja przerobiłem ją na C#.
    Ramka zapytania jest prosta i nie potrzeba nic dodawać dodatkowego
    np:


    Code:
    public byte[] getRamkaStatusNr1(byte NrSlave)
    
            {
                List<byte> RamkaDanych = new List<byte>();//Lista pomocnicza
                RamkaDanych.Add(NrSlave);   //Dodaj adr slave
                RamkaDanych.Add(0x03);      //Dodaj kod funkcji
                RamkaDanych.Add(0x1D);      //Dodaj nr rejestru (Hi)
                RamkaDanych.Add(0x4D);      //Dodaj nr rejestru (Lo)
                RamkaDanych.Add(0x00);      //Dodaj poczatek rejestru
                RamkaDanych.Add(0x01);      //Dodaj ilosc rejestrow
                byte[] Crc16 = ObliczCRC16(RamkaDanych); //Oblicz Crc16
                RamkaDanych.Add(Crc16[0]);  //Dodaj Crc16 (Hi)
                RamkaDanych.Add(Crc16[1]);  //Dodaj Crc16 (Lo)
                return (RamkaDanych.ToArray());
            }


    Skorzystałem tutaj z listy ale oczywiście nie trzeba tego robić
    bo można od razu wpisywać dane do tabeli :

    Code:
    byte[] FrameRTU=new byte[8]; // Ramka zapytania 
    
            FrameRTU[0]=         //Dodaj adr slave
            FrameRTU[1]=         //Dodaj kod funkcji
            FrameRTU[2]=         //Dodaj nr rejestru (Hi)
            FrameRTU[3]=         //Dodaj nr rejestru (Lo)
            itd.


    Kolega chce odczytać rejestr o numerze 4019
    a więc można to zrobić tak:

    Code:
    byte[] FrameRTU=new byte[8]; // Ramka zapytania 
    
            FrameRTU[0]=           //Dodaj adr slave
            FrameRTU[1]=0x03;    //Dodaj kod funkcji
            FrameRTU[2]=0x0F;    //Dodaj nr rejestru (Hi)
            FrameRTU[3]=0xB3     //Dodaj nr rejestru (Lo)
            //Dodaj ilosc rejestrow w naszym przypdaku 1 rejestr
            FrameRTU[4]=0x00  //Hi
            FrameRTU[5]=0x01  //Lo
            //Dodaj CRC16
            FrameRTU[6]=    //Hi
            FrameRTU[7]=   //Lo


    Jeśli komunikujemy się w trybie RTU to mamy mniej bajtów do wysłania i odebrania. Jeśli były by jakieś problemy to proszę pisac:)
    Dobrze jak by kolega podał jaki to miernik lub podrzucił
    dokumentację do niego.

    Pozdrawiam.
  • Level 9  
    Dziękuje za odpowiedzi !

    Ramkę mogę skopiować z testera MODBUS-a który poprawnie komunikuje się w trybie ASCII, oto ona:

    Code:
    :3A 30 33 30 33 30 46 42 34 30 30 30 31 33 36 0D 0A


    Tak więc nawet sumy liczyć nie muszę, ponieważ potrzebuje tylko odczytać jeden rejestr a jest ona tu zawarta. Natomiast myślę, że mam jakiś problem w samym programie, nie wiem ile bajtów ustawić w
    Code:
    WriteFile(hCom, &rs_buff, 36, &RS_ile, 0);

    Obecnie ustawione jest 36 ale jest to liczba zupełnie przypadkowa.

    Licznik z jakim próbuje się połączyć to LUMEL LS31

    No i oto program który wymodziłem, po diodach na konwerterze widzę, że wysyła ale nic nie przychodzi.

    Code:
    #include <windows.h> 
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    int main()
    {
       HANDLE hCom;    //uchwyt portu
       DCB dcb;        //konfiguracja portu
       BOOL fSuccess;
       DWORD RS_ile;   //ilosc bitow wyslanych
       int numer;
       char read_buffor;
       
       //otwarcie portu COM1 z prawami RW
       
        printf ("Wpisz numer portu COM:");
        scanf("%s", numer);
       
        hCom = CreateFile( TEXT("COM4"), GENERIC_READ | GENERIC_WRITE,
            0,    // exclusive access
            NULL, // default security attributes
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
     
       if (hCom == INVALID_HANDLE_VALUE)
       {
        printf("Brak polaczenia z portem nr. %u !!   Blad: %d.\n", numer, GetLastError());
        system("PAUSE");
        return 1;
       }

       //pobranie aktualnych ustawien portu
       fSuccess = GetCommState(hCom, &dcb);
       
      //ustawienie naszej konfiguracji
       dcb.BaudRate = CBR_9600;     // predkosc transmisji
       dcb.ByteSize = 7;             // ilosc bitow danych
       dcb.Parity = EVENPARITY;        // brak bitu parzystosci
       dcb.StopBits = ONESTOPBIT;    // dwa bity stopu
       fSuccess = SetCommState(hCom, &dcb);

       char rs_buff[30] = {':', '3', 'A', '3', '0', '3', '3', '3', '0', '4', '6', '4', '2', '3', '4', '3', '0', '3', '0', '3', '1', '3', '3', '3', '6', '0', 'D', '0', 'A', '3'};

       while (1 == 1){
         fSuccess = WriteFile(hCom, &rs_buff, 30, &RS_ile, 0);
         printf ("Wyslalem: %s \n", &rs_buff);
         Sleep(10);
         fSuccess = ReadFile(hCom, &read_buffor, 30, &RS_ile, 0);
         printf ("Odczytalem: %s \n", &read_buffor);
         Sleep(1000);
        }
       if (!fSuccess)
        {
          printf ("Nie moge wyslac!! %d.\n", GetLastError());
          system("PAUSE");
          return 4;
        }
        system("PAUSE");
       return 0;
    }


    A to zwraca po uruchomieniu:

    Code:
    Wyslalem: 2293520
    
    Odczytalem: 229350


    Zmieniłem parametr printf na z "d" na "s" i teraz program zwraca takie coś:
    Komunikacja modbus w c++ rs232 -> rs485

    Wygląda mi to na błąd w programie ale nie mogę go znaleźć ;/
  • Helpful post
    Automation specialist
    Witam


    Code:
       while (1 == 1){
    
         fSuccess = WriteFile(hCom, &rs_buff, 30, &RS_ile, 0);
         printf ("Wyslalem: %s \n", &rs_buff);
         
         //Zwieksz czas.
         Sleep(10);
         fSuccess = ReadFile(hCom, &read_buffor, 30, &RS_ile, 0);
         //Czemu read_buffor nie jest zadeklarowany jako wskaznik???
         printf ("Odczytalem: %s \n", &read_buffor);
         Sleep(1000);
        }
       // Ten warunek jest sprawdzny poza petla while
      // a petla ta bedzie sie wykonywac bez konca
      // a wiec nigdy nie sprawdzisz  fSuccess
       if (!fSuccess)
        {
          printf ("Nie moge wyslac!! %d.\n", GetLastError());
          system("PAUSE");
          return 4;
        }


    W mierniku jest tryb RTU a wiec proponuje przejsci na ten
    tryb w razie czego.


    Pytanie???

    Code:
    fSuccess = ReadFile(hCom, &read_buffor, 30, &RS_ile, 0);


    W tej linijce wpisujesz ze masz do odczytu 30 bajtów a
    skad wiesz ze tyle zawsze będziesz mial do odczytu ?????
    Powiedzmy ze chcę w trybie RTU odczytać
    100 rejestrów to ramka zapytania ma długość 8 bajtów
    ale ramka odpowiedzi bedzie miała dużo więcej w zalerzności
    od typu danej jakie będzie przechowywał dany rejestr.


    Code:
      //ustawienie naszej konfiguracji
    
       dcb.BaudRate = CBR_9600;     // predkosc transmisji
       dcb.ByteSize = 7;             // ilosc bitow danych
       dcb.Parity = EVENPARITY;        // brak bitu parzystosci
       dcb.StopBits = ONESTOPBIT;    // dwa bity stopu

    W mierniku masz takie sam ustawienia ????
    Bo ja w dokumentacji jakoś nie widze ze można
    tak ustawić chyba że źle patrze.

    Code:
    dcb.StopBits = ONESTOPBIT;    // dwa bity stopu 

    Dwa bity stopu ??? jak dla mnie to masz 1 ustawiony.


    Code:
     
    
    printf ("Wpisz numer portu COM:");
    scanf("%s", numer);
       
    hCom = CreateFile( TEXT("COM4"), GENERIC_READ | GENERIC_WRITE,
                0,    // exclusive access
               NULL, // default security attributes
               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);


    Po co wprowadzasz numer portu jak i tak ustawiasz na 4.

    Code:
     fSuccess = SetCommState(hCom, &dcb); 

    A gdzie sprawdzasz fSuccess???
    Z kodu widać ze po pętli while która i tak się nie skończy.




    Pytanie jaki używasz kod funkcji???
    Popraw kod.

    Pozdrawiam
  • Level 19  
    Ustaw flagi sterujące tak jak podałem (to fragment kodu działającego programu). Ponadto uważam, że 10ms to za małe opóźnienie przed odczytem danych z bufora danych przychodzących.

    Zapytanie wysyłane do urządzeń musi być bez błędów i dobrą sumą kontrolną ponieważ w zdecydowanej większości przypadków urządzenia odbierając błędną ramkę nic nie wysyłają (oleją po prostu zapytanie).

    Najlepiej na etapie tworzenia programu wysyłaj jakąkolwiek ramkę ale co do której masz 100% pewność, że jest prawidłowa. Jak odbierzesz dane z urządzenia tzn. że kod programu jest dobry i wtedy możesz próbować z nową ramką.

    Jeżeli cały czas będziesz odbierać ramkę o identycznej długości to bufor ustaw na taką właśnie wartość (jeden znak = jeden bajt).
  • Level 9  
    Quote:
    W tej linijce wpisujesz ze masz do odczytu 30 bajtów a
    skad wiesz ze tyle zawsze będziesz mial do odczytu ?????


    Nie wiem ale nie wiem również jak to ustawić :)

    Quote:
    Pytanie jaki używasz kod funkcji???


    4020

    Quote:
    fSuccess = ReadFile(hCom, &read_buffor, 30, &RS_ile, 0);
    //Czemu read_buffor nie jest zadeklarowany jako wskaznik???


    Nie do końca rozumiem, czemu wskaźnik ?


    Pozmieniałem trochę w programie, teraz zatrzymuje się on na
    Code:
    printf ("Wyslalem: %s \n", &rs_buff);

    i nie idzie dalej. Nie mam już sił i pomysłów do tego programu. Załączam obecny kod może ktoś znajdzie błąd. Jak coś widzicie nie tak, proszę poprawcie, lalik ze mnie w tej dziedzinie i nie daje sobie zupełnie rady /;

    edit:
    Ruszyło dalej, było za mało bitów w odczytywaniu ustawione, teraz przy wysyłaniu wywala error z
    Code:
    GetLastError()
    o numerze 998. Co to może być ?

    Błąd 998 - Invalid access to memory location.
    Czyżby zły typ zmiennej, a może chodzi o tej wskaźnik co kolega wspominał ?

    Code:
    #include <windows.h> 
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    int main()
    {
       HANDLE hCom;    //uchwyt portu
       DCB dcb;        //konfiguracja portu
       BOOL fSuccess;
       DWORD RS_ile;   //ilosc bitow wyslanych
       char read_buffor;
       int numer;
       //otwarcie portu COM4 z prawami RW
       
        hCom = CreateFile( TEXT("COM4"), GENERIC_READ | GENERIC_WRITE,
            0,    // exclusive access
            NULL, // default security attributes
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
     
       if (hCom == INVALID_HANDLE_VALUE)
       {
        printf("Brak polaczenia z portem nr. %u !!   Blad: %d.\n", numer, GetLastError());
        system("PAUSE");
        return 1;
       }

       //pobranie aktualnych ustawien portu
       fSuccess = GetCommState(hCom, &dcb);
       
      //ustawienie naszej konfiguracji
      dcb.BaudRate = CBR_9600;
      dcb.Parity = EVENPARITY; // ustawienie parzystości
      dcb.StopBits = ONESTOPBIT; // bity stopu
      dcb.ByteSize = 7; // bity danych

      dcb.fParity = FALSE; // sprawdzanie parzystości
      dcb.fDtrControl = DTR_CONTROL_DISABLE;
      dcb.fRtsControl = RTS_CONTROL_DISABLE;
      dcb.fOutxCtsFlow = FALSE;
      dcb.fOutxDsrFlow = FALSE;
      dcb.fDsrSensitivity = FALSE;
      dcb.fAbortOnError = FALSE;
      dcb.fOutX = FALSE;
      dcb.fInX = FALSE;
      dcb.fErrorChar = FALSE;
      dcb.fNull = FALSE;
     
      fSuccess = SetCommState(hCom, &dcb);

       char rs_buff[35] = {':', '3', 'A', '3', '0', '3', '3', '3', '0', '3', '3', '3', '0', '4', '6', '4', '2', '3', '4', '3', '0', '3', '0', '3', '0', '3', '1', '3', '3', '3', '6', '0', 'D', '0', 'A'};
       
       while (1 == 1) {
         fSuccess = WriteFile(hCom, &rs_buff, 35, &RS_ile, 0);
         printf ("Wyslalem: %s \n", &rs_buff);
         Sleep(100);
         fSuccess = ReadFile(hCom, &read_buffor, 35, &RS_ile, 0);
         printf ("Odczytalem: %s \n", &read_buffor);
         Sleep(1000);
         
       if (!fSuccess)
        {
          printf ("Nie moge wyslac!! %d.\n", GetLastError()); 
        }
        }
         
       system("PAUSE");
       return 0;
    }
  • Helpful post
    Level 19  
    U mnie działa tak więc kod jest prawidłowy !

    Code:
    #include <windows.h>
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    #define cbOutQueue 35 //rozmiar bufora danych wyjściowych
    #define cbInQueue 35 //rozmiar bufora danych wejściowych

    int __fastcall Write_Comm(HANDLE hCommDev, DWORD nNumberOfBytesToWrite);
    int __fastcall Read_Comm(HANDLE hCommDev, LPDWORD lpNumberOfBytesRead, DWORD Buf_Size);

    char rs_buff[cbOutQueue] = {':', '3', 'A', '3', '0', '3', '3', '3', '0', '3',
                                '3', '3', '0', '4', '6', '4', '2', '3', '4', '3',
                                '0', '3', '0', '3', '0', '3', '1', '3', '3', '3',
                                '6', '0', 'D', '0', 'A'};

    LPCTSTR query = rs_buff;

    char    Buffer_O[cbOutQueue]; // bufor danych wyjściowych
    char    Buffer_I[cbInQueue];  // bufor danych wejściowych

    DWORD   Number_Bytes_Read;

    DWORD   fdwEvtMask;
    COMSTAT Stat;
    DWORD   Errors;

    BOOL bResult = TRUE;

    int main()
    {
       HANDLE hCom;    //uchwyt portu
       DCB dcb;        //konfiguracja portu
       BOOL fSuccess;

       //otwarcie portu COM4 z prawami RW

       hCom = CreateFile( TEXT("COM4"), GENERIC_READ | GENERIC_WRITE,
          0,    // exclusive access
          NULL, // default security attributes
          OPEN_EXISTING, 0, NULL);

       if (hCom != INVALID_HANDLE_VALUE) // sprawdza, czy port jest otwarty prawidłowo
       {
          SetupComm(hCom, cbInQueue, cbOutQueue);
          dcb.DCBlength = sizeof(dcb);
          //pobranie aktualnych ustawien portu
          fSuccess = GetCommState(hCom, &dcb);

          //ustawienie naszej konfiguracji
          dcb.BaudRate = CBR_9600;
          dcb.Parity = EVENPARITY; // ustawienie parzystości
          dcb.StopBits = ONESTOPBIT; // bity stopu
          dcb.ByteSize = 7; // bity danych

          dcb.fParity = FALSE; // sprawdzanie parzystości
          dcb.fDtrControl = DTR_CONTROL_DISABLE;
          dcb.fRtsControl = RTS_CONTROL_DISABLE;
          dcb.fOutxCtsFlow = FALSE;
          dcb.fOutxDsrFlow = FALSE;
          dcb.fDsrSensitivity = FALSE;
          dcb.fAbortOnError = FALSE;
          dcb.fOutX = FALSE;
          dcb.fInX = FALSE;
          dcb.fErrorChar = FALSE;
          dcb.fNull = FALSE;

          fSuccess = SetCommState(hCom, &dcb);
       }
       else {

          switch ((int)hCom)
          {
             case IE_BADID:
                printf("Niewlasciwa nazwa portu lub port jest aktywny !\n");
                return 0;
          }
       }

       do {

          strcpy(Buffer_O, query);

          do {
             FlushFileBuffers(hCom);
          } while (Write_Comm(hCom, strlen(Buffer_O)) == 0); //-- wysyłanie zapytania

          printf ("Wyslalem: %s \n", Buffer_O);

          Sleep(100);

          Read_Comm(hCom, &Number_Bytes_Read, sizeof(Buffer_I)); //-- odbiór danych

          if (Number_Bytes_Read > 0)
          {
             printf ("Odczytalem: %s \n", Buffer_I);
          }
          else {
             printf ("Nie odebrano danych !\n");
          }

          Sleep(1000);

       } while (bResult);  // koniec nadrzędnego DO

       if (!fSuccess)
       {
          printf ("Nie moge wyslac !! %d.\n", GetLastError());
       }

       return 0;
    }

    //---------------------------------------------------------------------------

    int __fastcall Write_Comm(HANDLE hCommDev, DWORD nNumberOfBytesToWrite)
    {
       DWORD NumberOfBytesWritten;

       if (WriteFile(hCommDev, &Buffer_O[0], nNumberOfBytesToWrite,
                 &NumberOfBytesWritten , NULL) > 0)
        {
          WaitCommEvent(hCommDev, &fdwEvtMask, NULL);

          return TRUE;
        }
         else
          return FALSE;
    }
    //--------------------------------------------------------------------

    int __fastcall Read_Comm(HANDLE hCommDev, LPDWORD lpNumberOfBytesRead, DWORD Buf_Size)
    {
       DWORD nNumberOfBytesToRead;

       ClearCommError(hCommDev, &Errors ,&Stat);

        if (Stat.cbInQue > 0)
         {
            if (Stat.cbInQue > Buf_Size)
               nNumberOfBytesToRead = Buf_Size;
               else
                  nNumberOfBytesToRead = Stat.cbInQue;

          ReadFile(hCommDev, &Buffer_I[0], nNumberOfBytesToRead,
                     lpNumberOfBytesRead, NULL);
         }
          else
            *lpNumberOfBytesRead = 0;
        return TRUE;
    }


    Powodzenia !
  • Level 9  
    jabu74 - ekspert - Jesteś świetny ! Wielkie dzięki !!! :)

    Program działa jak złoto :) Co prawda nie dostaje jeszcze odpowiedzi od licznika ale myślę że to sprawa ramki.

    Dla sprawdzenia przełączyłem się na tryb RTU 8N1, i w MODBUS Poll idzie wszystko ładnie, skopiowałem z niego ramkę (03 03 0F B4 00 01 C6 DA) ale odpowiedzi nie ma.

    Tak więc ustawiłem takie same parametry jak w/w programie, tj.
    Code:
    dcb.BaudRate = CBR_9600; 
    
          dcb.Parity = NOPARITY; // ustawienie parzystości
          dcb.StopBits = ONESTOPBIT; // bity stopu
          dcb.ByteSize = 8; // bity danych


    No ale dalej brak odpowiedzi. Szukam więc dalej, może komuś przyjdzie jakiś pomysł do głowy do chętnie go przetestuje ;)

    Jeszcze raz wielkie dzięki !
  • Level 19  
    Jeżeli nie ma odpowiedzi to mogą być dwie przyczyny:

    1) zła ramka zapytania albo przynajmniej zła suma kontrolna
    2) złe ustawienia parametrów transmisji

    Tak jak pisałem wcześniej. Musisz znaleźć jakąś pewną sprawdzoną ramkę. Może jest coś w dokumentacji jakiś przykład ramki. Ta twoja ramka jest coś za długa przecież chciałeś odczytać wartość tylko z jednego rejestru.

    Ja bym jeszcze zrobił tak - jako, że są dwa znaki sumy kontrolnej to napisałbym program który wysyła ramkę zmieniając sumę kontrolną od '00' do 'FF' (256 kombinacji czyli 256 ramek do wysłania) i zakładając, że tylko suma kontrolna była zła musisz odebrać w końcu prawidłową ramkę.

    Program u mnie na sterowniku PLC działa bez najmniejszego problemu ale ja znam dokładnie jakie są ustawione parametry transmisji w urządzeniu, jaki jest numer urządzenia oraz mam dokumentację i tam są przykłady ramek tak więc mogłem go testować na sprawdzonych przykładach zapytań. Ponadto mam kontrolki na sterowniku i widzę, że dane dochodzą bo diody migają (nawet jak jest błędna ramka czy złe ustawienia transmisji).

    Dodano po 1 [godziny] 50 [minuty]:

    Znalazłem bardzo interesującą stronę http://www.simplymodbus.ca/index.html

    Wynika z tego, że suma kontrolna liczona jest przed zamianą na ASCII (sic!!!)

    Zakładając, że:

    1) adres urządzenia 5
    2) kod funkcji 4
    3) odczyt jednej komórki o adresie 4019 (HEX 0FB3) - a czy adres nie jest 40019 (dwa zera)?

    Program na sumę kontrolną zmienia się:

    Code:
    #include<stdio.h>
    
    #include<conio.h>

    int lrcgen (unsigned int *fptr,unsigned nb);

    void main()
    {
        unsigned int CMNDarray[4]= { 5, 4, 4019-1, 1};

        clrscr();

        printf(" LRC Calculated is %X ",lrcgen(CMNDarray,4));
    }

    int lrcgen (unsigned int *fptr, unsigned nb)
    {
        unsigned int lrc, sum = 0;

        while (nb--)
            sum += *fptr++;
       
        lrc = ~sum + 1; /*uzupełnienie do 2*/

        return (lrc);
    }


    Suma kontrolna LRC wynosi 0x44

    a zapytanie ma postać:

    Code:
    char rs_buff[17] = {':', '0', '5', '0', '4', '0', 'F', 'B', '2', '0', '0', '0', '1', '4', '4', 0x0D, 0x0A};


    Adres odczytywanej komórki wpisuje się pomniejszony o 1 czyli 0FB2 (HEX)

    Znaki kończące ramkę wpisuj 0x0D i 0x0A
  • Level 9  
    Sprawa wyjaśniając się staje się coraz to bardziej nie zrozumiała :)

    Na tą ramkę również nie ma odpowiedzi. Kod funkcji 3 a adres 4019 lub 4020.

    Pobrałem kilka testerów i wszystkie dostają poprawną odpowiedz, wysyłając przy tym ta sama ramkę, tj. 03 03 0F B4 00 01 C6 DA (w RTU)

    W ACSII też generują te same ramki i również dostają poprawną odpowiedz. Tak wiec stosuje tą ramkę w programie i odpowiedzi nie ma.

    Ustawiłem te same parametry portu co w testerach.

    Nurtuje mnie wciąż jedno zagadnienie, mianowicie w dokumentacji MODBUS-a jest napisane, że transmisja w RTU rozpoczyna się po 3,5 długości trwania pojedynczego znaku. Nie wiem jak tego dokonać ale w sumie to bez różnicy bo ASCII też nie odpowiada.

    Tak wiec podsumowując - czym różnią się wszystkie testery, że wysyłając tą sama ramkę otrzymują odpowiedz ?

    Czyli zakładając: adres 03, kod funkcji 03, adres 4020 mamy sumę 0x46.
    Poszukam przyczyny głębiej w ustawieniach portu bo nic innego sensownego nie widzę ;/

    EDIT:

    Udało się ! ;)

    Ramka poprawna: : 03 03 0F B2 00 01 38 0x0D, 0x0A

    Jednak z ten programik źle liczy sumę, pod ramkę podstawiłem sumę wygenerowaną przez tester i poszło.

    Podsumowując tamat:

    Na początku był problem z kodem programu, został on poprawiony przez kolegę jabu74 - ekspert któremu cześć i chwała ! :)

    Następnie nie mogliśmy uzyskać połączenia, doszukując się błędu w ramce. I słusznie, wysyłaliśmy różne ramki a odpowiedzi nie było. Było to spowodowane brakiem końca ramki tj. 0x0D oraz 0x0A oraz zła sumą kontrolną.

    W końcu gdy wygenerowaliśmy poprawną sumę oraz dodaliśmy znak końca ramki wszystko ruszyło poprawnie.

    Dziękuje wszystkim za poświęcenie czasu, szczególne podziękowania dla jabu74 - ekspert - bez Ciebie nic by nie ruszyło ! :)

    Pozdrawiam i jeszcze raz dziękuje !