嵌入式之以太网
W5500是韩国 WIZnet 公司推出的一款硬件 TCP/IP 嵌入式以太网控制器,集成了MAC(媒体访问控制)和 PHY(物理层),支持SPI 接口与主控芯片(如 STM32、Arduino、ESP32)通信。它专为嵌入式设备设计,能够无需操作系统实现稳定的网络连接,适用于 IoT、工业控制、智能家居等场景。
1.介绍
以太网是一种计算机局域网技术,主要用于在相对较小的地理范围内连接多台计算机和设备,如在一个办公室、一栋建筑物或一个校园内。以太网通常由以太网交换机、网络适配器(网卡)、网线等设备组成,通过特定的网络协议实现设备之间的数据传输。
以太网是一种 有线局域网(LAN)技术,由 IEEE 802.3 标准定义,用于在本地网络(如家庭、办公室、校园)中通过有线介质(双绞线、光纤等)连接设备。
以太网的主要特点是传输速度快、可靠性高、成本相对较低,并且易于安装和管理。它主要用于实现局部范围内的设备互联和资源共享,如文件共享、打印机共享、内部通信等。
以太网的层次:

以太网主要涉及 OSI 模型 的以下两层:
(1) 物理层(Physical Layer)
功能:定义硬件接口、电气特性(如电压)、物理介质(如网线、光纤)和比特流传输。
传输单位:比特(bit)。
(2) 数据链路层(Data Link Layer)
功能:提供相邻节点间的可靠数据传输,包括帧封装、错误检测(CRC)、MAC 寻址等。
子层:
MAC 子层:控制介质访问(如 CSMA/CD)、帧格式(源/目的 MAC 地址)。
LLC 子层(可选):为网络层提供统一接口(如 IEEE 802.2)。
传输单位:帧(Frame)。
2.常见的网络协议
IP协议
IP 协议是TCP/IP协议族中最为核心的协议,位于网络层。IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层——TCP或UDP层;相反,IP层也把从TCP或UDP层接收来的数据包传送到更低层。IP数据包是不可靠的。
IP 协议的主要功能包括:
寻址(Addressing):使用 IP 地址(如
192.168.1.1)唯一标识网络中的设备。路由(Routing):决定数据包从源主机到目标主机的传输路径。
分片与重组(Fragmentation & Reassembly):如果数据包太大,IP 层可以将其分片传输,并在目标端重组。
跨网络传输:在不同类型的网络(如以太网、Wi-Fi、光纤)之间传递数据。
TCP协议(传输层:很安全,但是资源消耗大。)
TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。4次挥手断开连接。因为这种“握手”机制,所以资源消耗大。
TCP提供的是一种可靠的数据流服务,采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。
IP 负责数据包的寻址和路由(送到哪),而 TCP 负责数据的可靠传输(怎么送)。
UDP协议(传输层:速度快,不安全,传输视频用)
UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。
UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证,可用于视频。
UDP与TCP位于同一层,但UDP不管数据包的顺序、错误或重发。
2.W5500简介
W5500 是韩国 WIZnet 公司推出的一款 硬件 TCP/IP 嵌入式以太网控制器,集成了 MAC(媒体访问控制)和 PHY(物理层),支持 SPI 接口 与主控芯片(如 STM32、Arduino、ESP32)通信。它专为嵌入式设备设计,能够 无需操作系统 实现稳定的网络连接,适用于 IoT、工业控制、智能家居等场景。

3.库的移植
首先我们要移植以太网的库:

wizchip.conf.c是 WIZnet 官方提供的 硬件 TCP/IP 芯片(如 W5500、W5100S)的底层驱动配置文件,用于适配不同 MCU 的硬件接口(如 SPI、GPIO)。以下是该文件的详细解析和配置方法:1. 文件功能
实现硬件抽象层(HAL):定义 SPI 读写、复位、中断等底层操作函数。
配置芯片参数:设置 MAC 地址、IP 地址、子网掩码等网络信息。
提供基础 API:供上层协议栈(如 Socket 层)调用。
1.wizchip.conf.c的重写
1.
这个相当于Freertos保护临界区作用的代码。进入临界区和退出临界区,保护代码不被打断。
2.片选信号

3.利用SPI的数据交换进行读写。

4.并且添加一个封装注册功能的函数。

在wizchip.cnof.c上的操作完成。
2.对SPI进行配置并且ping成功。
#ifndef _SPI_H
#define _SPI_H
#include"stm32f10x.h"
#include"delay.h"
#define SPI_CS_HIGH (GPIOD->ODR|=GPIO_ODR_ODR3)
#define SPI_CS_LOW (GPIOD->ODR&=~GPIO_ODR_ODR3)
void spi_init(void);
void spi_start(void);
void spi_stop(void);
uint8_t spi_swapdata(uint8_t data);
#endif
#include"spi.h"
void spi_init(void)
{
//开启时钟
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
RCC->APB2ENR|=RCC_APB2ENR_IOPDEN;
RCC->APB1ENR|=RCC_APB1ENR_SPI2EN;
RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;
RCC->APB2ENR|=RCC_APB2ENR_IOPGEN;
//配置GPIO PB15 复用推挽输出 MODE 11 CNF 10 PB14 浮空输入 MODE 00 CNF 01 片选PD3 通用推挽输出 mode 11 cnf 00
GPIOB->CRH|=(GPIO_CRH_MODE15|GPIO_CRH_MODE13);
GPIOB->CRH|=(GPIO_CRH_CNF15_1|GPIO_CRH_CNF13_1);
GPIOB->CRH&=~(GPIO_CRH_CNF15_0|GPIO_CRH_CNF13_0);
GPIOD->CRL|=GPIO_CRL_MODE3;
GPIOD->CRL&=~GPIO_CRL_CNF3;
GPIOB->CRH&=~GPIO_CRH_MODE14;
GPIOB->CRH&=~GPIO_CRH_CNF14;
GPIOB->CRH|=GPIO_CRH_CNF14_0;
//配置SPI
//配置为主模式
SPI2->CR1|=SPI_CR1_MSTR;
//配置软件片选
SPI2->CR1|=SPI_CR1_SSM;
SPI2->CR1|=SPI_CR1_SSI;
//分频
SPI2->CR1&=~SPI_CR1_BR;
//模式0
SPI2->CR1&=~SPI_CR1_CPHA;
SPI2->CR1&=~SPI_CR1_CPOL;
//高位先行
SPI2->CR1&=~SPI_CR1_LSBFIRST;
//8位数据帧格式
SPI2->CR1&=~SPI_CR1_DFF;
//使能
SPI2->CR1|=SPI_CR1_SPE;
}
void spi_start(void)
{
SPI_CS_LOW;
}
void spi_stop(void)
{
SPI_CS_HIGH;
}
uint8_t spi_swapdata(uint8_t data)
{
while ((SPI2->SR & SPI_SR_TXE)==0)
{
}
SPI2->DR=data;
while ((SPI2->SR & SPI_SR_RXNE)==0)
{
}
return (uint8_t)(SPI2->DR);
}
3.Ethernet的配置
#include "eth.h"
#include "spi.h"
#include "USART.h"
#include "w5500.h"
static void eth_rst(void);
uint8_t ip[4]={192,168,1,152};
uint8_t MAC[6]={110,120,130,140,150,160};
uint8_t getway[4]={192,168,1,1};
uint8_t mask[4]={255,255,255,0};
void eth_init(void)
{
spi_init();
ethernet_callback();
eth_rst();
setSHAR(MAC);
setSIPR(ip);
setSUBR(mask);
setGAR(getway);
}
static void eth_rst(void)
{
//PG7 通用推挽输出 MODE11 CNF 00
RCC->APB2ENR|=RCC_APB2ENR_IOPGEN;
GPIOG->CRL|=GPIO_CRL_MODE7;
GPIOG->CRL&=~GPIO_CRL_CNF7;
GPIOG->ODR&=~GPIO_ODR_ODR7;
Delay_ms(1);
GPIOG->ODR|=GPIO_ODR_ODR7;
Delay_ms(100);
}
初始化 W5500 硬件和网络参数,为 TCP/IP 通信奠定基础。
用
ping测试 IP 连通性,再用逻辑分析仪检查 SPI 通信波形。后续通过 Socket API 实现具体的 TCP 连接和数据传输。
4.TCP通信
既然IP已经联通了,后续通过 Socket API 实现具体的 TCP 连接和数据传输。
4.1 TCP的服务端的配置
#include "TCP.h"
uint8_t self=serve;
uint16_t pport;
uint8_t key=0;
uint8_t ipp[4]={0};
void tcp_serve(void)
{
uint8_t flag;
uint8_t state=getSn_SR(SN);
if(state==SOCK_CLOSED)
{
flag=socket(SN,Sn_MR_TCP,PORT,SF_TCP_NODELAY);
if(flag==SN)
{
printf("成功连接TCP\n");
key=0;
}
else
{
printf("连接失败\n");
}
}
else if(state==SOCK_INIT)
{
flag=listen(SN);
if(flag==SOCK_OK)
printf("侦听状态\n");
}
else if(state==SOCK_LISTEN)
{
}
else if(state==SOCK_ESTABLISHED)
{
if(key==0)
{
printf("正在连接\n");
pport=getSn_PORT(SN);
getSn_DIPR(SN,ipp);
printf("port=%d,ip=%d.%d.%d.%d",pport,ipp[0],ipp[1],ipp[2],ipp[3]);
key=1;
}
}
else if(state==SOCK_CLOSE_WAIT)
{
close(SN);
printf("关闭连接\n");
key=0;
}
}
void senddata(uint8_t *buffer,uint16_t len)
{
uint8_t state=getSn_SR(SN);
if(state==SOCK_ESTABLISHED)
{
send(SN,buffer,len);
}
}
void redata(uint8_t *buffer,uint16_t *leng)
{
uint8_t state=getSn_SR(SN);
*leng=0;
if(state==SOCK_ESTABLISHED)
{
if((getSn_IR(SN) & Sn_IR_RECV)) //先看看是否接收到消息
{
setSn_IR(SN,Sn_IR_RECV); //Sn_IR_RECV状态位置0
*leng=getSn_RX_RSR(SN); //查看接收的消息个数,并且传回去
if(*leng>0)
{
recv(SN,buffer,*leng); //接收消息
}
}
}
}
#ifndef __TCP_H
#define __TCP_H
#include "stm32f10x.h"
#include "USART.h"
#include "wizchip_conf.h"
#include "w5500.h"
#include "socket.h"
#define SN 0
#define client 0
#define serve 1
#define PORT 8088
void tcp_serve(void);
void senddata(uint8_t *buffer,uint16_t len);
void redata(uint8_t *buffer,uint16_t *leng);
#endif
5.UTP通信
1. TCP 的客户端 - 服务器模式(明确区分)
TCP 是面向连接的协议,通信前需要通过 “三次握手” 建立可靠连接,其客户端与服务器的角色在连接建立时就已明确:
- 服务器:长期监听特定端口(如 Web 服务的 80 端口),等待客户端连接请求。
- 客户端:主动发起连接请求,连接建立后才能传输数据,且通信过程中始终依赖连接状态。
2. UDP 的无连接特性(角色区分相对模糊)
UDP 是无连接协议,数据传输无需建立连接,直接通过 IP 地址和端口发送数据包,因此:
- 表面上看:UDP 通信中双方似乎可以 “双向任意发送数据”,没有严格的 “必须先由一方发起” 的限制,导致部分人认为其不区分客户端与服务器。
- 本质上:UDP 仍存在客户端与服务器的逻辑区分,只是角色定义更灵活,依赖应用层实现。
#include "UDP.h"
uint8_t key=0;
void udp_init(void)
{
uint8_t flag;
uint8_t state=getSn_SR(SN);
if(state==SOCK_CLOSED)
{
flag=socket(SN,Sn_MR_UDP,8888,0);
if(flag==SN)
{
printf("成功连接\n");
}
else
{
printf("连接失败\n");
}
}
}
void redata(uint8_t *buffer,uint16_t *leng,uint8_t *scradd,uint16_t *scrport)
{
uint8_t state=getSn_SR(SN);
memset(buffer,0,sizeof(uint8_t)*(*leng));
*leng=0;
if(state==SOCK_UDP)
{
if((getSn_IR(SN) & Sn_IR_RECV)) //先看看是否接收到消息
{
setSn_IR(SN,Sn_IR_RECV); //Sn_IR_RECV状态位置0
uint8_t temp=getSn_RX_RSR(SN); //查看接收的消息个数,并且传回去
if(temp>8) //UDP前8位是端口和ip
{
*leng=temp-8;
recvfrom(SN,buffer,*leng,scradd,scrport); //接收消息
}
}
}
}
void senddata(uint8_t *buffer,uint16_t len,uint8_t *desip,uint16_t desport)
{
uint8_t state=getSn_SR(SN);
if(state==SOCK_UDP)
{
sendto(SN, buffer, len, desip, desport);
}
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)