2014/04/14

Arduino Library: EtherCard for ENC28J60 Chip

買了一個ENC28J60的模組需要用到EtherCard函式庫(說明文件)
摸索數天後記錄一些使用心得
這個專案主要功能為:
  • Web Server 網頁伺服器
  • Client傳送From參數後轉發(POST)到另一台SERVER,並回傳文字或控制DI/DO



基本設置
#include <EtherCard.h>
static byte mac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
static byte ip[] = { 192,168,1,200 };
static byte gw[] = { 192,168,1,1 };
static byte dns[] = { 192,168,1,1 };
static byte server[] = { 192,168,1,100 };

byte Ethernet::buffer[800];
BufferFiller bfill;
char msg_str[20]; 
mac    每張網卡的身分證字號,我直接COPY別人的範例,不要跟網路上電腦重複就好
ip       此張網卡的IP,也是Arduino的IP
gw      gateway閘道器,如要傳送到不同網段需要透過這台
dns     網域名稱伺服器,解析網址用的(專案沒用到)
server 認證伺服器的IP,如果是用Domain要用另個方法

Ethernet::buffer[800]
給EtherCard用的緩衝區,儲存 內送/外寄 的封包,因為收發共用,如果同時要收發就要小心;800是緩衝區大小,太大Arduino記憶體會爆炸,就會出現很多奇怪的問題(淚,太小Web Server的HTML會送不出去(所以HTML不能太複雜)。

bfill 用來準備送HTML的緩衝區,其實有其他方法可以用,但是這個我覺得最簡單。
msg_str 存稍後Server回送的訊息用。

setup()
void setup () {
  Serial.begin(57600); 
  if (ether.begin(sizeof Ethernet::buffer, mac, 10) == 0){
    Serial.println( "Failed to access Ethernet controller");
  }
  if (!ether.staticSetup(ip, gw, dns)){
    Serial.println( "could not get a static IP");
  }else{
    ether.printIp("IP:  ", ether.myip);
    ether.printIp("GW:  ", ether.gwip);  
    ether.printIp("DNS: ", ether.dnsip); 
  }
  ether.copyIp(ether.hisip, server );
}
Serial.begin(57600)
開啟串列埠除錯,速度是57600,要跟Serial Monitor的速度一樣,不然會亂碼

ether.begin(sizeof Ethernet::buffer, mac, 10)
初始化網卡,參數為 緩衝區, MAC位址(在前面定義過的),
10 為模組上的CS接到Arduino的接腳編號,不寫預設是8;因為10~13這樣接很整齊,所以改接10

ether.staticSetup(ip, gw, dns)
設定該網卡的ip, gateway, dns。可以只傳ip進去

ether.printIp
會把設定進去的IP列印出來,可以做為確認

ether.copyIp
設定遠端伺服器的IP,沒有連伺服器可以刪掉


loop ()
void loop () { 
  word len = ether.packetReceive();
  word pos = ether.packetLoop(len);
  char *data = (char *) Ethernet::buffer + pos;
  if (pos){  
    if(strstr(data, "GET /msg") != 0) {
      ether.httpServerReply(msg());
    }
    if(strstr(data, "GET / ") != 0) {
      ether.httpServerReply(homePage()); // send web page data      
    }
    if(strstr(data, "POST /open") != 0 && strstr(data, "\r\n\r\n") != 0 ){
      Serial.println("Sending POST to REMOTE");
      
      char *buf;
      buf = (char *)malloc(strlen(strstr(data, "\r\n\r\n"))+1);
      strcpy(buf, strstr(data, "\r\n\r\n")+4);
      Serial.println( buf );
      
      ether.httpServerReply(doorOpen()); // send web page data 
      
      ether.printIp("SRV: ", ether.hisip); 
      ether.httpPost(PSTR("/dooraccess/index.php"), PSTR(""), NULL, buf, my_result_cb);
    }
  }
}
ether.packetReceive()  如有接收到Client的訊息會回傳到收到的長度
ether.packetLoop(len)  回傳訊息從哪裡開始,不是0表示有收到東西
簡單說:
當網卡收到封包時,會存到Ethernet::buffer這裡面,然後利用這兩個函數抓出訊息從哪開始,總長度多少;也就是收到的訊息是從 Ethernet::buffer + pos 開始,總共有len個位元。如果沒有加pos的長度,印出來會有怪怪的東西在前面。

因為後面要處理字串,(char *) Ethernet::buffer + pos 這樣實在太長,所以用另個char指標data儲存。

=================
要看懂下面的東西就要了解一下HTTP 1.1的封包範例
GET / HTTP/1.1
Host:localhost
Client打開瀏覽器到Arduino會送出這樣的封包
要知道使用者是要甚麼樣的頁面。就要看GET 後面接甚麼;
=================
strstr(data, "GET / ")  搜尋 data有沒有 GET /  的字串,注意有多個空白
ether.httpServerReply( homepage() ); 回傳一個HTML網頁,該網頁是從homepage()處理

homepage()
static word homePage() {
  bfill = ether.tcpOffset(); 
  bfill.emit_p(PSTR(
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Pragma: no-cache\r\n"
    "\r\n"
    "<!DOCTYPE html>"
    "<html lang='en'>"
    "<head>"
    "<meta charset='utf-8'>"
    "<title>CC Door Access Authenticate</title>"
    "</head>"
    "<body>"
    "<div style='width:350px;height:170px;border:4px solid #000;position:absolute;top:50%;left:50%;margin-top:-85px;margin-left:-175px'>"
    "<center><h4>CC Door Access Authenticate</h4></center>"
    "<form method='post' action='open'>"
    "<table border='0' cellspacing='0' cellpadding='5' width='100%' height='100%'>"
    "<tr><th>Account</th>"
    "<td><input type='text' name='account'></td></tr>"
    "<tr><th>Password</th>"
    "<td><input type='password' name='password'></td></tr>"
    "<tr><td colspan='2' align='center'><input type='submit' value='open'></td></tr>"
    "</table>"    
    "</form>"
    "</div>"
    "</body>"
    "</html>"));
    return bfill.position();
}
這個函式會回傳一個HTML網頁,該網頁有個Form表單會傳送值回Arduino
文字多寡視Ethernet::buffer多大,通常越精減愈好。

bfill = ether.tcpOffset()  使用前要先對bfill初始化
bfill.emit_p 把接下來的字串放進緩衝區
PSTR
我的理解是會把包起來的文字放進去EEPROM內,以解省記憶體空間,適合放一大串文字;
裡面的文字用雙引號包起來 多行不用分隔符號。

return bfill.position()
最後回傳bfill的位址讓httpServerReply知道從哪裡抓資料

HTML放變數的方法
static word msg() {    
  bfill = ether.tcpOffset(); 
  bfill.emit_p(PSTR(
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Pragma: no-cache\r\n"
    "\r\n"
    "$S"
  ), msg_str );
  return bfill.position();
}
就是在雙引號加上$S,其實還有以下幾種,選錯就會亂碼會奇怪的結果;然後在emit_p後方當參數傳進去(msg_str)。
$D = word data type
$L = long data type
$S = c string
$F = progmem string
$E = byte from the eeprom
(資料來源)


接收POST資料
    if(strstr(data, "POST /open") != 0 && strstr(data, "\r\n\r\n") != 0 ){       //&& timer > millis()
      Serial.println("Sending POST to REMOTE");
      
      char *buf;
      buf = (char *)malloc(strlen(strstr(data, "\r\n\r\n"))+1);
      strcpy(buf, strstr(data, "\r\n\r\n")+4);
      Serial.println( buf );
      
      ether.httpServerReply(doorOpen()); // send web page data 
      
      ether.printIp("SRV: ", ether.hisip); 
      ether.httpPost(PSTR("/dooraccess/index.php"), PSTR(""), NULL, buf, my_result_cb);
    }

跟GET / 類似,只是改成POST,open為Form內的Action指定的
"\r\n\r\n" 是收到封包內的內容分隔符號,表示接下來的都不是Header;檢查一下比較保險。

4~7行是將post過來的值存起來,前面有提到Ethernet::buffer是共用的,所以需要的資料先存到另個地方避免被蓋掉。

其中有用到幾個函數
malloc 定義記憶體空間大小,字串後方會多接一個 '\0' 所以算出來要+1
strlen  計算字串長度; String Length
strcpy
拷貝字串到另個變數,不能用 buf = copiedString; 這樣只是存該記憶體指標;String Copy。

ether.httpServerReply(doorOpen())  回傳doorOpen裡面的網頁
ether.printIp("SRV: ", ether.hisip);    印出Server IP,只是為了Debug
ether.httpPost()
傳送POST到遠端伺服器,這邊是要到遠端做認證,Arduino我主要拿來做控制硬體,認證就交給好寫多了的PHP Server。

參數依序為 伺服器網址(請用PSTR包), Server的Domain Name(我沒有所以放空值), 額外的Header(沒有所以放 Null), POST的參數, 伺服器回傳的回呼函數(callback)

GET / HTTP/1.1
Host:(Server的Domain Name會放在這邊) 

POST接收伺服器的函數
static void my_result_cb (byte status, word pos, word len) {  
  char *tmp = (char *) Ethernet::buffer + pos;
  Serial.print("<<< reply ");
  Serial.println(tmp);  

  if( strstr(tmp, "true") != 0 ){
      /* Pin On */
      Serial.println("SW ON");
      strcpy(msg_str, "Door is Opened" );
  }else{
      strcpy(msg_str, "ERROR: " );
      strcat(msg_str, strstr(tmp, "\r\n\r\n")+4 );
  }
}
傳到Server後,Server會回傳訊息(就像瀏覽器會顯示畫面一樣),需要用到這個函數處理
如果Server回傳'true',就讓PIN 通電做硬體控制,不是就輸出SERVER給的訊息
說明如下:
status      HTTP的CODE,200為成功,其他請參考WIKI
len, pos   ether.packetReceive(),  ether.packetLoop(len) 的回傳值。
tmp         (char *) Ethernet::buffer + pos 變數的捷徑
strcat     字串連接,把後面的字黏到前面變數的尾端。


Watch Dog設置
由於Server認證穩定度不是很高(網路不穩),也怕被攻擊,我可不想三天兩頭重開Arduino,藉由設置Watch Dog可以讓Arduino當機時自動復原。
1. 程式開頭引入Library
#include <avr/wdt.h>

2.setup() 內加入1秒的watch dog
wdt_enable (WDTO_1S);
其他的時間請參考這邊

3.loop() 加入重置
wdt_reset ();
不加這行每一秒就會自己重開= =


1 則留言:

  1. Mang thai hộ là gì?Không phải bất kì người phụ nữ nào cũng có khả năng sinh con được, nhiều yếu tố khiến không ít người phụ nữ phải gặp phải tình trạng vô sinh
    Mang thai 3 tháng cuối cần chú ý những gì?Giữ cho mình một tinh thần thoải mái, thư giãn. Có thể đăng ký vài suất massage thư giãn cho mẹ bầu để giảm bớt mệt mỏi và áp lực trong những ngày cuối thai kỳ.
    Tư thế nằm ngủ có lợi cho sức khoẻ của bà bầuGiấc ngủ đối với chúng ta là hết sức quan trọng, nó ảnh hưởng nghiêm trọng đến tình trạng sức khỏe, cũng như tinh thần của con người.
    Lợi ích của nước đối với sức khoẻ bà bầuTáo bón là một vấn đề ở đường tiêu hóa xảy ra khá phổ biến, nhất là trong thời kỳ mang thai. Táo bón tuy không quá nguy hiểm, nhưng rõ ràng khi bị mắc táo bón sẽ chẳng dễ chịu chút nào
    Khi mang thai ăn trứng ngỗng có tác dụng gì?Các ông bà xưa thường có câu: Ăn 7 trứng ngỗng sẽ sinh con trai, ăn 9 trứng ngỗng sinh con gái, chính vì thế mà mọi người tin tưởng vào việc ăn trứng ngông sẽ có tác dụng.
    Trứng đà điểu có tác dụng gì?Trứng đà điểu còn được dùng để quấy cùng bột cho trẻ em, món ăn đầy dinh dưỡng và giúp trẻ thông minh hơn.
    Dấu hiệu bà bầu sắp sinhĐã đến đến tháng của thai kì, thì bất kì người phụ nữ nào cũng tò mò, bồn chồn trông ngóng thiên thần mình được chào đời.
    Bà bầu ăn mực có sao không?Nhưng cũng có nhiều người có ý kiến rằng ăn mực chứa nhiều vitamin, canxi, protein rất tốt cho bà bầu và sự phát triển của thai nhi.
    Bệnh gút là gì?Cuộc sống càng ngày càng phát triển, con người có cuộc sống ấm no, đầy đủ hơn trước, nhưng chính điều này lại khiến nhiều người mắc bệnh nhiều hơn.
    Bệnh gút và cách điều trịBệnh gút là bệnh thuộc dạng viêm khớp, bệnh thường gặp ở nam giới nhiều hơn với các nữ giới, đây là bệnh khiến nhiều người lo lắng vì các cơn đau khó chịu từ các khớp
    Cách chữa bệnh gút hiệu quả bằng thảo dượcBệnh gút còn được gọi là căn bệnh nhà giàu, thường gặp phải ở những người trung niên, đặc biệt là ở nam giới

    回覆刪除