0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

有线网络通信实验1之LWIP移植

汽车电子威廉希尔官方网站 来源:滑小稽笔记 作者:电子威廉希尔官方网站 园地 2023-03-01 14:30 次阅读

24.1 DM9000概述

DM9000是一款完全集成的、性价比高、引脚数少、带有通用处理器接口的单芯片快速以太网控制器。一个10/100MPHY和4K双字的SRAM,它是出于低功耗和高性能目的设计的,其IO端口支持3.3V与5V电压。DM9000为适应各种处理器提供了8位、16位数据接口访问内部存储器。DM9000协议层接口完全支持使用10Mbps下3类、4类、5类非屏蔽双绞线和100Mbps下5类非屏蔽双绞线,这是完全遵照IEEE802.3u标准。它的自动协商功能将自动完成DM9000配置以使其发挥出最佳性能,它还支持IEEE802.3x全双工流量控制,DM9000的特性如下:

(1)支持处理器接口:I/O口的字节或字命令对内部存储器进行读写操作

(2)集成自适应(AUTO-MDIX)10/100M收发器

(3)半双工模式流量控制的背压模式

(4)IEEE802.3x全双工模式的流量控制

(5)支持唤醒帧,链路状态改变和远程唤醒

(6)内置16K字节SRAM

(7)内置3.3V至2.5V的调节器

(8)支持IP/TCP/UDP的校验和生成以及校验支持MAC接口

(9)支持自动加载EEPROM里面生产商ID和产品ID

(10)可选EEPROM配置

(11)超低功耗模式

A.功率降低模式(电缆侦测)

B.掉电模式

C.可选择1:1或1.25:1变压比例降低额外功率

(12)兼容3.3V和5.0V输入输出电压

DM9000的结构框图如下图所示。

图片

1、DM9000中断引脚电平设置

DM9000的INT引脚为中断输出引脚,默认情况下该引脚高电平有效。可以通过设置DM9000的EECK引脚来改变INT的有效电平,当EECK拉高以后,INT低电平有效,否则的话INT是高电平有效的。

2、DM9000数据位宽设置

DM9000支持8位和16位两种数据位宽,可以通过DM9000的EECS引脚设置其数据位宽,当EECS上拉的时候DM9000选择8位数据位宽,否则的话选择16位数据位宽。一般设置默认位宽为16位。

3、DM9000直接内存访问控制DMAC

DM9000支持DMA方式以简化对内部存储器的访问。在我们编程写好内部存储器地址后,就可以用一个读/写命令伪指令把当前数据加载到内部数据缓冲区,这样,内部存储器指定位置就可以被读/写命令寄存器访问。存储器地址将会自动增加,增加的大小与当前总线操作模式相同(8-bit或16-bit),接着下一个地址数据将会自动加载到内部数据缓冲区。内部存储器空间大小为16K字节。前3K字节单元用作发送包的缓冲区,其他13K字节用作接收包的缓冲区。所以在写存储器操作时,如果地址越界(即超出3K空间),在IMR寄存器bit7置位的情况下,地址指针将会返回到存储器0地址处。同样,在读存储器操作时,如果地址越界(即超出16K空间),在IMR寄存器bit7置位的情况下,地址指针将会返回到存储器0C00H地址处。

4、DM9000数据包发送

DM9000有两个发送数据包:index1和index2,同时存储在TXSRAM中。发送控制寄存器控制循环冗余校验码和填充的插入,其状态分别记录在发送状态寄存器1和发送状态寄存器2中。发送器的起始地址为00H,在软件或硬件复位后,默认的数据发送包为index1。首先,将数据写入TXSRAM中,然后,在发送数据包长度寄存器中把数据字节数写入字节计数寄存器。置位发送控制寄存器(02H)的bit0位,则DM9000开始发送index1数据包。在index1数据包发送结束之前,数据发送包index2被移入TXSRAM中。在index1数据包发送结束后,将index2数据字节数写入字节计数寄存器中,然后,置位发送控制寄存器(02H)的bit0位,则index2数据包开始发送。以此类推,后面的数据包都以此方式进行发送。

5、DM9000数据包接收

RXSRAM是一个环形数据结构。在软件或硬件复位后,RXSRAM的起始地址为C00H。每个接收数据包都包含有CRC校验域,数据域,以及紧跟其后的4字节包头域。4字节包头格式为:01H、状态、BYTE_COUNT低、BYTE_COUNT高。请注意:每个接收包的起始地址处在适当的地址边界,这取决于当前总线操作模式(8bit或者16bit)。DM9000是通过16位数据总线,挂在STM32的FSMC上面,DM9000的片选由FSMC_NE2控制,CMD则由FSMC_A7控制。这个连接方法,类似于TFTLCD显示,总共用到了22个IO口。

24.2 TCP/IP协议概述

TCP/IP中文名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。

TCP/IP协议不是TCP和IP这两个协议的合称,而是指因特网整个TCP/IP协议族。从协议分层模型方面来讲,TCP/IP由四个层次组成:网络接口层、网络层、传输层、应用层。OSI是传统的开放式系统互连参考模型,该模型将TCP/IP分为七层:物理层、数据链路层(网络接口层)、网络层(网络层)、传输层(传输层)、会话层、表示层和应用层(应用层)。TCP/IP模型与OSI模型对比如下表所示。

层级 OSI模型 TCP/IP模型
1 应用层 应用层
2 表示层
3 会话层
4 传输层 传输层
5 网络层 互联层
6 数据链路层 链路层
7 物理层

我们刚才介绍的DM9000相当于链路层,而LWIP提供的就是网络层的功能,应用层则需要用户自己编写代码去实现。

24.3 LWIP概述

LWIP是瑞典计算机科学院(SICS)的AdamDunkels等开发的一个小型开源的TCP/IP协议栈。LWIP是轻量级IP协议,有无操作系统的支持都可以运行,LWIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LWIP协议栈适合在低端的嵌入式系统中使用。目前LWIP的最新版本是2.1.2。在这里采用比较常用的1.4.1版本进行介绍。

LWIP的主要特性如下:

(1)ARP协议:以太网地址解析协议

(2)IP协议:包括IPv4和IPv6,支持IP分片与重装,支持多网络接口下数据转发

(3)ICMP协议:用于网络调试与维护

(4)IGMP协议:用于网络组管理,可以实现多播数据的接收

(5)UDP协议:用户数据报协议

(6)TCP协议:支持TCP拥塞控制,RTT估计,快速恢复与重传等

(7)提供三种用户编程接口方式:raw/callbackAPI、sequentialAPI、BSD-stylesocketAPI

(8)DNS:域名解析

(9)SNMP:简单网络管理协议

(10)DHCP:动态主机配置协议

(11)AUTOIP:IP地址自动配置

(12)PPP:点对点协议,支持PPPoE

24.4 DM9000驱动编写

24.4.1 DM9000寄存器介绍

(1) 网络控制寄存器 :NCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
- WAKEEN - FCTOL FDX LBK RST

Bit 6:置位时将启用唤醒功能。清除该位还将清除所有唤醒事件状态,软件复位后,该位将不受影响

0:开启

1:关闭

Bit 4:强制冲突模式,用于检测

Bit 3:内部PHY全双工模式

Bit 2~Bit 1:回环模式

00:正常

01:MAC内部回环

10:内部PHY100M模式数字回环

11:保留

Bit 0:软件复位,写1启动复位,10us后自动清零

(2) 网络状态寄存器 :NSR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
SPEED LINKST WAKEST - TXD2END TX1END RXOV -

Bit 7:网络速度,在使用内部PHY的情况下,0表示100Mbps,1表示10Mbps,当LINKST=0时,该位无意义

Bit 6:连接状态

0:连接失败

1:连接成功

Bit 5:唤醒状态,读或者对该位写1清0,复位后该位不受影响

0:产生唤醒事件

1:没有唤醒事件

Bit 3:发送数据包2完成标志,读或者对该位写1清0

Bit 2:发送数据包1完成标志,读或者对该位写1清0

Bit 1:接收缓存溢出标志

(3) 发送控制寄存器 :TCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
- TJDIS EXCECM PAD_DIS2 CRC_DIS2 PAD_DIS1 CRC_DIS1 TXREQ

Bit 6:Jabber传输禁止

0:进制Jabber传输定时器(2K字节)

1:使能

Bit 5:严重冲突模式检测

0:当冲突计数多于15则终止本次数据包

   1:始终尝试发送本次数据包

Bit 4:禁止为数据包2添加填充

Bit 3:禁止为数据包2添加CRC校验

Bit 2:禁止为数据包1添加填充

Bit 1:禁止为数据包1添加CRC校验

Bit 0:发送请求,发送完成后自动清除该位

(4) 接收控制寄存器 :RCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
- WTDIS DIS_LONG DIS_CRC ALL RUNT PRMSC RXEN

Bit 6:看门狗定时器进制

0:使能

   1:禁止

Bit 5:丢弃包长度超过1522字节的数据包

Bit 4:丢弃CRC校验错误的数据包

Bit 3:允许广播

Bit 2:允许小于最小长度的数据包

Bit 1:各种模式

Bit 0:接收使能

(5) 流控制阈值寄存器 :FCTR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
HWOT LWOT

Bit 7~Bit 4:接收缓存高位溢出门限

当接收SRAM空闲空间小于该门限值则发送一个暂停时间为FFFF H的暂停包,若该值为0,则没有接收条件,若该值为1,则默认值为3K字节的空闲空间

Bit 3~Bit 0:接收缓存低位溢出门限

当接收SRAM空闲空间大于该门限值则发送一个暂停时间为0000 H的暂停包,当溢出门限最高值的暂停包发送之后,溢出门限值最低值的暂停包才有效,默认值为8K

(6) 背压阈值寄存器 :BPTR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
BPHW JPT

Bit 7~Bit 4:背压阈值最高值

当接收SRAM空闲空间低于该阈值,则MAC将产生一个拥挤状态,默认值3K字节空闲空间

Bit 3~Bit 0:拥挤状态时间,默认为200us

JPT值 拥挤状态时间(us) JPT值 拥挤状态时间(us)
0000 5 1000 250
0001 10 1001 300
0010 15 1010 350
0011 25 1011 400
0100 50 1100 450
0101 100 1101 500
0110 150 1110 550
0111 200 1111 600

(7) 发送控制寄存器2 :TCR2

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
LED RLCP DTU ONEPM IFGS

Bit 7:LED模式

0:设置LED引脚为模式0或者根据EEPROM的设定

1:设置LED引脚为模式1

Bit 6:重试冲突延迟数据包

Bit 5:禁止重新发送“underruned”数据包

Bit 4:单包模式

0:发送完成前发送最多两个数据包的命令能被执行

1:发送完成前发送一个数据包的命令能被执行

Bit 3~Bit 0:帧间隔设置

0xxx:96 bit

1000:64 bit

1001:72 bit

1010:80 bit

1011:88 bit

1100:96 bit

1101:104 bit

1110:112 bit

1111:120 bit

(8) 中断状态寄存器 :ISR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
IOMODE - LNKCHG UNRUN ROO ROS PT PR

Bit 7:数据宽度选择

0:16位模式

1:8位模式

Bit 5:连接状态改变

Bit 4:发送“underrun”

Bit 3:接收计数器溢出

Bit 2:接收溢出

Bit 1:数据包发送

Bit 0:数据包接收

(9) 中断状态寄存器 :IMR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PAR - LNKCHGI UDRUNI ROOI ROSI PTI PRI

Bit 7:使能SRAM读写指针在指针地址超过SRAM的大小时自动跳回起始位置,需要驱动程序设置该位,若设置该位,REG_F5将自动设置为0x H

Bit 5:使能连接状态改变中断

Bit 4:使能发送“underrun”中断

Bit 3:使能接收计数器溢出中断

Bit 2:使能接收溢出中断

Bit 1:使能数据包发送中断

Bit 0:使能数据包接收中断

24.4.2 DM9000驱动

(1)初始化DM9000外设与寄存器配置

u8 DM9000_Init()
{
  u32 temp;
  //初始化和DM9000连接的IO和FSMC
  RCC->AHBENR |= 1<<8 ;            //使能FSMC时钟
  RCC->APB2ENR |= 1<<5 ;            //使能PORTD时钟
  RCC->APB2ENR |= 1<<6 ;            //使能PORTE时钟
  RCC->APB2ENR |= 1<<7 ;            //使能PORTF时钟
  RCC->APB2ENR |= 1<<8 ;            //使能PORTG时钟
  //PD7->DM9000_RST
  GPIOD->CRH &= 0x00FFF000 ;
  GPIOD->CRH |= 0xBB000BBB ;
  GPIOD->CRL &= 0x0F00FF00 ;
  GPIOD->CRL |= 0x30BB00BB ;
  //PE
  GPIOE->CRH &= 0x00000000 ;
  GPIOE->CRH |= 0xBBBBBBBB ;
  GPIOE->CRL &= 0x0FFFFFFF ;
  GPIOE->CRL |= 0xB0000000 ;
  //PF13-->FSMC_A7
  GPIOF->CRH &= 0xFF0FFFFF ;
  GPIOF->CRH |= 0x00B00000 ;
  //PG9->NE2
  GPIOG->CRH &= 0xFFFFFF0F ;
  GPIOG->CRH |= 0x000000B0 ;
  GPIOG->CRL &= 0xF0FFFFFF ;
  GPIOG->CRL |= 0x08000000 ;
  GPIOG->ODR |= 1<<6 ;
  EXIT_Config( 6, 6, 1 ) ;              //下降沿触发
  NVIC_Init( 0, 0, EXTI9_5_IRQn, 2 ) ;        //优先级最高
  //寄存器清零
  FSMC_Bank1->BTCR[ 2 ] = 0x00000000 ;
  FSMC_Bank1->BTCR[ 3 ] = 0x00000000 ;
  FSMC_Bank1E->BWTR[ 2 ] = 0x00000000 ;
  FSMC_Bank1->BTCR[ 2 ] |= 1<<12 ;        //存储器写使能
  FSMC_Bank1->BTCR[ 2 ] |= 1<<4 ;        //存储器数据宽度为16bit
  //操作BTR寄存器
  FSMC_Bank1->BTCR[ 3 ] |= 3<<8 ;        //数据保持时间(DATAST)为3个HCLK 4/72M=55ns
  FSMC_Bank1->BTCR[ 3 ] |= 0<<4 ;        //地址保持时间(ADDHLD)未用到
  FSMC_Bank1->BTCR[ 3 ] |= 1<<0 ;        //地址建立时间(ADDSET)为2个HCLK 2/72M=27ns
  FSMC_Bank1E->BWTR[ 2 ] = 0x0FFFFFFF ;      //闪存写时序寄存器
  FSMC_Bank1->BTCR[ 2 ] |= 1<<0 ;        //使能BANK1区域2
  temp = *( vu32* )( 0x1FFFF7E8 ) ;        //获取STM32的唯一ID的前24位作为MAC地址后三字节
  dm9000cfg.mode = DM9000_AUTO ;
   dm9000cfg.queue_packet_len = 0 ;
//DM9000的SRAM的发送和接收指针自动返回到开始地址,并开启接收中断
  dm9000cfg.imr_all = IMR_PAR|IMR_PRI ; 
  //初始化MAC地址
  dm9000cfg.mac_addr[ 0 ] = 2 ;
  dm9000cfg.mac_addr[ 1 ] = 0 ;
  dm9000cfg.mac_addr[ 2 ] = 0 ;
  dm9000cfg.mac_addr[ 3 ] = ( temp>>16 )&0xFF ;  //低三字节用STM32的唯一ID
  dm9000cfg.mac_addr[ 4 ] = ( temp>>8 )&0xFFF ;
  dm9000cfg.mac_addr[ 5 ] = temp&0xFF ;
  //初始化组播地址
  dm9000cfg.multicase_addr[ 0 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 1 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 2 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 3 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 4 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 5 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 6 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 7 ] = 0xFF ;
  DM9000_Reset() ;                //复位DM9000
  delay_ms( 100 ) ;
  //获取DM9000ID
  temp = DM9000_ReadReg( DM9000_VIDL ) ;
  temp |= DM9000_ReadReg( DM9000_VIDH )<<8 ;
  temp |= DM9000_ReadReg( DM9000_PIDL )<<16 ;
  temp |= DM9000_ReadReg( DM9000_PIDH )<<24 ;
  //读取ID错误
  if( temp!=DM9000_ID )
    return 1 ;
  DM9000_Set_PHYMode( dm9000cfg.mode ) ;    //设置PHY工作模式
  DM9000_WriteReg( DM9000_NCR, 0x00 ) ;
  DM9000_WriteReg( DM9000_TCR, 0x00 ) ;      //发送控制寄存器清零
  DM9000_WriteReg( DM9000_BPTR, 0x3F ) ;
  DM9000_WriteReg( DM9000_FCTR, 0x38 ) ;
  DM9000_WriteReg( DM9000_FCR, 0x00 ) ;
  DM9000_WriteReg( DM9000_SMCR, 0x00 ) ;    //特殊模式
  DM9000_WriteReg( DM9000_NSR, NSR_WAKEST|NSR_TX2END|NSR_TX1END ) ;  //清除发送状态
  DM9000_WriteReg( DM9000_ISR, 0x0F ) ;      //清除中断状态
  DM9000_WriteReg( DM9000_TCR2, 0x80 ) ;    //切换LED到mode1
  //设置MAC地址和组播地址
  DM9000_Set_MACAddress( dm9000cfg.mac_addr ) ;//设置MAC地址
  DM9000_Set_Multicast( dm9000cfg.multicase_addr ) ; //设置组播地址
  DM9000_WriteReg( DM9000_RCR, RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN ) ;
  DM9000_WriteReg( DM9000_IMR, IMR_PAR ) ; 
  DM9000_Get_SpeedAndDuplex() ;        //获取DM9000的连接速度和双工状态
  DM9000_WriteReg( DM9000_IMR, dm9000cfg.imr_all ) ;  //设置中断
  return 0 ;
}

注:函数中用到了未定义的数据类型,需要在sys.h中添加该类型的定义typedef volatile uint32_t vu32。

(2)DM9000内部寄存器读写函数

u8 DM9000_Init()
{
  u32 temp;
  //初始化和DM9000连接的IO和FSMC
  RCC->AHBENR |= 1<<8 ;            //使能FSMC时钟
  RCC->APB2ENR |= 1<<5 ;            //使能PORTD时钟
  RCC->APB2ENR |= 1<<6 ;            //使能PORTE时钟
  RCC->APB2ENR |= 1<<7 ;            //使能PORTF时钟
  RCC->APB2ENR |= 1<<8 ;            //使能PORTG时钟
  //PD7->DM9000_RST
  GPIOD->CRH &= 0x00FFF000 ;
  GPIOD->CRH |= 0xBB000BBB ;
  GPIOD->CRL &= 0x0F00FF00 ;
  GPIOD->CRL |= 0x30BB00BB ;
  //PE
  GPIOE->CRH &= 0x00000000 ;
  GPIOE->CRH |= 0xBBBBBBBB ;
  GPIOE->CRL &= 0x0FFFFFFF ;
  GPIOE->CRL |= 0xB0000000 ;
  //PF13-->FSMC_A7
  GPIOF->CRH &= 0xFF0FFFFF ;
  GPIOF->CRH |= 0x00B00000 ;
  //PG9->NE2
  GPIOG->CRH &= 0xFFFFFF0F ;
  GPIOG->CRH |= 0x000000B0 ;
  GPIOG->CRL &= 0xF0FFFFFF ;
  GPIOG->CRL |= 0x08000000 ;
  GPIOG->ODR |= 1<<6 ;
  EXIT_Config( 6, 6, 1 ) ;              //下降沿触发
  NVIC_Init( 0, 0, EXTI9_5_IRQn, 2 ) ;        //优先级最高
  //寄存器清零
  FSMC_Bank1->BTCR[ 2 ] = 0x00000000 ;
  FSMC_Bank1->BTCR[ 3 ] = 0x00000000 ;
  FSMC_Bank1E->BWTR[ 2 ] = 0x00000000 ;
  FSMC_Bank1->BTCR[ 2 ] |= 1<<12 ;        //存储器写使能
  FSMC_Bank1->BTCR[ 2 ] |= 1<<4 ;        //存储器数据宽度为16bit
  //操作BTR寄存器
  FSMC_Bank1->BTCR[ 3 ] |= 3<<8 ;        //数据保持时间(DATAST)为3个HCLK 4/72M=55ns
  FSMC_Bank1->BTCR[ 3 ] |= 0<<4 ;        //地址保持时间(ADDHLD)未用到
  FSMC_Bank1->BTCR[ 3 ] |= 1<<0 ;        //地址建立时间(ADDSET)为2个HCLK 2/72M=27ns
  FSMC_Bank1E->BWTR[ 2 ] = 0x0FFFFFFF ;      //闪存写时序寄存器
  FSMC_Bank1->BTCR[ 2 ] |= 1<<0 ;        //使能BANK1区域2
  temp = *( vu32* )( 0x1FFFF7E8 ) ;        //获取STM32的唯一ID的前24位作为MAC地址后三字节
  dm9000cfg.mode = DM9000_AUTO ;
   dm9000cfg.queue_packet_len = 0 ;
//DM9000的SRAM的发送和接收指针自动返回到开始地址,并开启接收中断
  dm9000cfg.imr_all = IMR_PAR|IMR_PRI ; 
  //初始化MAC地址
  dm9000cfg.mac_addr[ 0 ] = 2 ;
  dm9000cfg.mac_addr[ 1 ] = 0 ;
  dm9000cfg.mac_addr[ 2 ] = 0 ;
  dm9000cfg.mac_addr[ 3 ] = ( temp>>16 )&0xFF ;  //低三字节用STM32的唯一ID
  dm9000cfg.mac_addr[ 4 ] = ( temp>>8 )&0xFFF ;
  dm9000cfg.mac_addr[ 5 ] = temp&0xFF ;
  //初始化组播地址
  dm9000cfg.multicase_addr[ 0 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 1 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 2 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 3 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 4 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 5 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 6 ] = 0xFF ;
  dm9000cfg.multicase_addr[ 7 ] = 0xFF ;
  DM9000_Reset() ;                //复位DM9000
  delay_ms( 100 ) ;
  //获取DM9000ID
  temp = DM9000_ReadReg( DM9000_VIDL ) ;
  temp |= DM9000_ReadReg( DM9000_VIDH )<<8 ;
  temp |= DM9000_ReadReg( DM9000_PIDL )<<16 ;
  temp |= DM9000_ReadReg( DM9000_PIDH )<<24 ;
  //读取ID错误
  if( temp!=DM9000_ID )
    return 1 ;
  DM9000_Set_PHYMode( dm9000cfg.mode ) ;    //设置PHY工作模式
  DM9000_WriteReg( DM9000_NCR, 0x00 ) ;
  DM9000_WriteReg( DM9000_TCR, 0x00 ) ;      //发送控制寄存器清零
  DM9000_WriteReg( DM9000_BPTR, 0x3F ) ;
  DM9000_WriteReg( DM9000_FCTR, 0x38 ) ;
  DM9000_WriteReg( DM9000_FCR, 0x00 ) ;
  DM9000_WriteReg( DM9000_SMCR, 0x00 ) ;    //特殊模式
  DM9000_WriteReg( DM9000_NSR, NSR_WAKEST|NSR_TX2END|NSR_TX1END ) ;  //清除发送状态
  DM9000_WriteReg( DM9000_ISR, 0x0F ) ;      //清除中断状态
  DM9000_WriteReg( DM9000_TCR2, 0x80 ) ;    //切换LED到mode1
  //设置MAC地址和组播地址
  DM9000_Set_MACAddress( dm9000cfg.mac_addr ) ;//设置MAC地址
  DM9000_Set_Multicast( dm9000cfg.multicase_addr ) ; //设置组播地址
  DM9000_WriteReg( DM9000_RCR, RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN ) ;
  DM9000_WriteReg( DM9000_IMR, IMR_PAR ) ; 
  DM9000_Get_SpeedAndDuplex() ;        //获取DM9000的连接速度和双工状态
  DM9000_WriteReg( DM9000_IMR, dm9000cfg.imr_all ) ;  //设置中断
  return 0 ;
}

(3)设置MAC地址与组播地址

void DM9000_Set_MACAddress( u8 *macaddr )
{
  u8 i ;
  for( i=0; i<6; i++ )
    DM9000_WriteReg( DM9000_PAR+i, macaddr[ i ] ) ;
}
void DM9000_Set_Multicast( u8 *multicastaddr )
{
  u8 i;
  for( i=0; i<8; i++ )
    DM9000_WriteReg( DM9000_MAR+i, multicastaddr[ i ] ) ;
}

(4)获取与设置PHY工作模式

void DM9000_Set_MACAddress( u8 *macaddr )
{
  u8 i ;
  for( i=0; i<6; i++ )
    DM9000_WriteReg( DM9000_PAR+i, macaddr[ i ] ) ;
}
void DM9000_Set_Multicast( u8 *multicastaddr )
{
  u8 i;
  for( i=0; i<8; i++ )
    DM9000_WriteReg( DM9000_MAR+i, multicastaddr[ i ] ) ;
}

(5)复位

void DM9000_Reset()
{
  DM9000_RST = 0 ;                        //DM9000硬件复位
  delay_ms( 10 ) ;
  DM9000_RST = 1 ;                        //DM9000硬件复位结束
  delay_ms( 100 ) ;                        //等待DM9000准备就绪
   DM9000_WriteReg( DM9000_GPCR, 0x01 ) ;            //第1步:设置GPCR寄存器的bit0为1
  DM9000_WriteReg( DM9000_GPR, 0 ) ;              //第2步:设置GPR寄存器的bit1为0 
   DM9000_WriteReg( DM9000_NCR, 0x02|NCR_RST ) ;        //第3步:软件复位DM9000
  //等待DM9000软复位完成
  do 
  {
    delay_ms( 25 ) ;
  }while( DM9000_ReadReg( DM9000_NCR )&0x01 ) ;
  DM9000_WriteReg( DM9000_NCR, 0 ) ;
  DM9000_WriteReg( DM9000_NCR, 0x02|NCR_RST ) ;        //DM9000第2次软复位
  do
  {
    delay_ms( 25 ) ;
  }while( DM9000_ReadReg( DM9000_NCR )&0x01 ) ;
}

(6)发送

void DM9000_SendPacket( struct pbuf *p )
{
  struct pbuf *q ;
  u16 pbuf_index=0 ;
  u8 word[ 2 ], word_index=0 ;
  DM9000_WriteReg( DM9000_IMR,IMR_PAR ) ;            //关闭网卡中断
  DM9000->REG = DM9000_MWCMD ;              //发送的数据搬到DM9000 TX SRAM中
  q = p ;
  //向DM9000的TX SRAM中写入数据,一次写入两个字节数据
  //当要发送的数据长度为奇数的时候,需要将最后一个字节单独写入DM9000的TX SRAM中
   while( q )
  {
    if( pbuf_index

(7)接收

struct pbuf *DM9000_Receive_Packet()
{
  struct pbuf *p ;
  struct pbuf *q ;
  u32 rxbyte ;
  vu16 rx_status, rx_length ;
  u16 *data ;
  u16 dummy ;
  int len ;
  p = NULL ;
__error_retry:  
  DM9000_ReadReg( DM9000_MRCMDX ) ;              //假读
  rxbyte = ( u8 )DM9000->DATA ;                  //进行第二次读取
  //接收到数据
  if( rxbyte )
  {
    //rxbyte大于1,接收到的数据错误,挂了
    if( rxbyte>1 )
    {
      DM9000_WriteReg( DM9000_RCR, 0x00 ) ;
      DM9000_WriteReg( DM9000_ISR, 0x80 ) ;
      return ( struct pbuf* )p ;
    }
    DM9000->REG = DM9000_MRCMD ;
    rx_status = DM9000->DATA ;
    rx_length = DM9000->DATA ;
    p = pbuf_alloc( PBUF_RAW, rx_length, PBUF_POOL ) ;      //pbufs内存池分配pbuf
    //内存申请成功
    if( p!=NULL )
    {
      for( q=p; q!=NULL; q=q->next )
      {
        data = ( u16* )q->payload ;
        len = q->len ;
        while( len>0 )
        {
          *data = DM9000->DATA ;
          data ++ ;
          len -= 2 ;
        }
      }
    }
    //内存申请失败
    else
    {
            data = &dummy ;
      len = rx_length ;
      while( len )
      {
        *data = DM9000->DATA ;
        len -= 2 ;
      }
        }
    //根据rx_status判断接收数据是否出现错误,如果有任何一个出现的话丢弃该数据帧,
    //当rx_length小于64或者大于最大数据长度的时候也丢弃该数据帧
    if( ( rx_status&0xBF00 )||( rx_length<64 )||( rx_length>DM9000_PKT_MAX ) )
    {
      if( rx_length>DM9000_PKT_MAX )
      {
        DM9000_WriteReg( DM9000_NCR, NCR_RST ) ;      //复位DM9000
        delay_ms( 5 ) ;
      }
      //释放内存
      if( p!=NULL )
      pbuf_free( ( struct pbuf* )p ) ;
      p = NULL ;
      goto __error_retry ;
    }
  }
  else
  {
    DM9000_WriteReg( DM9000_ISR, ISR_PTS ) ;            //清除所有中断标志位
    dm9000cfg.imr_all = IMR_PAR|IMR_PRI ;              //重新接收中断
    DM9000_WriteReg( DM9000_IMR, dm9000cfg.imr_all ) ;
  }
  return ( struct pbuf* )p ;
}

24.5 LWIP协议移植

24.5.1 源码下载

可以从官网进行源码下载,LWIP官网:http://savannah.nongnu.org/projects/lwip/

24.5.2 源码结构

图片

打开从官网上下载下来的源码其中包括doc,src和test三个文件夹和5个其他文件。doc文件夹下包含了几个与协议栈使用相关的文本文档,doc文件夹里面有两个比较重要的文档:rawapi.txt和sys_arch.txt。rawapi.txt告诉读者怎么使用raw/callbackAPI进行编程,sys_arch.txt包含了移植说明,在移植的时候会用到。src文件夹是我们的重点,里面包含了LWIP的源码。test是LWIP提供的一些测试程序。打开src源码文件夹,src文件夹由4个文件夹组成:api、core、include、netif四个文件夹。api文件夹里面是LWIP的sequentialAPI(Netconn)和socketAPI两种接口函数的源码,要使用这两种API需要操作系统支持。core文件夹是LWIP内核源码,include文件夹里面是LWIP使用到的头文件,netif文件夹里面是与网络底层接口有关的文件。

24.5.3 添加LWIP源代码文件

(1)将lwip-2.1.2\\src目录下的5个文件夹复制到工程文件夹中新建的LWIP文件夹中,删除apps文件夹核include/lwip/apps文件夹。

(2)在LWIP文件夹中新建app和arch两个文件夹。

(3)在app文件夹中新建comm,tcp_client,tcp_server和udp_demo文件夹,最终文件结构如下图所示。

图片

(4)在app/comm目录下创建comm.c,comm.h,lwipopts.h三个文件。

(5)在arch目录下创建cc.h,cpu.h,perf.h,sys_arch.c和sys_arch.h五个文件。

(6)在include/netif目录下创建ethernetif.h文件。

(7)将之前的DM9000驱动,LCD驱动和内存管理驱动程序导入工程中。

(8)将下列文件添加到项目LWIP-NETIF,LWIP-CORE,LWIP-API,LWIP-APP,LWIP-ARCH中。

项目目录 文件
LWIP-NETIF etharp.c
LWIP-CORE autoip.c
ip.c ip_addr.c
dns.c init.c
netif.c pbuf.c
tcp_in.c tcp_out.c
LWIP-API api_lib.c
netifapi.c sockets.c
LWIP-APP comm.c
LWIP-ARCH sys_arch.c

注:将lwip/core目录下的sys.c文件与lwip/include/lwip目录下的sys.h重命名为lwip_sys.c和lwip_sys.h,以免与SYSTEM目录下的sys.c重名,产生编译错误。

24.5.4 arch目录下源码文件的修改

(1)arch/cc.h文件代码(该文件主要完成协议使用的数据类型的定义)

#ifndef _CC_H_
#define _CC_H_
#include "cpu.h"
#include "stdio.h"
//定义与平台无关的数据类型
typedef unsigned   char    u8_t;                    //无符号8位整数
typedef signed     char    s8_t;                    //有符号8位整数
typedef unsigned   short   u16_t;                    //无符号16位整数
typedef signed     short   s16_t;                    //有符号16位整数
typedef unsigned   long    u32_t;                    //无符号32位整数
typedef signed     long    s32_t;                    //有符号32位整数
typedef u32_t mem_ptr_t ;                        //内存地址型数据
typedef int sys_prot_t ;                          //临界保护型数据
#if OS_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev)    CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev)    CPU_INT_EN()
#endif
//method 3 is used in this port
#if OS_CRITICAL_METHOD == 3
#define SYS_ARCH_DECL_PROTECT(lev)  u32_t lev
#define SYS_ARCH_PROTECT(lev)    lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev)    OS_CPU_SR_Restore(lev)
#endif
//根据不同的编译器定义一些符号
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
//LWIP用printf调试时使用的一些数据类型
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
//宏定义
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \\
    do \\
    {   printf("Assertion \"%s\" failed at line %d in %s\\r\\n", x, __LINE__, __FILE__); \\
    } while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
#endif

(2)arch/cpu.h文件代码(负责定义CPU的大端小端模式)

#ifndef _CPU_H_
#define _CPU_H_
#define BYTE_ORDER LITTLE_ENDIAN                //小端模式
#endif

(3)arch/perf.h文件代码(用于系统测量与统计)

#ifndef _PERF_H_
#define _PERF_H_
#define PERF_START                        //空定义
#define PERF_STOP(x)                        //空定义
#endif

(4)arch/sys_arch.h文件代码(为了与操作系统共存使用的获取时间的函数,用于为LWIP提供时钟)

#ifndef _ARCH_SYS_ARCH_H_
#define _ARCH_SYS_ARCH_H_
#include "cc.h"
u32_t sys_now( void ) ;
#endif

(5)arch/sys_arch.c文件代码

#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/lwip_sys.h"
#include "lwip/mem.h"
#include "tim.h"
//为LWIP提供计时
extern uint32_t lwip_localtime;//lwip本地时间计数器,单位:ms
u32_t sys_now()
{
  return lwip_localtime ;
}

24.5.5 app/comm目录下源码文件的修改

(1)app/comm.c文件代码

#include "lwip/tcpip.h" 
#include "malloc.h"
#include "delay.h"
#include "usart1.h"
__lwip_dev lwipdev ;                          //lwip控制结构体 
struct netif lwip_netif ;                          //定义一个全局的网络接口
extern u32 memp_get_memorysize( void ) ;                //在memp.c里面定义
extern u8_t *memp_memory ;                      //在memp.c里面定义
extern u8_t *ram_heap ;                        //在mem.c里面定义
u32 TCPTimer=0 ;                            //TCP查询计时器
u32 ARPTimer=0 ;                            //ARP查询计时器
u32 lwip_localtime ;                          //lwip本地时间计数器,单位:ms
#if LWIP_DHCP
u32 DHCPfineTimer=0 ;                        //DHCP精细处理计时器
u32 DHCPcoarseTimer=0 ;                        //DHCP粗糙处理计时器
#endif
u8 lwip_comm_mem_malloc()
{
  u32 mempsize ;
  u32 ramheapsize ;
  mempsize = memp_get_memorysize() ;                //得到memp_memory数组大小
  memp_memory = mymalloc( SRAMIN, mempsize ) ;          //为memp_memory申请内存
//得到ram heap大小
  ramheapsize = LWIP_MEM_ALIGN_SIZE( MEM_SIZE )+2*LWIP_MEM_ALIGN_SIZE( 4*3 )+MEM_ALIGNMENT ; 
  ram_heap = mymalloc( SRAMIN, ramheapsize ) ;            //为ram_heap申请内存
  //有申请失败的
  if( !memp_memory||!ram_heap )
  {
    lwip_comm_mem_free() ;
    return 1 ;
  }
  return 0 ;
}
void lwip_comm_mem_free()
{   
  myfree( SRAMIN, memp_memory ) ;
  myfree( SRAMIN, ram_heap ) ;
}
void lwip_comm_default_ip_set( __lwip_dev *lwipx )
{
  //默认远端IP为:192.168.1.100
  lwipx->remoteip[ 0 ] = 192 ;  
  lwipx->remoteip[ 1 ] = 168 ;
  lwipx->remoteip[ 2 ] = 1 ;
  lwipx->remoteip[ 3 ] = 104 ;
  //MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
  lwipx->mac[ 0 ] = dm9000cfg.mac_addr[ 0 ] ;
  lwipx->mac[ 1 ] = dm9000cfg.mac_addr[ 1 ] ;
  lwipx->mac[ 2 ] = dm9000cfg.mac_addr[ 2 ] ;
  lwipx->mac[ 3 ] = dm9000cfg.mac_addr[ 3 ] ;
  lwipx->mac[ 4 ] = dm9000cfg.mac_addr[ 4 ] ;
  lwipx->mac[ 5 ] = dm9000cfg.mac_addr[ 5 ] ; 
  //默认本地IP为:192.168.1.30
  lwipx->ip[ 0 ] = 192 ;  
  lwipx->ip[ 1 ] = 168 ;
  lwipx->ip[ 2 ] = 1 ;
  lwipx->ip[ 3 ] = 30 ;
  //默认子网掩码:255.255.255.0
  lwipx->netmask[ 0 ] = 255 ;  
  lwipx->netmask[ 1 ] = 255 ;
  lwipx->netmask[ 2 ] = 255 ;
  lwipx->netmask[ 3 ] = 0 ;
  //默认网关:192.168.1.1
  lwipx->gateway[ 0 ] = 192 ;
  lwipx->gateway[ 1 ] = 168 ;
  lwipx->gateway[ 2 ] = 1 ;
  lwipx->gateway[ 3 ] = 1 ;
  lwipx->dhcpstatus = 0 ;      //没有DHCP
}
u8 lwip_comm_init()
{
  struct netif *Netif_Init_Flag ;      //调用netif_add()函数时的返回值,用于判断网络初始化是否成功
  struct ip_addr ipaddr ;        //ip地址
  struct ip_addr netmask ;      //子网掩码
  struct ip_addr gw ;          //默认网关
  //内存申请失败
  if( lwip_comm_mem_malloc() )
    return 1 ;
  //初始化DM9000AEP
  if( DM9000_Init() )
    return 2 ;
  lwip_init() ;            //初始化LWIP内核
  lwip_comm_default_ip_set( &lwipdev ) ; //设置默认IP等信息
//使用动态IP
#if LWIP_DHCP
  ipaddr.addr = 0 ;
  netmask.addr = 0 ;
  gw.addr = 0 ;
//使用静态IP
#else
  IP4_ADDR( &ipaddr, lwipdev.ip[ 0 ], lwipdev.ip[ 1 ], lwipdev.ip[ 2 ], lwipdev.ip[ 3 ] ) ;
  IP4_ADDR( &netmask, lwipdev.netmask[ 0 ], lwipdev.netmask[1] , lwipdev.netmask[ 2 ], lwipdev.netmask[ 3 ] ) ;
  IP4_ADDR( &gw, lwipdev.gateway[ 0 ], lwipdev.gateway[ 1 ], lwipdev.gateway[ 2 ], lwipdev.gateway[ 3 ] );
#endif
  //向网卡列表中添加一个网口
  Netif_Init_Flag = netif_add( &lwip_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input ) ; 
//如果使用DHCP的话
#if LWIP_DHCP
  lwipdev.dhcpstatus = 0 ;        //DHCP标记为0
  dhcp_start( &lwip_netif ) ;        //开启DHCP服务
#endif
  //网卡添加失败
  if( Netif_Init_Flag==NULL )
    return 3 ;
  //网口添加成功后,设置netif为默认值,并且打开netif网口
  else
  {
    netif_set_default( &lwip_netif ) ;    //设置netif为默认网口
    netif_set_up( &lwip_netif ) ;      //打开netif网口
  }
  return 0 ;                //操作OK
}
void lwip_pkt_handle()
{
  ethernetif_input( &lwip_netif ) ;      //从网络缓冲区中读取接收到的数据包并将其发送给LWIP处理
}
void lwip_periodic_handle()
{
#if LWIP_TCP
  //每250ms调用一次tcp_tmr()函数
  if( lwip_localtime-TCPTimer>=TCP_TMR_INTERVAL )
  {
    TCPTimer =  lwip_localtime ;
    tcp_tmr() ;
  }
#endif
  //ARP每5s周期性调用一次
  if( ( lwip_localtime-ARPTimer )>=ARP_TMR_INTERVAL )
  {
    ARPTimer = lwip_localtime ;
    etharp_tmr() ;
  }
//如果使用DHCP的话
#if LWIP_DHCP
  //每500ms调用一次dhcp_fine_tmr()
  if( lwip_localtime-DHCPfineTimer>=DHCP_FINE_TIMER_MSECS )
  {
    DHCPfineTimer = lwip_localtime ;
    dhcp_fine_tmr() ;
    if( ( lwipdev.dhcpstatus!=2 )&&( lwipdev.dhcpstatus!=0xFF ) )
      lwip_dhcp_process_handle() ;                                //DHCP处理
  }
  //每60s执行一次DHCP粗糙处理
  if( lwip_localtime-DHCPcoarseTimer>=DHCP_COARSE_TIMER_MSECS )
  {
    DHCPcoarseTimer = lwip_localtime ;
    dhcp_coarse_tmr() ;
  }
#endif
}
//如果使能了DHCP
#if LWIP_DHCP
void lwip_dhcp_process_handle()
{
  u32 ip=0, netmask=0, gw=0 ;
  switch( lwipdev.dhcpstatus )
  {
    //开启DHCP
    case 0:
      dhcp_start( &lwip_netif ) ;
      lwipdev.dhcpstatus = 1 ;            //等待通过DHCP获取到的地址
      break ;
    //等待获取到IP地址
    case 1:
    {
      ip = lwip_netif.ip_addr.addr ;          //读取新IP地址
      netmask = lwip_netif.netmask.addr ;        //读取子网掩码
      gw = lwip_netif.gw.addr ;            //读取默认网关 
      //正确获取到IP地址的时候
      if( ip!=0 )
      {
        lwipdev.dhcpstatus = 2 ;          //DHCP成功
        //解析出通过DHCP获取到的IP地址
        lwipdev.ip[ 3 ] = ( uint8_t )( ip>>24 ) ; 
        lwipdev.ip[ 2 ] = ( uint8_t )( ip>>16 ) ;
        lwipdev.ip[ 1 ] = ( uint8_t )( ip>>8 ) ;
        lwipdev.ip[ 0 ] = ( uint8_t )( ip ) ;
        //解析通过DHCP获取到的子网掩码地址
        lwipdev.netmask[ 3 ] = ( uint8_t )( netmask>>24 ) ;
        lwipdev.netmask[ 2 ] = ( uint8_t )( netmask>>16 ) ;
        lwipdev.netmask[ 1 ] = ( uint8_t )( netmask>>8 ) ;
        lwipdev.netmask[ 0 ] = ( uint8_t )( netmask ) ;
        //解析出通过DHCP获取到的默认网关
        lwipdev.gateway[ 3 ] = ( uint8_t )( gw>>24 ) ;
        lwipdev.gateway[ 2 ] = ( uint8_t )( gw>>16 ) ;
        lwipdev.gateway[ 1 ] = ( uint8_t )( gw>>8 ) ;
        lwipdev.gateway[ 0 ] = ( uint8_t )( gw ) ;
      }
      //通过DHCP服务获取IP地址失败,且超过最大尝试次数
      else if( lwip_netif.dhcp->tries>LWIP_MAX_DHCP_TRIES )
      {
        lwipdev.dhcpstatus = 0xFF ;          //DHCP超时失败
        //使用静态IP地址
        IP4_ADDR( &( lwip_netif.ip_addr ), lwipdev.ip[ 0 ], lwipdev.ip[ 1 ], lwipdev.ip[ 2 ], lwipdev.ip[ 3 ] ) ;
        IP4_ADDR( &( lwip_netif.netmask ), lwipdev.netmask[ 0 ], lwipdev.netmask[ 1 ], lwipdev.netmask[ 2 ], lwipdev.netmask[ 3 ] ) ;
        IP4_ADDR( &( lwip_netif.gw ), lwipdev.gateway[ 0 ], lwipdev.gateway[ 1 ], lwipdev.gateway[ 2 ], lwipdev.gateway[ 3 ] ) ;
      }
    }
    break;
    default :
    break;
  }
}
#endif

(2)app/comm.h文件代码

#ifndef _COMM_H_
#define _COMM_H_
#include "dm9000.h"
#define LWIP_MAX_DHCP_TRIES  4            //DHCP服务器最大重试次数
//lwip控制结构体
typedef struct  
{
  u8 mac[ 6 ] ;                    //MAC地址
  u8 remoteip[ 4 ] ;                  //远端主机IP地址 
  u8 ip[ 4 ] ;                    //本机IP地址
  u8 netmask[ 4 ] ;                  //子网掩码
  u8 gateway[ 4 ] ;                  //默认网关的IP地址
  vu8 dhcpstatus ;                  //dhcp状态
                          //0,未获取DHCP地址
                          //1,进入DHCP获取状态
                          //2,成功获取DHCP地址
                          //0XFF,获取失败
}__lwip_dev ;
extern __lwip_dev lwipdev ;                //lwip控制结构体
void lwip_pkt_handle( void ) ;
void lwip_periodic_handle( void ) ;
void lwip_comm_default_ip_set( __lwip_dev *lwipx ) ;
u8 lwip_comm_mem_malloc( void ) ;
void lwip_comm_mem_free( void ) ;
u8 lwip_comm_init( void ) ;
void lwip_dhcp_process_handle( void ) ;
#endif

注:函数中用到了未定义的数据类型,需要在sys.h中添加该类型的定义typedef volatile uint8_t vu8。

(3)app/lwipopts文件代码

#ifndef _LWIPOPTS_H_
#define _LWIPOPTS_H_
#define SYS_LIGHTWEIGHT_PROT  0
//NO_SYS==1:不使用操作系统
#define NO_SYS          1      //不使用UCOS操作系统
#define MEM_ALIGNMENT      4      //使用4字节对齐模式
//heap内存的大小,如果在应用中有大量数据发送的话这个值最好设置大一点
#define MEM_SIZE        10*1024    //内存堆大小
//memp结构的pbuf数量,如果应用从ROM或者静态存储区发送大量数据时,这个值应该设置大一点
#define MEMP_NUM_PBUF      10
#define MEMP_NUM_UDP_PCB    6      //UDP协议控制块(PCB)数量.每个活动UDP"连接"需要一个PCB
#define MEMP_NUM_TCP_PCB    10      //同时建立激活的TCP数量
#define MEMP_NUM_TCP_PCB_LISTEN  6    //能够监听的TCP连接数量
#define MEMP_NUM_TCP_SEG    20      //最多同时在队列中的TCP段数量
#define MEMP_NUM_SYS_TIMEOUT  5      //能够同时激活的timeout个数
//Pbuf选项
//PBUF_POOL_SIZE:pbuf内存池个数
#define PBUF_POOL_SIZE    10
//PBUF_POOL_BUFSIZE:每个pbuf内存池大小
#define PBUF_POOL_BUFSIZE  1500
//TCP选项
#define LWIP_TCP      1          //为1是使用TCP
#define TCP_TTL        255        //生存时间
//当TCP的数据段超出队列时的控制位,当设备的内存过小的时候此项应为0
#define TCP_QUEUE_OOSEQ    0
//最大TCP分段
#define TCP_MSS        ( 1500-40 )    //TCP_MSS = (MTU - IP报头大小 - TCP报头大小
//TCP发送缓冲区大小(bytes)
#define TCP_SND_BUF      ( 4*TCP_MSS )
//TCP_SND_QUEUELEN: TCP发送缓冲区大小(pbuf).这个值最小为(2 * TCP_SND_BUF/TCP_MSS) 
#define TCP_SND_QUEUELEN  ( 4* TCP_SND_BUF/TCP_MSS )
//TCP发送窗口
#define TCP_WND        ( 2*TCP_MSS )
//ICMP选项
#define LWIP_ICMP      1        //使用ICMP协议
//DHCP选项
//当使用DHCP时此位应该为1,LwIP 0.5.1版本中没有DHCP服务
#define LWIP_DHCP      1
//UDP选项
#define LWIP_UDP      1        //使用UDP服务
#define UDP_TTL        255        //UDP数据包生存时间
//静态选项
#define LWIP_STATS      0
#define LWIP_PROVIDE_ERRNO  1
//SequentialAPI选项
//LWIP_NETCONN==1:使能NETCON函数(要求使用api_lib.c)
#define LWIP_NETCONN    0
//Socket API选项
//LWIP_SOCKET==1:使能Socket API(要求使用sockets.c)
#define LWIP_SOCKET      0
#define LWIP_COMPAT_MUTEX  1
#define LWIP_SO_RCVTIMEO  1        //通过定义可以避免阻塞线程
//Lwip调试选项
//#define LWIP_DEBUG  1            //开启DEBUG选项
#define ICMP_DEBUG    LWIP_DBG_OFF    //开启/关闭ICMPdebug
#endif

24.5.6 include/netif/ethernetif.h文件修改

#ifndef _ETHERNETIF_H_
#define _ETHERNETIF_H_
#include "lwip/err.h"
#include "lwip/netif.h"
//网卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
err_t ethernetif_init( struct netif *netif ) ;
err_t ethernetif_input( struct netif *netif ) ;
#endif

24.5.7 netif/ethernetif.c文件修改

#include "netif/ethernetif.h"
#include "dm9000.h"
#include "comm.h"
#include "malloc.h"
#include "netif/etharp.h"
#include "string.h"
static err_t low_level_init( struct netif *netif )
{
  netif->hwaddr_len = ETHARP_HWADDR_LEN ;        //设置MAC地址长度,为6个字节
  //初始化MAC地址,不能与网络中其他设备MAC地址重复
  netif->hwaddr[ 0 ] = lwipdev.mac[ 0 ] ;
  netif->hwaddr[ 1 ] = lwipdev.mac[ 1 ] ;
  netif->hwaddr[ 2 ] = lwipdev.mac[ 2 ] ;
  netif->hwaddr[ 3 ] = lwipdev.mac[ 3 ] ;
  netif->hwaddr[ 4 ] = lwipdev.mac[ 4 ] ;
  netif->hwaddr[ 5 ] = lwipdev.mac[ 5 ] ;
  netif->mtu = 1500 ;                  //最大允许传输单元,允许该网卡广播和ARP功能
  netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP ;
  return ERR_OK ;
}
static err_t low_level_output( struct netif *netif, struct pbuf *p )
{
  DM9000_SendPacket( p ) ;
  return ERR_OK ;
}
static struct pbuf *low_level_input( struct netif *netif )
{
  struct pbuf *p ;
  p = DM9000_Receive_Packet() ;
  return p ;
}
err_t ethernetif_input( struct netif *netif )
{
  err_t err ;
  struct pbuf *p ;
  p = low_level_input( netif ) ;                    //调用low_level_input函数接收数据
  if( p==NULL )
    return ERR_MEM ;
  err = netif->input( p, netif );                                    //调用netif结构体中的input字段(一个函数)来处理数据包
  if( err!=ERR_OK )
  {
    LWIP_DEBUGF( NETIF_DEBUG, ( "ethernetif_input: IP input error\\n" ) ) ;
    pbuf_free( p ) ;
    p = NULL ;
  }
  return err ;
}
err_t ethernetif_init( struct netif *netif )
{
  LWIP_ASSERT( "netif!=NULL", ( netif!=NULL ) ) ;
//LWIP_NETIF_HOSTNAME
#if LWIP_NETIF_HOSTNAME
  netif->hostname = "lwip" ;                    //初始化名称
#endif
  netif->name[ 0 ] = IFNAME0 ;                  //初始化变量netif的name字段
  netif->name[ 1 ] = IFNAME1 ;                  //在文件外定义这里不用关心具体值
  netif->output = etharp_output ;                  //IP层发送数据包函数
  netif->linkoutput = low_level_output ;              //ARP模块发送数据包函数
  low_level_init( netif ) ;                      //底层硬件初始化函数
  return ERR_OK ;
}

24.5.8 其他文件修改

(1)头文件修改:主要是将lwip/sys.h修改为lwip/lwip_sys.h,因为我们在移植的时候将lwip/core/sys.c和lwip/include/lwip目录下的一个文件名称从sys改为了lwip_sys,所以导致程序引用的头文件也需要修改,需要修改的头文件有:timers.c,init.c,lwip_sys.c,mem.c,pbuf.c和memp.c。

(2)memp.c文件修改

①修改memp_memory的定义,之前的定义是位于170行的这么几行代码。

static u8_t memp_memory[MEM_ALIGNMENT - 1

#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )

#include "lwip/memp_std.h"

];

我们将这段代码屏蔽掉,重新添加定义u8_t *memp_memory;

②添加memp_get_memorysize函数

在333行插入以下函数代码

u32_t memp_get_memorysize()

{

u32_t length=0;

   length=(

                 MEM_ALIGNMENT-1 //全局型数组 为所有POOL分配的内存空间

                 //MEMP_SIZE表示需要在每个POOL头部预留的空间  MEMP_SIZE = 0

                 #define LWIP_MEMPOOL(name,num,size,desc)+((num)*(MEMP_SIZE+MEMP_ALIGN_SIZE(size)))

                 #include "lwip/memp_std.h"

                 );

   return length;

}

24.6 主函数编写

经过上面的步骤,我们已经成功移植了LWIP 1.4.1版本,现在我们通过编写主函数来初始化LWIP,让LWIP跑起来。

(1)添加定时器驱动,我们这里采用通用定时器3来完成LWIP的定时功能。

在tim.c文件中添加以下代码

#include "tim.h"
extern u32 lwip_localtime;                    //lwip本地时间计数器,单位:ms
void TIM3_IRQHandler()
{
  //溢出中断
  if( TIM3->SR&0x0001 )
    lwip_localtime +=10 ;                  //加10
  TIM3->SR &= ~( 1<<0 ) ;                  //清除中断标志位
}
void TIM3_Init( u16 arr, u16 psc )
{
  RCC->APB1ENR |= 1<<1 ;                  //TIM3时钟使能
   TIM3->ARR = arr ;                    //设定计数器自动重装值//刚好1ms
  TIM3->PSC = psc ;                    //预分频器7200,得到10Khz的计数时钟
  TIM3->DIER |= 1<<0 ;                                        //允许更新中断
  TIM3->CR1 |= 0x01 ;                    //使能定时器3
    NVIC_Init( 1, 3, TIM3_IRQn, 2 ) ;                //组2
}

在tim.h文件中添加以下代码

#ifndef _TIM_H_
#define _TIM_H_
#include "sys.h"
void TIM3_Init( u16 arr, u16 psc ) ;                //定时器3初始化
#endif

(2)主函数添加以下代码

#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "tim.h"
#include "lcd.h"
#include "malloc.h"
#include "dm9000.h"
#include "lwip/netif.h"
#include "comm.h"
#include "lwipopts.h"
int main()
{
  u8 buf[ 30 ];
   STM32_Clock_Init( 9 ) ;                                        //系统时钟设置
  SysTick_Init( 72 ) ;                          //延时初始化
  USART1_Init( 72, 115200 ) ;                      //串口初始化为115200
  LCD_Init() ;                            //初始化LCD
  TIM3_Init( 1000, 719 ) ;                        //定时器3频率为100hz
  my_mem_init( SRAMIN ) ;                      //初始化内部内存池
  while( lwip_comm_init() ) ;                      //lwip初始化
  //等待DHCP获取成功/超时溢出
  while( ( lwipdev.dhcpstatus!=2 )&&( lwipdev.dhcpstatus!=0xFF ) )
  {
    lwip_periodic_handle() ;                    //LWIP内核需要定时处理的函数
    lwip_pkt_handle() ;
  }
  POINT_COLOR=RED;
  LCD_ShowString( 30, 110, "LWIP Init Successed" ) ;
  //打印动态IP地址
  if( lwipdev.dhcpstatus==2 )
    sprintf( ( char* )buf, "DHCP IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  //打印静态IP地址
  else
    sprintf( ( char* )buf, "Static IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
  LCD_ShowString( 30, 130, buf ) ; 
  //得到网速
  if( ( DM9000_Get_SpeedAndDuplex()&0x02 )==0x02 )
    LCD_ShowString( 30, 150, "Ethernet Speed:10M" ) ;
  else
    LCD_ShowString( 30, 150, "Ethernet Speed:100M" ) ;
   while( 1 )
  {
    lwip_periodic_handle() ;
    lwip_pkt_handle() ;
    delay_ms( 2 ) ;
  }
}
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储器
    +关注

    关注

    38

    文章

    7487

    浏览量

    163800
  • DM9000
    +关注

    关注

    0

    文章

    24

    浏览量

    16890
  • 以太网控制器

    关注

    0

    文章

    39

    浏览量

    12724
收藏 人收藏

    评论

    相关推荐

    求labview和Linux网络通信实

    求labview和Linux网络通信实
    发表于 10-13 12:48

    线网络通信新手求助!

    各位大神,我是刚步入无线网络通信行业的新手,想问问大家,研究无线网络通信方向应该学哪些东西?大家有没有推荐的书什么的,希望得到大神们的指点,谢谢!!!
    发表于 12-14 18:07

    stm32+lwip网络通信

    stm32+lwip网络通信 在向上位机发送送数据时,发送的数据必须是常量,但是我要把AD转换的数据发送给上位机应该怎么办?求各路大神指点一二,急需,谢谢!!!
    发表于 10-28 21:26

    为什么UDP网络通信会不稳定?

    我的程序中嵌套了很多while(1)循环, 在每个循环的头部 加入LWIP_Polling(),然后按键通过网络发送参数 ,但是发送不了。感觉103的网络通信不太好用,以前用过107,
    发表于 08-07 01:23

    为什么网络通信实验用TCP客户端模式时连接不上?

    战舰V3上的实验50 网络通信实验,当用TCP服务器模式时,通信正常,用TCP客户端模式时连接不上,为何?是否有更新的网络通信实验代码?请提供。
    发表于 08-16 04:35

    如何才能让网络通信实验支持10个UDP链接?

    大家好,我在使用探索者STM32F4开发板,寄存器例程,实验55 网络通信实验。我修改了例程,想创建10个UDP端口监听,udppcb=udp_new()这里最多只能创建5个,5个之后都是返回失败。请问应该修改那个参数,可以创建10个UDP监听端口。
    发表于 08-27 22:48

    为什么网络通信实验网络助手上接受到的先是456个字节?

    网络通信实验里将ARMF407配置成sever,通过电脑上网络助手接受ARM发送到数据,将tcp_sever_sendbuf[1000]改成一个1000的大数组并赋值,但是在网络助手上接受到的先是456个字节然后再是544个字
    发表于 09-02 02:54

    请问谁有F407网络通信实验的视频吗?

    请问有没有F407网络通信实验的视频啊,能方便给个链接或者地址吗,谢谢了,急用嘞
    发表于 09-23 01:49

    如何把stm32f407网络通信实验调通?

    最近在做网络通信实验,没有调通,有没有大神指点一下
    发表于 10-30 04:35

    请问探索者网络通信实验的控制网页该怎么写呢?

    原子哥虽然有比较详细的讲解了网络通信实验,但是对于那个控制网页怎么写的却没有过多的涉及,请问有谁会写这种控制网页的,诚心求教!
    发表于 11-06 04:35

    stm32f767网络通信实验移植iar少了几个头文件

    原子哥,我用iar移植767网络通信实验,参考代码是标准例程~hal库版本,提示少了几个头文件,像ethernetif等我手动添加进去了,但是lwip_check.h中调用的config.h头文件
    发表于 03-24 23:54

    阿波罗STM32F767网络通信实验直接用网线连接电脑通信不成功

    STM32F767网络通信实验,直接用网线连接电脑,所有测试通信失败,但是用路由器所有通信正常。已关闭防火墙
    发表于 04-07 04:36

    基于原子STM32F4的摄像头与网络通信实验

    应单片机课设要求,做了一个摄像头拍照网络通信C/S实时LCD显示。该工程基于原子STM32F4的摄像头与网络通信实验,在此基础上,将其整合。1.预期功能:摄像头拍取的内容实时传输至LCD进行显示通过
    发表于 08-03 06:04

    通过演示实验深化对网络通信过程的理解

    通过演示实验深化对网络通信过程的理解,网络通信的威廉希尔官方网站 资料,很好很实用。
    发表于 03-28 10:39 17次下载

    从入门到精通-西门子工业网络通信实

    从入门到精通-西门子工业网络通信实战教材免费下载。
    发表于 04-21 14:52 43次下载