STM32提高篇: 以太网通讯
STM32提高篇: 以太网通讯
- 一.以太网通讯介绍
- 二.W5500芯片介绍
- 1.W5500芯片特点
- 2.W5500应用目标
- 3.接入框图
- 三.驱动移植
- 四.tcp通讯
- 五.udp通讯
- 六.http_server
一.以太网通讯介绍
以太网(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.接入框图
三.驱动移植
注册函数:
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);
}
复位和设置初始地址:
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服务器:
单片机作为服务器,使用本地电脑连接,成功则打印串口数据。
#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协议接收数据:
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客户端:
单片机作为客户端连接电脑端服务器。
/* 启动一个客户端 */
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协议发送数据:
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数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。不需要接收方确认,属于不可靠传输,可能丢包实际使用要求程序员编程验证。
发送数据:
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);}
}
接收数据:
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
初始化:
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);
}
启动初始化:
void App_HttpServer_Start(void)
{/* 启动服务器 */for(size_t i = 0; i < sizeof(sockeList); i++){httpServer_run(i);}
}
解析URL和控制小灯:
void App_HttpServer_DoAction(uint8_t action);
// 这个函数由http驱动层调用
void App_HttpServer_ParseUrl(uint8_t url[])
{// /index.html?action=2char *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);}
}