OVERALL HOME    Other material for programmers, not Arduino specific
ARDUINO: Top Page    Branch "A": Arduino Course    Branch "B": Arduino "How To"s and Projects
ArduServer and AdruSimpSrv: HOME
-d- Bookmark this on Delicious   Recommend to StumbleUpon

Arduino as web server with an ENC28J60: ArduServe

The Sourcecode

Input and output locally or across the internet

This page is browser friendly, by the way. Make your browser's window less wide than your whole screen and you will find the narrower columns much easier to read. For more tips, see my Power Browsing hints.

This page is not(?) enough

I strongly recommend that you at least skim through the information on my page about how this code was developed, but if you want just "the answer", here it is. At least here is the Arduino half of things. You use an ordinary web browser, e.g. Firefox, for the client. You can access an ArduServer over your LAN or across the internet.

ArduServe with ENC28J60....

(This page was started in 2011. Please see my "March 2015 ENC28J60 update" page before buying or using one of them! (That page will open in a new tab. Just close it to come back here.))

You will have to change one line if you don't want to use port 80.

You will have to change a second line if you want access across the internet, or if you haven't put your ArduServer on and are happy with mere LAN access.

If your LED is not on D8, and your phototransistor on A3, there will be other simple, single-line changes to make. The "beeper" for reporting a visit by a client goes on D8. (The hardware for the beeper should have a built in oscillator... as long as D8 is high, the sound should continue.)

Other than that, it should Just Work... if your interface is the ENC28J60. If you don't know, you may well have a W5100.

STOP PRESS: It seems that maybe there's an element of bad advice in some of the pages this is associated with.

It may be a bad idea to use any of the following at the same time as a W5100 based shield....

... and D13, but this last one only if you "care" that on the shield it goes to an LED, and from there through a resistor to ground. (This merely parallels the same thing on many Arduino and clone boards... BUT, to digress from the digression, BE AWARE that the software behind "setup()" and "loop()" pulse D13 high briefly before executing whatever you specified in "setup()".)

While the software this page is about is for the ENC28J60, the day may come when you wish to transfer the project to W5100 based hardware. As long as you use the ENC28J60, on the NuElectronics shield anyway, you only need to avoid D2 and D10-13. The shield also connect to ground, of course. It draws 5v from the main board, and has a connection to the reset line. (I actually had an ArduServer working fine for a few months with no connection to D2, but a re-examination of the schematic suggests that I ought to have a connection there. It is an output from the shield. Who knows what the ethernet library wants to see coming on on D2, but I'm not leaving the signal to chance.)

The ArduServer's hardware and software (from ver of 24Oct11) have been "upgraded" to respect these new design goals.

Modifying the software was easy.

To change the line used for, say, the phototransistor watching the LED in the line....

int8_t sensorPin = A1;  // designate the input pin for a sensor... a phototransistor used in example

...was changed to...

int8_t sensorPin = A3;  // designate the input pin for a sensor... a phototransistor used in example

... and that was all that was needed. I hadn't "planned" to need to change the allocation of the sensor to that analog input... but writing the code that way was just a matter of good programming. (I had to move one wire on your breadboard, too, of course, to move on from the original design.)

Sorry! I could have done without this, too! When will the Official Arduino Site have proper documentation of the ethernet shield and library??

The Arduino Sourcecode....


//N.B.: AT LEAST myport, myip, and baseurl[] probably need changing between
//what you download, and what you need for your circumstances.

/*See remark a few lines below here for source of right library

This software discussed in tutorial...

#include "etherShield.h"

static char ver[]="23Oct11";// KEEP THIS SHORT! I goes into the webpage xxx,
//and space is VERY limited.
//SEE ALSO place further down where, for now, "ver" info has to be entered
//a second time. Sigh. TO DO: Find way to have ver taken from the array, so
//hand-(re)-coding lower down not necessary.

/*Hardware requirements....

For NuElectronics END28J60 Ethernet shield:

D2 (I actually had an Arduserver working fine for a few months with no
         connection to D2, but a re-examination of the schematic suggests
         that I ought to have a connection there. It is an output from the
         shield. Who knows what the ethernet library wants to see coming
         on on D2, but I'm not leaving the signal to chance.)
5v (Into the shield)

For the "features" of ArduServer:
Two digital outputs. I am using...
  D7 for the "beep" that announces an access of the server
  D8 for the LED which clients can turn on or off
One analog input. I am using An3

Other pins have been avoided to keep the hardware
  here the same as other systems from the same
  author, but using Wiznet 5100 interfaces


/*Around since at least 17Apr2011

Derived from the example, "ethershield_webserver.pde"
supplied with the necessary library "etherShield". The demo
"ethershield_web_temperature.pde" was essential to me in
"getting to the next level" and FINALLY mastering creating
and using buttons. It also has some useful code, not
incorporated here, which would allow you to attach a
Dallas 1-Wire temperature sensor to your ArduServer, so
that the webpage served could report a temperature.

The library comes from

Info on using libraries included in tutorial cited at top

Changes to the program (not the library) were done by TK Boyd, of


Define the MAC address the device will present to the LAN.
  All MAC addresses on a given LAN must be different from each other.
  Note: The values are separated with commas. */

//uint8_t: Unsigned 8 bit datum. And array of them, in this case...
static uint8_t mymac[6] = {0x54,0x55,0x58,0x10,0x00,0x24};

/*Define the IP address the device will respond to on the LAN.
  All IP addresses on a given LAN must be unique.
  Note: The values are separated with commas.
  This should be the LOCAL address of the device, i.e.
  its address on the LAN it lives on, something like

static uint8_t myip[4] = {192,168,0,240};//Commas here... full stops
  //usually, in other contexts.

/*Define the port your server will use. Normally, you would opt for
  port 80, but others are possible, if you know what you are doing.
  If you use, say, 81, add :81 to what you put into your browser
  to access the server. It may be that you should not use a port
  outside of the range 1-254... see note at "mywwwport"*/

unsigned int myport=81;//80 will be fine for most people. TKB needs 81 at 7NC

/* To use this code for a server accessible only on your LAN,
   you would make the following "" if you
   dedicated that IP address of your LAN to the server.

   If you want your ArduServer to be "seen" from anywhere on the
   internet (or on your LAN) replace the digits with the URL for
   your system. In my case, for example, I would use...

   Once upon a time, if you were using a non-standard port, i.e.
   a port other than 80, you put that on the end of the baseurl.
   This is no longer true. Put JUST the URL for your
   ArduServer in the following variable. (The port specification
   will be appended by the software.)

   You WILL need to append the port specification each time you
   first access the server from a browser, but the result of
   clicking on "LED On" or "LED Off" will have the port number
   added automatically.


static char baseurl[]="http://mon7nc.dyndns.org";
char portasstring[5];//5 to accommodate port numbers of up to 9999
  //portasstring is filled during setup().

//Page buffer....

/*You may need to change the buffer size... if you change
  the amount of text on the webpage served, or the amount
  of code in your version of the program.

  N.B.: The size of the buffer is fussy... too big or too small, and
  you will get hard-to-debug problems. (Worst case: The code compiles
  and runs, but when you ask for the page it is meant to serve, the
  client waits and waits until the browser times out.

  Make the buffer too (unnecessarily) big, and you reduce the amount
  of code you can insert to add Good Stuff to your ArduServer
  without getting any benefit from the over-large buffer.

  Make the buffer too small, and I think you will encounter over-run
  and crash the server.

  I believe that if you add enough text to the page being served,
  you have to increase the buffer size.

  There may also be another limit to the amount of text which can
  be on a served page, but I'm not sure about this yet.

  "Text" in both cases discussed above includes the HTML tags.*/

#define BUFFER_SIZE 680
static uint8_t buf[BUFFER_SIZE+1];

//The following does NOT have to be changed, even if you've
//   changed MAC address, IP address or port.
static uint16_t mywwwport =myport; // listen port for tcp/www (max range 1-254)

#define STR_BUFFER_SIZE 22
static char strbuf[STR_BUFFER_SIZE+1];

EtherShield es=EtherShield();

//Added by sheepdogsoftware.co.uk...

//For the inputs and outputs extension of the demo...
int8_t bTmp;            //For misc chores
int8_t sensorPin = A3;  // designate the input pin for a sensor... a phototransistor used in example
   //"A3" is a funny sort of "number"... but it is set to what we need by things deep inside
   //the Arduino IDE.
in example
int8_t ledPin = 8;      // designate the pin for an output... LED used in example
int8_t beepPin = 7;     // designate the pin to drive "beep" which will indicate a request's arrival.
int sensorValue = 0;    // variable to store the value coming from the sensor
unsigned int iClicks=0; // 0-65,535. Variable to store number of requests made to server since most recent reboot.

//end, a block of new lines by TKB of sheepdogsoftware.co.uk

//The next two lines puzzle me, TKB. But even though I don't believe
//   they are doing what the original remark says, they do no harm,
//   and may be doing good, so I left them!

// prepare the webpage by writing the data to the tcp send buffer
uint16_t print_webpage(uint8_t *buf);
int8_t analyse_cmd(char *str);

/* ============  setup() =============================== */
void setup(){

//You can insert your own code here....

//....to initialize things for features you have added.

/*Here begins bits added by sheepdogsoftware.co.uk...
  For the inputs and outputs extension of the demo...*/
// Declare ledPin, beepPin as OUTPUTs, and set initial states:
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin,LOW);//establish initial state: LED off.

  pinMode(beepPin, OUTPUT);
  digitalWrite(beepPin,LOW);//establish initial state: No beep.

//(Input will be over sensorPin, which doesn't need initializing.)

//Transfer myport (the number) to portasstring (an array of
//   bytes, terminated by 0)... If port is 80, the ASCII for "8"
//   should be in the first byte of the array.

//There is probably a better way to fill the array, but this works!

//Setting myport=1234 doesn't result in a test which is easily monitored.
//If myport=1234, then after the following, portasstring[0] should be the ASCII
//   for 1, portastring[1] should be ASCII 2, etc... AND after the last
//   the next element of portasstring should hold the NUMBER zero.
//See also "81" example, below.
if (myport>999)
   {portasstring[bTmp]=((myport % 10000) / 1000) +48;
if (myport>99)
   {portasstring[bTmp]=((myport % 1000) / 100) +48;
if (myport>9)
   {portasstring[bTmp]=((myport % 100) / 10) +48;
//And then do the always true case: if (myport>0)...
   portasstring[bTmp]=(myport % 10)+48;
//And tack on a zero to terminate string...

//As a further example: To fill portasstring with "81", you would use...
//portasstring[2]=0;//Terminate with 0

//Change declaration of portasstring near start of program
//if a port higher than 9999 is required.

//End of bits added here by sheepdogsoftware.co.uk

/* HERE BEGINS code from NuElectronics web server demo*/

//initialize enc28j60
es.ES_enc28j60clkout(2); // change clkout from 6.25MHz to 12.5MHz

/* Magjack LEDs configuration, see enc28j60 datasheet, page 11 */
// LEDA=green LEDB=yellow
// 0x880 is PHLCON LEDB=on, LEDA=on
// enc28j60PhyWrite(PHLCON,0b0000 1000 1000 00 00);
// 0x990 is PHLCON LEDB=off, LEDA=off
// enc28j60PhyWrite(PHLCON,0b0000 1001 1001 00 00);
// 0x880 is PHLCON LEDB=on, LEDA=on
// enc28j60PhyWrite(PHLCON,0b0000 1000 1000 00 00);
// 0x990 is PHLCON LEDB=off, LEDA=off
// enc28j60PhyWrite(PHLCON,0b0000 1001 1001 00 00);

// 0x476 is PHLCON LEDA=links status, LEDB=receive/transmit
// enc28j60PhyWrite(PHLCON,0b0000 0100 0111 01 10);

//init the ethernet/ip layer:

}// end "setup()"

/* ============  loop() =============================== */
void loop(){

/*You can insert your own code here, to implement extra
     features of your own. Your code should not take long
     to execute, and it certainly should not "block"
     the processor.

You CAN (in fact I will!) add things in other places... but
     the chance of messing up the web serving is greater.*/

//End of best area for adding your own code.

/* HERE BEGINS code of "loop()" from NuElectronics
  web server demo*/

uint16_t plen, dat_p;
int8_t cmd;

plen = es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf);

//plen will be NOT equal to zero if there is a valid packet (without CRC error)

  // arp is broadcast if unknown but a host may also verify the mac address
  //      by sending it to a unicast address.

  // check if ip packets are for us:


  // tcp port www start, compare only the lower byte
  if (buf[IP_PROTO_P]==IP_PROTO_TCP_V&&buf[TCP_DST_PORT_H_P]==0&&buf[TCP_DST_PORT_L_P]==mywwwport){
    if (buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V){
       es.ES_make_tcp_synack_from_syn(buf); // make_tcp_synack_from_syn does already send the syn, ack
    if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V){
      es.ES_init_len_info(buf); // init some data structures
      if (dat_p==0){ // we can possibly have no data, just ack:
        if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V){
      if (strncmp("GET ",(char *)&(buf[dat_p]),4)!=0){
        	// head, post and other methods for possible status codes see:
          // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
          plen=es.ES_fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>200 OK</h1>"));
          goto SENDTCP;
	if (strncmp("/ ",(char *)&(buf[dat_p+4]),2)==0){
          goto SENDTCP;
      cmd=analyse_cmd((char *)&(buf[dat_p+5]));

/*Here we have a MAJOR "little tweak...." The "command handling" code altered.
  See http://sheepdogguides.com/arduino/art5serv.htm

//ORIGINAL: block starting... if (cmd==1){   .... became
             if ((cmd==1)||(cmd==2)){ //TKB version
             if (cmd==1)
//"Tweak" ends. Avoid inserting delays such as the tiny ones you
//    see here, which aren't, I hope, big enough to hurt the operation
//    of the server, but give the LED time to come on or go off
//    before the light level sensor is read, elsewhere.

SENDTCP:  es.ES_make_tcp_ack_from_any(buf); // send ack for http get
           es.ES_make_tcp_ack_with_data(buf,plen); // send data

}//end of "loop()"

/*============ Subordinate subroutines, from NuElectronics demo ===== */

uint8_t find_key_val(char *str,char *key){
// The returned value is stored in the global var strbuf

    uint8_t found=0;
    uint8_t i=0;
    char *kp;
    while(*str &&  *str!=' ' && found==0){
            if (*str == *kp){
                    if (*kp == '\0'){
                            if (*str == '='){
    if (found==1){
            // copy the value to a buffer and terminate it with '\0'
            while(*str &&  *str!=' ' && *str!='&' && i<STR_BUFFER_SIZE){
}// end of "find_key_val()"

int8_t analyse_cmd(char *str)
int8_t r=-1;

if (find_key_val(str,"cmd")){
    if (*strbuf < 0x3a && *strbuf > 0x2f){
            // is a ASCII number, return it
return r;
}// end of "analyse_cmd()"

/* ============  PRINT WEB PAGE ==================*/
/* You make changes within this subroutine to change
   what your webserver serves up to the world.

   Be careful about what you do here...
   Make changes incrementally, a bit at a time.

   The page must be keep disappointingly small in
   an Atmega 168. I would be interested in what
   you find with a bigger chip.                  */

uint16_t print_webpage(uint8_t *buf)

/*Additions by sheepdogsoftware.co.uk...
     Added a bit "deep" in the program, but put here so that
     page returned reflects state of sensor AFTER LED state
     changed by "command" handler.... */

/*Delay to let LED finish going on or off is already incorporated,
    at point LED state may change. */

sensorValue = analogRead(sensorPin);

char temp_string[10];
int i=0;
int i2;
unsigned int iTmp;

uint16_t plen;

plen=es.ES_fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"));//51 chars here.. incl 6 "escape "\"s
//                                     ^--- start of fifty one....                end...^

plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<h2>Visit <a href=\"http://arduserver.com\" target=\"_blank\">ArduServer.com<a> for explanation.<br>"));

//Won't work... try to find way to do this.... plen=es.ES_fill_tcp_data_p(buf,plen,PSTR(ver));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("v.23 Oct 11"));//Insert version ID in page. See "ver" at top of page
//Previous is crude "solution" to what should be done by the line above it.


plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<hr><form METHOD=get action=\""));
plen=es.ES_fill_tcp_data(buf,plen,":");  //Begin specification of port, in case not...
plen=es.ES_fill_tcp_data(buf,plen,portasstring);  // ...default port 80. One of two instances

plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<h2>Clicks: </h2>"));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<h1><font color=\"#22FF00\">"));

//Put a number in the output buffer...

//Originally, a random number...


//But now the value in iClicks, the number of server requests
//   handled since the last reset of the server.

//The code is CRUDE... but it works!
//I won't be offended if someone writes me
//   to tell me the right way to do this,
//   as long as the right way is transparent
//   for newbies.

//iClicks=65123;//To test

if (iClicks>9999)
   {iTmp=(iClicks / 10000) % 10;
if (iClicks>999)
   {iTmp=(iClicks / 1000) % 10;
if (iClicks>99)
   {iTmp=(iClicks / 100) % 10;
if (iClicks>9)
   {iTmp=(iClicks / 10) % 10;
if (iClicks>0)
   {iTmp=iClicks % 10;
//End of "put a number in the output buffer"

 while (temp_string[i]) {
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("</font></h1> ") );

/*... Begins here: "Move sensorValue to temp_string". Added by sheepdogsoftware.co.uk

 It isn't as bad as it looks, though I'm sure this can be Done Better.
 If you don't like it, re-write it, and send me the Proper Job.
 The complexity arises from the need to pass the
 integer in sensorValue to ES_fill_tcp_data_p in a particular way.*/

   //Assumes sensorValue <2000
   if (sensorValue>999)

   while (sensorValue>100)

   while (sensorValue>10)

   while (sensorValue>1)

   temp_string[i]=0;//Mark end of string

//end of "Move sensorValue to temp_string"

plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<h2>Light Level: </h2>"));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<h1><font color=\"#0000FF\">"));

   while (temp_string[i]) {

plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("</font></h1>") );

//Buttons ... First button...
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<input type=hidden name=cmd value=2>"));
//             See tutorial. "cmd" is just a name we elected to use. "name" is
//                   built into HTML, defined by it... but we could have
//                   used anything where we used "cmd"... as long as the code
//                   we've written elsewhere in this same program is written
//                   to look for what we've used.

plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<input type=submit value=\"LED On\">"));

//Second button...
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<form METHOD=get action=\""));
plen=es.ES_fill_tcp_data(buf,plen,":");  //Begin specification of port, in case not...
plen=es.ES_fill_tcp_data(buf,plen,portasstring);  // ...default port 80. Second of two instances

plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<input type=hidden name=cmd value=1>"));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("<input type=submit value=\"LED Off\">"));
//End of buttons HTLM


}//end of "print_webpage"

Below here: For inputs/outputs extension of demo,
additions by sheepdogsoftware.co.uk */

void LEDOnOff(byte bOnOff){
//if 0 passed: Turn LED off, if <>0 then on.

++iClicks;//Add 1 to the tally of clicks processed.

digitalWrite(beepPin,HIGH);//Make a brief beep as each request from client is serviced
delay(15);//Keep this short... say <30 (ms). The shorter the better,
//  really. But if too short, you won't hear any "beep"

if (bOnOff==0)
}//end of "LEDOnOff()"

I hope that was useful...

Remember: The code is explained for you at my page explaining the ArduSimpSrv creation.

Search for other things...

Please note that I have two other sites, and that the following search will not include them. They have their own search buttons.

My Sheepdog Guides site.
My Arunet site.

   Search this site or the web        powered by FreeFind
  Site search Web search
Site Map    What's New    Search
The search engine merely looks for the words you type, so....
*    Spell them properly.
*    Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I"....

You can also search this site without using forms.
Ad from page's editor: Yes.. I do enjoy compiling these things for you... hope they are helpful. However.. this doesn't pay my bills!!! If you find this stuff useful, (and you run an MS-DOS or Windows PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Want a site hosted, or email? You can also help me if you sign up via this link to 1&1's services. (I wouldn't recommend them unless I was happy after several years as one of their customers, but yes, they do pay me if you use this link! As do the Google advertisers, about whom I know nothing, of course.)

Valid HTML 4.01 Transitional Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org

CSS behind the page checked, at least once upon a time!, with http://jigsaw.w3.org/css-validator/
Why does this page cause a script to run? Because of the Google panels, and the code for the search button. Also, I have some of my pages' traffic monitored for me by eXTReMe tracker. They offer a free tracker. If you want to try one, check out their site. Why do I mention the script? Be sure you know all you need to about spyware.

Editor's Main Homepage
How to email or write this page's editor, Tom Boyd

....... P a g e . . . E n d s .....