0.前言
    去年(2013年)的整理了LwIP相关代码,并在STM32上“裸奔”成功。一直没有时间深入整理,在这里借博文整理总结。LwIP的移植过程细节很多,博文也不可能一一详细解释个别部分仅仅能点到为止。
    【本文要点】
    【1】不带操作系统的LwIP移植,LwIP版本号为1.4.1。

    【2】MCU为STM32F103VE,网卡为ENC28J60。

    【3】移植过程重点描写叙述ethernetif.c和LwIP宏配置等。
    【4】一个简单的TCP echo样例。

    【5】力求简单。没有DHCP功能,甚至没实用到网卡中断。

    【代码仓库】
    代码仓库位于Bitbucket(要源码请点击这里)。博文中不能把每一个细节描写叙述清楚。很多其它内容请參考代码仓库中的详细代码。

    【硬件说明】
    測试平台使用奋斗版,原理图请參考代码仓库中的DOC目录。

    【參考博文】
    学习嵌入式网络是一个循序渐进的过程,从浅入深从简单到复杂。
    【1】ENC28J60学习笔记——学习网卡
    【2】STM32NET学习笔记——索引——理解TCPIP协议栈
    【3】uIP学习笔记——初次应用协议栈
1.ethernetif.c的相关改动
    尽管LwIP移植过程比較复杂,可是仅仅要结合网卡详细功能,耐心改动ethernetif.c就可以。ethernetif.c重点实现网卡的三个功能,初始化,发送和接收。

    为了更好的配合lwIP,改动了ENC28J60学习笔记中部分驱动函数。

(换句话说,想要从0開始移植LwIP必须对操作网卡很熟悉)

    【1】初始化
static void
low_level_init(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state; /* set MAC hardware address length */
netif->hwaddr_len = ETHARP_HWADDR_LEN; /* set MAC hardware address */
netif->hwaddr[0] = 'A';
netif->hwaddr[1] = 'R';
netif->hwaddr[2] = 'M';
netif->hwaddr[3] = 'N';
netif->hwaddr[4] = 'E';
netif->hwaddr[5] = 'T'; /* maximum transfer unit */
netif->mtu = 1500; /* device capabilities */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* Do whatever else is needed to initialize interface. */
enc28j60_init(netif->hwaddr); // 【1】
}
    【说明】
        【1】 enc28j60_init(netif->hwaddr); low_level_init中指定了enc28j60中的网卡地址。

    【2】发送
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
struct ethernetif *ethernetif = netif->state;
struct pbuf *q; enc28j60_init_send(p->tot_len); //【1】initiate transfer(); #if ETH_PAD_SIZE
pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif for(q = p; q != NULL; q = q->next) {
/* Send the data from the pbuf to the interface, one pbuf at a
time. The size of the data in each pbuf is kept in the ->len
variable. */
enc28j60_writebuf( q->payload, q->len ); //【2】send data from(q->payload, q->len);
} enc28j60_start_send(); //【3】signal that packet should be sent(); #if ETH_PAD_SIZE
pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif LINK_STATS_INC(link.xmit); return ERR_OK;
}
    【说明】
        【1】enc28j60_init_send(p->tot_len); 初始化发送缓冲区大小, pbuf结构为一个链表,第一个pbuf结构体中的tot_len字段代表整个以太网数据包的大小。

        【2】enc28j60_writebuf( q->payload, q->len ); 通过遍历链表把内容填入ENC28J60的缓冲区中。
        【3】enc28j60_start_send();启动网卡发送。
    【3】接收
static struct pbuf *
low_level_input(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state;
struct pbuf *p, *q;
u16_t len; len = enc28j60_packet_getlen(); // 【1】 #if ETH_PAD_SIZE
len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif /* We allocate a pbuf chain of pbufs from the pool. */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (p != NULL) { #if ETH_PAD_SIZE
pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif for(q = p; q != NULL; q = q->next) {
enc28j60_readbuf (q->payload, q->len ); //【2】read data into(q->payload, q->len);
}
enc28j60_finish_receive(); //【3】acknowledge that packet has been read(); #if ETH_PAD_SIZE
pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif LINK_STATS_INC(link.recv);
} else {
enc28j60_finish_receive(); //【4】drop packet();
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
} return p;
}
    【说明】
        【1】len = enc28j60_packet_getlen(); 获得网卡中数据包的长度。
        【2】enc28j60_readbuf (q->payload, q->len);把网卡中的内容拷贝到内存池中。
        【3】enc28j60_finish_receive();接收完毕,移动网卡中缓冲区指针。
    【4】应用
        【1】LwIP网卡硬件初始化调用ethernetif_init就可以,该函数中调用了low_level_init,并指定了网卡输出函数low_level_output。

        【2】一旦网卡有数据进入。应马上代用ethernetif_input函数。

能够使用中断方法或查询方法。

2.lwipopt.h配置简述
    lwip中的配置选项很的多。了解全部的配置很不easy。本博文參考STM32官方的两个样例总结得到。

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__ #define SYS_LIGHTWEIGHT_PROT 0 #define NO_SYS 1 #define NO_SYS_NO_TIMERS 1 /* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
byte alignment -> define MEM_ALIGNMENT to 2. */
#define MEM_ALIGNMENT 4 /* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE (5*1024) /* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
sends a lot of data out of ROM (or other static memory), this
should be set high. */
#define MEMP_NUM_PBUF 10
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
per active UDP "connection". */
#define MEMP_NUM_UDP_PCB 6
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
connections. */
#define MEMP_NUM_TCP_PCB 10
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 6
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
segments. */
#define MEMP_NUM_TCP_SEG 12
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
timeouts. */
#define MEMP_NUM_SYS_TIMEOUT 3 /* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE 10 /* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE 1500 /* ---------- TCP options ---------- */
#define LWIP_TCP 1
#define TCP_TTL 255 /* Controls if TCP should queue segments that arrive out of
order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ 0 /* TCP Maximum segment size. */
#define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */ /* TCP sender buffer space (bytes). */
#define TCP_SND_BUF (2*TCP_MSS) /* TCP sender buffer space (pbufs). This must be at least = 2 *
TCP_SND_BUF/TCP_MSS for things to work. */
#define TCP_SND_QUEUELEN (6 * TCP_SND_BUF)/TCP_MSS /* TCP receive window. */
#define TCP_WND (2*TCP_MSS) /* ---------- ICMP options ---------- */
#define LWIP_ICMP 1 /* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
turning this on does currently not work. */
#define LWIP_DHCP 0 /* ---------- UDP options ---------- */
#define LWIP_UDP 1
#define UDP_TTL 255 /* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1 /**
* LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
*/
#define LWIP_NETCONN 0 /**
* LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
*/
#define LWIP_SOCKET 0 #endif /* __LWIPOPTS_H__ */
    【详细说明和改动】
    【1】未使用操作系统。全部NO_SYS定义为1。LWIP_NETCONN定义为0(表示不使用),LWIP_SOCKET定义为0(表示不使用)。

    【2】NO_SYS_NO_TIMERS定义为1,该定义为LwIP1.4.0以上版本号添加。详细可參考LwIP改动文档。
    【3】LWIP_DHCP被定义为0。关闭了DHCP功能以简化代码。

    【4】相比STM32官方样例。去除了校验码相关配置全部使用软件校验。STM32官方案例中使用了代码EMAC功能的MCU,该系列MCU中包含硬件校验功能,可是ENC28J60并没有此功能,所以仅仅能开启LwIP中的软件校验功能。

    
3.LwIP相关初始化
void LwIP_Config (void)
{
struct ip_addr ipaddr;
struct ip_addr netmask;
struct ip_addr gw; // 调用LWIP初始化函数
lwip_init(); IP4_ADDR(&ipaddr, 192, 168, 1, 16); // 设置网络接口的ip地址
IP4_ADDR(&netmask, 255, 255, 255, 0); // 子网掩码
IP4_ADDR(&gw, 192, 168, 1, 1); // 网关 // 初始化enc28j60与LWIP的接口。參数为网络接口结构体、ip地址、
// 子网掩码、网关、网卡信息指针、初始化函数、输入函数
netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input); // 把enc28j60设置为默认网卡
netif_set_default(&enc28j60); netif_set_up(&enc28j60);
}
    【说明】
        【1】通过netif_add初始化网卡IP地址,子网掩码和网关地址。此处使用静态IP地址。

        【2】netif_add须要传入两个函数指针,各自是网卡初始化函数和接收内容处理函数。ethernetif_init位于ethernetif.c而ethernet_input并不位于ethernetif.c,此处也不能使用ethernetif_input,事实上ethernet_input在函数ethernetif_input被调用,可是ethernet_input变了一个样子:
        netif->input(p, netif)!=ERR_OK
        【3】在带操作系统的移植中。最后一个參数使用tcpip_input。
4.while(1)部分
    timer_typedef tcp_timer, arp_timer;

    /* 设定查询定时器 ARP定时器 */
timer_set(&tcp_timer, CLOCK_SECOND / 10); // tcp处理定时器 100ms
timer_set(&arp_timer, CLOCK_SECOND * 5); // arp处理定时器 5s
while (1) { if (enc28j60_packet_getcount() != 0) {
ethernetif_input(&enc28j60);
} // TCP 定时处理
if (timer_expired(&tcp_timer)) {
timer_set(&tcp_timer, CLOCK_SECOND / 4);
tcp_tmr();
} // ARP 定时处理
if (timer_expired(&arp_timer)) {
timer_set(&arp_timer, CLOCK_SECOND * 5);
etharp_tmr();
}
}
    【说明】
    while(1)循环包含3个主要功能
    【1】一旦接受到数据包,立马调用 ethernetif_input。此处使用查询法而不是中断法(中断法效果类似)
    【2】定期处理TCP链接。定时时间为100ms。可依据情况适当缩小时间间隔。
    【3】定期更新ARP缓冲,可依据情况适当扩大时间间隔。
    【4】此处的timer通过systick实现。详细实现请參考代码仓库。

4.基本測试
    【1】ping实验
    此时网卡的静态IP地址为192.168.1.16,通过ping指令发送16个数据包
    ping 192.168.1.16 -n 16

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVrYWk4NzExMDU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" /> 

图1 ping实验
    【2】TCP Echo样例
    LwIP提供很多演示样例。TCP Echo演示样例位于contrib-1.4.1的apps目录中,目录名为tcpecho_raw)。改动TCP侦听port为10086。

    err = tcp_bind(echo_pcb, IP_ADDR_ANY, 10086);

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVrYWk4NzExMDU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

图2 TCP Echo样例
5.总结
    【1】移植和应用LwIP一定要耐心仔细。
    【2】一旦网卡接收到数据,应调用ethernetif_input函数。调用该函数让数据进入LwIP协议栈。
    【3】 netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);最后一个參数为ethernet_input,千万必要写成ethernetif_input。
6.參考资料
    很多其它细节内容请參考图书资料
    【1】《嵌入式网络系统设计——基于Atmel ARM7系列》
    【2】《STM32嵌入式系统开发实战指南——FreeRTOS与LwIP移植》

版权声明:本文博主原创文章,博客,未经同意,不得转载。

LwIP学习笔记——STM32 ENC28J60移植与入门的更多相关文章

  1. jQuery学习笔记(一):入门

      jQuery学习笔记(一):入门 一.JQuery是什么 JQuery是什么?始终是萦绕在我心中的一个问题: 借鉴网上同学们的总结,可以从以下几个方面观察. 不使用JQuery时获取DOM文本的操 ...

  2. Oracle学习笔记之四,SQL语言入门

    1. SQL语言概述 1.1 SQL语言特点 集合性,SQL可以的高层的数据结构上进行工作,工作时不是单条地处理记录,而对数据进行成组的处理. 统一性,操作任务主要包括:查询数据:插入.修改和删除数据 ...

  3. canvas学习笔记(下篇) -- canvas入门教程--保存状态/变形/旋转/缩放/矩阵变换/综合案例(星空/时钟/小球)

    [下篇] -- 建议学习时间4小时  课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...

  4. canvas学习笔记(中篇) -- canvas入门教程-- 颜色/透明度/渐变色/线宽/线条样式/虚线/文本/阴影/图片/像素处理

    [中篇] -- 建议学习时间4小时  课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...

  5. canvas学习笔记(上篇)-- canvas入门教程 -- canvas标签/方块/描边/路径/圆形/曲线

    [上篇] -- 建议学习时间4小时  课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...

  6. C#线程学习笔记九:async & await入门二

    一.异步方法返回类型 只能返回3种类型(void.Task和Task<T>). 1.1.void返回类型:调用方法执行异步方法,但又不需要做进一步的交互. class Program { ...

  7. pandas库学习笔记(二)DataFrame入门学习

    Pandas基本介绍——DataFrame入门学习 前篇文章中,小生初步介绍pandas库中的Series结构的创建与运算,今天小生继续“死磕自己”为大家介绍pandas库的另一种最为常见的数据结构D ...

  8. Spark (Python版) 零基础学习笔记(一)—— 快速入门

    由于Scala才刚刚开始学习,还是对python更为熟悉,因此在这记录一下自己的学习过程,主要内容来自于spark的官方帮助文档,这一节的地址为: http://spark.apache.org/do ...

  9. 【SSM】学习笔记(二)——SpringMVC入门

    原视频链接:https://www.bilibili.com/video/BV1Fi4y1S7ix/?p=43&spm_id_from=pageDriver&vd_source=8ae ...

随机推荐

  1. basename, dirname 在C语言中的使用

    basename作用是得到特定的路径中的最后一个'/',后面的内容 如/usr/bin,得到的内容就是bin 如果/sdcard/miui_recovery/backup 得到的内容就是backup ...

  2. uva 1393 - Highways(容斥原理)

    题目连接:uva 1393 - Highways 题目大意:给定一个m∗n的矩阵,将矩阵上的点两两相连,问有多少条直线至少经过两点. 解题思路:头一次做这样的题目,卡了一晚上. dp[i][j]即为i ...

  3. 采用Sambaserver由win平台,linux平台上传文件

    1.构造yum [root@db /]# cd /etc/yum.repos.d/ [root@db yum.repos.d]# vi yum.repo --改动光盘挂载位置,enabled设置为启动 ...

  4. redmine 出口中国的乱码

    pdf 这是redmine的bug.必须在个人账户更改将设立中国语文,足够的人才来解决. 顺便说一下,提示.以下更改文件的方法是无效的 /home/redmine/redmine-2.5.1/lib/ ...

  5. iOS开发多线程篇—多线程简介

    iOS开发多线程篇-多线程简介 一.进程和线程 1.什么是进程 进程是指在系统中正在执行的一个应用程序 每一个进程之间是独立的.每一个进程均执行在其专用且受保护的内存空间内 比方同一时候打开QQ.Xc ...

  6. iOS经常使用类别

    我们发现,慢慢积累了很多自己写的各种类别的. .今天,无私.张贴 1.NSDateFomatter @interface NSDateFormatter (MyCategory) + (id)date ...

  7. 玩转Web之Jsp(一)-----jsp中的静态包含(<%@include file="url"%>)与动态包含(<jsp:include>)

    在jsp中include有两种形式,其中<%@include file="url"%>是指令元素,<jsp:include page="" f ...

  8. ssh远程登录报错REMOTE HOST IDENTIFICATION HAS CHANGED!解决方式及原因

    注意,文档中的ip和指纹已经替换为了ip.ip.ip.ip 和aa:... ,以免引起不必要的误会. icode@test:~/lab/dir/sadf$ ssh remote_name@ip.ip. ...

  9. 高速压缩跟踪(fast compressive tracking)(CT)算法分析

    本文为原创,转载请注明出处:http://blog.csdn.net/autocyz/article/details/44490009 Fast Compressive Tracking (高速压缩跟 ...

  10. hibernate它 11.many2many双向

    表结构: 类图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29iZW5kaWFua3Vu/font/5a6L5L2T/fontsize/400/fi ...