Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

TCP/IP Stack, FAT16 System on a Microcontroller

4.53/5 (10 votes)
31 Jul 2011GPL35 min read 44.2K   2.1K  
TCP/IP Stack, FAT16 System on a Microcontroller

Introduction

This article is about how to port TCP/IP stack to a microprocessor, how to read SD card, and how to read FAT system based on SD card.

Background

Nowadays, there are lots of embedded project using TCP/IP to give the end user a http interface, or a simple TFTP interface to upload/download files. There are lots of simplified TCP/IP ports for different processors, like LWIP or uIP. I have tried to port a PIC processor TCP/IP stack to a AVR32 chip. From my point of view, this PIC TCP/IP stack code is clear and simple to understand.

Using the Code

The source code provided will be compiled on AVR32 Studio, and run on a Atmel EVK1105 board.

It has a http server up and running, a DHCP client, and basic IP, TCP support.

It also tries to read on board SD card slot by using SPI interface. Then it uses FAT16 structure to try to read and print out what's on this SD card provided this SD card is formatted with FAT16, and one/more files are put on root directories.

PART 1 TCP/IP ports

It first sends out a DHCP request, to request a IP address from DHCP server, if you want to use static IP address, then go to tcpipconfig.h file, comment out the following line:

C++
#define STACK_USE_DHCP

If you are using static IP address, go to conf_eth.h file, change the following lines to your specified IP addresses.

C++
#define ETHERNET_CONF_IPADDR0          192
#define ETHERNET_CONF_IPADDR1          168
#define ETHERNET_CONF_IPADDR2            1
#define ETHERNET_CONF_IPADDR3           89

It has basic MAC/PHY support, which from OSI network point of view, only has upto Data Link Layer (DLL) support, no TCP/IP stack on top of it. Then I copied PIC processor's TCP/IP stack to this project. Then I first make sure the IP check-sum function works, it is the basics of all TCP/IP ports.

After a IP check-sum routine is successful, then program has to determine a packet is UDP or TCP packet. If it is TCP packet, then it's TCP check-sum routine, TCP checksum is using pseudo tcp header plus tcp packet to calculate checksum, it's a chicken and egg game.

TCP connection establish and disconnect is a much complicated process, and it's basics of our HTTP server. UDP packet is simpler, and it has check-sum routine as well, both TCP and UDP checksum are using the same routine, like the following:

C++
WORD get_checksum(BYTE * pseudoHeader,WORD len1,BYTE * tcp_pkt,WORD len2)
{
	WORD word16;
	DWORD sum=0;
	WORD i;

	// make 16 bit words out of every two adjacent 8 bit words in the packet
	// and add them up
	for (i=0;i<len1;i=i+2){
		word16 =((pseudoHeader[i]<<8)&0xFF00)+(pseudoHeader[i+1]&0xFF);
		sum = sum + (DWORD) word16;
	}

	if (len1&1)//odd number of bytes
	{
		sum = sum + (DWORD) pseudoHeader[len1-1];
	}

	for (i=0;i<len2;i=i+2){
		word16 =((tcp_pkt[i]<<8)&0xFF00)+(tcp_pkt[i+1]&0xFF);
		sum = sum + (DWORD) word16;
	}

	if (len2&1)//odd number of bytes
	{
		sum = sum + (DWORD) tcp_pkt[len2-1];
	}

	while (sum>>16)
		sum = (sum & 0xFFFF)+(sum >> 16);

	// one's complement the result
	sum = ~sum;
	return (WORD)sum;
}

The EVK1105 has another LWIP port as well, but to my knowledge it's not easy to understand, if you want to add your own tcp/ip applications, like NTP, SMTP, etc. This MACB example has MACB reading routine, it only reads up-to 128 bytes each time, I have to change it slightly to read all the data received.

C++
len = ulMACBInputLength();
if( len != 0)
{
	// Let the driver know we are going to read a new packet.
	vMACBRead( NULL, 0, 0 );
	// Read enough bytes to fill this buf.
	i=0;
	recvd_pkt.len = len;
	while (TRUE){
		vMACBRead( data+128*i, 128, len );
		if (len>128) len-=128;
		else break;
		i++;
	}

	recvd_pkt.data = data;
	macb_example_receive_packet(&recvd_pkt);
}

I am still debugging http server, although DHCP client works.

As TCP is based on IP packet, the same with UDP packet, IP packet is from MAC layer. To draw a picture, their relation is like this:

untitled.JPG

To make sure our DHCP client works, we have to open up UDP packet with client port number 68, and server port 67, send our discover ip message.

C++
DHCPSocket = UDPOpen(DHCP_CLIENT_PORT,
                             &DHCPServerNode,
                             DHCP_SERVER_PORT);
                             
_DHCPSend(DHCP_DISCOVER_MESSAGE)

When there is something on UDP port (FindMatchingSocket function), we then use _DHCPReceive to receive address, then send back an request message with an IP address just received from server.

C++
_DHCPSend(DHCP_REQUEST_MESSAGE);	  

DHCP server will send back ACK message, then we can proceed to use this IP address.

DHCP server will allow us to lease this IP address for a while, then we can renew this IP address by sending an _DHCPSend(DHCP_REQUEST_MESSAGE).

HTTP server will first create one or several socket on port 80, start listening, if there is TCP client on port 80, serve them, then close this socket, start another listening process.

PART 2 FAT16 ports

EVK1105 comes with a SD card slot, we can simply read and write to this board by using SD examples which has been provided by Atmel company. EVK1105 also provide us a FAT example, which reads from only extra FLASH chip.

FAT system was developed by Microsoft, FAT has FAT16 and FAT32 format. To use my example code, please make sure to format SD card with FAT16.

All the hard disks, SD card, or other storage media connected to computer are using some sort of file systems, the simplest one is FAT system.

Any storage media first sector (first 512 bytes) is Master Boot Record (MBR), we have to read this sector first, it contains Partition Record, normally 4 records, each record occupy 16 bytes, which is like the following format:

C++
typedef struct{
	BYTE	boot_desc;
	BYTE	first_partition[3];
	BYTE	file_system_desc;
	BYTE	last_partition[3];
	DWORD	num_sectors_before_first;
	DWORD	num_sectors_in_partition;
}__attribute__((packed)) PARTITION_ENTRY;	  

First byte boot_desc either 0x80 or 0x00, which means whether this partition is bootable or not.

As our SD card only contains one partition, our MBR will looks like following:

C++
1a0:	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 

1b0:	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  03 

1c0:	06  00 	06  0f 	e0  c4 	65  00 	00  00 	9b  89 	07  00 	00  00 

1d0:	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 

1e0:	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 

1f0:	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	00  00 	55  aa	  

From the above MBR record, we get result:

  • MBR SIGNATURE=0xaa55
  • File System Type=0x6
  • LBA Begin:0x65 Partition size:0x7899b

Then we use this Logic Block Address (LBA), which is 0x65 in our case, read this sector (sector 0x65), remember each sector is 512 bytes, if using SPI interface, we are reading address 512*0x65 !

From this address, we get the following result:

C++
000:	eb  3c 	90  4d 	53  44 	4f  53 	35  2e 	30  00 	02  08 	06  00 

010:	02  00 	02  00 	00  f8 	f1  00 	3f  00 	ff  00 	65  00 	00  00 

020:	9b  89 	07  00 	00  00 	29  fe 	11  e1 	ac  4e 	4f  20 	4e  41 

030:	4d  45 	20  20 	20  20 	46  41 	54  31 	36  20 	20  20 	33  c9 

040:	8e  d1 	bc  f0 	7b  8e 	d9  b8 	00  20 	8e  c0 	fc  bd 	00  7c 

050:	38  4e 	24  7d 	24  8b 	c1  99 	e8  3c 	01  72 	1c  83 	eb  3a 

060:	66  a1 	1c  7c 	26  66 	3b  07 	26  8a 	57  fc 	75  06 	80  ca 

070:	02  88 	56  02 	80  c3 	10  73 	eb  33 	c9  8a 	46  10 	98  f7 

080:	66  16 	03  46 	1c  13 	56  1e 	03  46 	0e  13 	d1  8b 	76  11 

090:	60  89 	46  fc 	89  56 	fe  b8 	20  00 	f7  e6 	8b  5e 	0b  03 

0a0:	c3  48 	f7  f3 	01  46 	fc  11 	4e  fe 	61  bf 	00  00 	e8  e6 

0b0:	00  72 	39  26 	38  2d 	74  17 	60  b1 	0b  be 	a1  7d 	f3  a6 

0c0:	61  74 	32  4e 	74  09 	83  c7 	20  3b 	fb  72 	e6  eb 	dc  a0 

0d0:	fb  7d 	b4  7d 	8b  f0 	ac  98 	40  74 	0c  48 	74  13 	b4  0e 

0e0:	bb  07 	00  cd 	10  eb 	ef  a0 	fd  7d 	eb  e6 	a0  fc 	7d  eb 

0f0:	e1  cd 	16  cd 	19  26 	8b  55 	1a  52 	b0  01 	bb  00 	00  e8 

100:	3b  00 	72  e8 	5b  8a 	56  24 	be  0b 	7c  8b 	fc  c7 	46  f0 

110:	3d  7d 	c7  46 	f4  29 	7d  8c 	d9  89 	4e  f2 	89  4e 	f6  c6 

120:	06  96 	7d  cb 	ea  03 	00  00 	20  0f 	b6  c8 	66  8b 	46  f8 

130:	66  03 	46  1c 	66  8b 	d0  66 	c1  ea 	10  eb 	5e  0f 	b6  c8 

140:	4a  4a 	8a  46 	0d  32 	e4  f7 	e2  03 	46  fc 	13  56 	fe  eb 

150:	4a  52 	50  06 	53  6a 	01  6a 	10  91 	8b  46 	18  96 	92  33 

160:	d2  f7 	f6  91 	f7  f6 	42  87 	ca  f7 	76  1a 	8a  f2 	8a  e8 

170:	c0  cc 	02  0a 	cc  b8 	01  02 	80  7e 	02  0e 	75  04 	b4  42 

180:	8b  f4 	8a  56 	24  cd 	13  61 	61  72 	0b  40 	75  01 	42  03 

190:	5e  0b 	49  75 	06  f8 	c3  41 	bb  00 	00  60 	66  6a 	00  eb 

1a0:	b0  4e 	54  4c 	44  52 	20  20 	20  20 	20  20 	0d  0a 	52  65 

1b0:	6d  6f 	76  65 	20  64 	69  73 	6b  73 	20  6f 	72  20 	6f  74 

1c0:	68  65 	72  20 	6d  65 	64  69 	61  2e 	ff  0d 	0a  44 	69  73 

1d0:	6b  20 	65  72 	72  6f 	72  ff 	0d  0a 	50  72 	65  73 	73  20 

1e0:	61  6e 	79  20 	6b  65 	79  20 	74  6f 	20  72 	65  73 	74  61 

1f0:	72  74 	0d  0a 	00  00 	00  00 	00  00 	00  ac 	cb  d8 	55  aa	  

This will give us:

  • Bytes per sector: 512
  • Sectors per cluster: 8
  • Reserved Sectors before FAT: 6
  • Number of FAT: 2
  • Root Entry count: 512
  • FAT sectors: 241
  • Root Dir first sector: 488
  • Root Dir sectors: 32
  • BOOT SIGNATURE=0xaa55
  • It is FAT16 system!

Then we use above information to set up our FAT system, start open, read, write file procedures.

To be continued...

Thanks for reading.

History

  • 2011, July 19, Announced the project, no source code provided.
  • 2011, July 21, As this post is very popular, and requested by other members, I uploaded the source code, there are software bugs, code needs re-fracture, etc.
  • 2011, August 1, Added FAT16 reading part support.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)