嵌入式以太网通信Lwip

4 篇文章 0 订阅
2 篇文章 0 订阅

以太网结构

以太网是指遵守 IEEE 802.3 标准组成的局域网,由 IEEE 802.3 标准规定的主要是位于
参考模型的 物理层(PHY)数据链路层中的介质访问控制子层(MAC)

  • 物理层

物理层一般通过一个PHY芯片实现,10BASE-T网络传输数据没有时钟信号,采用曼彻斯特编码网速最高达10Mbps,100BASE-T网络采用4B/5B编码网速最高达100Mbps。以太网具有CSMA/CD 冲突检测机制。

  • MAC层

MAC 子层是属于数据链路层的下半部分,它主要负责与物理层进行数据交接,如是否
可以发送数据,发送的数据是否正确,对数据流进行控制等

mac数据包格式,图片来源于《零死角玩转STM32》mac数据包格式

1.前导字段: 报头,用于时钟同步,内容是7个0x55
2.帧起始定界符(SFD): 数据头,0xD5
3.MAC 地址: 在嵌入式的以太网控制器中可由程序进行配置
4.数据包类型: 本区域可以用来描述本 MAC 数据包是属于 TCP/IP 协议层的 IP 包、 ARP包还是 SNMP 包,也可以用来描述本 MAC 数据包数据段的长度。 如果该值被设置大于 0x0600,不用于长度描述,而是用于类型描述功能,表示与以太网帧相关的 MAC
客户端协议的种类.
5.数据段: 其长度可以 0~1500 字节间变化。
6.填充域: 协议要求整个 MAC 数据包的长度至少为 64 字节,所以填充无效数据
7.校验和域: CRC 校验序列


TCP/IP 协议栈

目前开源的适合嵌入式的有 uIP、 TinyTCP、 uC/TCP-IP、 LwIP 等等。其中LwIP 是目前在嵌入式网络领域被讨论和使用广泛的协议栈

图片来源于《零死角玩转STM32》在这里插入图片描述

LLC层网络层传输层应用层
处理传输错误;调节数据流解决子网路由拓扑问题、路径选择问题端到端的通讯调用传输层的接口来编写特定的应用程序
数据链路协议IP 协议、 ICMPTCP、 UDPTelnet , FTP , SMTP

以太网外设(ETH)

TH 支持两个工业标准接口介质独立接口(MII)和简化介质独立接口(RMII)用于与外部 PHY 芯片连接.ETH 还集成了站管理接口(SMI)接口专门用于与外部 PHY 通

  • SMI 接口

SMI 是 MAC 内核访问 PHY 寄存器标志接口,它由两根线组成,数据线 MDIO 和时钟线 MDC.最多能访问32个PHY。一次只能对一个 PHY 的其中一个寄存器进行访问。 SMI 最大通信频率为 2.5MHz,通过控制以太网MAC MII 地址寄存器 (ETH_MACMIIAR)的 CR 位可选择时钟频率

1.SMI 帧格式
报头(32Bit)起始操作PADDRRADDRTA数据(16Bit)空闲
0xffff0110读/01写PHY 地址PHY 寄存器地址状态转换域z
  • TA 为状态转换域, 若为读操作, MAC 输出两个位高阻态,而 PHY 芯片则在第一位时输出高阻态,第二位时输出“0”。若为写操作, MAC 输出“10”, PHY 芯片则输出高阻态

  • MII 和 RMII 接口

MII 需要 16 根通信线, RMII 只需 7 根通信,在功能上是相同的

MII 接口连接,图片来源于《零死角玩转STM32》在这里插入图片描述

RMII 接口连接,图片来源于《零死角玩转STM32》
在这里插入图片描述

TX_CLKRX_CLKCRSCOLRX_DVRX_ERREF_CLK
10Mbit/s 时为 2.5MHz,100Mbit/s 时为 25MHz载波侦听信号冲突检测信号接收数据有效信号接收错误信号线外部时钟源
非空闲状态时使能,全双工无效介质上存在冲突时使能由 PHY 芯片驱动MII:25MHz PHY提供TXRx clk, RMII:外部直接提供 50MHz时钟源,同时接入 MAC 和 PHY

对于 RMII 接口,是把 CRS 和 RX_DV 整合成 CRS_DV 信号线,当介质处于不同状态时会自切换该信号状态

  • MAC 过滤

针对目标地址过滤可以有三种,分别是单播、多播和广播目标地址过滤;针对源地址过滤就只有单播源地址过滤。广播目标地址过滤通过将帧过滤寄存器的 BFD 位置 1 使能,这使得 MAC 丢弃所有广播帧


LAN8720A

LAN8720A是飞利浦公司的10/100Mbps 的以太网物理层收发器,仅支持RMII接口。内部集成PLL,提供25M时钟源就可以倍频到50M,并在ref_clk引脚输出。

图片来源于《零死角玩转STM32》在这里插入图片描述

共用引脚:

1.PHYAD0 与 RXER: PHYAD0用于配置LAN8720A 地址,默认为0.系统上电会自动检测引脚电平并保存在特殊模式寄存器(R18)的 PHYAD 位中。
2.MODE[0] 与 RXD[0],MODE[1] 与 RXD[1], MODE[2]与 CRS_DV :
MODE[2:0]可设置通信速率,半双工/全双工,网线自适应
3. nINT与REFCLKO:当nINTSEL 引脚为低电平时,中断功能不可用,外部提供25M时钟源,引脚输出50M.放nINTSEL为高电平时,中断可用,50M时钟源输入单片机的REF_CLK 引脚和 LAN8720A 的 XTAL1/CLKIN 引脚
4. nINTSEL 与 LED2: 。。。
5.REGOFF 与 LED1: REGOFF低电平时选择内部+1.2V 稳压器,否则VDDCR 引脚输入+1.2V

寄存器:

R0: 基本控制寄存器,15位为Soft Reset
R1: 基本状态寄存器
R31: 特殊控制/状态寄存器,速度类型和自适应功能
Basic: IEEE要求的
Vendor-specific: 供应商自定义寄存器
Extended: ID 号、制造商、版本号


LwIP移植

LwIP全称为 Light Weight Internet Protocol,官网链接。可以下载两个压缩包:lwip-1.4.1.zip 和 contrib-1.4.1.zip。lwip-1.4.1.zip 包括了 LwIP 的实现代码, contrib-1.4.1.zip 包含了不同平台移植 LwIP 的驱动代码和使用 LwIP 实现的一些应用实例测试。ST官方也为LwIP提供了测试平台

解压 stsw-stm32070.rar,可在Library文件夹里找到stm32f4x7_eth.h文件,打开可以找到ETH_InitTypeDef宏定义,了解一下

移植正式开始:

ST官方文件介绍:

Standalone 文件夹: ETH 外设与 LwIP 连接的底层驱动函数
port 文件夹: LwIP 与 STM32 平台连接的相关文件
stm32f4x7_eth:类似以太网官方库文件
stm32f4x7_eth_bsp:GPIO,DMA初始化函数,和状态获取函数,ETH 外设相关的底层配置
stm32f4x7_eth_conf.h:以太网的复位延时,外设状态寄存器中断引脚IO等配置
lwipopts.h:用于剪裁LWIP的功能,如有无操作系统,DHCP等等,一般直接用ST官方的配置

LwIP文件介绍:

ethernetif:LwIP与ETH外设连接相关函数
netconf:存放 LwIP 配置相关代码,如mac地址,IP,网关,DHCP等信息

移植所需文件:

图片来源于《零死角玩转STM32》在这里插入图片描述

移植过程中需要整个LwIP-1.4.1文件夹,并且不需要改变LwIP里的文件。

第一步:在LwIP-1.4.1文件夹中新建port文件夹,再将stsw-stm32070中的Port/STM32F4x7 中的arch 和 Standalone 文件夹复制到新建的port文件夹中。

第二步:Bsp 文件下新建一个 ETH 文件夹,将stm32f4x7_eth.h 和 stm32f4x7_eth.c 改为stm32f429_eth放入bsp/ETH中,再stm32f4x7_eth_bsp.c、 stm32f4x7_eth_bsp.h 和stm32f4x7_eth_conf.h 三个文件放入bsp/ETH中,stm32f4x7_eth_bsp改名为stm32f429_phy

第三步:在stsw-stm32070 文件夹中找到 netconf.c、 tcp_echoclient.c、 lwipopts.h、 netconf.h 和 tcp_echoclient.h 五个文件
(路径: …\Project\Standalone\tcp_echo_client),直接拷贝到 App 文件夹(自己新建)中,需要额外的也可以自行添加。

第四步:向工程中添加文件。

图片来源于《零死角玩转STM32》在这里插入图片描述

首先须要阅读stm32f429_eth, stm32f429_phy, netconf, ethernetif, tcp_echoserver, tcp_echoclient(tcp或udp,主机或从机根据个人选择)这几个文件。


stm32f429_phy.c:
//8720芯片相关引脚配置
void  ETH_GPIO_Config(void)

//mac层DMA配置
static void ETH_MACDMA_Config(void)

void ETH_BSP_Config(void)
{
   /* Configure the GPIO ports for ethernet pins */
  ETH_GPIO_Config();

  /* Configure the Ethernet MAC/DMA */
   ETH_MACDMA_Config();

   /* Get Ethernet link status*/
   //如果 PHY 连接正常那么整个宏定义为 1,如    果不正常则为 0,它是通过 ETH_ReadPHYRegister 函数读取 PHY    的基本状态寄存器(PHY_BSR)并检测其 Link Status 位得到的
   if(GET_PHY_LINK_STATUS())
   {
      EthStatus |= ETH_LINK_FLAG;
   }
}

//无操作系统时该函数会被一直调用
void ETH_CheckLinkStatus(uint16_t PHYAddress)
{
  //state记录上一个连接状态
  static uint8_t status = 0;
  uint32_t t = GET_PHY_LINK_STATUS();
  //如果之前连接不上,现在连接上了
  if (t && !status) {
     //Set link up
      netif_set_link_up(&gnetif);
      status = 1;
   }
   // 如果之前连上现在断开了
  if (!t && status) {
      EthLinkStatus = 1;
      //Set link down
      netif_set_link_down(&gnetif);
     status = 0;
   }
}

//连接状态改变时调用此函数
void ETH_link_callback(struct netif *netif)
{
、、、、
    //如果链路状态启动了
    if(netif_is_link_up(netif))
    {
        if(ETH_InitStructure.ETH_AutoNegotiation !=           ETH_AutoNegotiation_Disable)
        {
        //设置为自适应模式          
ETH_WritePHYRegister(ETHERNET_PHY_ADDRESS, PHY_BCR, PHY_AutoNegotiation);
        }
    }
    else
    //没启动
    {

    }

}


stm32f429_eth_conf.h:

//用自己的延时函数
#ifdef USE_Delay
#include “SysTick.h”
#define eth_delay delay_10ms
#else
//用lwip自带的延时
#define eth_delay ETH_Delay
#endif

//配置复位延时时间50ms
#define  LAN8720A_RESET_DELAY  ((uint32_t)0x00000005)


netconf.h:

定义使不使用DHCP,只有路由器才需要
//#define USE_DHCP /* enable DHCP, if disabled static address is used */

//配置调试信息串口输出
#define SERIAL_DEBUG

/*静态IP地址 */
#define IP_ADDR0 192
#define IP_ADDR1 168
#define IP_ADDR2 8
#define IP_ADDR3 166

MAC地址,端口,子网掩码


netconf.c:

//初始化LwIP协议栈,在main函数中调用
void LwIP_Init(void)
{

//设置IP地址,网关,mac地址。。。
IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);

//向LwIP协议栈申请添加一个网卡设备
//gnetif—网卡设备属性结构体指针
//NULL—用户自定义字段,一般直接赋NULL
//ethernetif_init—网卡设备初始化函数地址,该函数在ethernetif.c中定义
//ethernet_input—以太网帧接收函数,在 etharp.c定义
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

//设置以太网设备为默认网卡
netif_set_default(&gnetif);

启动网卡

//设置连接状态改变时调用的回调函数
netif_set_link_callback(&gnetif, ETH_link_callback);
}

void LwIP_Pkt_Handle(void)
{
//在以太网缓冲中读取一个接受包并把它发给LwIP处理
ethernetif_input(&gnetif);

}

该函数无限循环调用查询链路状态,galocaltime—当前时间
void LwIP_Periodic_Handle(__IO uint32_t localtime)
{
  对于TCP功能,每250ms执行一次tcp_tmr();
  对于arp功能,每5s执行一次etharp_tmr();
  对于链路状态检测,每 1s 执行一次 ETH_CheckLinkStatus();

}


lwipopts.h:
该文件存放一些宏定义,用于配置以太网的功能,直接使用官方配置


tcp_echoclient.c:
//用于创建 TCP 从设备并启动与 TCP 服务器连接
void tcp_echoclient_connect(void)
{
  //创建一个新 TCP 协议控制块(pcb—protocal control block)
  echoclient_pcb = tcp_new();

  //将TCP客户端连接到指定IP端口的TCP服务端
tcp_connect(echoclient_pcb,&DestIPaddr,DEST_PORT,tcp_echoclient_connected);

//TCP建立连接后被调用
static err_t tcp_echoclient_connected(void *arg, struct tcp_pcb *tpcb,err_t err)
{
  //申请内存空间存放echoclient 结构体
  es = (struct echoclient *)mem_malloc(sizeof(struct echoclient));

  //申请数据发送缓冲
   es->p_tx = pbuf_alloc(PBUF_TRANSPORT, strlen((char*)data) , PBUF_POOL);

  //copy data to pbuf
  //p_tx—发送数据指针
   pbuf_take(es->p_tx, (char*)data, strlen((char*)data));

   //设置TCP发送回调函数
   tcp_sent(tpcb, tcp_echoclient_sent);

   //设置TCP轮询回调函数
   tcp_poll(tpcb, tcp_echoclient_poll, 1);

   //设置TCP接受回调函数
  tcp_recv(tpcb, tcp_echoclient_recv);

   //最后发送数据
  tcp_echoclient_send(tpcb,es);

如果连接不成功则断开连接

}

//TCP接受回调函数
static err_t tcp_echoclient_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  //先判断接受帧是否为空
   if (p == NULL)
  …

  //如果没有传输错误,则接受数据
   tcp_recved(tpcb, p->tot_len);
  …
}

//TCP发送回调函数
static void tcp_echoclient_send(struct tcp_pcb *tpcb, struct echoclient * es)
{

}

//TCP轮询回调函数,500ms执行一次
static err_t tcp_echoclient_poll(void *arg, struct tcp_pcb *tpcb)
{

  //如果有数据待发送
  tcp_echoclient_send(tpcb, es);

}
//接受远端信号回应时被调用
static err_t tcp_echoclient_sent(void *arg, struct tcp_pcb *tpcb, u16_t
len)
{

}


示例

  • 初始化:
    1.以太网驱动初始化
    ETH_BSP_Config();
    2.LwIP协议栈初始化
    LwIP_Init();

  • 线程:
    /* check if any packet received /
    if (ETH_CheckFrameReceived()) {
       /
    process received ethernet packet /
       LwIP_Pkt_Handle();
    }
    /
    handle periodic timers for LwIP */
    LwIP_Periodic_Handle(LocalTime);

这些都是必须一直在main函数中调用的,其中的LocalTime需要在一个10ms的中断函数中自加

  • 发送:
    tcp_echoclient_connected();

  • 接受:
    在指定的TCP/UDP接受回调函数里面接受数据
    err_t tcp_echoclient_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)

其中参数p是接受数据的结构体,p->len是接受数据,p->payload是接受数据地址

  • 其他例程可以在ST官方文件的project/standalone中找到
  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值