czwartek, 4 czerwca 2020

[39] Arduino - Serwer oraz Klient TCP na ENC28J60

W tym poście chciałbym opisać sposób komunikacji pomiędzy klientem a serwerem TCP.


Podłączenie:

W tym przypadku wykorzystałem gotową nakładkę na Arduino Nano, która pozwala w bardzo łatwy sposób podłączyć ze sobą te układy.


Poniżej podstawowy opis podłączenia:

  • Pin SS (CS) - D10
  • Pin MOSI (SI) - D11
  • Pin MISO (SO) - D12
  • Pin SCK - D13

Dodatkowo należy podłączyć zasilanie do 5V oraz GND. W przykładach wykorzystuje bibliotekę UIPEthernet, która domyślnie posiada przyporządkowanie pinów takie jak powyżej.

Przykłady autora biblioteki:


W celu sprawdzenia działania można wykorzystać przykłady udostępnione przez autora biblioteki. Poniżej zmodyfikowane wersje dla TCP Client oraz TCP Server:

TCP Klient:

  1. #include <UIPEthernet.h>
  2. #include "utility/logging.h"
  3. #define MACADDRESS 0x48,0x53,0x4B,0x03,0x55,0x05
  4. #define MYIPADDR 169,254,24,158
  5. #define MYIPMASK 255,255,255,0
  6. #define MYDNS 192,168,1,1
  7. #define MYGW 169,254,24,20
  8. #define LISTENPORT 1000
  9. EthernetClient client;
  10. unsigned long next;
  11. void PrintNetworkSettings()
  12. {
  13.   Serial.print("localIP: ");
  14.   Serial.println(Ethernet.localIP());
  15.   Serial.print("subnetMask: ");
  16.   Serial.println(Ethernet.subnetMask());
  17.   Serial.print("gatewayIP: ");
  18.   Serial.println(Ethernet.gatewayIP());
  19.   Serial.print("dnsServerIP: ");
  20.   Serial.println(Ethernet.dnsServerIP());
  21. }
  22. void setup() {
  23.   Serial.begin(115200);
  24.   Serial.print("Client start: ");
  25.   uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};
  26.   Ethernet.begin(mac); //Configure IP address via DHCP
  27.   //Ethernet.begin(mac,myIP,myDNS,myGW,myMASK); //Without DHCP
  28.   PrintNetworkSettings();
  29. }
  30. void loop() {
  31.   if (((signed long)(millis() - next)) > 0)
  32.   {
  33.       next = millis() + 5000;
  34.       // replace hostname with name of machine running tcpserver.pl
  35.       // if (client.connect("server.local",5000))
  36.       if (client.connect(IPAddress(192,168,0,1),1000))
  37.       {
  38.           client.println(F("DATA from Client"));
  39.           int size;
  40.           while ((client.available()==0) && (millis()<next)) { }
  41.           while((size = client.available()) > 0)
  42.           {
  43.               uint8_t* msg = (uint8_t*)malloc(size+1);
  44.               memset(msg, 0, size+1);
  45.               size = client.read(msg,size);
  46.               free(msg);
  47.           }
  48.           //disconnect client
  49.           client.stop();
  50.       }
  51.       else
  52.       {
  53.       }
  54.   }
  55. }

TCP Server:

  1. #define MACADDRESS 0x00,0x01,0x02,0x03,0x04,0x05
  2. #define MYIPADDR 192,168,1,6
  3. #define MYIPMASK 255,255,255,0
  4. #define MYDNS 192,168,1,1
  5. #define MYGW 192,168,1,1
  6. #define LISTENPORT 1000
  7. #define UARTBAUD 115200
  8. #include <UIPEthernet.h>
  9. #include "utility/logging.h"
  10. EthernetServer server = EthernetServer(LISTENPORT);
  11. void PrintNetworkSettings()
  12. {
  13.   Serial.print("localIP: ");
  14.   Serial.println(Ethernet.localIP());
  15.   Serial.print("subnetMask: ");
  16.   Serial.println(Ethernet.subnetMask());
  17.   Serial.print("gatewayIP: ");
  18.   Serial.println(Ethernet.gatewayIP());
  19.   Serial.print("dnsServerIP: ");
  20.   Serial.println(Ethernet.dnsServerIP());
  21. }
  22. void setup() {
  23.   Serial.begin(UARTBAUD);
  24.   Serial.println("Start Server");
  25.   uint8_t mac[6] = {MACADDRESS};
  26.   /*
  27.   uint8_t myIP[4] = {MYIPADDR};
  28.   uint8_t myMASK[4] = {MYIPMASK};
  29.   uint8_t myDNS[4] = {MYDNS};
  30.   uint8_t myGW[4] = {MYGW};
  31.   Ethernet.begin(mac,myIP,myDNS,myGW,myMASK);
  32.   */
  33.   Ethernet.begin(mac);  //DHCP
  34.   PrintNetworkSettings();
  35.   server.begin();
  36. }
  37. void loop() {
  38.   size_t size;
  39.   if (EthernetClient client = server.available())
  40.   {
  41.       while((size = client.available()) > 0)
  42.       {
  43.           uint8_t* msg = (uint8_t*)malloc(size+1);
  44.           memset(msg, 0, size+1);
  45.           size = client.read(msg,size);
  46.           free(msg);
  47.       }
  48.       client.stop();
  49.     }
  50. }

Oby przypadki można zweryfikować pojedynczo np. przez użycie programu hercules.

Wersja pierwsza:


Po wciśnięciu przycisku klient lub serwer wysyłają informację o wciśnięciu przycisku. W zależności od tego gdzie dany przycisk został wciśnięty.

TCP Server:

  1. #include <UIPEthernet.h>
  2. //---------------------------------------
  3. /* Define protocol frame */
  4. #define START_ACK 0x01
  5. #define CONTOL_IN_OUT_FRAME 0x23
  6. #define KEEP_ALIVE_FRAME 0x45
  7. //-------------------------------------
  8. #define IN_1 2
  9. #define IN_2 3
  10. #define IN_3 4
  11. #define IN_4 5
  12. #define OUT_1 6
  13. #define OUT_2 7
  14. #define OUT_3 8
  15. #define OUT_4 9
  16. #define OUT_STATUS_DIODE 15
  17. //----------------------------------------
  18. #define MACADDRESS 0x48,0x53,0x4B,0x03,0x55,0x05
  19. #define MYIPADDR 169,254,24,158
  20. #define MYIPMASK 255,255,255,0
  21. #define MYDNS 192,168,1,1
  22. #define MYGW 169,254,24,20
  23. #define LISTENPORT 1000
  24. #define UARTBAUD 115200
  25. //----------------------------------------
  26. EthernetServer server = EthernetServer(LISTENPORT);
  27. uint16_t countConnection = 0;
  28. uint8_t sendFlag = 0;
  29. unsigned long time_now;
  30. int buttonPrevState1 = 0;
  31. int buttonPrevState2 = 0;
  32. int buttonPrevState3 = 0;
  33. int buttonPrevState4 = 0;
  34. void CheckBtnMonostable(int * buttonPrevState, int buttonState, uint8_t pinNumber)
  35. {
  36.     if (buttonState == LOW && *buttonPrevState == 0)
  37.     {
  38.       *buttonPrevState = 1;
  39.       Serial.print("Wcisniety: ");
  40.       Serial.println(pinNumber, DEC);
  41.       uint8_t buf[5] = {0x01, 0x02, 0x34, pinNumber, 0x01};
  42.       int val = server.write(buf, 5);
  43.     }
  44.     else if (buttonState == HIGH && *buttonPrevState == 1)
  45.     {
  46.       *buttonPrevState = 0;
  47.       Serial.print("Puszczony: ");
  48.       Serial.println(pinNumber, DEC);
  49.       uint8_t buf[5] = {0x01, 0x02, 0x34, pinNumber, 0x00};
  50.       int val = server.write(buf, 5);
  51.     }
  52. }
  53. void InitGpio(uint8_t gpioInOut)
  54. {
  55.   if(gpioInOut == 0) //Input
  56.   {
  57.     pinMode(IN_1, INPUT_PULLUP);
  58.     pinMode(IN_2, INPUT_PULLUP);
  59.     pinMode(IN_3, INPUT_PULLUP);
  60.     pinMode(IN_4, INPUT_PULLUP);
  61.   }
  62.   else if(gpioInOut == 1) //Output
  63.   {
  64.     pinMode(OUT_1, OUTPUT);
  65.     pinMode(OUT_2, OUTPUT);
  66.     pinMode(OUT_3, OUTPUT);
  67.     pinMode(OUT_4, OUTPUT);
  68.     digitalWrite(OUT_1, LOW);
  69.     digitalWrite(OUT_2, LOW);
  70.     digitalWrite(OUT_3, LOW);
  71.     digitalWrite(OUT_4, LOW);
  72.   }
  73. }
  74. void InitDiodeStatusGpio(void)
  75. {
  76.   pinMode(OUT_STATUS_DIODE, OUTPUT);
  77.   digitalWrite(OUT_STATUS_DIODE, LOW);
  78. }
  79. void SetPinStatus(uint8_t pinNumber, uint8_t pinState)
  80. {
  81.   /*
  82.      msg[3] pin number from 1 to 4
  83.      msg[4] pin state 0 is LOW, 1 is HIGH
  84.   */
  85.   digitalWrite((pinNumber + 5), pinState);
  86. }
  87. void PrintNetworkSettings()
  88. {
  89.   Serial.print("localIP: ");
  90.   Serial.println(Ethernet.localIP());
  91.   Serial.print("subnetMask: ");
  92.   Serial.println(Ethernet.subnetMask());
  93.   Serial.print("gatewayIP: ");
  94.   Serial.println(Ethernet.gatewayIP());
  95.   Serial.print("dnsServerIP: ");
  96.   Serial.println(Ethernet.dnsServerIP());
  97. }
  98. void setup() {
  99.   uint8_t mac[6] = {MACADDRESS};
  100.   uint8_t myIP[4] = {MYIPADDR};
  101.   uint8_t myMASK[4] = {MYIPMASK};
  102.   uint8_t myDNS[4] = {MYDNS};
  103.   uint8_t myGW[4] = {MYGW};
  104.   //------------------------------------
  105.   Serial.begin(115200);
  106.   Serial.println("Start");
  107.   //------------------------------------
  108.   InitGpio(0);
  109.   InitGpio(1);
  110.   InitDiodeStatusGpio();
  111.   //------------------------------------
  112.   Ethernet.begin(mac);
  113.   Serial.println("Ethernet.begin");
  114.   //Ethernet.begin(mac,myIP,myDNS,myGW,myMASK);
  115.   //Serial.println( "ServerBegin");
  116.   PrintNetworkSettings();
  117.   server.begin();
  118. }
  119. void loop()
  120. {
  121.   int buttonState1 = digitalRead(IN_1);
  122.   int buttonState2 = digitalRead(IN_2);
  123.   int buttonState3 = digitalRead(IN_3);
  124.   int buttonState4 = digitalRead(IN_4);
  125.   CheckBtnMonostable(&buttonPrevState1, buttonState1, 1);
  126.   CheckBtnMonostable(&buttonPrevState2, buttonState2, 2);
  127.   CheckBtnMonostable(&buttonPrevState3, buttonState3, 3);
  128.   CheckBtnMonostable(&buttonPrevState4, buttonState4, 4);
  129.   size_t size;
  130.   EthernetClient client = server.available();
  131.   if(client)
  132.   {
  133.       digitalWrite(OUT_STATUS_DIODE, HIGH);
  134.       time_now = millis();
  135.       countConnection = 0;
  136.       sendFlag = 0;
  137.      
  138.       while((size = client.available()) > 0)
  139.       {
  140.         uint8_t* msg = (uint8_t*)malloc(size+1);
  141.         memset(msg, 0, size+1);
  142.         size = client.read(msg,size);
  143.        
  144.         if(msg[0] == START_ACK)  /* Sprawdź czy bit startu*/
  145.         {
  146.           if(msg[2] == CONTOL_IN_OUT_FRAME) /* Ramka zmiany stanu wejscia wyjscia*/
  147.           {
  148.             if(msg[1] == 0x02)
  149.             {
  150.               SetPinStatus(msg[3], msg[4]);
  151.               client.write(msg,size);
  152.             }
  153.           }
  154.           else if(msg[2] == KEEP_ALIVE_FRAME)
  155.           {
  156.             countConnection = 0;
  157.             sendFlag = 0;
  158.             digitalWrite(OUT_STATUS_DIODE, HIGH);
  159.           }
  160.         }
  161.         else { client.write("Undefined",9); }
  162.         free(msg);
  163.       }
  164.   }
  165.   //Send Keep Alive Frame if no communication occurs
  166.   if(millis() - time_now > 5000){
  167.     time_now = millis();
  168.     if(sendFlag == 0)
  169.     {
  170.       uint8_t buf[5] = {0x01, 0x02, 0x45};
  171.       int val = server.write(buf, 3);
  172.       sendFlag = 1;
  173.     }
  174.    }
  175.    if(sendFlag == 1 && millis() - time_now > 2000)
  176.    {
  177.         time_now = millis();
  178.         countConnection = 0;
  179.         sendFlag = 0;
  180.         digitalWrite(OUT_STATUS_DIODE, LOW);
  181.    }
  182. }

TCP Klient:

  1. #include <UIPEthernet.h>
  2. //---------------------------------------
  3. /* Define protocol frame */
  4. #define START_ACK 0x01
  5. #define CONTROL_IN_OUT_FRAME 0x23
  6. #define BTN_SERVER_CLICK 0x34
  7. #define KEEP_ALIVE_FRAME 0x45
  8. //---------------------------------------
  9. /* Define pin numbers */
  10. #define IN_1 2
  11. #define IN_2 3
  12. #define IN_3 4
  13. #define IN_4 5
  14. #define OUT_1 6
  15. #define OUT_2 7
  16. #define OUT_3 8
  17. #define OUT_4 9
  18. #define OUT_SER_1 16
  19. #define OUT_SER_2 17
  20. #define OUT_SER_3 18
  21. #define OUT_SER_4 19
  22. #define OUT_STATUS_DIODE 15
  23. //---------------------------------------
  24. EthernetClient client;
  25. unsigned long time_now;
  26. uint16_t keepAliceMillisPeriod = 6000;
  27. //---------------------------------------
  28. int buttonPrevState1 = 0;
  29. int buttonPrevState2 = 0;
  30. int buttonPrevState3 = 0;
  31. int buttonPrevState4 = 0;
  32. //----------------------------------------------------------------
  33. void CheckBtnMonostable(int * buttonPrevState, int buttonState, uint8_t pinNumber)
  34. {
  35.     if (buttonState == LOW && *buttonPrevState == 0)
  36.     {
  37.       *buttonPrevState = 1;
  38.       uint8_t buf[5] = {0x01, 0x02, 0x23, pinNumber, 0x01};
  39.       int val = client.write(buf, 5);
  40.       Serial.println(val, DEC);
  41.     }
  42.     else if (buttonState == HIGH && *buttonPrevState == 1)
  43.     {
  44.       *buttonPrevState = 0;
  45.       uint8_t buf[5] = {0x01, 0x02, 0x23, pinNumber, 0x00};
  46.       int val = client.write(buf, 5);
  47.       Serial.println(val, DEC);
  48.     }
  49. }
  50. void CheckBtnBistable(int * buttonPrevState, int buttonState, uint8_t pinNumber)
  51. {
  52.    if (buttonState == LOW)
  53.    {
  54.      *buttonPrevState = 1;
  55.      uint8_t buf[5] = {0x01, 0x02, 0x23, pinNumber, 0x01};
  56.      int val = client.write(buf, 5);
  57.      Serial.println(val, DEC);
  58.    }
  59. }
  60. void SetPinStatus(uint8_t pinNumber, uint8_t pinState)
  61. {
  62.   /*
  63.      msg[3] pin number from 1 to 4
  64.      msg[4] pin state 0 is LOW, 1 is HIGH
  65.   */
  66.   digitalWrite((pinNumber + 5), pinState);
  67. }
  68. void DefineInputOutput_SetDefaultState(void)
  69. {
  70.   pinMode(IN_1, INPUT_PULLUP);
  71.   pinMode(IN_2, INPUT_PULLUP);
  72.   pinMode(IN_3, INPUT_PULLUP);
  73.   pinMode(IN_4, INPUT_PULLUP);
  74.   pinMode(OUT_1, OUTPUT);
  75.   pinMode(OUT_2, OUTPUT);
  76.   pinMode(OUT_3, OUTPUT);
  77.   pinMode(OUT_4, OUTPUT);
  78.   digitalWrite(OUT_1, LOW);
  79.   digitalWrite(OUT_2, LOW);
  80.   digitalWrite(OUT_3, LOW);
  81.   digitalWrite(OUT_4, LOW);
  82.   pinMode(OUT_SER_1, OUTPUT);
  83.   pinMode(OUT_SER_2, OUTPUT);
  84.   pinMode(OUT_SER_3, OUTPUT);
  85.   pinMode(OUT_SER_4, OUTPUT);
  86.   digitalWrite(OUT_SER_1, HIGH);
  87.   digitalWrite(OUT_SER_2, HIGH);
  88.   digitalWrite(OUT_SER_3, HIGH);
  89.   digitalWrite(OUT_SER_4, HIGH);
  90.   pinMode(OUT_STATUS_DIODE, OUTPUT);
  91.   digitalWrite(OUT_STATUS_DIODE, LOW);
  92. }
  93. //----------------------------------------------------------------
  94. void setup()
  95. {
  96.   uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};
  97.   Serial.begin(115200);
  98.   Serial.println("Client start");
  99.   DefineInputOutput_SetDefaultState();
  100.   Ethernet.begin(mac); //Configure IP address via DHCP
  101. }
  102. void loop() {
  103.   //-------------------------------------------
  104.   int buttonState1 = digitalRead(IN_1);
  105.   int buttonState2 = digitalRead(IN_2);
  106.   int buttonState3 = digitalRead(IN_3);
  107.   int buttonState4 = digitalRead(IN_4);
  108.   //-------------------------------------------
  109.   if(!client.connected())
  110.   {
  111.     Serial.println("123");
  112.     digitalWrite(OUT_STATUS_DIODE, LOW);
  113.     client.connect(IPAddress(150,153,100,92),1000);  
  114.   }
  115.   if (client.connected())
  116.   {
  117.     CheckBtnMonostable(&buttonPrevState1, buttonState1, 1);
  118.     CheckBtnMonostable(&buttonPrevState2, buttonState2, 2);
  119.     CheckBtnMonostable(&buttonPrevState3, buttonState3, 3);
  120.     CheckBtnMonostable(&buttonPrevState4, buttonState4, 4);
  121.     int size;
  122.     while((size = client.available()) > 0) {
  123.        uint8_t* msg = (uint8_t*)malloc(size+1);
  124.        memset(msg, 0, size+1);
  125.        size = client.read(msg,size);
  126.        time_now = millis();
  127.        
  128.        if(msg[0] == START_ACK)  /* Sprawdź czy bit startu*/
  129.        {
  130.           if(msg[2] == CONTROL_IN_OUT_FRAME) /* Ramka zmiany stanu wejscia wyjscia*/
  131.           {
  132.             if(msg[1] == 0x02)
  133.             {
  134.               Serial.println("Client zmiana");
  135.               SetPinStatus(msg[3], msg[4]);
  136.               digitalWrite(OUT_STATUS_DIODE, HIGH);
  137.             }
  138.           }
  139.           else if(msg[2] == BTN_SERVER_CLICK)
  140.           {
  141.             digitalWrite((msg[3] + 15), !msg[4]);
  142.           }
  143.           else if(msg[2] == KEEP_ALIVE_FRAME)
  144.           {
  145.             uint8_t buf[5] = {0x01, 0x02, 0x45};
  146.            
  147.             client.write(buf, 3);
  148.             time_now = millis();
  149.             digitalWrite(OUT_STATUS_DIODE, HIGH);
  150.           }
  151.         }
  152.         else {
  153.           Serial.println("Unknown frame");
  154.         }
  155.        free(msg);
  156.      }
  157.    }
  158.    else { }
  159.    if(millis() - time_now > keepAliceMillisPeriod){
  160.       time_now = millis();
  161.       digitalWrite(OUT_STATUS_DIODE, LOW);
  162.       client.stop();
  163.       Serial.println("Con active");
  164.    }
  165. }

Wersja druga:


W tym przykładzie serwer odpytuje dwóch klientów o stan przycisków jak i o stan wyjść. Reakcja nie jest tak szybka jak w przypadku pierwszego przykładu. Natomiast zapewniona jest większa niezawodność przez ciągłą kontrolę i sprawdzanie stanu wejść oraz wyjść. 

Serwer wysyła informację najpierw do jednego a potem do drugiego klienta aby wykonał akcję np. otwarcie drzwi itp itd. Po przesłaniu zapytania do pierwszego urządzenia wysyła taką samą informację do drugiego. Gdy on również nie wykona żadnej akcji to następuje ponowne zaalarmowanie klienta 1.

Konfiguracja dla obu programów jest zapisana w wewnętrznej pamięci EEPROM. Dzięki temu po wysłaniu zmiany do programu, za pomocą odpowiedniej ramki, można układ dowolnie konfigurować.

Programy z tej części posta można pobrać z dysku Google pod tym linkiem. Głównym powodem jest ich dosyć duża objętość, która utrudnia umieszczenie ich bezpośrednio w poście.