LwIP学习笔记——STM32 ENC28J60移植与入门

发布者:幸福之舞最新更新时间:2017-02-20 来源: eefocus关键字:LwIP  STM32  ENC28J60  移植与入门 手机看文章 扫描二维码
随时随地手机看文章

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学习笔记——初次应用协议栈

    【4】Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32——更加实用的做法

1.ethernetif.c的相关修改

    虽然LwIP移植过程比较复杂,但是只要结合网卡具体功能,耐心修改ethernetif.c即可。ethernetif.c重点实现网卡的三个功能,初始化,发送和接收。

    为了更好的配合lwIP,修改了ENC28J60学习笔记中部分驱动函数。(换句话说,想要从0开始移植LwIP必须对操作网卡非常熟悉)

    【1】初始化


  1. static void  

  2. low_level_init(struct netif *netif)  

  3. {  

  4.   struct ethernetif *ethernetif = netif->state;  

  5.    

  6.   /* set MAC hardware address length */  

  7.   netif->hwaddr_len = ETHARP_HWADDR_LEN;  

  8.   

  9.   /* set MAC hardware address */  

  10.   netif->hwaddr[0] = 'A';  

  11.   netif->hwaddr[1] = 'R';  

  12.   netif->hwaddr[2] = 'M';  

  13.   netif->hwaddr[3] = 'N';  

  14.   netif->hwaddr[4] = 'E';  

  15.   netif->hwaddr[5] = 'T';  

  16.    

  17.   /* maximum transfer unit */  

  18.   netif->mtu = 1500;  

  19.    

  20.   /* device capabilities */  

  21.   /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */  

  22.   netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;  

  23.    

  24.   /* Do whatever else is needed to initialize interface. */  

  25.   enc28j60_init(netif->hwaddr); // 【1】  

  26. }  

    【说明】

        【1】 enc28j60_init(netif->hwaddr); low_level_init中指定了enc28j60中的网卡地址。

    【2】发送


  1. static err_t  

  2. low_level_output(struct netif *netif, struct pbuf *p)  

  3. {  

  4.   struct ethernetif *ethernetif = netif->state;  

  5.   struct pbuf *q;  

  6.   

  7.   enc28j60_init_send(p->tot_len); //【1】initiate transfer();  

  8.    

  9. #if ETH_PAD_SIZE  

  10.   pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */  

  11. #endif  

  12.   

  13.   for(q = p; q != NULL; q = q->next) {  

  14.     /* Send the data from the pbuf to the interface, one pbuf at a 

  15.        time. The size of the data in each pbuf is kept in the ->len 

  16.        variable. */  

  17.     enc28j60_writebuf( q->payload, q->len ); //【2】send data from(q->payload, q->len);  

  18.   }  

  19.   

  20.   enc28j60_start_send(); //【3】signal that packet should be sent();  

  21.   

  22. #if ETH_PAD_SIZE  

  23.   pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */  

  24. #endif  

  25.    

  26.   LINK_STATS_INC(link.xmit);  

  27.   

  28.   return ERR_OK;  

  29. }  

    【说明】

        【1】enc28j60_init_send(p->tot_len); 初始化发送缓冲区大小, pbuf结构为一个链表,第一个pbuf结构体中的tot_len字段代表整个以太网数据包的大小。

        【2】enc28j60_writebuf( q->payload, q->len ); 通过遍历链表把内容填入ENC28J60的缓冲区中。

        【3】enc28j60_start_send();启动网卡发送。

    【3】接收


  1. static struct pbuf *  

  2. low_level_input(struct netif *netif)  

  3. {  

  4.   struct ethernetif *ethernetif = netif->state;  

  5.   struct pbuf *p, *q;  

  6.   u16_t len;  

  7.   

  8.   len = enc28j60_packet_getlen(); // 【1】  

  9.   

  10. #if ETH_PAD_SIZE  

  11.   len += ETH_PAD_SIZE; /* allow room for Ethernet padding */  

  12. #endif  

  13.   

  14.   /* We allocate a pbuf chain of pbufs from the pool. */  

  15.   p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);  

  16.    

  17.   if (p != NULL) {  

  18.   

  19. #if ETH_PAD_SIZE  

  20.     pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */  

  21. #endif  

  22.   

  23.     for(q = p; q != NULL; q = q->next) {  

  24.       enc28j60_readbuf (q->payload, q->len ); //【2】read data into(q->payload, q->len);  

  25.     }  

  26.     enc28j60_finish_receive(); //【3】acknowledge that packet has been read();  

  27.   

  28. #if ETH_PAD_SIZE  

  29.     pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */  

  30. #endif  

  31.   

  32.     LINK_STATS_INC(link.recv);  

  33.   } else {  

  34.     enc28j60_finish_receive(); //【4】drop packet();  

  35.     LINK_STATS_INC(link.memerr);  

  36.     LINK_STATS_INC(link.drop);  

  37.   }  

  38.   

  39.   return p;  

  40. }  

    【说明】

        【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中的配置选项非常的多,了解所有的配置非常不容易。本博文参考STM32官方的两个例子总结得到。 


  1. #ifndef __LWIPOPTS_H__  

  2. #define __LWIPOPTS_H__  

  3.   

  4. #define SYS_LIGHTWEIGHT_PROT 0  

  5.   

  6. #define NO_SYS 1  

  7.   

  8. #define NO_SYS_NO_TIMERS 1  

  9.   

  10. /* ---------- Memory options ---------- */  

  11. /* MEM_ALIGNMENT: should be set to the alignment of the CPU for which 

  12.    lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2 

  13.    byte alignment -> define MEM_ALIGNMENT to 2. */  

  14. #define MEM_ALIGNMENT 4  

  15.   

  16. /* MEM_SIZE: the size of the heap memory. If the application will send 

  17. a lot of data that needs to be copied, this should be set high. */  

  18. #define MEM_SIZE (5*1024)  

  19.   

  20. /* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application 

  21.    sends a lot of data out of ROM (or other static memory), this 

  22.    should be set high. */  

  23. #define MEMP_NUM_PBUF 10  

  24. /* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One 

  25.    per active UDP "connection". */  

  26. #define MEMP_NUM_UDP_PCB 6  

  27. /* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP 

  28.    connections. */  

  29. #define MEMP_NUM_TCP_PCB 10  

  30. /* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP 

  31.    connections. */  

  32. #define MEMP_NUM_TCP_PCB_LISTEN 6  

  33. /* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP 

  34.    segments. */  

  35. #define MEMP_NUM_TCP_SEG 12  

  36. /* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active 

  37.    timeouts. */  

  38. #define MEMP_NUM_SYS_TIMEOUT 3  

  39.   

  40.   

  41. /* ---------- Pbuf options ---------- */  

  42. /* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */  

  43. #define PBUF_POOL_SIZE 10  

  44.   

  45. /* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */  

  46. #define PBUF_POOL_BUFSIZE 1500  

  47.   

  48.   

  49. /* ---------- TCP options ---------- */  

  50. #define LWIP_TCP 1  

  51. #define TCP_TTL 255  

  52.   

  53. /* Controls if TCP should queue segments that arrive out of 

  54.    order. Define to 0 if your device is low on memory. */  

  55. #define TCP_QUEUE_OOSEQ 0  

  56.   

  57. /* TCP Maximum segment size. */  

  58. #define TCP_MSS (1500 - 40)  /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */  

  59.   

  60. /* TCP sender buffer space (bytes). */  

  61. #define TCP_SND_BUF (2*TCP_MSS)  

  62.   

  63. /* TCP sender buffer space (pbufs). This must be at least = 2 * 

  64.    TCP_SND_BUF/TCP_MSS for things to work. */  

  65. #define TCP_SND_QUEUELEN (6 * TCP_SND_BUF)/TCP_MSS  

  66.   

  67. /* TCP receive window. */  

  68. #define TCP_WND (2*TCP_MSS)  

  69.   

  70.   

  71. /* ---------- ICMP options ---------- */  

  72. #define LWIP_ICMP 1  

  73.   

  74. /* ---------- DHCP options ---------- */  

  75. /* Define LWIP_DHCP to 1 if you want DHCP configuration of 

  76.    interfaces. DHCP is not implemented in lwIP 0.5.1, however, so 

  77.    turning this on does currently not work. */  

  78. #define LWIP_DHCP 0  

  79.   

  80. /* ---------- UDP options ---------- */  

  81. #define LWIP_UDP 1  

  82. #define UDP_TTL 255  

  83.   

  84. /* ---------- Statistics options ---------- */  

  85. #define LWIP_STATS 0  

  86. #define LWIP_PROVIDE_ERRNO 1  

  87.   

  88. /** 

  89.  * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c) 

  90.  */  

  91. #define LWIP_NETCONN 0  

  92.   

  93. /** 

  94.  * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c) 

  95.  */  

  96. #define LWIP_SOCKET 0  

  97.   

  98. #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相关初始化


  1. void LwIP_Config (void)  

  2. {  

  3.     struct ip_addr ipaddr;  

  4.     struct ip_addr netmask;  

  5.     struct ip_addr gw;  

  6.      

  7.     // 调用LWIP初始化函数  

  8.     lwip_init();  

  9.      

  10.     IP4_ADDR(&ipaddr, 192, 168, 1, 16); // 设置网络接口的ip地址  

  11.     IP4_ADDR(&netmask, 255, 255, 255, 0);    // 子网掩码  

  12.     IP4_ADDR(&gw, 192, 168, 1, 1);   // 网关  

  13.      

  14.     // 初始化enc28j60与LWIP的接口,参数为网络接口结构体、ip地址、  

  15.     // 子网掩码、网关、网卡信息指针、初始化函数、输入函数  

  16.     netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);  

  17.      

  18.     // 把enc28j60设置为默认网卡  

  19.     netif_set_default(&enc28j60);  

  20.      

  21.     netif_set_up(&enc28j60);  

  22. }  

    【说明】

        【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)部分


  1. timer_typedef tcp_timer, arp_timer;  

  2.   

  3. /* 设定查询定时器 ARP定时器 */  

  4. timer_set(&tcp_timer, CLOCK_SECOND / 10); // tcp处理定时器 100ms  

  5. timer_set(&arp_timer, CLOCK_SECOND * 5); // arp处理定时器 5s  

  6. while (1) {  

  7.      

  8.     if (enc28j60_packet_getcount() != 0) {  

  9.         ethernetif_input(&enc28j60);  

  10.     }  

  11.      

  12.     // TCP 定时处理  

  13.     if (timer_expired(&tcp_timer)) {  

  14.         timer_set(&tcp_timer, CLOCK_SECOND / 4);  

  15.         tcp_tmr();  

  16.     }  

  17.      

  18.     // ARP 定时处理  

  19.     if (timer_expired(&arp_timer)) {  

  20.         timer_set(&arp_timer, CLOCK_SECOND * 5);  

  21.         etharp_tmr();  

  22.     }  

  23. }  

    【说明】

    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

 

图1 ping实验

    【2】TCP Echo例子

    LwIP提供很多示例,TCP Echo示例位于contrib-1.4.1的apps文件夹中,文件夹名为tcpecho_raw)。修改TCP侦听端口为10086。

    err = tcp_bind(echo_pcb, IP_ADDR_ANY, 10086);


图2 TCP Echo例子

5.总结

    【1】移植和应用LwIP一定要耐心细致。

    【2】一旦网卡接收到数据,应调用ethernetif_input函数,调用该函数让数据进入LwIP协议栈。

    【3】 netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);最后一个参数为ethernet_input,千万必要写成ethernetif_input。

6.参考资料

    更多细节内容请参考图书资料

    【1】《嵌入式网络系统设计——基于Atmel ARM7系列》

    【2】《STM32嵌入式系统开发实战指南——FreeRTOS与LwIP联合移植》


关键字:LwIP  STM32  ENC28J60  移植与入门 引用地址:LwIP学习笔记——STM32 ENC28J60移植与入门

上一篇:SWD与JTAG区别及使用情况
下一篇:FreeRTOS CortexM3 M4中断优先级设置总结

推荐阅读最新更新时间:2024-03-16 15:34

STM32学习记录14 ucosii中的串口中断
先看邵老师的书中怎么写 里面写到: C/OS中,中断服务子程序要用汇编语言来写。然而,如果用户使用的C语言编译器支持在线汇编语言的话,用户可以直接将中断服务子程序代码放在C语言的程序文件中。 再看《M3权威指南》2.11.2节与9.11节中讲到Cortex-M3在进入异常时自动压栈。。。。返回时自动出栈,再也不需要汇编语言编写了。也就是说我们可以使用C语言来编写中断服务程序。并且省去了上面程序清单的(1)(5)(6). 其实ucos中的终端和裸奔的中断写法基本一致,只是加了几条语句,如下为串口中断的写法: void USART1_IRQHandler(void) { uint8_t RxData; O
[单片机]
<font color='red'>STM32</font>学习记录14 ucosii中的串口中断
ARM嵌入式开发--STM32时钟设置
大家都知道在使用单片机时,时钟速度决定于外部晶振或内部RC振荡电路的频率,是不可以改变的。而ARM的出现打破了这一传统的法则,可以通过软件随意改变时钟速度。这一出现让我们的设计更加灵活,但是也给我们的设计增加了复杂性。为了让用户能够更简单的使用这一功能,STM32的库函数已经为我们设计的更加简单方便。 在比较靠前的版本中,我们需要向下面那样设置时钟: ErrorStatus HSEStartUpStatus; /************************************************************************************** * FunctionName : R
[单片机]
STM32移植USB驱动总结
//=========================By xiaowei /* */ //========================= 1、硬件介绍 1、SGM7227,USB高速切换开关,OE是芯片使能,低电平才能使总线导通; S脚是切换控制; USB协议 https://segmentfault.com/a/1190000015995506 2、软件移植 移植文件分析 stm32自带USB接口,OTG-FS(全速)和OTG-HS(高速),因为stm32f4只带有高速PHY,想使用高速模式,就需要外扩高速PHY,在此为USB3300。 系统配置一个Device端口,一个Host端口; Device端
[单片机]
<font color='red'>STM32</font><font color='red'>移植</font>USB驱动总结
stm32存储结构& 存储器映射
1 STM32系统结构 要想深刻理解STM32的存储器,需要首先知道STM32的系统结构。 如Figure 1,是STM32系统结构框图。 根据STM32 Reference manual (RM0008)中的描述,如图: 可以得知STM32系统结构的组成,每一个模块更为详细的内容,请参考相关文档。 RM0008文档中可以看出,STM32采用的是Cortex-M3内核,因此,有必要了解Cortex-M3的存储器结构。 图中还可以看出,Cortex-M3是通过各个总线和Flash、SROM相连接的。 2 STM32内核(Cortex-M3)的结构 以下是Cortex-M3模块框图: 该Co
[单片机]
<font color='red'>stm32</font>存储结构& 存储器映射
基于STM32的MIT-BIH心电数据D/A回放设计
基于STM32的MIT-BIH心电数据D/A回放设计,对整体设计方案、硬件组成、软件设计等进行了介绍。通过读取心电数据将其进行D/A转换,输出波形与原始波形进行比较,较好地实现了回放功能。由此可见,该系统的性能指标达到了设计要求。能很好地实现心电数据回放,为一系列心电算法的仿真实践及实时心电监护仪的研制打好了基础。 心电信号是人类最早开展研究并应用于临床医学的生物电信号之一,通过对心电信号的分析处理能有效地预测心脏疾病。如何利用心电数据开发研究相关的医疗设备是对科研人员至关重要。心电数据的回放就是将原有的存储的MIT-BIH心电数据,根据其存储的格式,利用设计的系统通过D/A转换最终从终端回放出模拟信号。本文介绍基于ARM
[单片机]
基于<font color='red'>STM32</font>的MIT-BIH心电数据D/A回放设计
stm32单片机GPIO端口的特点及应用解析
一、GPIO的综合描述 stm32每一个GPIO端口拥有2个32bits的configuration寄存器(GPIOx_CRL,GPIOx_CRH),2个32bits的数据寄存器(GPIOx_IDR,GPIOx_ODR),1个32bits的set/reset寄存器(GPIOx_BSRR),1个16bits的reset寄存器(GPIOx_BRR)和1个32bits的Lock寄存器(GPIOx_LCKR)。 (一)每一个IO引脚都可以使用软件配置为以下几种模式: 1. 浮空输入 2. 带上拉输入 3. 带下拉输入 4. 模拟输入 5. 开漏输出——(此模式可实现hotpower说的真双向IO) 6. 推挽输出 7. 复用功能的推挽
[单片机]
使用ST-Link Utility去除STM32芯片读写保护
问题:使用ISP/J-Link/ST-Link等无法下载代码,提示芯片写保护;读芯片信息时提示读保护。 原因:一般是修改了选项字节。 解决方法:这里使用ST-Link Utility来修改选项字节。 使用ST-Link连接到STM32芯片,点击Connect。 存在读保护。 修改选项字节。 将读保护等级修改为Level 0。 打钩的扇区会添加写保护,点击Unselect all不选择写保护。 写入选项字节后Flash会被擦除。 能正常写入程序。
[单片机]
使用ST-Link Utility去除<font color='red'>STM32</font>芯片读写保护
STM32如何通过内部VREF得到实际的VDDA值
我们经常会使用STM32 ADC功能测试外部电压,在一些精度不高的场合,我们一般就用3.3V作为参考电压来计算测到的电压值。不过,这种情况很少见,可能只有单片机学习板才会这样使用。因为我们使用的3.3V稳压芯片,很少有标准的3.300V输出,有可能是3.270V,也可能是3.345V,而且,还存在个体差异,这个板子上的电压是3.294V,另外一个板子上面,就可能是3.312V。如果我们都用3.300来计算的话,同样的电压,测出来的结果就会存在mV级别的差异。 在实际使用中,我们一般使用外部基准电压芯片,例如,100脚的STM32一般都有VREF引脚,就是用来接外部基准电压芯片,例如REF3133,输出的电压是标准的3.300V。
[单片机]
<font color='red'>STM32</font>如何通过内部VREF得到实际的VDDA值
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
热门活动
换一批
更多
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

更多精选电路图
换一换 更多 相关热搜器件
更多每日新闻
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved