分享

Uip + Stm32移植问题总结

 细寒 2014-04-22

作者:Changing发表时间:07-26 21:37分类:电子相关3 Comments

uIP 由瑞典计算机科学学院(网络嵌入式系统小组)的Adam Dunkels (http:///adam/uip/)开发。其源代码由C 语言编写,并完全公开,有了这个TCP/IP协议栈,让嵌入式可以实现的功能更为丰富。可以作为WebClient 向指定网站提交数据,可以作为WebServer作为网页服务器,提供一个小型的动态页面访问功能。由于是开源的免费协议栈,据说Uip没有考虑协议安全的问题。
首先介绍下移植的环境: stm32 + ENC28J60网络模块 
IMG_20100101_103506.jpg
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应用例子,可以参照这个例子实现应用
Uip+stm32 MDK下工程建立
QQ截图20120726204704.png
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文件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "uip.h"
#include "ENC28J60.h"
/*---------------------------------------------------------------------------*/
void
tapdev_init(unsigned char *my_mac)
{
    enc28j60Init(my_mac);
}
/*---------------------------------------------------------------------------*/
unsigned int
tapdev_read(void)
{
    return enc28j60PacketReceive(UIP_CONF_BUFFER_SIZE,uip_buf);
}
/*---------------------------------------------------------------------------*/
void
tapdev_send(void)
{
    enc28j60PacketSend(uip_len,uip_buf);
}
/*---------------------------------------------------------------------------*/
写网卡驱动程序,与具体硬件相关。这一步比较费点时间,不过好在大部分网卡芯片的驱动程序都有代码借鉴或移植。驱动需要提供三个函数,以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如下:
1
2
3
4
5
6
7
8
9
10
11
#include "clock-arch.h"
#include "stm32f10x.h"
extern __IO int32_t g_RunTime;
/*---------------------------------------------------------------------------*/
clock_time_t
clock_time(void)
{
    return g_RunTime;
}
/*---------------------------------------------------------------------------*/
使用stm32 滴答定时器中断代码:
User/stm32f10x_it.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__IO int32_t g_RunTime = 0;
void SysTick_Handler(void)
{
    static uint8_t s_count = 0;
    if (++s_count >= 10)
    {
        s_count = 0;
        g_RunTime++;    /* 全局运行时间每10ms增1 */
        if (g_RunTime == 0x80000000)
        {
            g_RunTime = 0;
        }      
    }
}
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任何应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define UIP_CONF_LOGGING         0                //logging off
typedef int uip_tcp_appstate_t;         //出错可注释
typedef int uip_udp_appstate_t;         //出错可注释
/*#include "smtp.h"*/
/*#include "hello-world.h"*/
/*#include "telnetd.h"*/
/*#include "webserver.h"*/
/*#include "dhcpc.h"*/
/*#include "resolv.h"*/
/*#include "webclient.h"*/
#include "app_call.h"                    //加入一个Uip的数据接口文件
uIP 在接受到底层传来的数据包后,调用UIP_APPCALL( ),将数据送到上层应用程序处理。
User/app_call.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stm32f10x.h"
#ifndef UIP_APPCALL
    #define UIP_APPCALL                 Uip_Appcall
#endif
#ifndef UIP_UDP_APPCALL
    #define UIP_UDP_APPCALL             Udp_Appcall
#endif
void Uip_Appcall(void);
void Udp_Appcall(void);
void Uip_Appcall(void)
{
     
}
void Udp_Appcall(void)
{
     
}
4.加入uIP 的的主循环代码架构
User/main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
#include "uip.h"
#include "uip_arp.h"
#include "tapdev.h"
#include "timer.h"
#include "ENC28J60.h"
#include "SPI.h"
#define  PRINTF_ON  1
#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
#ifndef NULL
#define NULL (void *)0
#endif /* NULL */
static unsigned char mymac[6] = {0x04,0x02,0x35,0x00,0x00,0x01};
void RCC_Configuration(void);
void GPIO_Configuration(void);
void USART_Configuration(void);
int main(void)
{
    int i;
    uip_ipaddr_t ipaddr;
    struct timer periodic_timer, arp_timer;
    RCC_Configuration();
    GPIO_Configuration();
    USART_Configuration();
    SPInet_Init();
    timer_set(&periodic_timer, CLOCK_SECOND / 2);
    timer_set(&arp_timer, CLOCK_SECOND * 10);
    SysTick_Config(72000);          //配置滴答计时器
    //以太网控制器驱动初始化
    tapdev_init(mymac);
   
    //Uip 协议栈初始化
    uip_init();
    uip_ipaddr(ipaddr, 192, 168, 1, 15);     //配置Ip
    uip_sethostaddr(ipaddr);
    uip_ipaddr(ipaddr, 192, 168, 1, 1);     //配置网关
    uip_setdraddr(ipaddr);
    uip_ipaddr(ipaddr, 255, 255, 255, 0);   //配置子网掩码
    uip_setnetmask(ipaddr);
    while(1){
   
        uip_len = tapdev_read();                                //从网卡读取数据
         
        if(uip_len > 0)
        {                                                       //如果数据存在则按协议处理
            if(BUF->type == htons(UIP_ETHTYPE_IP)) {         //如果收到的是IP数据,调用uip_input()处理
                uip_arp_ipin();                                    
                uip_input();
                /* If the above function invocation resulted in data that
                   should be sent out on the network, the global variable uip_len is set to a value > 0. */
                if(uip_len > 0)
                {
                  uip_arp_out();
                  tapdev_send();
                }
            }else if(BUF->type == htons(UIP_ETHTYPE_ARP)){    //如果收到的是ARP数据,调用uip_arp_arpin处理
                uip_arp_arpin();
                /* If the above function invocation resulted in data that
                   should be sent out on the network, the global variable uip_len is set to a value > 0. */
                if(uip_len > 0)
                {
                  tapdev_send();
                }
            }
     
        }else if(timer_expired(&periodic_timer)){           //查看0.5s是否到了,调用uip_periodic处理TCP超时程序
              timer_reset(&periodic_timer);
              for(i = 0; i < UIP_CONNS; i++) {
     
                    uip_periodic(i);
     
                    /* If the above function invocation resulted in data that
                       should be sent out on the network, the global variable uip_len is set to a value > 0. */
     
                    if(uip_len > 0)
                    {
                      uip_arp_out();
                      tapdev_send();
                    }
              }
                                 
              for(i = 0; i < UIP_UDP_CONNS; i++)
              {
     
                    uip_udp_periodic(i);                                //处理udp超时程序
     
                    /* If the above function invocation resulted in data that
                       should be sent out on the network, the global variable uip_len is set to a value > 0. */
     
                    if(uip_len > 0)
                    {
                      uip_arp_out();
                      tapdev_send();
                    }
              }
           
              /* Call the ARP timer function every 10 seconds. */            //10s到了就处理ARP
              if(timer_expired(&arp_timer))
              {
                    timer_reset(&arp_timer);
                    uip_arp_timer();
              }
        }
    }
}
/*******************************Stm32 Set***************************************/
void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;                                                                                                                                                                                                                                                       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        
    GPIO_Init(GPIOA , &GPIO_InitStructure);
     
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;          
    GPIO_Init(GPIOA , &GPIO_InitStructure);
}
void RCC_Configuration(void)
{
    /* 定义枚举类型变量 HSEStartUpStatus */
    ErrorStatus HSEStartUpStatus;
    /* 复位系统时钟设置*/
    RCC_DeInit();
    /* 开启HSE*/
    RCC_HSEConfig(RCC_HSE_ON);
    /* 等待HSE起振并稳定*/
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
    /* 判断HSE起是否振成功,是则进入if()内部 */
    if(HSEStartUpStatus == SUCCESS)
    {
        /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */
        RCC_HCLKConfig(RCC_SYSCLK_Div1);
        /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */
        RCC_PCLK2Config(RCC_HCLK_Div1);
        /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */
        RCC_PCLK1Config(RCC_HCLK_Div2);
        /* 设置FLASH延时周期数为2 */
        FLASH_SetLatency(FLASH_Latency_2);
        /* 使能FLASH预取缓存 */
        FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
        /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */
        RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
        /* 使能PLL */
        RCC_PLLCmd(ENABLE);
        /* 等待PLL输出稳定 */
        while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
        /* 选择SYSCLK时钟源为PLL */
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
        /* 等待PLL成为SYSCLK时钟源 */
        while(RCC_GetSYSCLKSource() != 0x08);
    }
    /* 打开APB2总线上的GPIOA时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE);
         
}
  
void USART_Configuration(void)
{
    USART_InitTypeDef USART_InitStructure;
    USART_ClockInitTypeDef USART_ClockInitStructure;
    USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
    USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
    USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;                                                                                                                                                     
    USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
    USART_ClockInit(USART1 , &USART_ClockInitStructure);
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
    USART_Init(USART1,&USART_InitStructure);
    USART_Cmd(USART1,ENABLE);
}
#if  PRINTF_ON
int fputc(int ch,FILE *f)
{
    USART_SendData(USART1,(u8) ch);
    while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
    return ch;
}
#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 测试。。
QQ截图20120726201101.png

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多