Witajcie moi drodzy
Ostatnio zająłem się nieco głębiej kwestią obsługi ENC28J60 w Mikro C na PIC32MX w połączeniu z nową biblioteką Network Ethernet Library z Libstocka. Jest to biblioteka dostępna za darmo i wspiera różne mikrokontrolery, nie tylko PIC.
Prędko zorientowałem się jednak, że dokumentacja tej biblioteki jest niezwykle uboga i poza przykładem 'serwera HTTP' praktycznie nie ma nawet na czym się wzorować.
W związku z tym postanowiłem opracować dla Was kilka sprawdzonych, działających przykładów jak można tej biblioteki używać. Wszystko opiszę poniżej i załączę pełne kody i projekty do pobrania.
Działanie niektórych przykładów zobrazuję również za pomocą podglądu pakietów w Wireshark.
Zawartość tematu
W temacie przedstawię cztery tryby pracy PIC32MX250F128B z ENC28J60 w MikroC PRO for PIC32, a mianowicie:
- tryb serwera TCP (TCP to protokół połączeniowy, wymaga nawiązania połączenia, zapewnia dotarcie danych do celu)
- tryb klienta TCP
- tryb serwera UDP (UDP to protokół bezpołączeniowy, po prostu wysyła dane bez procesu nawiązywania połączenia, nie daje gwarancji że pakiet dojdzie)
- tryb klienta UDP
Działanie każdego trybu dam przykład opracowany na bazie materiałów z sieci oraz dokumentacji i forum Mikro C.
Komunikacje z PICem będę testować za pomocą komputera z systemem Windows.
Do każdego z tych przykładów załączę pełny kod i projekty do pobrania, tak by zainteresowani mogli bez problemu odwzorować moje kroki i bazować na tym swoje eksperymenty.
Użyty software
Niniejszy temat opracowałem korzystając z następującego oprogramowania:
- Mikro C PRO for PIC32 - do pisania i kompilowania wsadu na PIC32
- Visual C++ 2008 Express Edition - do pisania i kompilowania kilku programów do testów komunikacji z PICem (serwer TCP, klient i serwer UDP)
- Putty - do testów serwera TCP na PICu (Putty w roli klienta TCP, tryb RAW)
- Wireshark - do podejrzenia pakietów przesyłanych między PICem a moim urządzeniem
Użyta biblioteka Ethernet od Mikro C
Na samym początku należy tutaj wspomnieć, że biblioteka Ethernetowa (obejmująca ENC28J60 i podobne) dostępna wraz z IDE Mikroelektroniki jest wersją ograniczoną i przestarzałą. Również przestarzała jest dokumentacja dostępna w sieci:
https://download.mikroe.com/documents/compilers/mikroc/pic32/help/ethernet_library.htm
Ta biblioteka powyżej nie wspiera nawet trybu klienta TCP PICa (czyli możemy na niej postawić serwer HTTP na PICu, ale nie możemy z PICa podłączyć się do innego serwera).
Najbardziej aktualna wersja biblioteki Ethernetowej dostępna jest do ściągnięcia osobno, tutaj, na Libstock:
https://libstock.mikroe.com/projects/view/107/network-ethernet-library
Ostatnia wersja tej biblioteki pochodzi z 2017-07-28 i oferuje:
Cytat:
Ethernet library designed to simplify handling of the underlying hardware (ENC24J600/ENC28J60 and internal ethernet module on PIC18, PIC32, FT90x, TIVA and STM32).
Library supports: TCP Stack (Server/Client), UDP, IPv4 protocol, ARP requests, ICMP echo requests, ARP client with cache, DNS client, DHCP client.
Bibliotekę tą instalujemy poprzez 'Package Manager' od MikroE. Sama biblioteka jest darmowa, więc załączam tutaj jej kopię zapasową:
Biblioteka ta niestety jest bardzo uboga w przykłady i dokumentacje - dlatego postaram się to w tym temacie naprawić.
Użyta płytka z PIC32MX
Projekt zrealizowałem na własnej płytce developerskiej z PIC32MX250F128B w obudowie DIP, konwerterem USB-UART MCP2221 i dodatkowymi peryferiami:
O tej płytce więcej można poczytać tutaj:
https://www.elektroda.pl/rtvforum/topic3554375.html
Jednakże do projektu nie użyłem żadnego z dodatków znajdujących się na tej płytce, jedynie samego PIC32MX250F128B z niezbędnym osprzętem do uruchomienia (przycisk RESET, itp), więc w zasadzie moje kroki można odwzorować nawet na płytce stykowej z samym PICem.
Użyty moduł ENC28J60
W projekcie użyłem gotowego modułu ENC28J60 z Aliexpress. Moduł podpisany jest "MNI ENC28J60" (ale pewnie chodziło im o MINI) i korzysta ze złącza Magjack HanRun HR91105A 15/10.
Moduł kosztuje jakieś 3$, czyli około 13 złotych.
Zdjęcia modułu:
Moduł ten podłączamy do mikrokontrolera poprzez interfejs SPI.
W przypadku tego modułu warto pamiętać, że jest on zasilany z napięcia 3.3V (tak jak użyty tutaj PIC32) a nie 5V i 5V może go uszkodzić.
Moduł ten dostępny jest w wielu sklepach online, czasem występuje w innych wersjach (nieco inne PCB) ale to nie robi nam żadnej różnicy.
Podłączenie płytki i modułu
Dla wszystkich zaprezentowanych tu przykładów hardware będzie ten sam - tak jak na zdjęciu poniżej.
Zasadniczo mamy tu ENC28J60 podłączony do portu SPI z PIC32MX250F128B wraz z niezbędnym osprzętem, konwerter USB-UART bym mógł na wirtualnym porcie szeregowym wypisywać co się dzieje na PICu (w razie potrzeby) i oczywiście programator PICKIT3 bym miał jak wgrywać wsad na PICa.
Samo podłączenie wyprowadzeń przedstawiłem na obrazku poniżej:
Na początek trzeba zwrócić uwagę na jedno - PIC32MX oferuje tzw. PPS - Peripheral Pin Select, czyli funkcjonalność która pozwala w miarę możliwości zmapować różne peryferia na różne piny.
To jakie peryferia możemy zmapować na jakie piny wyszczególnione jest w nocie katalogowej PICa - są pewne ograniczenia.
Również nie wszystkie funkcje można tak mapować - SCK na przykład jest tutaj niemapowane, znajduje się na sztywno na pinie 25 (w przypadku obudowy DIP28).
Podsumowując, połączenia jakie wykonałem to:
- (oczywiście) zasilanie modułu ENC do 3.3V i masa do masy
- pin RST (RESET) do RB9 - to może być dowolny pin digital
- pin CS (Chip Select) do RB15 - to może być dowolny pin digital
- pin SCK (sygnał zegara SPI) - do SCK1, pin 25 na DIP28, to musi być ten pin, nie jest remapowalny
- pin DI (data input SPI) - do SDO zremapowanego za pomocą PPS na RB13
- pin DO (data output SPI) do SDI zremapowanego za pomocą PPS na RB8
- pin CLK (wyjście zegara ENC) - niepodłączony
- pin INT (przerwanie od ENC) - niepodłączony
- pin WOL (wake on LAN od ENC) - niepodłączony
Nie użyłem żadnych konwerterów poziomów logicznych bo zarówno ENC jak i PIC32 korzystają z 3.3V.
Podłączenie płytki i modułu - konfiguracja w kodzie
Wszystkie poniżej przedstawione kody korzystają z jednej konfiguracji pinów i IP, którą przedstawię poniżej.
Piny SDI/SDO ustawione są za pomocą PPS:
Kod: C / C++
Wykonywane jest to raz na starcie PICa. Warto pamiętać, że w bitach konfiguracji może być włączona 'jednokrotne ustawienie PPS' i ponowna próba zmiana roli pinów może nie zadziałać.
Piny CS/RST są ustawione w klasyczny sposób jako rejestry:
Kod: C / C++
Są to jak już wspomniałem zwykłe piny w trybie digital i można zastąpić je dowolnymi innymi wolnymi wyprowadzeniami.
Parametry IP naszego urządzenia oraz sieci w której się znajduje zdefiniowane są w tym fragmencie kodu:
Kod: C / C++
Dodatkowo poniżej są dwie linijki opcjonalne, tylko dla przykładów w trybie klienta, czyli namiary na serwer - jego IP i port:
Kod: C / C++
Warto tutaj wspomnieć, że użyta biblioteka również obsługuje dynamicznie przypisywane adresy IP czyli DHCP - ale ja tutaj tego nie użyłem i wpisałem IP na sztywno.
PIC32 w roli serwera TCP
Na początek zrealizowałem coś prostego.
Najczęściej właśnie spotykałem się z ENC28J60 w roli serwera, a przynajmniej w połączeniu z PICami i to było wspierane już w pierwszych wersjach biblioteki dla ENC28J60.
Więc sprawdzenie tego powinno być tylko formalnością.
Najczęściej taki serwer jest serwerem HTTP (protokół HTTP korzysta z TCP), ale ja dla odmiany postanowiłem zrobić prosty serwer komend odbierający dane wysyłane przez program Putty.
Na PICu zaimplementowałem prosty system odbioru komend tekstowych i trzy komendy które zasadniczo nie robią nic - tylko odpowiadają wybranym komunikatem. Dodatkowo dałem komendę 'bye', która zamyka socket połączenia TCP po stronie PICa oraz komendę 'port', która wysyła z jakiego numeru portu się połączyliśmy.
Oto najważniejszy fragment kodu:
Kod: C / C++
Raczej kod mówi sam za siebie. Funkcja Net_Ethernet_28j60_UserTCP musi nazywać się tak jak nazywa, gdyż wywołuje ją automatycznie biblioteka Ethernetowa.
W Putty natomiast połączyłem się z tym serwerem w trybie RAW:
Po połączeniu dało się wysyłać komendy, wielokrotnie bez zerwania połączenia:
Komenda 'bye' również działała poprawnie, ale nie mam jak tego pokazać na zrzucie ekranu, bo ona momentalnie zamykała program Putty (całkowicie z niego wychodziła).
Następnie wykonałem test dwóch klientów na raz - otworzyłem dwie instancje Putty i przetestowałem różne komendy z 'port' włącznie.
Pierwszy klient miał port 8699 a drugi 8746 i oba były w stanie wysyłać komendy jednocześnie.
Proces podłączenia TCP można również bardzo dobrze zobrazować za pomocą narzędzia Wireshark, które pozwala podejrzeć przesyłane przez sieć pakiety.
Na początku, po podłączeniu się przez Putty nawiązywany jest tunel TCP ale nie ma przesłanych jeszcze właściwych dla nas danych:
Na powyższym zrzucie ekranu z Wiresharka widać tzw. słynny handshake TCP, czyli wymianę pakietów kolejno z flagami: SYN, potem: SYN i ACK i końcowo: ACK.
Cały handshake dzieje się dla nas w sposób niewidoczny i obsługuje go soft biblioteki Ethernet.
Po chwili nieaktywności pojawia się kolejny pakiet, który służy tylko na celu podtrzymania połączenia:
W momencie gdy wyślemy tekst przez Putty widzimy, że pojawia się znacznie więcej pakietów niż można by się spodziewać - to dlatego, że korzystamy z TCP który jest protokołem połączeniowym i gwarantuje dostarczenie danych, więc odebranie każdego fragmentu pakietu potwierdza tzw. ACK:
Następnie połączenie zakończyłem wysyłając poprzez Putty komende 'bye' która wywołuje zamknięcie socketu na PICu i jej rezultat w pakietach wygląda tak:
Wnioski:
- nasz serwer jest w stanie obsłużyć co najmniej dwóch klientów jednocześnie (utrzymuje obu połączenie TCP bez wymogu reconnectu)
- po stronie PICa możemy zerwać połączenie kiedy chcemy (za pomocą Net_Ethernet_28j60_disconnectTCP)
- Putty wysyła osobnym pakietem \r\n po wciśnięciu enter i to trzeba obsłużyć (jest w ifie w powyższym kodzie)
- z serwerem TCP nie ma żadnych problemów
- tak samo można zrobić serwer HTTP
Załączniki:
- projekt Mikro C na PIC32 - PIC32MX250F128B_8MHz_SPI_ENC28J60_Lib_TCP_SV.zip:
PIC32 w roli klienta TCP
Teraz zaprezentuję coś przeciwnego, czyli PIC32 tutaj będzie klientem TCP. PIC32 nawiąże połączenie z serwerem i wyśle mu jakieś dane (na początek jakiś krótki tekst).
Ten tryb pracy w Mikro C jest nieco bardziej problematyczny i początkowo nie był wspierany - nie jest wcale wspierany w podstawowej wersji biblioteki Ethernetowej, tej którą dostajemy z kompilatorem.
Do tego trybu pracy potrzebny będzie nam jakiś serwer - ja użyłem gotowego, prostego serwera napisanego w języku C za pomocą bibliotek Winsock pod Windowsa, stąd:
https://docs.microsoft.com/en-us/windows/win32/winsock/complete-server-code
Lekko ten kod zmodyfikowałem, tak by zliczał ilość odebranych pakietów i po przetworzeniu połączenia oczekiwał na kolejne.
Całość skompilowałem w Visual C++ 2008 Express Edition, ale oczywiście powinno się to dać skompilować w wielu różnych IDE/kompilatorach C/C++ pod Windowsa.
Oto zrzut ekranu z pomyślnej kompilacji programu z Visuala:
I dla równowagi - zrzut ekranu z Mikro C PRO for PIC32:
Kod z MikroC bazuje tutaj na funkcji Net_Ethernet_28j60_connectTCP która nawiązuje połączenie TCP z serwerem o danym IP i porcie. Następnie po udanym połączeniu transfer danych rozpoczynany jest za pomocą Net_Ethernet_28j60_startSendTCP, a same dane są przesyłane w Net_Ethernet_28j60_UserTCP.
PIC wysyła dane do serwera tylko raz, jeśli chcemy by wysłał ponownie to musimy go zresetować (najwygodniej przyciskiem RESET z płytki, ale można też przez programator).
Rezultat działań na Windowsie:
Wygląda na to, że wszystko działa.
Wnioski:
- nowa biblioteka od Mikro C pozwala użyć nam PICa w roli klienta TCP
- przed wysyłaniem danych przez TCP musimy nawiązać w pełni poprawne połączenie i oczekiwanie na to jest zrealizowane w kodzie
Załączniki:
- projekt Visual Studio na Windows - msvc_PIC32eth_TCPserver.zip:
- projekt Mikro C na PIC32 - PIC32MX250F128B_8MHz_SPI_ENC28J60_Lib_TCP_CL.zip:
Zaraz spróbujemy wysłać coś więcej.
PIC32 w roli klienta TCP - duże ilości danych
Sprawdziłem już proste przesyłanie krótkiego fragmentu przez TCP, więc teraz pora na coś bardziej praktycznego.
Czyli na przesłanie większej ilości danych - najlepiej kilkunastu kilobajtów - by też sprawdzić przy okazji czy biblioteka od Mikro C wspiera fragmentacje pakietów.
W tym celu odpowiednio zmodyfikowałem program na PICa tak, by kolejno wysyłał liczby. Użyłem do tego funkcji sprintf.
Przyjąłem, że każda liczba będzie wysyłana jako tekst ASCII i będzie oddzielona zerem (bajtowym, NULL terminating character) od kolejnych liczb.
TCP nie gwarantuje tego, że pakiet nie zostanie podzielony na części, więc po stronie Windowsa musiałem odpowiednio buforować odebrane dane i ponownie dzielić je na fragmenty tekstu reprezentujące liczby.
Ilość liczb do wysłania określiłem za pomocą #define:
Kod: C / C++
Oto najważniejszy fragment kodu z PICa:
Kod: C / C++
A same liczby są generowane w ten sposób:
Kod: C / C++
Po stronie serwera dodałem również nieco statystyk transferu, czas połączenia, zliczanie liczby odebranych bajtów oraz średniej prędkości przesyłu.
Pora sprawdzić, czy to wszystko działa.
Serwer na Windowsie szybko łapie połączenie:
I na naszych oczach widać jak przesyłane są kolejne liczby:
Ostatecznie, transfer dochodzi do końca i nie gubi żadnej liczby (warunek if nie obejmuje 15000):
Z zebranych danych też widzimy, że prędkość transmisji wyniosła tutaj około 3700 bajtów na sekundę a w sumie przesłano 77 748 bajtów.
Po zresetowaniu PICa transfer następuje ponownie (bez potrzeby resetowania serwera na Windowsie):
(proszę zignorować te #INF00, to wynik dzielenia przez zero, a dokładniej przez zerowy czas w sekundach w momencie rozpoczęcia transferu).
Same pakiety TCP można łatwo podejrzeć w narzędziu Wireshark (za pomocą filtru ip.addr == 192.168.0.77):
Wnioski:
- nie jesteśmy tu limitowani rozmiarami buforów RX/TX z ENC28J60 - jesteśmy w stanie na bieżąco generować naprawdę duże pakiety i wysyłać je w obrębie jednego połączenia TCP
- ze strony ENC28J60 mamy możliwość zamknięcia połączenia TCP i serwer się o tym dowie (nie trzeba czekać na timeout)
Załączniki:
- projekt Visual Studio na Windows - msvc_PIC32eth_TCPserver_bigData.zip:
- projekt Mikro C na PIC32 - PIC32MX250F128B_8MHz_SPI_ENC28J60_Lib_TCP_CL_BigPackets.zip:
PIC32 w roli serwera UDP
UDP jest nieco łatwiejsze niż TCP ponieważ jest bezpołączeniowe (nie wymaga ustanowienia połączenia między hostami, tylko wysyła pakiety 'w ciemno'), ale i tak przygotowałem dla niego przykład.
Serwer UDP robimy na PICu podobnie jak serwer TCP - po prostu używamy funkcji Net_Ethernet_28j60_UserUDP zamiast Net_Ethernet_28j60_UserTCP. Zmienia się też sposób wysyłania danych oraz musimy z funkcji zwrócić ilość bajtów jaką dajemy do wysłania, reszta działa analogicznie.
Oto najważniejszy fragment kodu z PICa:
Kod: C / C++
Do testowania tego użyłem prostego klienta UDP napisanego w C, pobranego stąd:
https://github.com/youFrivolous/sw-project/wiki/UDP-TCP-Example-in-C
Odpowiednio go zmodyfikowałem, m. in. by pokazywał długość odebranych pakietów.
Oto rezultat działania na Windowsie:
Jak widać komunikacja działa, numer portu można by lepiej sformatować (jako unsigned int) ale to tylko kosmetyka.
Bardzo ładnie można przedstawić tą komunikację w Wiresharku - widać tam pakiet przesłany od nas do serwera i jego odpowiedź zwrotną (na zrzucie ekranu jest zarówno okno mojego programu na Windowsie jak i przebieg pakietów):
Wnioski:
- serwer UDP robimy na PICu analogicznie tak jak TCP, ale należy pamiętać by zwrócić rozmiar odpowiedzi z funkcji odbierającej pakiet
- struktura przekazywana przez bibliotekę Ethernetową do funkcji obsługującej pakiet UDP jest inna niż ta od TCP i nie należy ich pomylić (wtedy program się skompiluje ale na pewno nie zadziała jak powinien, bo struktury mają inne pola)
Załączniki:
- projekt Visual Studio na Windows - msvc_PIC32eth_UDPclient.zip:
- projekt Mikro C na PIC32 - PIC32MX250F128B_8MHz_SPI_ENC28J60_Lib_UDP_SV.zip:
PIC32 w roli klienta UDP
Ten przykład będzie chyba najprostszy. Wysłanie pakietu po UDP w omawianej bibliotece sprowadza się do użycia jednej funkcji: Net_Ethernet_28j60_sendUDP.
Oczywiście, nawet ta funkcja nie ma swojej dokumentacji w sieci - po wyszukaniu w Google znajdujemy tylko forum Mikro C:
Ale nam to nie przeszkodzi z niej skorzystać.
Używa się jej tak:
Kod: C / C++
Argumenty funkcji to adres IP celu (w postaci czterech bajtów), port docelowy, port źródłowy, dane pakietu oraz długość pakietu.
W oparciu o to zrealizowałem prosty program na PIC który wysyła do mojego komputera bieżący czas w sekundach (zliczany przerwaniem). Kod z głównej pętli main:
Kod: C / C++
Kod przerwania:
Kod: C / C++
Do testowania tego użyłem prostego serwera UDP napisanego w C, pobranego stąd:
https://github.com/youFrivolous/sw-project/wiki/UDP-TCP-Example-in-C
Ten serwer po prostu oczekuje na dane i wypisuje ja na ekranie. Wypisuje też informacje skąd je odebrał (jaki port oraz adres IP).
Oto rezultat działania:
Jak widać kolejne pakiety są poprawnie odbierane.
Jest to tryb bezpołączeniowy, UDP, więc możemy całkiem wyłączyć serwer, włączyć go od nowa i pakiety będą dalej odbierane:
Jeśli w trakcie wysyłania danych np. wyjmę przewód Ethernowy z modułu ENC28J60, to widać że brakuje paru pakietów:
Same pakiety UDP można łatwo podejrzeć w narzędziu Wireshark (za pomocą filtru ip.addr == 192.168.0.77):
Wnioski:
- wysyłanie UDP w tej bibliotece realizujemy po prostu wywołując jedną funkcję
- widać tu bardzo ładnie że UDP jest protokołem bezpołączeniowym i wysyła pakiet tylko raz, nie sprawdza wcale czy doszedł do celu (w przeciwieństwie do TCP, który wspiera retransmisje)
Załączniki:
- projekt Visual Studio na Windows - msvc_PIC32eth_UDPserver.zip:
- projekt Mikro C na PIC32 - PIC32MX250F128B_8MHz_SPI_ENC28J60_Lib_UDP_CL.zip:
Co można by dalej zrobić?
Przedstawiłem tutaj zasadniczo tylko podstawy pracy z TCP i UDP w oparciu o słabo udokumentowaną bibliotekę z Libstocka i moim celem było raczej "danie komuś wędki" a nie przygotowanie pełnego gotowca.
Na bazie przedstawionych tutaj przykładów komunikacji można:
- zrobić prosty serwer HTTP na PICu (oczywiście)
- zrobić prosty klient HTTP na PICu (wysyłający np. dane o temperaturze do naszego serwera postawionego na np. darmowym serwerze hostingowym HTTP w sieci Internet)
- zrobić prosty klient HTTP usługi czas/pogoda (pobieranie pogody, czasu dla lokacji)
- zrobić prosty klient RSS (pobieranie newsów tekstowych)
- zrobić własny klient (lub serwer) FTP w oparciu o PICa
- i wiele, wiele innych
Co do moich testów prędkości transferu TCP, chciałbym tylko zaznaczyć, że wykonywane one były po sieci LAN (PIC przewodowo do routera, router przez WiFi do komputera) więc oczywiście prędkość ta w przypadku przesyłania przez internet pewnie będzie o wiele mniejsza.
Podsumowanie
Przedstawiłem tutaj cztery tryby pracy ENC28J60 w oparciu o PIC32MX250F128B i bibliotekę Network Ethernet Library dla Mikro C PRO for PIC32 z LibStocka.
Działanie każdego trybu sprawdziłem za pomocą mojego komputera z systemem Windows i udokumentowałem tutaj zrzutami ekranu.
Wszystkie opracowane kody i projekty dałem do pobrania - w razie problemów z ich użyciem postaram się pomóc.
Na koniec chciałbym jeszcze zaznaczyć, że użyta tutaj biblioteka wspiera różne mikrokontrolery, nie tylko PIC32, więc nawet jeśli robicie coś pod inną rodzinę PICów w Mikro C to też możecie użyć moich przykładów.
Fajne? Ranking DIY Pomogłem? Kup mi kawę.
