STM32提高篇: 以太网通讯
一.以太网通讯介绍
以太网(Ethernet)是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线、电子信号和介质访问控制的内容。以太网是目前应用最普遍的局域网技术,取代了其他局域网标准如令牌环、FDDI和ARCNET。
以太网的标准拓扑结构为总线型拓扑,但目前的快速以太网(100BASE-T、1000BASE-T标准)为了减少冲突,将能提高的网络速度和使用效率最大化,使用交换机(Switch hub)来进行网络连接和组织。如此一来,以太网的拓扑结构就成了星型;但在逻辑上,以太网仍然使用总线型拓扑和CSMA/CD(Carrier Sense Multiple Access/Collision Detection,即载波多重存取/碰撞侦测)的总线技术。
经过长期的发展,以太网已成为应用最为广泛的局域网,包括标准以太网(10 Mbit/s)、快速以太网(100 Mbit/s)、千兆以太网(1000 Mbit/s)和万兆以太网(10 Gbit/s)等。IEEE 802.3规范则是基于以太网的标准制定的,并与以太网标准相互兼容。
二.W5500芯片介绍
要进行通讯,需要相应的硬件支持,在嵌入式应用领域,应用最广泛的一个以太网芯片就是W5500,素有以太之王的称号。
是韩国半导体公司WIZnet提供的一款高性价比的以太网芯片。其全球独一无二的全硬件TCPIP协议栈专利技术,解决了嵌入式以太网的接入问题,简单易用,安全稳定,是物联网设备的首选解决方案。
W5500 集成了 TCP/IP 协议栈,10/100M 以太网数据链路层(MAC)及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。
久经市场考验的WIZnet全硬件 TCP/IP 协议栈支持 TCP,UDP,IPv4,ICMP,ARP,IGMP以及PPPoE协议。W5500内嵌32K字节片上缓存以供以太网包处理。 如果你使用 W5500,你只需要一些简单的Socket编程就能实现以太网应用。这将会比其他嵌入式以太网方案更加快捷、简便。
用户可以同时使用8个硬件Socket独立通讯。 W5500 提供了SPI(外设串行接口)从而能够更加容易与外设 MCU 整合。而且,W5500的使用了新的高效SPI协议支持80MHz 速率,从而能够更好的实现高速网络通讯。为了减少系统能耗,W5500提供了网络唤醒模式(WOL)及掉电模式供客户选择使用。
1.W5500芯片特点
(1)支持硬件TCP/IP协议:TCP,UDP,ICMP,IPv4,ARP,IGMP,PPPoE。
(2)支持8个独立端口(Socket)同时通讯。
(3)支持掉电模式。
(4)支持网络唤醒。
(5)支持高速串行外设接口(SPI模式 0,3)。
(6)内部32K字节收发缓存。
(7)内嵌10BaseT/100BaseTX 以太网物理层(PHY)。
(8)支持自动协商(10/100-Based 全双工/半双工)。
(9)不支持 IP 分片。
(10)3.3V工作电压,I/O 信号口5V耐压。
(11)LED状态显示(全双工/半双工,网络连接,网络速度,活动状态)。
(12)LQFP48无铅封装(7x7mm,间距 0.5mm)
2.W5500应用目标
W5500适合于以下嵌入式应用:
(1)家庭网络设备:机顶盒、个人录像机、数码媒体适配器。
(2)串行转以太网:门禁控制、LED 显示屏、无线 AP 继电器等。
(3)并行转以太网:POS/微型打印机、复印机。
(4)USB 转以太网:存储设备、网络打印机。
(5)GPIO 转以太网:家庭网络传感器。
(6)安全系统:数字录像机、网络摄像机、信息亭。
(7)工厂和楼宇自动化控制系统。
(8)医疗监测设备。
(9)嵌入式服务器。
3.接入框图

三.驱动移植
注册函数:
c
void user_register_function(void)
{
/* 注册片选函数 */
reg_wizchip_cs_cbfunc(wizchip_cs_select, wizchip_cs_deselect);
/* 注册spi读写的函数 */
reg_wizchip_spi_cbfunc(wizchip_spi_readbyte, wizchip_spi_writebyte);
/* 注册临界区函数 (有实时操作系统的时候才会用到) */
reg_wizchip_cris_cbfunc(wizchip_cris_enter, wizchip_cris_exit);
}
复位和设置初始地址:
c
static void App_Eth_RST(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;
/* pg7: 推挽输出 mode=11 cnf=00 */
GPIOG->CRL |= GPIO_CRL_MODE7;
GPIOG->CRL &= ~GPIO_CRL_CNF7;
/* RST引脚拉低不低于500us */
GPIOG->ODR &= ~GPIO_ODR_ODR7;
Delay_ms(1);
GPIOG->ODR |= GPIO_ODR_ODR7;
Delay_ms(100);
}
uint8_t ip[] = {192, 168, 32, 223};
uint8_t ga[] = {192, 168, 32, 1};
uint8_t sub[] = {255, 255, 255, 0};
uint8_t mac[] = {110, 120, 13, 140, 150, 160};
void App_Eth_Init(void)
{
/* 初始化spi驱动 */
Driver_SPI_Init();
/* 软重启芯片 */
App_Eth_RST();
/* 注册函数 */
user_register_function();
/* 设置ip地址相关 */
/* ip地址 */
setSIPR(ip);
/* mac地址 */
setSHAR(mac);
/* 子网掩码 */
setSUBR(sub);
/* 设置网关 */
setGAR(ga);
printf("ip地址设置完成\r\n");
}
数据导通:
四.tcp通讯
使用TCP协议 :
面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP时面向连接的所以只能用端到端的通讯。
启动TCP服务器:
单片机作为服务器,使用本地电脑连接,成功则打印串口数据。
c
#define SN 0 /* socket 的编号 */
#define CLIENT 0
#define SERVER 1
uint8_t connected = 0;
uint8_t self = SERVER;
/* 启动一个TCP的服务器 */
void App_Tcp_ServerStart(void)
{
self = SERVER;
uint8_t status = getSn_SR(SN);
// printf("%#x\r\n", status);
if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
{
uint8_t r = socket(SN, Sn_MR_TCP, 8888, SF_TCP_NODELAY);
if(r == SN)
{
printf("socket 0打开成功\r\n");
}
else
{
printf("socket 0打开失败 %d\r\n", r);
}
connected = 0;
}
else if(status == SOCK_INIT) /* 表示socket的已经打开, 并处于tcp模式 */
{
/* 监听客户端的连接 */
uint8_t r = listen(SN);
if(r == SOCK_OK)
{
printf("socket 0监听成功\r\n");
}
else
{
printf("socket 0监听失败 %d\r\n", r);
}
connected = 0;
}
else if(status == SOCK_CLOSE_WAIT)
{
printf("失去与客户端的连接\r\n");
close(SN);
}
}
打印成功:
TCP协议接收数据:
c
uint8_t clientIp[4];
uint16_t clientPort;
/* 接收tcp协议传输的数据 */
void App_Tcp_ReceiveData(uint8_t data[], uint16_t *dataLen)
{
uint8_t status = getSn_SR(SN);
// printf("%#x\r\n", status);
if(status == SOCK_ESTABLISHED) /* 表示客户端已经连接成功服务器 */
{
if(connected == 0 && self == SERVER)
{
/* 获取对方的ip地址和端口号 */
getSn_DIPR(SN, clientIp);
clientPort = getSn_DPORT(SN);
printf("客户端连接建立成功:ip = %d.%d.%d.%d, port = %d\r\n",
clientIp[0],
clientIp[1],
clientIp[2],
clientIp[3],
clientPort);
connected = 1;
}
if(getSn_IR(SN) & Sn_IR_RECV) /* 收到tcp数据 */
{
setSn_IR(SN, Sn_IR_RECV); /* 写1清除 */
/* 收到的数据长度 */
*dataLen = getSn_RX_RSR(SN);
recv(SN, data, *dataLen); /* 接收数据 */
}
}
}
启动TCP客户端:
单片机作为客户端连接电脑端服务器。
c
/* 启动一个客户端 */
uint8_t serverIp[4] = {192, 168, 32, 228}; /* 服务端的ip地址: (电脑的ip地址) */
uint16_t serverPort = 9999;
void App_Tcp_ClientStart(void)
{
self = CLIENT;
uint8_t status = getSn_SR(SN);
if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
{
uint8_t r = socket(SN, Sn_MR_TCP, 8888, SF_TCP_NODELAY);
if(r == SN)
{
printf("socket 0打开成功\r\n");
}
else
{
printf("socket 0打开失败 %d\r\n", r);
}
connected = 0;
}
else if(status == SOCK_INIT) /* 表示socket的已经打开, 并处于tcp模式 */
{
/* 客户端需要主动区连接tcp服务器 */
int8_t r = connect(SN, serverIp, serverPort);
if(r == SOCK_OK)
{
printf("客户端连接服务器成功\r\n");
App_Tcp_SendData("hello, this stm32 tcp client!", 29);
}
else
{
printf("客户端连接服务器失败 %d\r\n", r);
}
connected = 0;
}
else if(status == SOCK_CLOSE_WAIT)
{
printf("失去与服务端的连接\r\n");
close(SN);
}
}
TCP协议发送数据:
c
void App_Tcp_SendData(uint8_t data[], uint16_t dataLen)
{
if(dataLen == 0) return;
uint8_t status = getSn_SR(SN);
if(status == SOCK_ESTABLISHED)
{
send(SN, data, dataLen);
}
}

五.udp通讯
使用UDP协议 :
面向无连接的通信协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。不需要接收方确认,属于不可靠传输,可能丢包实际使用要求程序员编程验证。
发送数据:
c
void App_UDP_SendData(uint8_t data[], uint16_t dataLen, uint8_t *sIp, uint16_t sPort)
{
uint8_t status = getSn_SR(SN);
if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
{
uint8_t r = socket(SN, Sn_MR_UDP, 8888, 0);
if(r == SN)
{
printf("socket 0打开成功\r\n");
}
else
{
printf("socket 0打开失败 %d\r\n", r);
}
}
else if(status == SOCK_UDP)
{
/* 发送数据 */
int32_t r = sendto(SN, data, dataLen, sIp, sPort);
printf("发送完毕.... %d\r\n", r);
}
}
接收数据:
c
void App_UDP_ReceiveData(uint8_t data[], uint16_t *dataLen, uint8_t *rIp, uint16_t *rPort)
{
memset(data, 0, strlen(data));
*dataLen = 0;
uint8_t status = getSn_SR(SN);
if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
{
uint8_t r = socket(SN, Sn_MR_UDP, 8888, 0);
if(r == SN)
{
printf("socket 0打开成功\r\n");
}
else
{
printf("socket 0打开失败 %d\r\n", r);
}
}
else if(status == SOCK_UDP)
{
/* 先判断是否收到数据 */
if(getSn_IR(SN) & Sn_IR_RECV)
{
setSn_IR(SN, Sn_IR_RECV);
/* 从寄存器读取到数据的长度, 比实际大8个字节 */
uint16_t tmp = getSn_RX_RSR(SN);
if(tmp > 0)
{
*dataLen = tmp - 8;
recvfrom(SN, data, *dataLen, rIp, rPort);
}
}
}
}

六.http_server
初始化:
c
void App_HttpSever_Init(void)
{
Driver_LED_Init();
/* 初始化一个httpserver */
httpServer_init(txBuff, rxBuff, 8, sockeList);
/* 注册一个网页: 首页 */
reg_httpServer_webContent((uint8_t *)"index.html", (uint8_t *)content);
}
启动初始化:
c
void App_HttpServer_Start(void)
{
/* 启动服务器 */
for(size_t i = 0; i < sizeof(sockeList); i++)
{
httpServer_run(i);
}
}
解析URL和控制小灯:
c
void App_HttpServer_DoAction(uint8_t action);
// 这个函数由http驱动层调用
void App_HttpServer_ParseUrl(uint8_t url[])
{
// /index.html?action=2
char *index = strstr((char *)url, "action");
if(index != NULL)
{
uint8_t action = (uint8_t)(*(index + 7));
App_HttpServer_DoAction(action);
}
}
void App_HttpServer_DoAction(uint8_t action)
{
if(action == '1')
{
Drviver_LED_On(LED_2);
}
else if(action == '2')
{
Drviver_LED_Off(LED_2);
}
else if(action == '3')
{
Drviver_LED_Toggle(LED_2);
}
}