Ethernet TCP/IP Source Code Driver Project
Section 04. Using The Driver In Your Project

04. Using The Driver In Your Project

a) General Information

Network Protocol Analyzer Utilities

Anyone undertaking development of TCP/IP projects should download a copy of Wireshark from:

http://www.wireshark.org

This is a very popular free network protocol analyzer which allows you to see exactly what you device is sending and receiving.

A couple of tips when using Wireshark:

Using filter lines such as:

eth.addr == 00:50:c2:50:10:32

can be a very useful way of removing other network traffic captured from the Wireshark screen, so you can concentrate only on the packets being sent to and from your device.

If you are using Wireshark on a PC that is not generating or receiving the packets you want to monitor remember that if you are using an Ethernet Switch it will typically only send packets out of its ports to which a destination device is connected (unless a packet is broadcast). In this situation using an Ethernet Hub instead of a Switch is a really simple way of seeing all of the packets any other device connected to the Hub is seeing. An Ethernet Hub is non intelligent and simply distributes incoming and outgoing packets on a send to everyone basis, whereas an Ethernet Switch only transfers packets to ports that need to receive them.

PC Ethernet Ports

When developing and testing TCP/IP functions using a computer that has more than one Ethernet connection (e.g. a RJ45 port and a WiFi card) it is often a good idea to disable all but one of them so that you know for sure which port your computer will use and which port to capture packets from when using Wireshark.

b) Basic Driver Notes

The eth-main.c and eth-main.h files are the central files of the TCP/IP driver. The central stack operations and definitions are provided in these files. If you want to get a feel for how the stack works at the core level have a quick scan through the tcp_ip_process_stack() function.

On power up your initialisation function needs to call the tcp_ip_initialise() function which initialises the driver and automatically calls any other driver initialisation functions required by the selected driver / stack components. This function also initialises the nic (network interface controller) IC, provides it with its MAC address and enables the receiving of packets.

All your application then needs to do is regularly call the tcp_ip_process_stack() function. Typically this is done as part of an applications main loop. Whilst some the driver / stack components are initiated by the user application (for instance sending email), there are other components that are handled completely automatically by the driver, for instance responding to ARP requests. Therefore the driver needs to be called regularly to check for received packets, transmit automatically generated responses and check for updating of automatic processes such as DHCP.

The stack is designed as a single thread and no stack component is permitted to halt operation in wait states. Every operation that may require a delay or wait is handled by simple state machines so that completion of tasks or delays is checked for and handled each time the tcp_ip_process_stack() function is called. Therefore the stack cannot cause the user application to become halted.

This manual documents all of the features of the driver an how to use them. Due to the very broad nature of TCP/IP and all of its components this manual doesn’t attempt to explain in depth how all of the individual protocols work, as these are already widely documented in many books and online resources. However this manual does explain how each of the driver / stack components work in addition to the very comprehensive commenting throughout the source code, should you wish to gain an understanding of it. There is no need to understand the inner workings of the stack though and you can simply use the driver as a plug in module for your overall application, following the instructions in these beginning sections of this manual for simple information on how to configure and use it.

Some Of The Terminology Used In This Manual

Port

UDP and TCP ports are simply a number that is used in the packet IP header to designate the port a packet has been sent from and the port the packet is being sent to. UDP and TCP have pre defined lists of ports with many being assigned to particular uses. For instance TCP port 80 is defined as being the default port on which to contact a web server. If you design a web server you don’t have to use port 80, but it’s the default port that a browser will use to try and connect to a web server. As well as all the pre-defined ports there are also lots of undefined ports. Therefore a web browser would send a page request packet ‘to’ port 80 and would select a port number that it is not already using to send the packet ‘from’, for instance 3561. When a web server responds it will send its response packet(s) to the port 3561 and from port 80.

Sockets

A socket is simply a term used to describe a connection. Both UDP and TCP sockets are opened by defining a local UDP or TCP port number the socket should use. Once a socket is opened any packets that are received where the IP header ‘to’ port number matches the port number assigned to an open socket will be passed for processing to the handler function for that socket.

Server & Client

When talking about TCP connections a client is a device that is making a TCP connection to a remote device and a server is a device has a TCP socket listening for connections from a remote device. Therefore don’t think of a server as just a typical PC running server software, but rather any device or PC that has a TCP port open listening for an incoming connection. A device can be both client and server at the same time by opening some sockets as client connections and some sockets as listening server connections.

c) Using UDP

For more detailed information about how UDP works see the ‘How The Driver Works’ section later in this manual.

The driver allows your application to create both client and server UDP (User Datagram Protocol) sockets. If your application is only utilising functionality such as DHCP, SNTP, etc then these components of the stack / driver will automatically use UDP without you needing to work directly with it. However if your application needs to communicate directly with another device or PC the UDP examples below provides the means to communicate in an unmanaged way.

Although these examples demonstrate typical functionality well, UDP is a connectionless protocol so you don’t have to think in terms of client and server and you don’t actually create a socket as one or the other. Once you open a socket you can use it to transmit and receive as you wish, effectively behaving as both a client and a server if required.

Use UDP Server Sockets To Receive From Remote Clients

Your application may create as many UDP server sockets as it needs from the UDP sockets that are available (the number of available sockets is set by UDP_NO_OF_AVAILABLE_SOCKETS define). When a socket is created nothing is sent, but the driver will automatically pass packets received by the socket to your handler for processing. Once you open a socket you must regularly check for received packets until you close the socket, to avoid the TCP/IP stack stalling while it waits for your handler to remove a received packet.


static BYTE our_udp_socket = UDP_INVALID_SOCKET;
static BYTE our_udp_server_state = SM_OPEN_SOCKET;
BYTE data;
BYTE array_buffer[4];

if (!nic_linked_and_ip_address_valid)
{
	//----- WE ARE NOT CONNECTED OR DO NOT YET HAVE AN IP ADDRESS -----
	our_udp_server_state = SM_OPEN_SOCKET;

	//Ensure our socket is closed if we have just lost the Ethernet connection
	udp_close_socket(our_udp_socket);

	return;		//Exit as we can't do anything without a connection
}

switch (our_udp_server_state)
{
case SM_OPEN_SOCKET:
	//----- OPEN SOCKET -----
//(Leave device_info as null to setup to receive from anyone, remote_port
//can be anything for rx)
	our_udp_socket = udp_open_socket(0x00, 6451, 1);
	if (our_udp_socket != UDP_INVALID_SOCKET)
	{
		our_udp_server_state = SM_PROCESS_SOCKET;
		break;
	}
	//Could not open a socket - none currently available - keep trying
	break;

case SM_PROCESS_SOCKET:
	//----- PROCESS SOCKET -----
	if (udp_check_socket_for_rx(our_udp_socket))
	{
		//SOCKET HAS RECEIVED A PACKET - PROCESS IT

		//READ THE PACKET AS REQURIED
		if (!udp_read_next_rx_byte(&data))
		{
			//Error - no more bytes in rx packet
		}
		//OR USE
		if (!udp_read_rx_array (array_buffer, sizeof(array_buffer)))
		{
			//Error - no more bytes in rx packet
		}

		//DUMP THE PACKET
		udp_dump_rx_packet();

		//SEND RESPONSE
		our_udp_server_state = SM_TX_RESPONSE;
	}
	break;

case SM_TX_RESPONSE:
	//----- TX RESPONSE -----
	//SETUP TX

	//To respond to the sender leave our sockets remote device info as
//this already contains the remote device settings
	//Or to broadcast on our subnet do this:
	udp_socket[our_udp_socket].remote_device_info.ip_address.val =
our_ip_address.val | ~our_subnet_mask.val;
	udp_socket[our_udp_socket].remote_device_info.mac_address.v[0] = 0xff;
	udp_socket[our_udp_socket].remote_device_info.mac_address.v[1] = 0xff;
	udp_socket[our_udp_socket].remote_device_info.mac_address.v[2] = 0xff;
	udp_socket[our_udp_socket].remote_device_info.mac_address.v[3] = 0xff;
	udp_socket[our_udp_socket].remote_device_info.mac_address.v[4] = 0xff;
	udp_socket[our_udp_socket].remote_device_info.mac_address.v[5] = 0xff;
	udp_socket[our_udp_socket].remote_port = 6450;
	udp_socket[our_udp_socket].local_port = 6451;

	if (!udp_setup_tx(our_udp_socket))
	{
		//Can't tx right now - try again next time

		//Return the socket back to broadcast ready to receive from anyone again
//Only enable the line below if you are broadcasting responses and
//don't want to miss incoming packets to this socket from other devices
		//udp_socket[our_udp_socket].remote_device_info.ip_address.val =
			0xffffffff;		

		break;
	}

	//WRITE THE UDP DATA
	udp_write_next_byte('H');
	udp_write_next_byte('i');
	udp_write_next_byte(0x00);
	//You can also use udp_write_array()

	//SEND THE PACKET
	udp_tx_packet();

	//RETURN THE SOCKET BACK TO BROADCAST READY TO RECEIVE FROM ANYONE AGAIN
	udp_socket[our_udp_socket].remote_device_info.ip_address.val = 0xffffffff;

	our_udp_server_state = SM_PROCESS_SOCKET;
	break;

} //switch (our_udp_server_state)

Use UDP Client Sockets To Transmit To Remote Servers

Your application may create as many UDP client sockets as it needs from the UDP sockets that are available (the number of available sockets is set by UDP_NO_OF_AVAILABLE_SOCKETS define). Before a socket can be created you need to determine the IP and MAC address of the device you wish to connect to (use ARP if you need to determine the MAC address), or you may instead transmit to a broadcast address without using ARP. The UDP driver will then send or receive data as required.

The following is a full example of how your application can create and process a client socket:


static BYTE our_udp_socket = UDP_INVALID_SOCKET;
static BYTE our_udp_client_state = SM_OPEN_SOCKET;
static DEVICE_INFO remote_device_info;
BYTE data;
BYTE array_buffer[4];

if (!nic_linked_and_ip_address_valid)
{
	//----- WE ARE NOT CONNECTED OR DO NOT YET HAVE AN IP ADDRESS -----
	our_udp_client_state = SM_IDLE;

	//Ensure our socket is closed if we have just lost the Ethernet connection
	udp_close_socket(our_udp_socket);
	return;		//Exit as we can't do anything without a connection
}

switch (our_udp_client_state)
{
case SM_IDLE:
	//----- DO NOTHING -----
	break;

case SM_OPEN_SOCKET:
	//----- OPEN SOCKET -----

	//Set to broadcast on our subnet (alternatively set the IP and MAC address
//to a remote devices address - use ARP first if the MAC address is unknown)
	remote_device_info.ip_address.val = our_ip_address.val | ~our_subnet_mask.val;
	remote_device_info.mac_address.v[0] = 0xff;
	remote_device_info.mac_address.v[1] = 0xff;
	remote_device_info.mac_address.v[2] = 0xff;
	remote_device_info.mac_address.v[3] = 0xff;
	remote_device_info.mac_address.v[4] = 0xff;
	remote_device_info.mac_address.v[5] = 0xff;

	//Set the port numbers as desired
	our_udp_socket = udp_open_socket(&remote_device_info, (WORD)6453, (WORD)6452);
	if (our_udp_socket != UDP_INVALID_SOCKET)
	{
		our_udp_client_state = SM_TX_PACKET;
		break;
	}
	//Could not open a socket - none currently available - keep trying
	break;

case SM_TX_PACKET:
	//----- TX PACKET TO REMOTE DEVICE -----
	//SETUP TX
	if (!udp_setup_tx(our_udp_socket))
	{
		//Can't tx right now - try again next time
		break;
	}

	//WRITE THE TCP DATA
	udp_write_next_byte('H');
	udp_write_next_byte('i');
	udp_write_next_byte(0x00);
	//You can also use udp_write_array()

	//SEND THE PACKET
	udp_tx_packet();

	udp_client_socket_timeout_timer = 10;
	our_udp_client_state = SM_WAIT_FOR_RESPONSE;
	break;

case SM_WAIT_FOR_RESPONSE:
	//----- WAIT FOR RESPONSE -----
	if (udp_check_socket_for_rx(our_udp_socket))
	{
		//SOCKET HAS RECEIVED A PACKET - PROCESS IT

		//READ THE PACKET AS REQURIED
		if (!udp_read_next_rx_byte(&data))
		{
			//Error - no more bytes in rx packet
		}
		//OR USE
		if (!udp_read_rx_array (array_buffer, sizeof(array_buffer)))
		{
			//Error - no more bytes in rx packet
		}

		//DUMP THE PACKET
		udp_dump_rx_packet();

		//EXIT
		our_udp_client_state = SM_CLOSE_SOCKET;
	}

	if (udp_client_socket_timeout_timer == 0)
	{
		//TIMED OUT - NO RESPONSE FROM REMOTE DEVICE
		our_udp_client_state = SM_CLOSE_SOCKET;
	}
	break;

case SM_CLOSE_SOCKET:
	//----- CLOSE THE SOCKET -----
	udp_close_socket(our_udp_socket);

	our_udp_client_state = SM_IDLE;
	break;

} //switch (our_udp_client_state)

Getting Packet Sender Data When A Packet Is Received

If you want to know the IP or MAC address of the remote device a packet has been received from they are available in the following variables:


sender_ip_addr.val = udp_socket[our_udp_socket].remote_device_info.ip_address.val;
sender_mac_addr.v[0] = udp_socket[our_udp_socket].remote_device_info.mac_address.v[0];
sender_mac_addr.v[1] = udp_socket[our_udp_socket].remote_device_info.mac_address.v[1];
sender_mac_addr.v[2] = udp_socket[our_udp_socket].remote_device_info.mac_address.v[2];
sender_mac_addr.v[3] = udp_socket[our_udp_socket].remote_device_info.mac_address.v[3];
sender_mac_addr.v[4] = udp_socket[our_udp_socket].remote_device_info.mac_address.v[4];
sender_mac_addr.v[5] = udp_socket[our_udp_socket].remote_device_info.mac_address.v[5];

If you need to know if received packet was broadcast or sent to our unique IP address:


sender_ip_address_packet_was_sent_to = udp_socket[our_udp_socket].destination_ip_address.val;

Designing your own protocol

If you are designing your own UDP protocol and will be transferring large amounts of data remember to bear in mind the available receive buffer space in the network interface controller (nic) IC you are using. As packets are received, either to the device directly or broadcast, they will be stored in the nic’s receive buffer until your application has read and discarded them. Therefore if you could potentially overfill the nic’s receive buffer your communication protocol should be designed so that only a limited amount of data is sent before an acknowledge response is required or a delay is inserted, or so that there is sufficient time between groups of packets to allow them to have been processed by the receiving devices. If you don’t and the nic’s receive buffer becomes full it will simply dump packets it receives until there is space available again.

Multiple sockets using same local port number

You may open more than 1 socket with the same local port number, to allow multiple remote devices to connect to a single port number. When a new UDP packet is received the handler looks for the first used UDP socket using that port number that is set to receive from the remote device the packet is from or set to receive from any device. If one is found the packet is passed to that socket.

If your application returns a socket back to the receive from anyone state using:


udp_socket[socket].remote_device_info.ip_address.val = 0xffffffff;

as soon as any packet is received to that socket (i.e. as part of the receive handler), then there is no need to create additional sockets using the same port number as that socket will always accept incoming packets from any sender to that port.

Viewing The State Of The UDP Sockets

The array udp_socket contains the current status and all the variables associated with each of the UDP sockets that are available to the stack. Every process that opens a UDP socket is given a BYTE socket number, which is the pointer into the udp_socket array. It can sometimes be useful to view the array in a watch window when debugging.

d) Using TCP

For more detailed information about how TCP works see the ‘How The Driver Works’ section later in this manual.

The driver allows your application to create both client and server TCP (Transmission Control Protocol) sockets. If your application is only utilising functionality such as email, HTTP server, etc then these parts of the stack / driver will automatically use TCP without you needing to work directly with it. However if your application needs to communicate directly with another device or PC the TCP examples below provides the means to communicate in a managed way.

Create TCP Server Sockets To Accept A Connection From A Remote Client

Your application may create as many TCP server sockets as it needs from the TCP sockets that are available (the number of available sockets is set by the TCP_NO_OF_AVAILABLE_SOCKETS define). When a socket is created nothing is sent, but the driver will automatically accept connection requests to the socket and then pass packets received by the socket to your handler for processing. Once you open a socket you must regularly check for received packets or retransmissions until you close the socket, to avoid the TCP/IP stack stalling while it waits for your handler to remove a received packet.

The following is a full example of how your application can create and process a server socket:


static BYTE our_tcp_server_socket = TCP_INVALID_SOCKET;
static BYTE our_tcp_server_state = SM_OPEN_SOCKET;
BYTE data;
BYTE array_buffer[4];

if (!nic_linked_and_ip_address_valid)
{
	//----- WE ARE NOT CONNECTED OR DO NOT YET HAVE AN IP ADDRESS -----
	our_tcp_server_state = SM_OPEN_SOCKET;

	//Ensure our socket is closed if we have just lost the Ethernet connection
	tcp_close_socket_from_listen(our_tcp_server_socket);

	return;		//Exit as we can't do anything without a connection
}

switch (our_tcp_server_state)
{
case SM_OPEN_SOCKET:
	//----- OPEN SOCKET -----
//We will listen on port 4101 (change as required)

//We shouldn't have a socket currently, but make sure
	if (our_tcp_server_socket != TCP_INVALID_SOCKET)
		tcp_close_socket(our_tcp_server_socket);

our_tcp_server_socket = tcp_open_socket_to_listen(4101);

if (our_tcp_server_socket != TCP_INVALID_SOCKET)
	{
		our_tcp_server_state = SM_WAIT_FOR_CONNECTION;
		break;
	}
	//Could not open a socket - none currently available - keep trying
	break;

case SM_WAIT_FOR_CONNECTION:
	//----- WAIT FOR A CLIENT TO CONNECT -----
	if(tcp_is_socket_connected(our_tcp_server_socket))
	{
		//A CLIENT HAS CONNECTED TO OUR SOCKET
		our_tcp_server_state = SM_PROCESS_CONNECTION;

//Set our client has been lost timeout (to avoid client
//disappearing and causing this socket to never be closed)
		tcp_server_socket_timeout_timer = 10;
	}
	break;

case SM_PROCESS_CONNECTION:
	//----- PROCESS CLIENT CONNECTION -----

	if (tcp_server_socket_timeout_timer == 0)
	{
		//THERE HAS BEEN NO COMMUNICATIONS FROM CLIENT TIMEOUT
//RESET SOCKET AS WE ASSUME CLIENT HAS BEEN LOST
		tcp_close_socket(our_tcp_server_socket);
//As this socket is a server the existing connection will be closed
//but the socket will be reset to wait for a new connection (use
//tcp_close_socket_from_listen if you want to fully close it)
		our_tcp_server_state = SM_WAIT_FOR_CONNECTION;
	}

	if (tcp_check_socket_for_rx(our_tcp_server_socket))
	{
		//SOCKET HAS RECEIVED A PACKET - PROCESS IT
		tcp_server_socket_timeout_timer = 10;	//Reset our timeout timer

		//READ THE PACKET AS REQURIED
		if (tcp_read_next_rx_byte(&data) == 0)
		{
			//Error - no more bytes in rx packet
		}
		//OR USE
		if (tcp_read_rx_array (array_buffer, sizeof(array_buffer)) == 0)
		{
			//Error - no more bytes in rx packet
		}

		//DUMP THE PACKET
		tcp_dump_rx_packet();

		//SEND RESPONSE
		our_tcp_server_state = SM_TX_RESPONSE;
	}

	if (tcp_does_socket_require_resend_of_last_packet(our_tcp_server_socket))
	{
		//RE-SEND LAST PACKET TRANSMITTED
		//(TCP requires resending of packets if they are not acknowledged and to
		//avoid requiring a large RAM buffer the application needs to remember
		//the last packet sent on a socket so it can be resent if required).
		our_tcp_server_state = SM_TX_RESPONSE;
	}

	if(!tcp_is_socket_connected(our_tcp_server_socket))
	{
		//THE CLIENT HAS DISCONNECTED
		our_tcp_server_state = SM_WAIT_FOR_CONNECTION;
	}

	break;

case SM_TX_RESPONSE:
	//----- TX RESPONSE -----
	if (!tcp_setup_socket_tx(our_tcp_server_socket))
	{
		//Can't tx right now - try again next time
		break;
	}

	//WRITE THE TCP DATA
	tcp_write_next_byte('H');
	tcp_write_next_byte('i');
	tcp_write_next_byte(0x00);
	//You can also use tcp_write_array()

	//SEND THE PACKET
	tcp_socket_tx_packet(our_tcp_server_socket);

	our_tcp_server_state = SM_PROCESS_CONNECTION;
	break;
}

If you want to know the IP or MAC address of the remote client they are available in the following variables:


sender_ip_addr.val = tcp_socket[our_tcp_server_socket].remote_device_info.ip_address.val;
sender_mac_addr.v[0] = tcp_socket[our_tcp_server_socket].remote_device_info.mac_address.v[0];
sender_mac_addr.v[1] = tcp_socket[our_tcp_server_socket].remote_device_info.mac_address.v[1];
sender_mac_addr.v[2] = tcp_socket[our_tcp_server_socket].remote_device_info.mac_address.v[2];
sender_mac_addr.v[3] = tcp_socket[our_tcp_server_socket].remote_device_info.mac_address.v[3];
sender_mac_addr.v[4] = tcp_socket[our_tcp_server_socket].remote_device_info.mac_address.v[4];
sender_mac_addr.v[5] = tcp_socket[our_tcp_server_socket].remote_device_info.mac_address.v[5];

Create TCP Client Sockets To Connect To A Remote TCP Server Socket

Your application may create as many TCP client sockets as it needs from the TCP sockets that are available (the number of available sockets is set by TCP_NO_OF_AVAILABLE_SOCKETS). The TCP driver will then attempt to connect to the specified port on the remote device, using ARP first if the MAC address is unknown. Once connected you may then send or receive data before you or the remote device close the connection.

The following is a full example of how your application can create and process a client socket:


static BYTE our_tcp_client_socket = TCP_INVALID_SOCKET;
static WORD our_tcp_client_local_port;
static BYTE our_tcp_client_state = SM_OPEN_SOCKET;
static DEVICE_INFO remote_device_info;
BYTE data;
BYTE array_buffer[4];

if (!nic_linked_and_ip_address_valid)
{
	//----- WE ARE NOT CONNECTED OR DO NOT YET HAVE AN IP ADDRESS -----
	our_tcp_client_state = SM_OPEN_SOCKET;

	//Ensure our socket is closed if we have just lost the Ethernet connection
	tcp_close_socket(our_tcp_client_socket);

	return;		//Exit as we can't do anything without a connection
}

if (our_tcp_client_socket != TCP_INVALID_SOCKET)
{
	//----- CHECK OUR CLIENT SOCKET HASN'T DISCONNECTED -----
	if ((tcp_socket[our_tcp_client_socket].sm_socket_state == SM_TCP_CLOSED) || (tcp_socket[our_tcp_client_socket].local_port != our_tcp_client_local_port))		//If the local port has changed then our socket was closed and taken by some other application process since we we're last called
		our_tcp_client_socket = TCP_INVALID_SOCKET;

	if (our_tcp_client_state == SM_WAIT_FOR_DISCONNECT)
		our_tcp_client_state = SM_OPEN_SOCKET;
	else if (our_tcp_client_state != SM_OPEN_SOCKET)
		our_tcp_client_state = SM_COMMS_FAILED;
}

switch (our_tcp_client_state)
{
case SM_OPEN_SOCKET:
	//----- OPEN SOCKET -----
	remote_device_info.ip_address.v[0] = 192;	//The IP address of the remote
	remote_device_info.ip_address.v[1] = 168;	//device we want to connect
	remote_device_info.ip_address.v[2] = 0;	//to (change as required)
	remote_device_info.ip_address.v[3] = 20;
	remote_device_info.mac_address.v[0] = 0;	//Set to zero so TCP
	remote_device_info.mac_address.v[1] = 0;	//will automatically use ARP
	remote_device_info.mac_address.v[2] = 0;	//to find MAC address
	remote_device_info.mac_address.v[3] = 0;
	remote_device_info.mac_address.v[4] = 0;
	remote_device_info.mac_address.v[5] = 0;

	//Connect to remote device port 4102 (the port it is listening on – change
//as required)
//We shouldn't have a socket currently, but make sure
	if (our_tcp_client_socket != TCP_INVALID_SOCKET)
			tcp_close_socket(our_tcp_client_socket);

	our_tcp_client_socket = tcp_connect_socket(&remote_device_info, 4102);
	if (our_tcp_client_socket != TCP_INVALID_SOCKET)
	{
		our_tcp_client_local_port = tcp_socket[our_tcp_client_socket].local_port;
//Set our wait for connection timeout
		tcp_client_socket_timeout_timer = 10;

		our_tcp_client_state = SM_WAIT_FOR_CONNECTION;
		break;
	}
	//Could not open a socket - none currently available - keep trying

	break;

case SM_WAIT_FOR_CONNECTION:
	//----- WAIT FOR SOCKET TO CONNECT -----
	if (tcp_is_socket_connected(our_tcp_client_socket))
		our_tcp_client_state = SM_TX_PACKET;

	if (tcp_client_socket_timeout_timer == 0)
	{
		//CONNECTION REQUEST FAILED
		our_tcp_client_state = SM_COMMS_FAILED;
	}

	break;

case SM_TX_PACKET:
	//----- TX PACKET TO REMOTE DEVICE -----
	if (!tcp_setup_socket_tx(our_tcp_client_socket))
	{
		//Can't tx right now - try again next time
		break;
	}

	//WRITE THE TCP DATA
	tcp_write_next_byte('H');
	tcp_write_next_byte('i');
	tcp_write_next_byte(0x00);
	//You can also use tcp_write_array()

	//SEND THE PACKET
	tcp_socket_tx_packet(our_tcp_client_socket);

//Set our wait for response timeout
	tcp_client_socket_timeout_timer = 10;

our_tcp_client_state = SM_WAIT_FOR_RESPONSE;
	break;

case SM_WAIT_FOR_RESPONSE:
	//----- WAIT FOR RESPONSE -----

	if (tcp_client_socket_timeout_timer == 0)
	{
		//WAIT FOR RESPOSNE TIMEOUT
		tcp_close_socket(our_tcp_client_socket);
		our_tcp_client_state = SM_COMMS_FAILED;
	}

	if (tcp_check_socket_for_rx(our_tcp_client_socket))
	{
		//SOCKET HAS RECEIVED A PACKET - PROCESS IT

		//READ THE PACKET AS REQURIED
		if (tcp_read_next_rx_byte(&data) == 0)
		{
			//Error - no more bytes in rx packet
		}
		//OR USE
		if (tcp_read_rx_array (array_buffer, sizeof(array_buffer)) == 0)
		{
			//Error - no more bytes in rx packet
		}

		//DUMP THE PACKET
		tcp_dump_rx_packet();

		our_tcp_client_state = SM_REQUEST_DISCONNECT;
	}

	if (tcp_does_socket_require_resend_of_last_packet(our_tcp_client_socket))
	{
		//RE-SEND LAST PACKET TRANSMITTED
		//(TCP requires resending of packets if they are not acknowledged and to
		//avoid requiring a large RAM buffer the application needs to remember
		//the last packet sent on a socket so it can be resent if requried).
		our_tcp_client_state = SM_TX_PACKET;
	}

	if(!tcp_is_socket_connected(our_tcp_client_socket))
	{
		//THE CLIENT HAS DISCONNECTED
		our_tcp_client_state = SM_COMMS_FAILED;
	}
	break;

case SM_REQUEST_DISCONNECT:
	//----- REQUEST TO DISCONNECT FROM REMOTE SERVER -----
	tcp_request_disconnect_socket (our_tcp_client_socket);

//Set our wait for disconnect timeout
	tcp_client_socket_timeout_timer = 10;

our_tcp_client_state = SM_WAIT_FOR_DISCONNECT;
	break;

case SM_WAIT_FOR_DISCONNECT:
	//----- WAIT FOR SOCKET TO BE DISCONNECTED -----

	if (tcp_is_socket_closed(our_tcp_client_socket))
	{
		our_tcp_client_state = SM_COMMS_COMPLETE;
	}

	if (tcp_client_socket_timeout_timer == 0)
	{
		//WAIT FOR DISCONNECT TIMEOUT
//Force the socket closed at our end
		tcp_close_socket(our_tcp_client_socket);
		our_tcp_client_state = SM_COMMS_FAILED;
	}
	break;

case SM_COMMS_COMPLETE:
	//----- COMMUNICATIONS COMPLETE -----
	break;

case SM_COMMS_FAILED:
	//----- COMMUNICATIONS FAILED -----
	break;
}

Multiple sockets using same local port number

You may open more than 1 server socket with the same local port number, to allow multiple remote devices to connect to a single port number. In this case when a new TCP packet is received the handler looks to see if the remote device is already connected to any of the local TCP sockets. If it is the packet is passed to that socket. If not the packet is passed to the first open socket that is not currently connected to a client.

Viewing The State Of The TCP Sockets

The array tcp_socket contains the current status and all the variables associated with each of the TCP sockets that are available to the stack. Every process that opens a TCP socket is given a BYTE socket number, which is a pointer into the tcp_socket array. It can be useful to view the array in a watch window when debugging.

e) Using HTTP Server

For more detailed information about how HTTP works see the ‘How The Driver Works’ section later in this manual.

The driver includes a very powerful embedded HTTP (Hypertext Transfer Protocol) server which may be used to provide simple web pages, pages with dynamically generated data and pages that allow a user to edit settings, enter data and upload files. The ‘Generating HTTP Web Content’ section later in this manual contains further information on designing your web files.

Storing Files In Program Memory

The HTTP_USING_C_FILES define should be un-commented for this file storage option.

The simplest (and fastest) method of including your html, image and any other web files is to use the web pages converter application to automatically output them all as a single C header file. The outputted html_c.h file should then be copied into your project directory and it contains all of the individual files that we’re converted. You don’t need to do anything more.

Storing Files In External Memory

The HTTP_USING_BINARY_FILES define should be un-commented for this file storage option.

If you don’t have the program memory space available or maybe you want to use cheaper lower cost external flash memory to store your html, image and any other web files you can use the web pages converter application to automatically output them all as a single binary data block file called html_bin.bin together with a C header file called html_bin.h The .h file contains all of the filenames and their address within the data block html_bin.bin. This allows the HTTP driver to automatically find files as they are requested. However you need to provide two functions:

You will need to implement the means to transfer the contents of the html_bin.bin file into your flash memory.

You also need to provide the function defined as HTTP_BINARY_FILE_NEXT_BYTE in eth-http.h. The function will be called with the address of the next byte that is required by the HTTP driver and it must retrieve the byte from the flash memory and return it.

Storing Files Using An External Filing System

The HTTP_USING_FILING_SYSTEM define should be un-commented for this file storage option.

If you are using a filing system in your application (for instance one of the embedded-code.com FAT drivers) you can alternatively store your html, image and any other web files using this file system. This option is slightly harder for you to implement, as your own functions must located requested files and return specified bytes from each file on demand to the HTTP driver. However this approach allows you to easily change the content of the files the HTTP driver serves. You need to provide these functions:

You will need to implement the means to transfer all of the files into your filing system. If using a removable memory card this is obviously as simple as copying the files onto it using a PC.

You need to provide the function defined as HTTP_EXTERNAL_FILE_FIND_FILE in eth-http.h. The function is called each time a new request is received from a HTTP client and it needs to search the available files and return the file size and a pointer / address of the first byte if the file is found.

You also need to provide the function defined as HTTP_EXTERNAL_FILE_NEXT_BYTE in eth-http.h. This function will be called with the pointer / address of the next byte that is required by the HTTP driver and it must retrieve the byte from the filing system and return it.

Note that if you don’t fancy implementing functions to handle providing the individual web content files you could instead use the ‘Storing Files In External Memory’ option with an external filing system. In this case you would simply store the html_bin.bin file that is output by the Web Pages Converter Application in your filing system / on your memory volume. If you do this there is only a single file to deal with and the driver will automatically work out the address of each byte within this binary file that it needs. You would just implement the function defined as HTTP_BINARY_FILE_NEXT_BYTE in eth-http.h to return the requested byte from this single file.

Serving Basic Web Pages

To serve basic web pages you don’t need to do anything more. As pages and their linked files are requested the HTTP driver will automatically find and return them without any intervention required by the user application.

Providing Dynamic Data

To provide dynamically generated content within your web pages, that is values, images, text etc that is added to the page as it is sent to a client, the driver provides a very simply yet powerful dynamic data function. As a .htm file is read by the HTTP driver and added to a TCP packet it looks for the tilde ‘~’ character, which acts as a special marker. When this character is found the driver doesn’t add it to the TCP packet but instead continues reading the following variable name until it gets to a trailing hyphen ‘-‘ character. This variable name is then passed to the optional function in your application which you define with the HTTP_DYNAMIC_DATA_FUNCTION define in eth-http.h. Your function should then return a null terminated string which the driver will send before carrying on with the rest of the .htm file. Further details can be found in the ‘Generating HTTP Web Content’ section later in this manual.

This simple approach is actually incredibly powerful. As an HTML file is simply an ASCII text file you are not limited to adding dynamically generated text that the user sees, but you can actually dynamically add anything you wish to a file as it is sent, for instance selecting which image to display, adding hidden script code or values, etc. The only limit is a maximum of 100 characters per variable name.

In addition to the variable name the TCP socket ID is also passed to your applications function, allowing it to identify a user by their unique MAC or IP address if desired.

See the eth-http.h file for full details of the function you need to include.

Receiving Inputs From Clients

Being able to generate dynamic data is great but in many embedded applications the user needs to be able to alter settings or enter values. The HTTP driver provides a complete set of input handling capabilities. The ‘Sending Data To Your Embedded Http Server’ section later in this manual provides details of the standard GET and POST input methods that may be used in your web pages to provide one or more input values from a html form or with a page request. As each input is received from a client the HTTP driver will call the optional function in your application which you define with HTTP_PROCESS_INPUT_FUNCTION in eth-http.h. The call will include the input name string (i.e. the name of the item on a form), the value entered or selected, the filename that is being requested, and the TCP socket number in case you need to identify the client by their unique MAC or IP address. Your function may alter the filename being requested if required so that the a different file is returned to the client, which can be useful for instance when asking users to log in so that they can be re-directed to a login success page if the details they entered are correct.

Whenever a client sends a request to the HTTP driver that includes inputs, each of the inputs will be passed to your applications function before the requested file is returned. This allows your application to update values as necessary which then may be included in the page sent back to the client, for instance confirming the settings they have just changed.

See the eth-http.h file for full details of the function you need to include.

The included sample project web page ‘Setup POP3’ section is a working example of a GET form.

The included sample project web page ‘Setup SMTP’ section is a working example of a POST “application/x-www-form-urlencoded” form.

Receiving Data Blocks & Files From Clients

One piece of functionality that is often missing or not fully implemented in embedded HTTP drivers is the ability to receive multipart form data using the ‘multipart/form-data’ type of POST request. The simpler ‘application/x-www-form-urlencoded’ POST request is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type ‘multipart/form-data’ is used instead to solve these problems and is also the standard method supported by all mainstream browsers to allow files to be uploaded by a user (the PUT request, whilst part of the HTTP specification, is not generally supported by browsers).

The ‘Sending Data To Your Embedded Http Server’ section later in this manual provides further details of the POST input methods.

The HTTP driver provides simple but effective handling of this type of POST request, and decodes the multipart data before passing it to the user application, so that the application gets the data exactly as submitted by a user.

When a multipart/form-data POST request is received the HTTP driver will call the following functions in your application to receive the input data. Each of these optional functions needs to be defined in eth-http.h.

HTTP_POST_MULTIPART_HEADER_FUNCTION

This function is called with each header found for a new multipart section of the input request (a request may contain one or more sections, each of which relates to an individual input). It will be called 1 or more times, and it signifies that when HTTP_POST_MULTIPART_NEXT_BYTE_FUNCTION is next called it will be with the data for this new section of the multipart message (i.e. any call to this function means your application is about to receive data for a new multipart section, so reset whatever your application needs to reset to start dealing with the new data).

HTTP_POST_MULTIPART_NEXT_BYTE_FUNCTION

This function is called with each decoded byte of a multipart section. The data you get here is the same as the data submitted by the user (the driver deals with all decoding).

HTTP_POST_LAST_MULTIPART_DONE_FUNCTION

This function is called after the last byte has been received for a multipart section, to allow your application to carry out any operations required with the data just received.

See the eth-http.h file for full details of the functions you need to include.

The included sample project web page ‘Upload File’ section is a working example of a POST “multipart/form-data” form.

Authorising Users

You may optionally include define HTTP_AUTHORISE_REQUEST_FUNCTION in eth-http.h. If you do this function in your application will be called each time a request is received from an HTTP client. Your function can then check the senders IP address or MAC address and optionally block the request from being serviced. Alternatively your function can also check the filename that is being requested and optionally modify it to re-direct the request to a different file. This is a great feature for applications where only certain users are permitted access or where users must log in before they can access certain files.

See the eth-http.h file for full details of the function you need to include.

HTTP 1.1 compliance

This driver generally complies with HTTP 1.1 apart from the following:

Chunked Transfer Coding is a requirement of HTTP 1.1 but is not supported by this driver. Chunked encoding allows a client to modify the body of a message in order to transfer it as a series of chunks, each with its own size indicator. The main purpose of this is to allow dynamically produced content to be transferred. This is not typically a requirement of an embedded application and to avoid a large amount of additional code space being required to handle this it is not supported.

f) Using HTTP Client

The driver includes a really useful HTTP client fucntion.  This allows the driver to connect to a remote HTTP server (a typical web server) and download individual files.  This is a great way of allowing embedded devices to access up to date information via the internet which may be uploaded and updated using standard web publishing tools or web services.  The files downaloded can be any type of file, not just html files, allowing your device to periodically updated it’s own media files for instance.

Requesting A File From A HTTP Server

Call the start_http_client_request function:


CONSTANT BYTE REMOTE_HTTP_SERVER_STRING[] = {"www.somedomain.com"};
CONSTANT BYTE REMOTE_HTTP_FILENAME_STRING[] = {"devices/file01.html"};

	if (start_http_client_request(REMOTE_HTTP_SERVER_STRING, REMOTE_HTTP_FILENAME_STRING))
	{
		//The request was started.
		//The HTTP_CLIENT_REQUEST_RECEIVE_FUNCTION function will deal with receiving the response if sucessful
	}

Processing Received HTTP Files

 

Define this with your applications function that will receive the files from the http server:

HTTP_CLIENT_REQUEST_RECEIVE_FUNCTION

Your function definition needs to be:


void my_function_name (BYTE op_code, DWORD content_length, BYTE *requested_host_url, BYTE *requested_filename)

op_code

0 = error – http client failed.

1 = OK.  First section of TCP file data ready to be read.  Function should use the tcp_read_next_rx_byte or tcp_read_rx_array functions to read all of the data from this TCP packet before returning.

2 = Next section of TCP file data ready to be read.  Function should use the tcp_read_next_rx_byte or tcp_read_rx_array functions to read all of the data from this TCP packet before returning.

0xff = The remote server has closed the connection (this will mark the end of the file if content-length was not provided by the server.

content_length

The file length specified by the server at the start of the response.  Note that the server is not requried to specify this (and many don’t) and in this instance the value will be 0xffffffff;

requested_host_url

Pointer to a null terminated string containing the host url that was originally requested

requested_filename

Pointer to a null terminated string containing the filename that was originally requested

Read each data byte in the packet using:


    while (tcp_read_next_rx_byte(&my_byte_variable))
    {

This function is always called after a request, either to either indicate the request failed, or with 1 or more packets of file data. If the request was sucessful the file data packets will be received in the correct order and the tcp data may simply be stored as it is read. The HTTP Client get request headers specify that no encoding may be used by the remote server so the file will be received exactly as it is stored on the server.

An example function


//*************************************************
//*************************************************
//********** PROCESS HTTP CLIENT REQUEST **********
//*************************************************
//*************************************************
void process_http_client_request (BYTE op_code, DWORD content_length, BYTE *requested_host_url, BYTE *requested_filename)
{
	static DWORD byte_id;
	BYTE data;

	if (op_code == 0)
	{
		//--------------------------
		//----- REQUEST FAILED -----
		//--------------------------

		return;
	}
	else if (op_code == 0xff)
	{
		//-----------------------------------------------
		//----- REMOTE SERVER HAS CLOSED CONNECTION -----
		//-----------------------------------------------
		//(This will mark the end of the file if content-length was not provided by the server)

		return;
	}
	else if (op_code == 1)
	{
		//-------------------------------------
		//----- FIRST PACKET OF FILE DATA -----
		//-------------------------------------
		byte_id = 0;		

	}

	//-------------------------
	//----- READ THE DATA -----
	//-------------------------
	while (tcp_read_next_rx_byte(&data))
	{
		//----- GOT NEXT BYTE -----
		//data has the next byte

		byte_id++;
		if (byte_id >= content_length)
		{
			//----- WE HAVE READ ALL OF THE FILE DATA -----

		}
	}
}

g) Using ARP

For more detailed information about how ARP works see the ‘How The Driver Works’ section later in this manual.

ARP (Address Resolution Protocol) is the method used to discover a devices hardware MAC address when only its IP address is known. An IP address is not fixed and may be assigned to a device and later changed. A MAC address however is unique and assigned to an Ethernet interface at manufacture. Although IP addresses are used to communicate between devices, it is the MAC address that is used at the lowest level and this is the address your NIC (Network Interface Controller) IC will compare to determine if a packet should be received and passed to the stack or not.

Received ARP requests are dealt with automatically by the stack – no application intervention is required.

The driver / stack components will typically carry out ARP automatically when communicating with a remote device. The following information is provided in case you want to use ARP for a specific reason in your application.

Call the arp_resolve_ip_address() function with the IP address that needs resolving (that you want the mac address to be returned for)

Periodically call the arp_is_resolve_complete() function to determine when the ARP response has been received .

The calling function must use a timeout in case of no response.

If trying to resolve an IP address that is not on the same subnet then the resolve function will automatically resolve the address for the network gateway (our_gateway_ip_address) which should then do its job and forward on communications to the remote device. Therefore for IP addresses outside of the same subnet bear in mind that just because ARP was successful the remote IP address is not necessarily actually available.


	//----- RESOLVING AN IP ADDRESS EXAMPLE -----
	IP_ADDR destination_ip_address;
	MAC_ADDR destination_mac_address;

	case SM_GET_DESTINATION_MAC_ADDRESS:
		//----- DO THE ARP REQUEST -----
		destination_ip_address.v[0] = 213;
		destination_ip_address.v[1] = 171;
		destination_ip_address.v[2] = 193;
		destination_ip_address.v[3] = 5;

		if (arp_resolve_ip_address(&destination_ip_address))
		{
			arp_response_timeout_timer = 10;
			our_state = SM_GET_DESTINATION_MAC_ADDRESS_WAIT
		}
		//Could not transmit right now - try again next time
		break;

	case SM_GET_DESTINATION_MAC_ADDRESS_WAIT:
		//----- WAIT FOR ARP RESPONSE -----
		if (arp_is_resolve_complete (&destination_ip_address, &destination_mac_address))
		{
			//ARP IS COMPLETE
		}

		if (arp_response_timeout_timer == 0)
		{
			//ARP FAILED - REMOTE DEVICE NOT FOUND
		}
		break;

h) Using DHCP

For more detailed information about how DHCP works see the ‘How The Driver Works’ section later in this manual.

DHCP (Dynamic Host Configuration Protocol) is an optional but widely used protocol that may be used by networked devices (clients) to automatically obtain an IP address, subnet mask and gateway address on power up and to renew this information periodically.

When the DHCP driver is used the following should be done by your application initialisation function prior to calling the tcp_ip_initialise() function. These variables may also be changed later at any time to switch between manual and DHCP IP settings and the driver will automatically handle the changeover.


	//----- TO USE A MANUALLY CONFIGURED IP SETTINGS -----
	eth_dhcp_using_manual_settings = 1;
	our_ip_address.v[0] = 192;			//MSB
	our_ip_address.v[1] = 168;
	our_ip_address.v[2] = 0;
	our_ip_address.v[3] = 51;			//LSB
	our_subnet_mask.v[0] = 255;			//MSB
	our_subnet_mask.v[1] = 255;
	our_subnet_mask.v[2] = 255;
	our_subnet_mask.v[3] = 0;			//LSB
	our_gateway_ip_address.v[0] = 192;
	our_gateway_ip_address.v[1] = 168;
	our_gateway_ip_address.v[2] = 0;
	our_gateway_ip_address.v[3] = 1;

or


	//----- TO USE DHCP CONFIGURED IP SETTINGS -----
	eth_dhcp_using_manual_settings = 0;

If your DHCP server stores name information and you want to include your device name as part of the DHCP process you can use the following pointer. Some DHCP servers will store received names of devices, others will not (and may use NetBIOS if need be instead). There is no requirement to pass a name using DHCP and it is typically used where there is a specific need on a network.


	//SET NAME TO BE RETURED TO DHCP SERVER IN DHCP PACKETS
	eth_dhcp_our_name_pointer = &netbios_our_network_name[0];
	//Use the NetBIOS name (if NetBIOS is being used) or replace with any
	//ram array containing your null terminated device name - fixed length
	//of 15 bytes excluding terminating null.

DHCP is dealt with automatically by the stack if DHCP is included – no application intervention is required

When enabled the driver will automatically obtain an IP address from a DHCP server and renew it as required

The following variable may be useful in your user application

our_ip_address_is_valid //= 1 if we have valid IP settings (either manually or from a DHCP server), 0 otherwise

i) Using NetBIOS

For more detailed information about how NetBIOS works see the ‘How The Driver Works’ section later in this manual.

The driver responds to NetBIOS (Network Basic Input/Output System) requests from other local network devices where the name in the request matches the name given to the driver. Therefore to use the NetBIOS function all you need to do is ensure there is a UDP socket available for NetBIOS to use and load the name to be used into the following array:


	netbios_our_network_name[0] = 'e';		//16 byte null terminated array
	netbios_our_network_name[1] = 'm';		//(Not case sensitive)
	netbios_our_network_name[2] = 'b';
	netbios_our_network_name[3] = 'e';
	netbios_our_network_name[4] = 'd';
	netbios_our_network_name[5] = 'd';
	netbios_our_network_name[6] = 'e';
	netbios_our_network_name[7] = 'd';
	netbios_our_network_name[8] = '-';
	netbios_our_network_name[9] = 'd';
	netbios_our_network_name[10] = 'e';
	netbios_our_network_name[11] = 'v';
	netbios_our_network_name[12] = 'i';
	netbios_our_network_name[13] = 'c';
	netbios_our_network_name[14] = 'e';
	netbios_our_network_name[15] = 0x00;

The driver will automatically open a UDP socket and listen for NetBIOS requests. When a request is received with a query name that matches this the driver automatically responds with a NetBIOS response containing the IP address.

When using NetBIOS you may simply enter the name instead of an IP address into computer applications. For instance if using this drivers HTTP server you may enter the name on its own in your browsers address bar to view the devices web interface.

There is one potential complication when using a NetBIOS name in a web browser. Windows will generally perform a DNS query for an new name first, as most browser entered names will be domain names of devices on the internet. If DNS fails Windows will then send a NetBIOS request to see if the name is on the local network. However if your gateway is using a DNS service that returns a valid address even if DNS has failed (for instance OpenDNS which does this to show search and advertising results when DNS fails) the browser will not know that DNS actually failed, will display the fake web page and NetBIOS will never be attampted. The solution to this is to:

Configure your DNS service not to perform this action

Configure your gateway router to use a different DNS service

Setup Windows with a registry change to make NetBIOS happen first

Add DNS records to your local DNS server for your local NetBIOS devices.

j) Using DNS

For more detailed information about how DNS works see the ‘How The Driver Works’ section later in this manual.

DNS (Domain Name System) is an internet protocol that allows human friendly names to be converted into computer friendly numbers (IP addresses). A DNS server simply receives a request for the IP address of a server from a provided text name (e.g. www.yahoo.com). Your devices internet connection will be provided via some form of ‘router’ and this router should incorporate a DNS server. The driver simply uses the address of the network gateway (either automatically learnt on power up using DHCP or manually provided by the user) and sends its DNS requests to be looked up by the gateway. The gateway responds with the IP address if found. The exact feature required by the gateway device is ‘recursion’ and this is provided by virtually all modern routers, from full blown servers to simple low cost DSL routers.

DNS requests are automatically dealt with by the stack, for instance when connecting to a POP3 or SMTP server.

No user application intervention is required. The following example is shown in case you want to use DNS for a specific reason in your application. Note that only 1 DNS resolution may be carried out at a time.


IP_ADDR dns_resolved_ip_address;

	//----- START NEW DNS QUERY -----
	if (!do_dns_query(temp_string, QNS_QUERY_TYPE_HOST))
	{
		//DNS query not currently available (already doing a query)
//try again next time
	}

	//----- IS DNS QUERY IS COMPLETE? -----
	//(Do this periodically until complete)
	dns_resolved_ip_address = check_dns_response();
	if (dns_resolved_ip_address.val == 0xffffffff)
	{
		//DNS QUERY FAILED
		//(Timed out or invalid response)
	}
	else if (dns_resolved_ip_address.val)
	{
		//DNS QUERY SUCESSFUL
		//Store the IP address
		my_remote_device_ip_address.val = dns_resolved_ip_address.val;
	}
	else
	{
		//DNS NOT YET COMPLETE
	}

k) Using POP3

For more detailed information about how POP3 works see the ‘How The Driver Works’ section later in this manual.

POP3 (Post Office Protocol version 3) and IMAP4 are the two most prevalent Internet standard protocols for receiving email. POP3 tends to be more suited to general embedded devices and is therefore the protocol supported by this driver. A POP3 email account will be provided by any typical internet service provider (many don’t support IMAP).

The POP3 driver allows you to specify a POP3 server URL, username and password, using either constants or variables. POP3 is carried out completely automatically by the driver and once triggered by a call to email_start_receive() the driver will use DNS to resolve the address of the POP3 server, connect to it, authenticate itself and then query how many emails are in the inbox. The driver will then read each email in turn and as it does it will first pass the email subject string to a specially defined function in your application and then each line of the email, for your application to process as desired. As the reading of each email is completed a final call is made to the function in your application with your return value indicating to the driver if it should delete or leave the email in the POP3 inbox.

MIME is used as the standard for attachments and non ASCII text in e-mail. Although POP3 does not require MIME formatted email, essentially most Internet email comes MIME formatted. This driver does not provide MIME decoding of email. Where you are sending ASCII text via email this is not a limitation as you simply send the email as plain text and the text will be located at the start of the email body section. Although it may be followed by MIME message fields and boundaries, you can simply ignore these. Sending an email as plain text ensures that the textual contents of the email will be sent as entered and simple parsing techniques can be used by your application when reading each line of the received email body. If you wish to send file attachments via email these will be MIME encoded by the sending software and your application will need to provide MIME decoding functionality. This driver does not provide this for you as typically the receiving of emails by embedded devices is simply required to pass configuration data or commands and these can be contained in the email body as text. As MIME has multiple encoding options and a receiver has no control over which encoding type will have been used by the sender of the email it is code heavy to implement this decoding. However if you require it for your application it is certainly possible to implement as each line of the MIME encoded email body is passed to your application. MIME decoding is undertaken for the HTTP driver POST method and these functions may be helpful to incorporate email MIME decoding.

Alternative methods of passing a file to your embedded device are to use the HTTP driver POST method via a web page or use POP3 email as a method of sending a trigger and web URL to the device and then using the much more straightforward method of the device using the driver to connect to the web site via TCP and download the file.

Each call by the driver to your application function includes the emails senders address so that you can optionally verify who sent the email or store it to respond with an email sent using SMTP.

To avoid potential problems POP3 receive can not be started if SMTP send is currently active

The driver includes an optional pointer pop3_email_progress_string_pointer which is updated at each stage of the sending process. If your device includes a screen then you may wish to display the state of the email receive process to the user as it occurs, to confirm the operation is happening or so the user can determine at which stage the operation fails if there is a problem and take appropriate action, such as correcting the entered mailbox settings.

See the eth-pop3.h file for full details of the functions you need to include.

An example of how to receive emails using POP3


	//-------------------------------
	//----- START EMAIL RECEIVE -----
	//-------------------------------
	//If POP3_USING_CONST_ROM_SETTINGS is commented out load the byte arrays you are using for the following string defines:
	POP3_SERVER_STRING
	POP3_USERNAME_STRING
	POP3_PASSWORD_STRING

	email_start_receive();		//Trigger receiving email

//----------------------------------------------------------------
//----- FUNCTION TO PROCESS EACH LINE OF EACH RECEIVED EMAIL -----
//----------------------------------------------------------------
BYTE process_pop3_received_email_line (BYTE status, BYTE *string, BYTE *sender_email_address)
{
	BYTE count;
	BYTE *p_dest;

	if (status == 0)
	{
		//----- START OF A NEW EMAIL - THIS IS THE SUBJECT -----
		//Process as desired

		return(0);
	}
	else if (status == 1)
	{
		//----- NEXT LINE OF THIS EMAIL -----
		//Process as desired

		return(0);
	}
	else
	{
		//----- END OF EMAIL -----
		return(1);	//0x01 = delete email, 0x00 = don't delete this email
	}
}

An example of how to use the optional progress information strings


	//Include the DO_POP3_PROGRESS_STRING define to enable these messages
	//Edit the strings in eth-pop3.h as desired
	if (email_is_receive_active())
	{
		//----- WE ARE RECEIVING EMAIL -----
		//CHECK FOR UPDATE USER DISPLAY
		if (pop3_email_progress_string_update)
		{
			//RECEIVE EMAIL STATUS HAS CHANGED - UPDATE DISPLAY
			pop3_email_progress_string_update = 0;
//Display the status null terminated string to the user
//on our screen
			//... = &pop3_email_progress_string[0];
		}
	}

Alternatively the POP3 state machine variable sm_pop3 may be used to determine the current state of email receive.

l) Using SMTP

For more detailed information about how SMTP works see the ‘How The Driver Works’ section later in this manual.

SMTP (Simple Mail Transfer Protocol) is the standard protocol used by mail clients to send emails. A SMTP server facility is typically provided with an internet connection (for instance by a DSL or dial up internet provider) and also by hosting providers who often provide remote a SMTP which may be used with authentication. The advantage of an internet providers SMTP server is that it will typically be free, however there may be requirements on the sender settings of emails and a device will need to be configured with the settings required by an individual connection. The advantage of a remote SMTP server is that all embedded devices can use the same server and login details regardless of where they are physically connected to the internet.

Whilst the receiving of POP3 emails is potentially made harder by the use of MIME formatting, sending emails with MIME is much easier. This is because it is the sender that decides how to MIME format an email and therefore the driver can use a pre determined approach that is simple and doesn’t require large amounts of program memory. MIME is used as the standard for attachments and non ASCII text in e-mail. Although SMTP is not required to send MIME formatted email, most Internet email is MIME formatted.

The SMTP driver allows you to specify a SMTP server URL, a username and password (if authentication is used), the recipients email address, the senders email address, the subject and an optional filename, using either constants or variables. SMTP is carried out completely automatically by the driver and once triggered by a call to email_start_send() the driver will use DNS to resolve the address of the SMTP server, connect to it, authenticate itself if necessary and then send an email with an optional file attachment. As the email is sent a special function in your application is called to request each byte of the email text and then each byte of the file attachment. The email is automatically MIME formatted and the file attachment BASE64 encoded by the driver, allowing any file type to be sent.

As TCP packets may need to be resent should a packet be lost, a variable is passed to the user application which is normally zero. However if it is not zero the application needs to be move back the specified number of bytes, as the driver is having to resend the previous packet. This may typically be accomplished by in your application using a simple byte counter or pointer variable to determine the next byte to be sent.

To avoid potential problems SMTP send can not be started if POP3 receive is currently active

The driver includes an optional pointer smtp_email_progress_string_pointer which is updated at each stage of the sending process. If your device includes a screen then you may wish to display the state of the email send process to the user as it occurs, to confirm the operation is happening or so the user can determine at which stage the operation fails if there is a problem and take appropriate action, such as correcting the entered server settings.

An example of how to send an email using SMTP


	static WORD send_email_body_next_byte;
	static DWORD send_email_file_next_byte;

	//----- START EMAIL SEND -----
	//If SMTP_USING_CONST_ROM_SETTINGS is commented out load the byte arrays
//your using for the following string defines:
	SMTP_SERVER_STRING
	SMTP_USERNAME_STRING
	SMTP_PASSWORD_STRING
	SMTP_TO_STRING
	SMTP_SENDER_STRING
	SMTP_SUBJECT_STRING

	send_email_body_next_byte = 0;
	send_email_file_next_byte = 0;
	email_start_send(1, 1);		//Trigger send email. Use authenticated
//login, include a file attachment

//---------------------------------------------------------------------------
//----- FUNCTION TO PROVIDE EACH BYTE OF EMAIL BODY AND FILE ATTACHMENT -----
//---------------------------------------------------------------------------
BYTE provide_smtp_next_data_byte (BYTE sending_email_body, BYTE start_of_new_tcp_packet, WORD resend_move_back_bytes, BYTE* next_byte)
{
	if (sending_email_body)
	{
		//-------------------------------------------
		//----- PROVIDE NEXT BYTE OF EMAIL BODY -----
		//-------------------------------------------
		if (start_of_new_tcp_packet)
		{
			//LAST PACKET CONFIMRED AS SENT - IF WE NEED TO STORE VALUES
//FOR THE NEXT PACKET OF DATA TO BE SENT DO IT NOW

		}
		if (resend_move_back_bytes)
		{
			//WE NEED TO RESEND THE LAST PACKET
			//(if should always be true but check in case of error)
			if (resend_move_back_bytes >= send_email_body_next_byte)
				send_email_body_next_byte -= resend_move_back_bytes;
		}

		//GET NEXT BYTE OF TEXT
		if (send_email_body_text_array[send_email_body_next_byte] != 0x00)
		{
			//NEXT BYTE TO SEND
			*next_byte =
send_email_body_text_array[send_email_body_next_byte++];
			return(1);
		}
		else
		{
			//ALL BYTES SENT
			return(0);
		}
	}
	else
	{
		//------------------------------------------------------
		//----- PROVIDE NEXT BYTE OF EMAIL FILE ATTACHMENT -----
		//------------------------------------------------------
		if (start_of_new_tcp_packet)
		{
			//LAST PACKET CONFIMRED AS SENT - IF WE NEED TO STORE VALUES
//FOR THE NEXT PACKET OF DATA TO BE SENT DO IT NOW

		}
		if (resend_move_back_bytes)
		{
			//WE NEED TO RESEND THE LAST PACKET
			//(if should always be true but check in case of error)
			if (resend_move_back_bytes >= send_email_file_next_byte)
				send_email_file_next_byte -= resend_move_back_bytes;
		}

		//GET NEXT BYTE OF FILE ATTACHMENT
		if (our_file_to_send_size > send_email_file_next_byte)
		{
			//NEXT BYTE TO SEND
			*next_byte = our_file_to_send_array[send_email_file_next_byte++];
			return(1);
		}
		else
		{
			//ALL BYTES SENT
			return(0);
		}
	}
}

An example of how to use the optional progress information strings


	//Include the DO_SMTP_PROGRESS_STRING define to enable these messages
	//Edit the strings in eth-smtp.h as desired
	if (email_is_send_active())
	{
		//----- WE ARE SENDING AN EMAIL -----
		//CHECK FOR UPDATE USER DISPLAY
		if (smtp_email_progress_string_update)
		{
			//SEND EMAIL STATUS HAS CHANGED - UPDATE DISPLAY
			smtp_email_progress_string_update = 0;
//Display the status string to the user on our screen
			//... = &smtp_email_progress_string[0];
		}
	}

Alternatively the SMTP state machine variable sm_smtp may be used to determine the current state of email send.

m) Using ICMP

For more detailed information about how ICMP works see the ‘How The Driver Works’ section later in this manual.

ICMP (Internet Control Message Protocol) is provided by the driver to respond to ping requests received. When a ping request is received (which actually is an ICMP ‘Echo request’) the driver will automatically respond with an ICMP ‘Echo reply’ message. ICMP ECHO requests are automatically dealt with by the stack and no user application intervention is required

n) Using SNTP

For more detailed information about how SNTP works see the ‘How The Driver Works’ section later in this manual.

A SNTP (Simple Network Time Protocol) client is included to allow your embedded device to retrieve the current time from a public NTP server. This can be a very useful way of avoiding including a battery backed real time clock in your system. A common misconception regarding SNTP and NTP is that it allows a device to automatically retrieve the current time without any special programming or end user configuration required. What SNTP actually provides is a 32 bit value of the number of seconds since 00:00:00 on 1 January 1900. Whilst this is the current time the value requires processing to be useful for most applications to deal with time zones, daylight saving and leap years. A typical usage of SNTP is to provide the means for the user to set the current time and to then use SNTP to accurately track time from that point. For instance if your device has its time set by a user and immediately sends a SNTP request, it will receive the current SNTP seconds value back. The device now knows the entered date and time equates to the received SNTP time value and can store this to non volatile memory. Whilst powered the device should keep track of time using its own oscillator. Every second that passes the internal real time clock can be incremented and a live count of the SNTP value incremented. Periodically (every few hours for instance) the device can send an SNTP request and compare the value received back with its live seconds count value. If the values differ, implying that the devices internal real time clock has become incorrect, the real time clock can be adjusted accordingly. Once the internal real time clock value has been adjusted (if it needs to be) the device then saves the new real time clock value and SNTP seconds value over the previous values in the non volatile memory. This carry’s on, ensuring that the devices real time clock is regularly corrected, but not too often to avoid swamping SNTP servers or burning out the non volatile memory.

Should the device loose power, when it next powers up it can read its last saved real time clock value and SNTP seconds count from the non volatile memory and then send a SNTP request. The received value can then be compared to the saved SNTP seconds count and the difference used to update the real time clock value to the current time.

This provides an elegant and relatively simple method of providing a non volatile real time clock for embedded devices that will be connected to the internet. However remember that you will need to allow for daylight saving and leap years yourself, and this can typically be accomplished by creating some form of hard coded lookup table of the relevant dates these will occur on for a block of future years.

The NTP timestamp is represented as a 64-bit unsigned fixed-point number. The integer part (seconds) is in the first 32 bits, and the fraction part in the last 32 bits. This driver deals with the integer seconds value only, and ignores the fraction part as inherent internet communication delays make using SNTP with an accuracy better than a second difficult. However it would be easy to modify the driver code to read the fractional part of the value if desired.

The maximum 32 bit value that can be represented is 4,294,967,295 seconds. Since some time in 1968 (second 2,147,483,648), the most significant bit has been set and the entire 64-bit field will overflow on 7 February 2036 (second 4,294,967,296). A simple and convenient way to extend the useful life of NTP timestamps is to use the following convention: If bit 0 is set, the UTC time is in the range 1968-2036, and UTC time is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, the time is in the range 2036-2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February 2036.

Below is an example of how to get the current SNTP time from your application:


	//----- START SNTP GET TIME PROCESS -----
	sntp_get_time();

//----- SNTP GET TIME EVENTS FUNCTION -----
//We have set this function to be called in eth-sntp.h when we trigger the
//SNTP client.  Its called once when the SNTP request gets sent (after DNS
//and ARP lookup has occurred, in case we want to start a tight timeout timer)
//and once when the response is received.
void sntp_send_receive_handler (BYTE event, DWORD sntp_seconds_value)
{

	if (event == 1)
	{
		//----- JUST SENT SNTP REQUEST -----

	}
	else if (event == 2)
	{
		//----- JUST RECEIVED SNTP RESPONSE -----
		//sntp_seconds_value is SNTP server time in seconds

	}
	else if (event == 3)
	{
		//----- SNTP FAILED -----
		//There was no response at one of the steps to get a SNTP response

	}
}

o) Using Driver With A New Network Interface Controller IC

Using the driver with a new nic (Network Interface Controller) requires adjusting the functions in the nic.c file to suit your nic (this file contains just the functions that are nic specific and doesn’t contain any of the general TCP/IP stack driver functions). This is simply a case of adapting each of the functions that are called by the stack to suit accessing your particular nic and following this guide should make the process straightforward.

Copy A Pair Of Sample Files

First copy the sample nic.c and nic.h pair of files for the included nic models that is most similar to your nic, to use as a reference. These can then be modified as necessary for your nic.

Nic Specific Defines

Go through each of the defines in the nic.h file and update as required for your nic.

IO Defines

Alter the IO defines to provide all of the input and output pins required by your nic and serial port access if your nic uses a serial rather than parallel interface.

Bus Access Delay Defines

NIC_DELAY_READ_WRITE

This define may be used with parallel interface nic’s to add in one or more null (no operation) instructions to allow data bus signals to stabilise during read or write access. 2 layer PCB’s (no ground plane) and PCB’s with long track lengths are more likely to require this. If you are experiencing problems setting up a new nic or PCB it is worth using this define to give a reasonable delay and then remove it later once you have the nic working.

Nic Specific Functions

The sample nic.c file you have copied may contain functions other than the below, but they will simply be included to support and be called by these main functions (i.e. used locally and not of interest to the rest of the TCP/IP driver).

If your nic has a 16 (or 32 bit) interface then use a sample file which also has an interface wider than 8 bits to see the simple technique to deal with the functions below that work with reading and writing individual bytes.

void nic_initialise (BYTE init_config)

Adjust to provide resetting of the nic and initialisation of all registers that require initialisation. Only 1 transmit buffer is required and all of the nics remaining memory can be allocated to receiving packets. Transmitted packets can be discarded once sent or if send fails.

This function also needs to set the nic’s MAC address from the following array:

our_mac_address

If the nic supports both 10Mbps and 100Mbps then use the BYTE value it is called with to set the nic’s speed:

0 = allow speed 10 / 100 Mbps

1 = force speed to 10 Mbps

Typically the interrupt output pin needs to be configured to be active when a packet has been received or the link status has changed.

Finally, enable packet reception and do the following:

//—– DO FINAL FLAGS SETUP —–

nic_is_linked = 0;

nic_speed_is_100mbps = 0;

nic_rx_packet_waiting_to_be_dumped = 0;

WORD nic_check_for_rx (void)

Read the relevant nic register and return 0 if no rx waiting or the number of bytes in the packet if rx is waiting

void nic_setup_read_data (void)

Set the nic’s read pointer to the beginning of the next unprocessed received packet so that the next operation can read the nic and data will be transferred from the data buffer

BYTE nic_read_next_byte (BYTE *data)

Read the next byte from the nic’s buffer memory (nic_setup_read_data will have already been called) and return 1 if the read was successful or 0 if nic_rx_bytes_remaining is zero and there are no more bytes in the rx buffer.

If a byte was read do the following:

nic_rx_bytes_remaining–;

BYTE nic_read_array (BYTE *array_buffer, WORD array_length)

Read the specified number of bytes from the nic’s receive buffer (nic_setup_read_data will have already been called) and return 1 if the read was successful or 0 if nic_rx_bytes_remaining becomes zero and there are no more bytes in the rx buffer.

As each byte is read do the following:

nic_rx_bytes_remaining–;

void nic_move_pointer (WORD move_pointer_to_ethernet_byte)

Move the nic’s read pointer to a specified byte location ready to be read next (a value of 0 = the first byte of the Ethernet header). Also do the following:

nic_rx_bytes_remaining = nic_rx_packet_total_ethernet_bytes – move_pointer_to_ethernet_byte;

void nic_rx_dump_packet (void)

Discard any remaining bytes in the current received packet and free up the nic for the next received packet. Also do the following:

//EXIT IF PACKET HAS ALREADY BEEN DISCARDED

if(nic_rx_packet_waiting_to_be_dumped == 0)

return;

//Discard the packet

//Flag that packet has been dumped

nic_rx_packet_waiting_to_be_dumped = 0;

BYTE nic_setup_tx (void)

Check the nic to see if it is ready to accept a new packet to be transmitted. If not return 0. If yes then it needs to set up the nic ready for the first byte of the data area (Ethernet frame) to be sent. The following checks may need to be made depending on your nic:

Check the nic isn’t overflowed

Check the nic isn’t tied up still sending the last transmitted packet

If also needs to:

nic_tx_len = 0;

void write_eth_header_to_nic (MAC_ADDR *remote_mac_address, WORD ethernet_packet_type)

This function can be a copy from the sample driver used.

void nic_write_next_byte (BYTE data)

Write the byte to the nic (nic_setup_tx will have already been called). It needs to do the following before outputting the byte:

if(nic_tx_len >= 1536)

return;

nic_tx_len++;

 

void nic_write_array (BYTE *array_buffer, WORD array_length)

Write the specified number of bytes to the nic (nic_setup_tx will have already been called). It needs to do the following before outputting each byte:

if(nic_tx_len >= 1536)

break;

nic_tx_len++;

void nic_write_tx_word_at_location (WORD byte_address, WORD data)

The byte_address supplied will be word aligned. Move the nic’s transmit buffer pointer to the specified location, write the value and then restore the pointer back to its previous location. This function is used when writing information required at the beginning of a packet that is not necessarily known before the end of the packet is written (such as length).

void nix_tx_packet (void)

Transmit the packet that has been constructed in the nic’s transmit buffer. The following needs to be included:

//If packet is below minimum length add pad bytes —–

while (nic_tx_len < 60)

nic_write_next_byte(0×00);

//Add the Ethernet CRC if the nic doesn’t do this automatically

//(most do)

BYTE nic_ok_to_do_tx (void)

Return 0 if the nic is not currently connected, is waiting for a received packet to be finished with and dumped (nic_rx_packet_waiting_to_be_dumped == 1) or is busy with a previous transmission. Otherwise return 1.

Getting A New Nic To Work

Often the nic manufacturer will provide sample code showing how to initialise, read and write the nic which can be used as the basis for the functions this driver requires. If problems are experienced its often useful to create a simple function to read the entire contents of the nic’s control registers so that you can debug why the nic isn’t doing what you expect.

One of the simplest ways to check a new nic is working correctly is to enable the drivers DHCP function, ensure there is a DHCP server on the network and then monitor the network traffic using Wireshark or a similar program to see if the DHCP packets are sent and received correctly. It that is successful then ping the device the driver is running on to confirm that array reading and writing is also working. If not breakpoint at important points in the above functions to find out where the problem is that needs to be corrected.