以太网是指遵守 IEEE 802.3 标准组成的局域网,由 IEEE 802.3 标准规定的主要是位于
参考模型的 物理层(PHY) 和 数据链路层中的介质访问控制子层(MAC)
物理层一般通过一个PHY芯片实现,10BASE-T网络传输数据没有时钟信号,采用曼彻斯特编码网速最高达10Mbps,100BASE-T网络采用4B/5B编码网速最高达100Mbps。以太网具有CSMA/CD 冲突检测机制。
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 校验序列
目前开源的适合嵌入式的有 uIP、 TinyTCP、 uC/TCP-IP、 LwIP 等等。其中LwIP 是目前在嵌入式网络领域被讨论和使用广泛的协议栈
LLC层 | 网络层 | 传输层 | 应用层 |
---|---|---|---|
处理传输错误;调节数据流 | 解决子网路由拓扑问题、路径选择问题 | 端到端的通讯 | 调用传输层的接口来编写特定的应用程序 |
数据链路协议 | IP 协议、 ICMP | TCP、 UDP | Telnet , FTP , SMTP |
TH 支持两个工业标准接口介质独立接口(MII)和简化介质独立接口(RMII)用于与外部 PHY 芯片连接.ETH 还集成了站管理接口(SMI)接口专门用于与外部 PHY 通
SMI 是 MAC 内核访问 PHY 寄存器标志接口,它由两根线组成,数据线 MDIO 和时钟线 MDC.最多能访问32个PHY。一次只能对一个 PHY 的其中一个寄存器进行访问。 SMI 最大通信频率为 2.5MHz,通过控制以太网MAC MII 地址寄存器 (ETH_MACMIIAR)的 CR 位可选择时钟频率
报头(32Bit) | 起始 | 操作 | PADDR | RADDR | TA | 数据(16Bit) | 空闲 |
---|---|---|---|---|---|---|---|
0xffff | 01 | 10读/01写 | PHY 地址 | PHY 寄存器地址 | 状态转换域 | – | z |
TA 为状态转换域, 若为读操作, MAC 输出两个位高阻态,而 PHY 芯片则在第一位时输出高阻态,第二位时输出“0”。若为写操作, MAC 输出“10”, PHY 芯片则输出高阻态
MII 需要 16 根通信线, RMII 只需 7 根通信,在功能上是相同的
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 信号线,当介质处于不同状态时会自切换该信号状态
针对目标地址过滤可以有三种,分别是单播、多播和广播目标地址过滤;针对源地址过滤就只有单播源地址过滤。广播目标地址过滤通过将帧过滤寄存器的 BFD 位置 1 使能,这使得 MAC 丢弃所有广播帧
LAN8720A是飞利浦公司的10/100Mbps 的以太网物理层收发器,仅支持RMII接口。内部集成PLL,提供25M时钟源就可以倍频到50M,并在ref_clk引脚输出。
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全称为 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宏定义,了解一下
移植正式开始:
Standalone 文件夹: ETH 外设与 LwIP 连接的底层驱动函数
port 文件夹: LwIP 与 STM32 平台连接的相关文件
stm32f4x7_eth:类似以太网官方库文件
stm32f4x7_eth_bsp:GPIO,DMA初始化函数,和状态获取函数,ETH 外设相关的底层配置
stm32f4x7_eth_conf.h:以太网的复位延时,外设状态寄存器中断引脚IO等配置
lwipopts.h:用于剪裁LWIP的功能,如有无操作系统,DHCP等等,一般直接用ST官方的配置
ethernetif:LwIP与ETH外设连接相关函数
netconf:存放 LwIP 配置相关代码,如mac地址,IP,网关,DHCP等信息
移植所需文件:
移植过程中需要整个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 文件夹(自己新建)中,需要额外的也可以自行添加。
第四步:向工程中添加文件。
首先须要阅读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是接受数据地址