目录
网络协议简介
-
常用网络协议
TCP/IP是一个协议簇,包含众多网络协议
如TCP、IP、HTTP、FTP、MQTT、UDP、HRP、DHCP、DNS等等
-
网络协议的分层模型
应用层: HTTP、FTP、DNS、SMTP邮件协议 应用层处理用户和网络应用之间的通信
传输层:TCP、UDP协议 传输层负责网络中不同设备的连接和传输
网络层:IP、ICMP、HRP协议 网络层在不同网络之间传输数据包(帧格式路由)
链路层:MAC层 链路层凭MAC地址在直连设备之间传输数据
物理层:物理传输介质
-
协议层报文间的封装与拆封
当用户发送数据时,将数据在应用层向下交给传输层,传输层会在数据前面加上传输层首部,然后向下交给网络层,网络层会在数据前面加上网络层首部,下交给链路层,链路层加上链路层首部后交给网卡,网卡将数据转换成物理链路上的电平信号,将数据发送到网络中
STM32F4以太网MAC简介
MAC简介:
STM32F407自带有10/100Mbits/s数据传输速率的以太网内核(以太网网络接口卡的控制器,负责以太网中数据帧的发送和接收),这个以太网MAC内核有如下特性:
1. 支持外部PHY接口(以太网外部端口芯片,如LAN8720)实现10/100Mbit/s数据传输速率
2. 通过符合IEEE802.3的MII接口与外接的快速以太网PHY进行通信(MAC内核通过MII或RMII与PHY进行数据交换)
3. 支持全双工和半双工操作
4. 报头和帧起始数据(SFD)在发送路径中插入、在接收路径中删除
5. 可逐帧控制CRC和pad自动生成
6. 可编程帧长度,支持高达16KB的巨型帧
7. 可编程帧间隔(40-69位时间,以8为步长)
8. 支持通过MDIO接口配置和管理PHY设备(通过SMI接口对PHY进行配置)
需要注意的是以太网DMA有各2KB的发送和接收缓冲区
SMI接口(站管理接口):
F407的SMI接口有3种,SMI 和 MII、RMII
SMI称为站管理接口,程序中可以通过这个接口来访问PHY寄存器,对PHY进行配置
SMI接口有两条线,数据线MDIO和时钟线MDC。SMI允许应用程序通过时钟线MDC和数据线MDIO访问任意PHY寄存器,可支持访问多达32个PHY。应用程序可以从32个PHY中选择一个PHY,发送控制数据或接收状态信息。任意给定时间内只能对一个PHY的一个寄存器进行寻址。
MDC:周期性时钟线,提供最大2.5MHz的参考时序。在空闲状态下为低电平
MDIO:数据输入/输出线,通过MDC时钟信号与PHY设备之间同步传输信息
MII接口(介质独立接口):
介质独立接口(MII)定义了10/100Mbit/s的数据传输速率下MAC子层与PHY之间的互连方式,程序通过该接口使得MAC内核与PHY之间完成数据传输
TX_CLK和RX_CLK为发送和接收连续时钟,当内核速率为10Mbit/s时发送和接收时钟为2.5MHZ,当速率为100Mbit/s时为25MHZ(因为发送和接收都是4根线,发送接收线速率=内核速率/4)。
要生成TX_CLK和RX_CLK时钟,必须向外部PHY提供25MHZ时钟,通常我们使用25M的晶振,也可以使用STM32芯片的MCO引脚输出25MHZ的时钟。
RMII接口(精简介质独立接口):
精简介质独立接口(RMII) 规范用于降低10/100Mbit/s下微控制器与以太网PHY之间的引脚数
根据IEEE 802.3u标准,MII包括16个数据和控制信号的引脚,RMII规范将引脚数减少为7个,但要求RMII的参考时钟必须是50MHz
RMII时钟源通常使用50MHz的时钟来驱动PHY,或使用PLL电路生成50MHz的频率来驱动PHY
由于Lan8720内部具备倍频功能,因此外部时钟源只需25MHz,会自动倍频后返回到REF_CLK线上,得到50MHz的时钟频率
LAN8720
LAN8720简介
LAN8720是低功耗的10/100MHz以太网PHY层芯片,支持通过RMII接口与以太网MAC层通信,内置10-BASE-T/100-BASE-TX全双工模块传输模块,支持10Mbps和100Mbps。LAN8720可以通过自协商的方式确定与目的主机最佳的连接方式(速度和双工模式)。支持HPAuto-MDIX自动翻转功能,无需更换网线即可将连接更改为直连(TX接TX,RX接RX)和交叉连接(TX接RX)。
1. 支持RMII接口以减少引脚数
2. 支持全双工和半双工模式
3. 可以使用25M晶振以降低成本
4. 支持SMI串行管理接口
5. 支持MAC接口
LAN8720地址设置
前面说了MAC内核可以通过SMI接口来读写PHY(LAN8720的寄存器),SMI最多可以控制32个PHY芯片,通过不同的PHY芯片地址来对PHY芯片进行操作。LAN8720通过设置RXER/PHYAD0引脚来设置其PHY地址,默认情况下为0,其地址设置如下表所示。STM32F407开发板使用的是默认地址,也就是0x00。
nINT/REFCLKO配置
nINTSEL引脚用于设置nINT/REFCLKO引脚的功能。通常默认为低电平。nINT/SEL配置如下表所示:
REFCLK-In模式
当工作在REFCLK-In模式时,50MHz的外部时钟信号应接到LAN8720的XTAL1/CKIN引脚和STM32F407的RMII-REF-CLK引脚上
REFCLK-Out模式
为了降低成本,LAN8720可以从外部的25MHz的晶振中产生REF_CLK时钟。但要使用此功能时,REFCLKO引脚应工作在REFCLK-Out模式,此时REF_CLK时钟源如下图所示。图上外接了一个25MHz的时钟源,REFCLKO接口将内部倍频到50MHz的晶振通过REF_CLK接口供给MAC内核。
LAN-8720寄存器
PHY是由IEEE 802.3定义的。一般通过SMI对PHY进行管理和控制,也就是读写PHY内部寄存器。PHY内部寄存器的地址空间为5位,可以定义0~31共32个寄存器,但是随着PHY芯片功能的增加,很多PHY芯片采用分页技术来扩展地址空间,定义更多的寄存器,此处不讨论该情况。
IEEE 802.3定义了0~15这16个寄存器的功能,16~31寄存器由芯片制造商自由定义。下面介绍3个重要的LAN8720寄存器
BCR基础控制寄存器(0)
位15 软件复位 置1此位自动清零
位14 回测 0正常运行 1回测模式 回测模式下发送的数据会环回到发送接口,测试网络用
位13 速度选择 0为10Mbps,1为100Mbps。使用自动协商时此位自动失能
位12 自动协商功能 0关闭 1打开
位11 掉电 0正常运行 1进入掉电模式 进入掉电模式前自动协商必须失能
位10 隔离 0正常运行 1PHY的RMII接口电气隔离
位9 重启自动协商功能 0正常运行 1重启自动协商功能 此位自动清零
位8 双工模式 0半双工 1全双工 开启自动协商后此位自动失效
位7:0 保留
BSR基础状态寄存器(1)
位15 100BASE-T4(4根线) 0不支持T4 1支持T4
位14 100BAST-TX全双工 0不支持TX全双工 1支持TX全双工
位13 100BASE-TX半双工 0不支持TX半双工 1支持TX半双工
位12 10BAST-T全双工 0不支持10MBps全双工 1支持10MBps全双工
位11 10BAST-T半双工 0不支持10MBps半双工 1支持10MBps半双工
位10:6 保留
位5 自动协商功能 0自动协商功能未完成 1自动协商功能完成
位4 远端错误 0无远端错误 1检测到远端错误
位3 自协商功能是否可执行 0可执行自协商功能 1不可执行自协商功能
位2 连接状态 0连接断开 1连接建立
位1 Jabber检测(数据传输异常) 0未检测到Jabber 1检测到Jabber
位0 扩展功能 0不支持扩展寄存器 1支持扩展寄存器
LAN8720特殊功能寄存器(31)
LAN8720特殊功能寄存器中的2~4bit是我们关心的,因为从这3个bit中我们可以判断当前开发板网络的双工方式和网速。这个寄存器的地址和bit的意义需要我们手动添加到ST的以太网驱动库文件stm32f4x7_eth_conf.h中,而BCR和BSR寄存器定义在stm32f4x7_eth.h中
硬件连接
以太网DMA描述符
DMA描述符介绍
STM32F407有一个以太网专用的DMA,DMA可以在CPU完全不干预的情况下,通过描述符有效地将数据从源传送到目标,接收缓冲区和发送缓冲区的数据都可以通过以太网DMA来传送。
共有两个描述符,一个用于发送,一个用于接收。描述符是一种链表,最后一个描述符会指回第一个描述符以构成环形结构。描述符链表位于主机(RAM)的物理存储空间,两个链表的基址分别写入ETH_DMARDLAR(Descriptor List Address Register)寄存器和ETH_DMATDLAR寄存器中。每个描述符最多可指向两个缓冲区。描述符一共有两种结构: 环形结构和链接结构。发送和接收描述符在程序中共用一个结构体,通过存入的寄存器不同来进行功能区分(RDLAR和TDLAR寄存器)。
DMA描述符链接结构
ST提供给我们的以太网驱动库使用的描述符是链接结构。链接结构的第二个缓冲区用来存放下一个描述符的地址,最后一个描述符指向第一个描述符。
描述符注意事项:
1、一个以太网数据包可以跨越一个或多个DMA描述符
2、一个DMA描述符只能用于一个以太网数据包
3、DMA描述符列表中的最后一个描述符指向第一个,形成链式结构
在ST的以太网驱动库stm32f4x7_eth.h中有个结构体ETH_DMADESCTypeDef,该结构体就是以太网描述符。发送描述符和接收描述符都使用该结构,但
描述符又分为常规描述符和增强描述符,常规描述符和增强描述符又有发送描述符和接收描述符两种。
在stm32f4x7_eth.c中定义了两个DMA描述符数组,一个用于DMA接收,一个用于DMA发送
__align(4)
ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB];/* DMA接收描述符数组*/
__align(4)
ETH_DMADESCTypeDef DMATxDscrTab[ETH_TXBUFNB];/* DMA发送描述符数组 */
__align(4)
uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* DMA接收缓冲区 */
__align(4)
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* DMA发送缓冲区 */
这两个数组和缓冲区的定义通常是要手动屏蔽掉的,因为此处宏定义的数组大小比较大,占用较多的RAM空间。一般屏蔽后使用动态内存管理自定义同名数组。
LWIP无操作系统移植
ST以太网驱动库
ST提供的以太网驱动库有三个文件
LAN8720的复位引脚默认是接地的,也就是默认复位,不然上电后发热严重(模拟芯片发热问题),初始化时需要手动把复位引脚拉高停止复位
网卡驱动
LAN8720驱动写完以后,我们还要编写ethernetif.c文件。LWIP作者提供了一个ethernet.c文件架构。我们需要根据实际使用的网络芯片来完善这些函数。
LAN8720.c主要程序原理
LAN8720.c中定义了以太网中断服务函数ETH_IRQHandler,中断可以提高数据的发送和接收效率
以太网中断服务函数包含了数据的接收处理流程,详细代码就不赘述了,结合重点代码简单介绍
//以太网中断服务函数
void ETH_IRQHandler(void)
{
while(ETH_GetRxPktSize(DMARxDescToGet)!=0) //检测是否收到数据包 DMARxDescToGet是当前正在使用的接收描述符
{
lwip_pkt_handle(); //进行数据处理
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R); //清除DMA接收完成标志位
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS); //清除DMA正常中断标志位
}
以太网DMA描述符包括4个字段
状态码Status、
控制缓冲区ControlBuffersize、
缓冲区Buffer1Addr、
缓冲区Buffer2NextDescAddr
32位状态码的最高位是OWN标志位,
OWN=1代表描述符由DMA处理(未接收数据),OWN=0代表描述符由CPU处理(接收到数据)
以太网中断服务函数ETH_IRQHandler代码流程包括:
首先使用while关键字对当前接收描述符的大小进行循环判断DMARxDescToGet
如果关键字状态"寄存器"的OWN=0、ES=0、LS=1,则代表当前描述符是链式描述符的最后一个描述符,通过Status&FL宏取出状态码中的描述符大小(含4字节CRC校验位)Early/Last Status
如果描述符大小不为0,则代表描述符链表装载完数据,进行数据处理
数据处理是调用网卡协议栈LWIP直接对网卡数据进行解包处理
/*
**当接收到数据后调用
*/
void lwip_pkt_handle(void)
{
ethernetif_input(&lwip_netif);//从网络缓冲区中读取接收到的数据包并将其发送给LWIP处理
}
//网卡接收数据(lwip直接调用) MAC内核接收到数据要交给LWIP协议栈去解包处理
//netif:网卡结构体指针
//返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p=low_level_input(netif); //网卡数据包接收函数
if(p==NULL) return ERR_MEM;
err=netif->input(p, netif);
if(err!=ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
return err;
}
///用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t len;
int l =0;
FrameTypeDef frame;
u8 *buffer;
p = NULL;
frame=ETH_Rx_Packet();
len=frame.length;//得到包大小
buffer=(u8 *)frame.buffer;//得到包数据地址
p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
if(p!=NULL)
{
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);
l=l+q->len;
}
}
frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return p;
}