微控制器上的 TCP/IP 堆栈和 FAT16 系统






4.53/5 (9投票s)
微控制器上的 TCP/IP 堆栈和 FAT16 系统
引言
本文介绍了如何将 TCP/IP 堆栈移植到微处理器,如何读取 SD 卡,以及如何基于 SD 卡读取 FAT 文件系统。
背景
如今,许多嵌入式项目都使用 TCP/IP 为最终用户提供 HTTP 接口,或简单的 TFTP 接口以上传/下载文件。有许多针对不同处理器的简化 TCP/IP 移植版本,例如 LWIP 或 uIP。我尝试将 PIC 处理器的 TCP/IP 堆栈移植到 AVR32 芯片。从我的观点来看,这个 PIC TCP/IP 堆栈代码清晰易懂。
Using the Code
提供的源代码将在 AVR32 Studio 中编译,并在 Atmel EVK1105 板上运行。
它具备一个正在运行的 HTTP 服务器、一个 DHCP 客户端,以及基本的 IP、TCP 支持。
它还尝试通过 SPI 接口读取板载 SD 卡插槽。然后,它使用 FAT16 结构来尝试读取和打印 SD 卡上的内容,前提是该 SD 卡已格式化为 FAT16,并且根目录中包含一个或多个文件。
第一部分 TCP/IP 移植
它首先发送 DHCP 请求,以从 DHCP 服务器请求 IP 地址。如果您想使用静态 IP 地址,请转到tcpipconfig.h文件,注释掉以下行
#define STACK_USE_DHCP
如果您使用的是静态 IP 地址,请转到conf_eth.h文件,将以下行更改为您指定的 IP 地址。
#define ETHERNET_CONF_IPADDR0 192
#define ETHERNET_CONF_IPADDR1 168
#define ETHERNET_CONF_IPADDR2 1
#define ETHERNET_CONF_IPADDR3 89
它具备基本的 MAC/PHY 支持,从 OSI 网络角度来看,仅支持数据链路层 (DLL),其上没有 TCP/IP 堆栈。然后我将 PIC 处理器的 TCP/IP 堆栈复制到此项目中。然后我首先确保 IP 校验和函数正常工作,这是所有 TCP/IP 移植的基础。
在 IP 校验和例程成功后,程序必须确定数据包是 UDP 还是 TCP 数据包。如果它是 TCP 数据包,那么就是 TCP 校验和例程,TCP 校验和是使用伪 TCP 头加上 TCP 数据包来计算校验和,这是一个先有鸡还是先有蛋的问题。
TCP 连接的建立和断开是一个更复杂的过程,也是我们 HTTP 服务器的基础。UDP 数据包更简单,并且也具备校验和例程,TCP 和 UDP 校验和都使用相同的例程,如下所示
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;
}
EVK1105 还有一个 LWIP 移植版本,但据我所知,它并不容易理解。如果您想添加自己的 tcp/ip 应用程序,例如 NTP、SMTP 等。这个 MACB 示例包含 MACB 读取例程,每次只读取最多 128 字节,我不得不稍微修改它以读取所有接收到的数据。
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);
}
我仍在调试 HTTP 服务器,尽管 DHCP 客户端工作正常。
由于 TCP 基于 IP 数据包,与 UDP 数据包一样,IP 数据包来自 MAC 层。为了画一张图,它们的关系如下:
为了确保我们的 DHCP 客户端正常工作,我们必须打开 UDP 数据包,客户端端口号为 68,服务器端口为 67,发送我们的发现 IP 消息。
DHCPSocket = UDPOpen(DHCP_CLIENT_PORT,
&DHCPServerNode,
DHCP_SERVER_PORT);
_DHCPSend(DHCP_DISCOVER_MESSAGE)
当 UDP 端口上有内容时(FindMatchingSocket
函数),我们使用_DHCPReceive
接收地址,然后发送一个请求消息,其中包含刚刚从服务器接收到的 IP 地址。
_DHCPSend(DHCP_REQUEST_MESSAGE);
DHCP 服务器将发送 ACK 消息,然后我们就可以继续使用此 IP 地址了。
DHCP 服务器将允许我们将此 IP 地址租用一段时间,然后我们可以通过发送_DHCPSend(DHCP_REQUEST_MESSAGE)
来续订此 IP 地址。
HTTP 服务器将首先创建端口 80 上的一个或多个套接字,开始监听,如果端口 80 上有 TCP 客户端,则为其服务,然后关闭该套接字,开始另一个监听过程。
第二部分 FAT16 移植
EVK1105 配备了 SD 卡插槽,我们可以通过使用 Atmel 公司提供的 SD 示例来简单地读取和写入此板。EVK1105 还提供了 FAT 示例,该示例仅从额外的 FLASH 芯片读取。
FAT 文件系统由 Microsoft 开发,FAT 有 FAT16 和 FAT32 格式。要使用我的示例代码,请确保使用 FAT16 格式化 SD 卡。
所有连接到计算机的硬盘、SD 卡或其他存储介质都使用某种文件系统,最简单的一种是 FAT 系统。
任何存储介质的第一个扇区(前 512 字节)是主引导记录 (MBR),我们必须先读取这个扇区,它包含分区记录,通常有 4 条记录,每条记录占 16 字节,格式如下:
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;
第一个字节boot_desc
要么是 0x80,要么是 0x00,这表示该分区是否可启动。
由于我们的 SD 卡只包含一个分区,我们的 MBR 将如下所示:
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
从上面的 MBR 记录中,我们得到结果:
- MBR SIGNATURE=0xaa55
- 文件系统类型=0x6
- LBA Begin:0x65 Partition size:0x7899b
然后我们使用这个逻辑块地址 (LBA),在本例中为 0x65,读取该扇区(扇区 0x65),请记住每个扇区是 512 字节,如果使用 SPI 接口,我们正在读取地址 512*0x65!
从这个地址,我们得到以下结果:
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
这将给我们
- 每扇区字节数:512
- 每簇扇区数:8
- FAT 之前的保留扇区数:6
- FAT 数量:2
- 根目录条目数:512
- FAT 扇区数:241
- 根目录第一个扇区:488
- 根目录扇区数:32
- BOOT SIGNATURE=0xaa55
- 它是 FAT16 系统!
然后我们使用上述信息来设置我们的 FAT 系统,开始打开、读取、写入文件的过程。
待续...
感谢阅读。
历史
- 2011 年 7 月 19 日,项目宣布,未提供源代码。
- 2011 年 7 月 21 日,由于此帖子非常受欢迎,并且有其他成员要求,我上传了源代码,其中存在软件错误,代码需要重构等。
- 2011 年 8 月 1 日,添加了 FAT16 读取部分支持。