Moduł ENC28J60
Moduł ENC28J60 pozwala na połączenie mikrokontrolerów z siecią pracującą w standardzie 10/100/1000Base-T. Moduł posiada 8-Kbyte bufor, generator CRC, automatyczny detektor kolizji (w przypadku detekcji retransmituje dane), komunikacja z mikrokontrolerem odbywa się za pośrednictwem magistrali SPI z prędkością maksymalną 20MHz.
Napięcie zasilania układu wynosi 3,3V jednak jego wejścia dopuszczają użycie 5V.
Tabela 1. Specyfikacja modułu ENC28J60
Napięcie zasilania | 3,3V |
Prędkość transmisji | 10 Mb/s |
Tabela 2. Wyprowadzenia modułu ENC28J60
Pin | Nazwa | Opis |
1 | CLKOUT | Wyprowadzenie sygnału zegarowego |
2 | INT | Interrupt |
3 | WOL | Wake On Lan |
4 | SO | Dane wychodzące (SPI) |
5 | SI | Dane wchodzące (SPI) |
6 | SCK | Zegar (SPI) |
7 | CS | Chip Select |
8 | RESET | Reset układu |
9 | VCC | 3,3v (zasilanie) |
10 | GND | GND (zasilanie) |
Układ testowy
Układ testowy został zbudowany w oparciu o mikrokotroler PIC18F2550 taktowany zewnętrznym rezonatorem kwarcowym o częstotliwości 25MHz. Do mikrokontrolera podłączono LED którego włączenie i wyłączenie realizowane jest przez generowaną przez układ stronę internetową. Dostęp do sieci realizowany jest przez moduł ENC28J60. Urządzenie jest dostępne w sieci lokalnej pod adresem http://192.168.0.6
Tabela 3. Podłączenie ENC28J60 z PIC18F2550
PIN ENC | PIN PIC |
4 (SO) | 18 (C7) |
5 (SI) | 13 (C2) |
6 (SCK) | 17 (C6) |
7 (CS) | 11 (CO) |
8 (RST) | 12 (C1) |
9 (VCC) | 8 lub 19 (VDD) |
10 (GND) | 20 (VSS) |
Program
Program dla mikrokontrolerów został napisany w CCS C. Koniecznym jest dołączenie do projektu “biblioteki” umożliwiającej obsługę protokołu TCP/IP. Została ona przeportowana do CCS C ogólnodostępnej wersji udostępnionej przez Microchip.
Głównymi elementami programu na które należy zwrócić uwagę w pliku “picweb.h” są:
- Podłączenie biblioteki TCP/IP:
#include "tcpip/stacktsk.c"//include Microchip TCP/IP Stack
- Ustawienie adresu fizycznego MAC:
void MACAddrInit(void) { MY_MAC_BYTE1=0; MY_MAC_BYTE2=1; MY_MAC_BYTE3=2; MY_MAC_BYTE4=3; MY_MAC_BYTE5=4; MY_MAC_BYTE6=5; }
- Ustawienie adresu IP, maski oraz bramy:
void IPAddrInit(void) { //IP address of this unit MY_IP_BYTE1=192; MY_IP_BYTE2=168; MY_IP_BYTE3=0; MY_IP_BYTE4=6; //network gateway MY_GATE_BYTE1=192; MY_GATE_BYTE2=168; MY_GATE_BYTE3=0; MY_GATE_BYTE4=3; //subnet mask MY_MASK_BYTE1=255; MY_MASK_BYTE2=255; MY_MASK_BYTE3=255; MY_MASK_BYTE4=0; }
Głównymi elementami programu na które należy zwrócić uwagę w pliku “picweb.c” są:
- “Uruchomienie” obsługi poszczególnych usług sieciowych z biblioteki:
//Internet Control Message Protocol, internetowy protokół komunikatów kontrolnych #define STACK_USE_ICMP 1 //Address Resolution Protocol , protokół sieciowy umożliwiający mapowanie logicznych adresów warstwy sieciowej #define STACK_USE_ARP 1 //Transmission Control Protocol/Internet Protocol, teoretyczny model warstwowej struktury protokołów komunikacyjnych #define STACK_USE_TCP 1 //Prosty serwer HTTP #define STACK_USE_HTTP 1
- Własna definicja pinów portu SPI do komunikacji z modułem ENC28J60:
#define PIN_ENC_MAC_SO PIN_C7 // PIC <<<< ENC #define PIN_ENC_MAC_SI PIN_C2 // PIC >>>> ENC #define PIN_ENC_MAC_CLK PIN_C6 #define PIN_ENC_MAC_CS PIN_C0 #define PIN_ENC_MAC_RST PIN_C1 #define PIN_ENC_MAC_INT PIN_D2 #define PIN_ENC_MAC_WOL PIN_D1 #define ENC_MAC_USE_SPI FALSE #define mac_enc_spi_tris_init()*0xF95=(0b10000110);
Powyższe definicje powodują iż układ staje się prostym serwerem stron internetowych. Uruchomienie obsługi modułu ENC28J60 jak i jego wstępna konfiguracja odbywa się w funkcji main():
MACAddrInit(); IPAddrInit(); StackInit(); delay_ms(10); while(TRUE) { StackTask(); }
Przykłady zastosowania i opisy funkcji serwera stron internetowych.
Urządzenie testowe ma na celu wyświetlenie użytkownikowi formularza w przeglądarce internetowej i umożliwienie obsługi LED. Formularz ma również informować o aktualnym stanie LED.
Uruchomienie prostego serwera stron definicją:
#define STACK_USE_HTTP 1
Dało dostęp do następujących funkcji:
- http_get_page() – obsługa wywołania strony głównej jak i podstron (np. strony informacyjnej http://192.168.0.6/info)
- http_exec_cgi() – reakcja serwera na zapytania GET wysłane formularzem z HTML
- http_format_char() – funkcja walidująca dokument HTML i podstawiająca w treść strony dynamiczne dane (np. temperaturę odczytaną przez mikrokontroler z podłączonego do niego termometru)
Kod stron internetowych w HTML
Przewidziano dwie strony. Strona główna będzie zawierać formularz sterujący LED, strona Info będzie jedynie prostym przykładem obsługi podstron i zawierać będzie komunikat informacyjny iż stronę obsługuje mikrokontroler PIC.
Obie strony będą zawierać proste menu umożliwiające poruszanie się między nimi.
Utworzono dwie zmienne zawierające źródła stron:
//zrodlo strony index const char HTML_INDEX_PAGE[]=" <HTML><BODY> <H1>PIC okH1> <AHREF=\"/info\">infoA> <hr/> <FORMMETHOD=GET> LED1:<br/> <INPUTtype=\"radio\"name=\"led1\"value=\"1\" %1>ON<br/> <INPUTtype=\"radio\"name=\"led1\"value=\"0\" %0>OFF<br/> <INPUTTYPE=\"submit\"value=\"Ustaw\"> FORM> BODY>HTML> "; //zrodlo strony info const char HTML_INFO_PAGE[]=" <HTML><BODYBGCOLOR=#FFFFFFTEXT=#000000> <H1>INFOH1> <AHREF=\"/\">glownaA> <hr/> to strona z serwera na mikrokontrolerze PIC<br/> i ukladzie ENC28J60<br/> BODY>HTML> ";
http_get_page
To podstawowa funkcja obsługująca serwer stron internetowych. Zależnie od zapytania – podstrony podanej w URL, wysyła do przeglądarki zawartość konkretnej zmiennej zawierającej źródło danej strony.
int32 http_get_page(char *file_str) { int32 file_loc=0; static char index[]="/"; static char info[]="/info"; if (stricmp(file_str,index)==0){ file_loc=label_address(HTML_INDEX_PAGE); } else if (stricmp(file_str,info)==0){ file_loc=label_address(HTML_INFO_PAGE); } return(file_loc); }
W przypadku braku podstrony w URL zostanie wysłana zawartość zmiennej HTML_INDEX_PAGE, w przypadku podania w URL podstrony “info” (http://192.168.0.6/info) zostanie wysłana do przeglądarki internetowej zawartość zmiennej HTML_INFO_PAGE.
http_exec_cgi()
Strona główna (HTML_INDEX_PAGE) zawiera formularz wysyłany metodą GET. Serwer wykrywając owo zdarzenie wywołuje funkcję http_exec_cgi()
//Obsluga GET ?led1=1 void http_exec_cgi(int32 file, char *key, char *val) { static char led1_key[]="led1"; int8 v; //led1 if (stricmp(key,led1_key)==0) { v=atoi(val); if (v) { output_high(PIN_A0); LedState = 1; } else { output_low(PIN_A0); LedState = 0; } } }
Metoda GET powoduje “dołączenie” do URL zmiennej (po znaku zapytania) oraz jej wartości np. http://192.168.0.6/?led1=1.
Wartość zmiennej led1 wynosząca 1 oznacza w tym przypadku podanie wysokiego stanu na PIN_A0 mikrokontrolera, czyli włączenie LED. Wartość wynosząca 0 (bądź inna niż jeden – gdyż użyto “else”) spowoduje podanie stanu niskiego na PIN_A0 do którego podłączony jest LED, nastąpi jego wyłączenie.
Formularz w HTML pozwala jedynie szybko skonstruować owy adres, możliwe jest włączenie, bądź wyłączenie LED wpisując w pasek przeglądarki powyższy adres który spowoduje wykonanie funkcji po stronie serwera.
http_format_char()
Funkcja zostaje automatycznie wywołana w przypadku napotkania w źródle strony znaku “%” i nazwy zmiennej. Jeśli została ona zdefiniowana (np. %0) następuje jej podmiana w kodzie na daną wartość.
W omawianym przypadku pole typu “radio” formularza HTML:
<INPUTtype=\"radio\"name=\"led1\"value=\"1\" %1>ON<br/> <INPUTtype=\"radio\"name=\"led1\"value=\"0\" %0>OFF<br/>
ma zostać ustawione automatycznie w pozycji zależnej od stanu LED, który jest przechowywany w zmiennej LedState.
Zaznaczenie pola radio w HTML można dokonać poprzez dopisanie “checked”. Zależnie od stanu zmiennej LedState jedna z dwóch zmiennych (%1 lub %0) otrzymuje wartość “checked” natomiast druga staje się pusta.
//zmienna przechowujaca stan LED, 0-wylaczona 1-wlaczona char LedState=0; // ten przyklad formatuje dwie zmienne z HTML %0 i %1 podstawiajac za nie odpowiednie wartosci int8 http_format_char(int32 file, char id, char *str, int8 max_ret) { char new_str[10]; int8 len=0; memcpy(str, 0, sizeof(str)); switch(id) { //zmienna %0 czy wylaczona case '0': if (LedState == 0){ sprintf(new_str,"checked"); } else { memset(new_str, 0, sizeof(new_str)); } len=strlen(new_str); break; //zmienna %1 czy wlaczona case '1': if (LedState == 1){ sprintf(new_str,"checked"); } else { memset(new_str, 0, sizeof(new_str)); } len=strlen(new_str); break; } if (len) { if (len>max_ret) {len=max_ret;} memcpy(str,new_str,sizeof(new_str)); } return(len); }