Uip + Stm32移植问题总结
uIP 由瑞典计算机科学学院(网络嵌入式系统小组)的Adam Dunkels (http://dunkels.com/adam/uip/)开发。其源代码由C 语言编写,并完全公开,有了这个TCP/IP协议栈,让嵌入式可以实现的功能更为丰富。可以作为
WebClient 向指定网站提交数据,可以作为
WebServer作为网页服务器,提供一个小型的动态页面访问功能。由于是开源的免费协议栈,据说Uip没有考虑协议安全的问题。
首先介绍下移植的环境: stm32 + ENC28J60网络模块

Enc28j60是带SPI 接口的独立以太网控制器,可以用mcu控制spi来实现tcp/ip数据流的收发,所以要先完成Enc28j60的驱动程序,再整合Uip。Uip是用标准的C语言实现,所以移植Uip在51单片机和stm32上类似。
经过几天的琢磨,已经将Uip的几个示例稳定运行。Uip中apps下的例子相互之间存在冲突,源程序中也有一些Error 要修改,我将Uip的文件结构做了一些调整。
Uip文件结构
先介绍下Uip下各个目录文件的功能:
├─apps apps目录 下为uip提供的一些应用示例
│ ├─dhcpc
│ ├─hello-world
│ ├─resolv
│ ├─smtp
│ ├─telnetd
│ ├─webclient
│ └─webserver
│ └─httpd-fs
├─doc doc下放置的为说明文档,程序中用不上
│ └─html
├─lib lib下为内存块管理函数源码
├─uip uip下为uip和核心实现源码
└─unix unix环境里的uip应用例子,可以参照这个例子实现应用
│ ├─dhcpc
│ ├─hello-world
│ ├─resolv
│ ├─smtp
│ ├─telnetd
│ ├─webclient
│ └─webserver
│ └─httpd-fs
├─doc doc下放置的为说明文档,程序中用不上
│ └─html
├─lib lib下为内存块管理函数源码
├─uip uip下为uip和核心实现源码
└─unix unix环境里的uip应用例子,可以参照这个例子实现应用
Uip+stm32 MDK下工程建立

stm32的目录结构建立可以参考
stm32 开发环境MDK+库文件配置
User 放置 stm32 SPI配置以及Uip配置和Enc28j60和Uip的接口函数
uip下为uip的核心实现源码以及内存管理源码(即为Uip/uip+Uip/lib)
dev下为Enc28j60的驱动函数源码
apps为uip的各个示例应用源码(Uip/apps下的文件)包括smtp,rsolve,dhcp,telnetd,以及webclient
webserver 的文件结构较为复杂,独立一个文件夹
Uip移植
Uip的移植可以参考uip的unix的文件结构。
1. Uip的数据通过网卡Enc28j60从物理层剥离,所以需要先配置Uip和Enc28j60的数据交互。这个部分在tapdev.c文件中:
01 | #include "uip.h" |
02 | #include "ENC28J60.h" |
03 |
04 | /*---------------------------------------------------------------------------*/ |
05 | void |
06 | tapdev_init(unsigned char *my_mac) |
07 | { |
08 | enc28j60Init(my_mac); |
09 | } |
10 | /*---------------------------------------------------------------------------*/ |
11 | unsigned int |
12 | tapdev_read( void ) |
13 | { |
14 | return enc28j60PacketReceive(UIP_CONF_BUFFER_SIZE,uip_buf); |
15 | } |
16 | /*---------------------------------------------------------------------------*/ |
17 | void |
18 | tapdev_send( void ) |
19 | { |
20 | enc28j60PacketSend(uip_len,uip_buf); |
21 | } |
22 | /*---------------------------------------------------------------------------*/ |
写网卡驱动程序,与具体硬件相关。这一步比较费点时间,不过好在大部分网卡芯片的驱动程序都有代码借鉴或移植。驱动需要提供三个函数,以Enc28j60 驱动为例。
tapdev_init():网卡初始化函数,初始化网卡的工作模式。
tapdev_read(void):读包函数。将网卡收到的数据放入全局缓存区uip_buf 中,返回包的长度,赋给uip_len。
void tapdev_send(void):发包函数。将全局缓存区uip_buf 里的数据(长度放在uip_len 中)发送出去。
2.由于uIP 协议栈需要使用时钟,为TCP 和ARP 的定时器服务。因此使用单片机的定时器或是stm32的滴答定时器用作时钟,每20ms 让计数tick_cnt 加1,这样,25 次计数(0.5S)满了后可以调用TCP 的定时处理程序。10S 后可以调用ARP 老化程序。uIP1.0 版本,增加了timer.c/timer.h,专门用来管理时钟,修改clock-arch.c如下:
01 | #include "clock-arch.h" |
02 | #include "stm32f10x.h" |
03 |
04 | extern __IO int32_t g_RunTime; |
05 | /*---------------------------------------------------------------------------*/ |
06 | clock_time_t |
07 | clock_time( void ) |
08 | { |
09 | return g_RunTime; |
10 | } |
11 | /*---------------------------------------------------------------------------*/ |
使用stm32 滴答定时器中断代码:
User/stm32f10x_it.c
01 | __IO int32_t g_RunTime = 0; |
02 | void SysTick_Handler( void ) |
03 | { |
04 | static uint8_t s_count = 0; |
05 | if (++s_count >= 10) |
06 | { |
07 | s_count = 0; |
08 |
09 | g_RunTime++; /* 全局运行时间每10ms增1 */ |
10 | if (g_RunTime == 0x80000000) |
11 | { |
12 | g_RunTime = 0; |
13 | } |
14 | } |
15 | } |
3.uipopt.h/uip-conf.h 是配置文件,用来设置本地的IP 地址、网关地址、MAC 地址、全局缓冲区的大小、支持的最大连接数、侦听数、ARP 表大小等。可以根据需要配置。
#define UIP_FIXEDADDR 1
决定uIP是否使用一个固定的IP地址。
如果uIP使用一个固定的IP地址,应该置位(set)这些uipopt.h中的选项。如果不的话,则应该使用宏uip_sethostaddr(),uip_setdraddr() 和 uip_setnetmask()。
#define UIP_PINGADDRCONF 0 Ping IP地址赋值。
#define UIP_FIXEDETHADDR 0 指明uIP ARP模块是否在编译时使用一个固定的以太网MAC地址。
#define UIP_TTL 255 uIP发送的IP packets的IP TTL (time to live)。
#define UIP_REASSEMBLY 0 uIP支持IP packets的分片和重组。
#define UIP_REASS_MAXAGE 40 一个IP fragment在被丢弃之前可以在重组缓冲区中存在的最大时间。
#define UIP_UDP 0 是否编译UDP的开关。
#define UIP_ACTIVE_OPEN 1 决定是否支持uIP打开一个连接。
#define UIP_CONNS 10 同时可以打开的TCP连接的最大数目。由于TCP连接是静态分配的,减小这个数目将占用更少的RAM。每一个TCP连接需要大约30字节的内存。
#define UIP_LISTENPORTS 10 同时监听的TCP端口的最大数目。每一个TCP监听端口需要2个字节的内存。
#define UIP_RECEIVE_WINDOW 32768 建议的接收窗口的大小。如果应用程序处理到来的数据比较慢,那么应该设置的小一点(即,相对与uip_buf缓冲区的大小来说),相反如果应用程序处理数据很快,可以设置的大一点(32768字节)。
#define UIP_URGDATA 1 决定是否支持TCP urgent data notification。
#define UIP_RTO 3 The initial retransmission timeout counted in timer pulses.不要改变
#define UIP_MAXRTX 8 在中止连接之前,应该重发一个段的最大次数。不要改变
#define UIP_TCP_MSS (UIP_BUFSIZE – UIP_LLH_LEN – 40) TCP段的最大长度。它不能大于UIP_BUFSIZE – UIP_LLH_LEN – 40.
#define UIP_TIME_WAIT_TIMEOUT 120 一个连接应该在TIME_WAIT状态等待多长。不要改变
#define UIP_ARPTAB_SIZE 8 ARP表的大小。如果本地网络中有许多到这个uIP节点的连接,那么这个选项应该设置为一个比较大的值。
#define UIP_BUFSIZE 1500 uIP packet缓冲区不能小于60字节,但也不必大于1500字节。
#define UIP_STATISTICS 1 决定是否支持统计数字。统计数字对调试很有帮助,并展示给用户。
#define UIP_LOGGING 0 输出uIP登陆信息。
#define UIP_LLH_LEN 14 链接层头部长度。对于SLIP,应该设置成0。
uip-conf.h 中增加几个主要结构体定义,不include任何应用
01 | #define UIP_CONF_LOGGING 0 //logging off |
02 |
03 | typedef int uip_tcp_appstate_t; //出错可注释 |
04 | typedef int uip_udp_appstate_t; //出错可注释 |
05 |
06 | /*#include "smtp.h"*/ |
07 | /*#include "hello-world.h"*/ |
08 | /*#include "telnetd.h"*/ |
09 | /*#include "webserver.h"*/ |
10 | /*#include "dhcpc.h"*/ |
11 | /*#include "resolv.h"*/ |
12 | /*#include "webclient.h"*/ |
13 |
14 | #include "app_call.h" //加入一个Uip的数据接口文件 |
uIP 在接受到底层传来的数据包后,调用UIP_APPCALL( ),将数据送到上层应用程序处理。
User/app_call.c
01 | #include "stm32f10x.h" |
02 |
03 | #ifndef UIP_APPCALL |
04 | #define UIP_APPCALL Uip_Appcall |
05 | #endif |
06 |
07 | #ifndef UIP_UDP_APPCALL |
08 | #define UIP_UDP_APPCALL Udp_Appcall |
09 | #endif |
10 |
11 | void Uip_Appcall( void ); |
12 | void Udp_Appcall( void ); |
13 |
14 |
15 | void Uip_Appcall( void ) |
16 | { |
17 | |
18 | } |
19 |
20 | void Udp_Appcall( void ) |
21 | { |
22 | |
23 | } |
4.加入uIP 的的主循环代码架构
User/main.c
001 | #include "stm32f10x.h" |
002 | #include "stdio.h" |
003 | #include "string.h" |
004 |
005 | #include "uip.h" |
006 | #include "uip_arp.h" |
007 | #include "tapdev.h" |
008 | #include "timer.h" |
009 | #include "ENC28J60.h" |
010 | #include "SPI.h" |
011 |
012 | #define PRINTF_ON 1 |
013 |
014 | #define BUF ((struct uip_eth_hdr *)&uip_buf[0]) |
015 |
016 | #ifndef NULL |
017 | #define NULL (void *)0 |
018 | #endif /* NULL */ |
019 |
020 | static unsigned char mymac[6] = {0x04,0x02,0x35,0x00,0x00,0x01}; |
021 |
022 | void RCC_Configuration( void ); |
023 | void GPIO_Configuration( void ); |
024 | void USART_Configuration( void ); |
025 |
026 | int main( void ) |
027 | { |
028 | int i; |
029 | uip_ipaddr_t ipaddr; |
030 | struct timer periodic_timer, arp_timer; |
031 |
032 | RCC_Configuration(); |
033 | GPIO_Configuration(); |
034 | USART_Configuration(); |
035 | SPInet_Init(); |
036 |
037 | timer_set(&periodic_timer, CLOCK_SECOND / 2); |
038 | timer_set(&arp_timer, CLOCK_SECOND * 10); |
039 |
040 | SysTick_Config(72000); //配置滴答计时器 |
041 |
042 | //以太网控制器驱动初始化 |
043 | tapdev_init(mymac); |
044 | |
045 | //Uip 协议栈初始化 |
046 | uip_init(); |
047 |
048 | uip_ipaddr(ipaddr, 192, 168, 1, 15); //配置Ip |
049 | uip_sethostaddr(ipaddr); |
050 | uip_ipaddr(ipaddr, 192, 168, 1, 1); //配置网关 |
051 | uip_setdraddr(ipaddr); |
052 | uip_ipaddr(ipaddr, 255, 255, 255, 0); //配置子网掩码 |
053 | uip_setnetmask(ipaddr); |
054 |
055 | while (1){ |
056 | |
057 | uip_len = tapdev_read(); //从网卡读取数据 |
058 | |
059 | if (uip_len > 0) |
060 | { //如果数据存在则按协议处理 |
061 | if (BUF->type == htons(UIP_ETHTYPE_IP)) { //如果收到的是IP数据,调用uip_input()处理 |
062 |
063 | uip_arp_ipin(); |
064 | uip_input(); |
065 |
066 | /* If the above function invocation resulted in data that |
067 | should be sent out on the network, the global variable uip_len is set to a value > 0. */ |
068 |
069 | if (uip_len > 0) |
070 | { |
071 | uip_arp_out(); |
072 | tapdev_send(); |
073 | } |
074 |
075 | } else if (BUF->type == htons(UIP_ETHTYPE_ARP)){ //如果收到的是ARP数据,调用uip_arp_arpin处理 |
076 |
077 | uip_arp_arpin(); |
078 |
079 | /* If the above function invocation resulted in data that |
080 | should be sent out on the network, the global variable uip_len is set to a value > 0. */ |
081 |
082 | if (uip_len > 0) |
083 | { |
084 | tapdev_send(); |
085 | } |
086 | } |
087 | |
088 | } else if (timer_expired(&periodic_timer)){ //查看0.5s是否到了,调用uip_periodic处理TCP超时程序 |
089 |
090 | timer_reset(&periodic_timer); |
091 | for (i = 0; i < UIP_CONNS; i++) { |
092 | |
093 | uip_periodic(i); |
094 | |
095 | /* If the above function invocation resulted in data that |
096 | should be sent out on the network, the global variable uip_len is set to a value > 0. */ |
097 | |
098 | if (uip_len > 0) |
099 | { |
100 | uip_arp_out(); |
101 | tapdev_send(); |
102 | } |
103 | } |
104 | |
105 | for (i = 0; i < UIP_UDP_CONNS; i++) |
106 | { |
107 | |
108 | uip_udp_periodic(i); //处理udp超时程序 |
109 | |
110 | /* If the above function invocation resulted in data that |
111 | should be sent out on the network, the global variable uip_len is set to a value > 0. */ |
112 | |
113 | if (uip_len > 0) |
114 | { |
115 | uip_arp_out(); |
116 | tapdev_send(); |
117 | } |
118 | } |
119 | |
120 | /* Call the ARP timer function every 10 seconds. */ //10s到了就处理ARP |
121 | if (timer_expired(&arp_timer)) |
122 | { |
123 | timer_reset(&arp_timer); |
124 | uip_arp_timer(); |
125 | } |
126 | } |
127 | } |
128 |
129 | } |
130 | /*******************************Stm32 Set***************************************/ |
131 |
132 | void GPIO_Configuration( void ) |
133 | { |
134 | GPIO_InitTypeDef GPIO_InitStructure; |
135 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; |
136 |
137 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; |
138 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; |
139 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
140 | |
141 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; |
142 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; |
143 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
144 | } |
145 |
146 | void RCC_Configuration( void ) |
147 | { |
148 | /* 定义枚举类型变量 HSEStartUpStatus */ |
149 | ErrorStatus HSEStartUpStatus; |
150 |
151 | /* 复位系统时钟设置*/ |
152 | RCC_DeInit(); |
153 | /* 开启HSE*/ |
154 | RCC_HSEConfig(RCC_HSE_ON); |
155 | /* 等待HSE起振并稳定*/ |
156 | HSEStartUpStatus = RCC_WaitForHSEStartUp(); |
157 | /* 判断HSE起是否振成功,是则进入if()内部 */ |
158 | if (HSEStartUpStatus == SUCCESS) |
159 | { |
160 | /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */ |
161 | RCC_HCLKConfig(RCC_SYSCLK_Div1); |
162 | /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */ |
163 | RCC_PCLK2Config(RCC_HCLK_Div1); |
164 | /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */ |
165 | RCC_PCLK1Config(RCC_HCLK_Div2); |
166 | /* 设置FLASH延时周期数为2 */ |
167 | FLASH_SetLatency(FLASH_Latency_2); |
168 | /* 使能FLASH预取缓存 */ |
169 | FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); |
170 | /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */ |
171 | RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); |
172 | /* 使能PLL */ |
173 | RCC_PLLCmd(ENABLE); |
174 | /* 等待PLL输出稳定 */ |
175 | while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); |
176 | /* 选择SYSCLK时钟源为PLL */ |
177 | RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); |
178 | /* 等待PLL成为SYSCLK时钟源 */ |
179 | while (RCC_GetSYSCLKSource() != 0x08); |
180 | } |
181 | /* 打开APB2总线上的GPIOA时钟*/ |
182 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE); |
183 | |
184 | } |
185 |
186 | |
187 | void USART_Configuration( void ) |
188 | { |
189 | USART_InitTypeDef USART_InitStructure; |
190 | USART_ClockInitTypeDef USART_ClockInitStructure; |
191 |
192 | USART_ClockInitStructure.USART_Clock = USART_Clock_Disable; |
193 | USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; |
194 | USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge; |
195 | USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable; |
196 | USART_ClockInit(USART1 , &USART_ClockInitStructure); |
197 |
198 | USART_InitStructure.USART_BaudRate = 9600; |
199 | USART_InitStructure.USART_WordLength = USART_WordLength_8b; |
200 | USART_InitStructure.USART_StopBits = USART_StopBits_1; |
201 | USART_InitStructure.USART_Parity = USART_Parity_No; |
202 | USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; |
203 | USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; |
204 | USART_Init(USART1,&USART_InitStructure); |
205 |
206 | USART_Cmd(USART1,ENABLE); |
207 | } |
208 |
209 | #if PRINTF_ON |
210 |
211 | int fputc ( int ch, FILE *f) |
212 | { |
213 | USART_SendData(USART1,(u8) ch); |
214 | while (USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); |
215 | return ch; |
216 | } |
217 |
218 | #endif |
5.解决编译过程中的错误。归总如下:
- Uip/uip-split.c 注释所有的 tcpip_output()函数 消除uip_fw_output()函数的注释
- Uip/memb.c 中 memb_free()函数 返回值 return -1 改为 return 1
- Apps/resolv.c 中resolv_conf() 中
//resolv_conn = uip_udp_new(dnsserver, HTONS(53));
resolv_conn = uip_udp_new((uip_ipaddr_t*)dnsserver, HTONS(53));
解决完所有问题后,编译成功后下载到stm32,ping 测试。。
