嵌入式以太网通信Lwip

以太网结构

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

  • 物理层

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

  • MAC层

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

mac数据包格式,图片来源于《零死角玩转STM32》嵌入式以太网通信Lwip_第1张图片

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》嵌入式以太网通信Lwip_第2张图片

LLC层 网络层 传输层 应用层
处理传输错误;调节数据流 解决子网路由拓扑问题、路径选择问题 端到端的通讯 调用传输层的接口来编写特定的应用程序
数据链路协议 IP 协议、 ICMP TCP、 UDP Telnet , 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) 起始 操作 PADDR RADDR TA 数据(16Bit) 空闲
0xffff 01 10读/01写 PHY 地址 PHY 寄存器地址 状态转换域 z
  • TA 为状态转换域, 若为读操作, MAC 输出两个位高阻态,而 PHY 芯片则在第一位时输出高阻态,第二位时输出“0”。若为写操作, MAC 输出“10”, PHY 芯片则输出高阻态

  • MII 和 RMII 接口

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

MII 接口连接,图片来源于《零死角玩转STM32》嵌入式以太网通信Lwip_第3张图片

RMII 接口连接,图片来源于《零死角玩转STM32》
嵌入式以太网通信Lwip_第4张图片

TX_CLK RX_CLK CRS COL RX_DV RX_ER REF_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》嵌入式以太网通信Lwip_第5张图片

共用引脚:

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_第6张图片

移植过程中需要整个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》嵌入式以太网通信Lwip_第7张图片

首先须要阅读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, ðernetif_init, ðernet_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中找到

你可能感兴趣的:(嵌入式,通信协议,stm32)