一、W5500以太网模块
W5500 是一款由 WIZnet 公司生产的以太网控制芯片,它集成了一个全双工的 10/100Mbps 以太网 MAC 和 PHY,以及一个硬件的 TCP/IP 协议栈。W5500 芯片通常被用于嵌入式系统中,为微控制器提供网络连接的能力,使得设备能够通过以太网进行数据传输和通信。
W5500 提供了 SPI(外设串行接口)从而能够更加容易与外设 MCU 整合。而且, W5500 的使用了新的高效 SPI 协议支持 80MHz 速率,从而能够更好的实现高速网络通讯。为了减少系统能耗, W5500 提供了网络唤醒模式(WOL)及掉电模式供客户选择使用。
在开始之前,我们先了解下网络地址信息。
网络地址信息是计算机网络中用于标识和定位设备、主机、网络等资源的数字标识符,在网络通信中起着至关重要的作用,它能帮助数据包在不同的网络节点间传递并到达目标设备。网络地址通常由 IP地址、子网掩码、网关地址 及 DNS地址 等组成,在不同层级上起着不同的作用。
- 地址: IP 地址是计算机网络中用于唯一标识设备的地址。每台连接到网络上的设备都有一个IP地址,用于在网络上进行通信。通常以四个点分十进制数表示,每个数值的范围为 0 - 255,例如:192.168.1.1。
- 子网掩码: 子网掩码用于将一个 IP 地址划分为网络部分和主机部分。子网掩码的 1 对应的 IP 地址部分为 网络地址,0 对应的 IP 地址为 主机地址,例如一个设备的 IP 地址为 192.168.1.100,子网掩码为 255.255.255.0,使用 IP 地址和子网掩码做与运算(AND 运算)得出 192.168.1.0,则说明该设备属于192.168.1.0 网段设备,如果它想与 192.168.2.0网段的设备进行通讯,则需要将数据交给网关进行处理。
- 网关地址: 网关地址是当设备需要访问不同网络时,数据包首先经过的设备的 IP 地址。这个设备通常是网络边缘的路由器或防火墙,帮助本地设备与外部网络(如互联网)通信。
- DNS 地址: DNS 地址是域名系统的 IP 地址,DNS 是用于将易于记忆的域名转换为计算机能够理解的 IP 地址。例如,当您在浏览器中键入 wiznet.io 时,DNS 服务器将把 wiznet.io 这个域名解析为相应的 IP 地址(可能是 183.111.138.249或其它)。一般我们的 DNS 地址设置为公共 DNS 服务器地址(例如中国电信提供的114.114.114.114),也可以设置为私有 DNS 服务器地址(例如在您自己的网络中运行的服务器)。
- MAC 地址: MAC 地址是网络接口卡在数据链路层(OSI 模型第二层)使用的唯一标识符。每个网络设备都应该被分配有一个唯一的 MAC 地址,用于在局域网中表示设备。它的长度为 6 字节,通常以 16 进制格式表示。前三个字节标识了设备制造商,例如 WIZnet 的设备的 MAC 地址前三个字节为 00 08 DC,后三个字节表示该厂商的不同设备。需要注意的是,MAC 地址的首字节必须为偶数,奇数则为组播地址。
当 IP 地址冲突和 MAC 地址冲突时,都会导致网络无法通信。
二、W5500的移植
W5500 的官方参考代码地址如下:https://www.w5500.com/code.html,它的官方库链接如下:https://github.com/Wiznet/ioLibrary_Driver。
我们将下载的官方库解压,进入到 【Ethernet】子文件,将其中的 socket.h
、socket.c
、wizchip_conf.h
和 wizchip_conf.c
文件拷贝到自己的工程中,并将 【Ethernet】路径添加到工程的搜索路径中。我们还需要将其中的【W5500】子文件夹中的 w5500.c
和 w5500.h
文件也拷贝到自己的工程中。
首先,我们需要在 wizchip_conf.h
头文件中确认芯片是否是 W5500,如果不是,请选择 W5500。
然后,我们还需要确定数据长度模式是固定长度模式还是可变长度模式。
在 wizchip_conf.c
文件中,官方提供了一些接口,需要用户自己补充。
void wizchip_cris_enter(void) {} // 进入临界区
void wizchip_cris_exit(void) {} // 退出临界区
void wizchip_cs_select(void) {} // SPI片选使能
void wizchip_cs_deselect(void) {} // SPI片选使能
iodata_t wizchip_bus_readdata(uint32_t AddrSel) { return * ((volatile iodata_t *)((ptrdiff_t) AddrSel)); } // SPI总线读函数
void wizchip_bus_writedata(uint32_t AddrSel, iodata_t wb) { *((volatile iodata_t*)((ptrdiff_t)AddrSel)) = wb; } // SPI总线写函数
uint8_t wizchip_spi_readbyte(void) { return 0; } // SPI读一个字节
void wizchip_spi_writebyte(uint8_t wb) {} // SPI写一个字节
void wizchip_spi_readburst(uint8_t* pBuf, uint16_t len) {} // SPI读多个字节
void wizchip_spi_writeburst(uint8_t* pBuf, uint16_t len) {} // SPI写多个字节
实现 SPI 片选使能函数:
/*** @brief Default function to select chip.* @note This function help not to access wrong address. If you do not describe this function or register any functions,* null function is called.*/
//void wizchip_cs_select(void) {};
void wizchip_cs_select(void)
{W5500_CS(0);
}
实现 SPI 片选失能函数:
/*** @brief Default function to deselect chip.* @note This function help not to access wrong address. If you do not describe this function or register any functions,* null function is called.*/
//void wizchip_cs_deselect(void) {};
void wizchip_cs_deselect(void)
{W5500_CS(1);
}
实现 SPI 读一个字节函数:
/*** @brief Default function to read in SPI interface.* @note This function help not to access wrong address. If you do not describe this function or register any functions,* null function is called.*/
//uint8_t wizchip_spi_readbyte(void) {return 0;};
uint8_t wizchip_spi_readbyte(void)
{uint8_t value = 0;if (HAL_SPI_Receive(&g_w5500_spi_handle, &value, 1, 1000) != HAL_OK) {value = 0;}return value;
}
实现 SPI 写一个字节函数:
/*** @brief Default function to write in SPI interface.* @note This function help not to access wrong address. If you do not describe this function or register any functions,* null function is called.*/
//void wizchip_spi_writebyte(uint8_t wb) {};
void wizchip_spi_writebyte(uint8_t wb)
{HAL_SPI_Transmit(&g_w5500_spi_handle, &wb, 1, 1000);
}
实现 SPI 读多个字节函数:
/*** @brief Default function to burst read in SPI interface.* @note This function help not to access wrong address. If you do not describe this function or register any functions,* null function is called.*/
//void wizchip_spi_readburst(uint8_t* pBuf, uint16_t len) {};
void wizchip_spi_readburst(uint8_t* pBuf, uint16_t len)
{HAL_SPI_Receive(&g_w5500_spi_handle, pBuf, len, 1000);
}
实现 SPI 写多个字节函数:
/*** @brief Default function to burst write in SPI interface.* @note This function help not to access wrong address. If you do not describe this function or register any functions,* null function is called.*/
//void wizchip_spi_writeburst(uint8_t* pBuf, uint16_t len) {};
void wizchip_spi_writeburst(uint8_t* pBuf, uint16_t len)
{HAL_SPI_Transmit(&g_w5500_spi_handle, pBuf, len, 1000);
}
在实现上述函数之后,我们还需要手动调用如下注册函数注册。
void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void)); // 注册进入/退出临界区函数
void reg_wizchip_cs_cbfunc(void(*cs_sel)(void), void(*cs_desel)(void)); // 注册SPI片选使能/失能函数
void reg_wizchip_spi_cbfunc(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb)); // 注册SPI读/写一个字节函数
void reg_wizchip_bus_cbfunc(iodata_t(*bus_rb)(uint32_t addr), void (*bus_wb)(uint32_t addr, iodata_t wb)); // 注册SPI读/写函数
/*** @brief 注册W5500函数* */
void register_wizchip_function(void)
{// reg_wizchip_cris_cbfunc(wizchip_cris_enter, wizchip_cris_exit); // 注册退出和进入临界区,使用操作系统时用reg_wizchip_cs_cbfunc(wizchip_cs_select, wizchip_cs_deselect); // 注册SPI片选使能和失能reg_wizchip_spi_cbfunc(wizchip_spi_readbyte, wizchip_spi_writebyte); // 注册SPI读写一个字节// reg_wizchip_spi_cbfunc(wizchip_bus_readdata, wizchip_bus_writedata); // 注册SPI读写多个字节
}
三、设备初始化
#define W5500_CS_GPIO_PORT GPIOD
#define W5500_CS_GPIO_PIN GPIO_PIN_8
#define RCC_W5500_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()#define W5500_RESET_GPIO_PORT GPIOD
#define W5500_RESET_GPIO_PIN GPIO_PIN_9
#define RCC_W5500_RESET_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()#define W5500_INTERRUPT_GPIO_PORT GPIOD
#define W5500_INTERRUPT_GPIO_PIN GPIO_PIN_10
#define RCC_W5500_INTERRUPT_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()#define W5500_CS(x) do{ x ? \HAL_GPIO_WritePin(W5500_CS_GPIO_PORT, W5500_CS_GPIO_PIN, GPIO_PIN_SET):\HAL_GPIO_WritePin(W5500_CS_GPIO_PORT, W5500_CS_GPIO_PIN, GPIO_PIN_RESET);\}while(0)#define W5500_RESET(x) do{ x ? \HAL_GPIO_WritePin(W5500_RESET_GPIO_PORT, W5500_RESET_GPIO_PIN, GPIO_PIN_SET):\HAL_GPIO_WritePin(W5500_RESET_GPIO_PORT, W5500_RESET_GPIO_PIN, GPIO_PIN_RESET);\}while(0)
W5500 初始化函数:
SPI_HandleTypeDef g_w5500_spi_handle;/*** @brief W5500初始化函数* * @param hspi SPI句柄*/
void W5500_Init(SPI_HandleTypeDef *hspi)
{g_w5500_spi_handle = *hspi;GPIO_InitTypeDef GPIO_InitStruct = {0};RCC_W5500_CS_GPIO_CLK_ENABLE();RCC_W5500_RESET_GPIO_CLK_ENABLE();RCC_W5500_INTERRUPT_GPIO_CLK_ENABLE();// 推挽输出GPIO_InitStruct.Pin = W5500_CS_GPIO_PIN;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(W5500_CS_GPIO_PORT, &GPIO_InitStruct);// 推挽输出GPIO_InitStruct.Pin = W5500_RESET_GPIO_PIN;HAL_GPIO_Init(W5500_RESET_GPIO_PORT, &GPIO_InitStruct);// 上拉输入GPIO_InitStruct.Pin = W5500_INTERRUPT_GPIO_PIN;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(W5500_INTERRUPT_GPIO_PORT, &GPIO_InitStruct);W5500_Reset(); // 重启芯片register_wizchip_function(); // 调用注册函数
}
W5500 复位函数:
/*** @brief W5500复位* */
void W5500_Reset(void)
{W5500_RESET(0);// 拉低复位引脚最低500ms可以使芯片重启HAL_Delay(500);W5500_RESET(1);HAL_Delay(500);
}
默认网络信息:
wiz_NetInfo g_w5500_net_info =
{.mac = {0x00, 0x08, 0xdc, 0x12, 0x22, 0x12}, // MAC地址,一共6个字节,别与别人的冲突.ip = {192, 168, 3, 210}, // IP地址,一共4个字节,前三段与电脑一致,处于同一个网段中,最后一段注意不要与别人冲突.gw = {192, 168, 1, 1}, // 网关.sn = {255, 255, 255, 0}, // 子网掩码.dns = {144, 144, 144, 144}, // DNS服务器.dhcp = NETINFO_STATIC // 是否使用DHCP
};
我们可以在电脑的终端中输入 ipconfig
命令查看电脑连接的路由器所在的局域网的 IP 和网关。
W5500 设置 MAC 地址函数:
/*** @brief W5500设置MAC地址* */
void W5500_SetMac(void)
{setSHAR(g_w5500_net_info.mac); // 设置MAC地址printf("设置MAC地址成功\r\n");
}
W5500 设置 IP 地址函数:
/*** @brief W5500设置IP地址* */
void W5500_SetIp(void)
{setSIPR(g_w5500_net_info.ip); // 设置IP地址setSUBR(g_w5500_net_info.sn); // 设置子网掩码setGAR(g_w5500_net_info.gw); // 设置网关printf("设置IP地址成功\r\n");
}
获取网络信息函数:
/*** @brief 打印网络信息函数* */
void PrintInfo(void)
{uint8_t info[6] = {0};wiz_NetInfo netInfo;ctlnetwork(CN_GET_NETINFO, (void*)&netInfo); // 获取网络信息ctlwizchip(CW_GET_ID,(void*)info); // 显示网络信息if(netInfo.dhcp == NETINFO_DHCP) {printf("\r\n=== %s NET CONF : DHCP ===\r\n",(char*)info);}else {printf("\r\n=== %s NET CONF : Static ===\r\n",(char*)info);}printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n", netInfo.mac[0], netInfo.mac[1], netInfo.mac[2], netInfo.mac[3], netInfo.mac[4], netInfo.mac[5]);printf("SIP: %d.%d.%d.%d\r\n", netInfo.ip[0], netInfo.ip[1], netInfo.ip[2], netInfo.ip[3]);printf("GAR: %d.%d.%d.%d\r\n", netInfo.gw[0], netInfo.gw[1], netInfo.gw[2], netInfo.gw[3]);printf("SUB: %d.%d.%d.%d\r\n", netInfo.sn[0], netInfo.sn[1], netInfo.sn[2], netInfo.sn[3]);printf("DNS: %d.%d.%d.%d\r\n", netInfo.dns[0], netInfo.dns[1], netInfo.dns[2], netInfo.dns[3]);printf("===========================\r\n");
}
main() 函数:
int main(void)
{HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200, g_usart1_rx_buffer);SPI_Init(&g_spi1_handle, SPI1, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, SPI_BAUDRATEPRESCALER_4, SPI_FIRSTBIT_MSB);W5500_Init(&g_spi1_handle);W5500_SetMac(); // 设置MAC地址W5500_SetIp(); // 设置IP地址PrintInfo(); // 打印网络信息while (1){}return 0;
}
将程序下载进开发板后,就可以在终端中使用 ping 命令,查看是否能 ping 通。
推荐 SPI 使用工作模式 0,时钟分频因子为 4 分频。
四、DHCP动态分配IP
上面,我们使用的是静态设置 IP,如果这个地址已经被占用了,强制配置芯片为这个地址就会失败,无法配置成功。DHCP 就可解决这个问题,给客户端分配一个可用的 IP。DHCP(Dynamic Host Configuration Protocol),动态主机配置协议,是一个应用层协议。当我们将客户主机 IP 地址设置为动态获取方式时,DHCP 服务器就会根据 DHCP 协议给客户端分配 IP,使得客户机能够利用这个 IP 上网。
从图示中我们可以直观明了地看出 DHCP 地工作原理,一般为四个阶段:
- 发现阶段:客户端接入网络时以广播形式发送
DHCP Discover
报文 (目的 IP 是 255.255.255.255,源 IP 是0.0.0.0)寻找 DHCP 服务器,报文中含客户端 MAC 地址。若服务器和客户端不在同一子网,会通过中继代理(如路由器)转发。 - 提供阶段:DHCP 服务器收到
Discover
报文后, 从 IP 地址池选一个未分配的 IP 地址,将其和子网掩码、默认网关、 DNS 服务器地址等信息封装进DHCP Offer
报文,以广播或单播方式发给客户端,可能会有多个 Offer 报文。 - 请求阶段:客户端收到多个
Offer
后选择一个,以广播形式发送DHCP Request
报文请求分配该 IP 地址等配置信息,且发送 ARP 请求检查 IP 地址唯一性。 - 确认阶段:服务器收到
Request
报文后,检查 IP 地址是否可用。若可用, 将以广播或单播的形式发送DHCP Ack
报文,客户端收到后完成网络配置正常上网。若不可用,发送DHCP Nak
报文,客户端收到后重新发起Discover
流程。
如果我们想要使用 DHCP 功能,需要将 W5500 的官方库下的【Internet】文件夹下的【DHCP】文件夹下文件拷贝到自己工程中。
官方库中提供的 DHCP 运行程序代码如下:
uint8_t DHCP_run(void)
{uint8_t type;uint8_t ret;if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;if(getSn_SR(DHCP_SOCKET) != SOCK_UDP)socket(DHCP_SOCKET, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);ret = DHCP_RUNNING;type = parseDHCPMSG();switch ( dhcp_state ) {case STATE_DHCP_INIT :DHCP_allocated_ip[0] = 0;DHCP_allocated_ip[1] = 0;DHCP_allocated_ip[2] = 0;DHCP_allocated_ip[3] = 0;send_DHCP_DISCOVER();dhcp_state = STATE_DHCP_DISCOVER;break;case STATE_DHCP_DISCOVER :if (type == DHCP_OFFER){
#ifdef _DHCP_DEBUG_printf("> Receive DHCP_OFFER\r\n");
#endifDHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];send_DHCP_REQUEST();dhcp_state = STATE_DHCP_REQUEST;} else ret = check_DHCP_timeout();break;case STATE_DHCP_REQUEST :if (type == DHCP_ACK) {#ifdef _DHCP_DEBUG_printf("> Receive DHCP_ACK\r\n");
#endifif (check_DHCP_leasedIP()) {// Network info assignment from DHCPdhcp_ip_assign();reset_DHCP_timeout();dhcp_state = STATE_DHCP_LEASED;} else {// IP address conflict occurredreset_DHCP_timeout();dhcp_ip_conflict();dhcp_state = STATE_DHCP_INIT;}} else if (type == DHCP_NAK) {#ifdef _DHCP_DEBUG_printf("> Receive DHCP_NACK\r\n");
#endifreset_DHCP_timeout();dhcp_state = STATE_DHCP_DISCOVER;} else ret = check_DHCP_timeout();break;case STATE_DHCP_LEASED :ret = DHCP_IP_LEASED;if ((dhcp_lease_time != INFINITE_LEASETIME) && ((dhcp_lease_time/2) < dhcp_tick_1s)) {#ifdef _DHCP_DEBUG_printf("> Maintains the IP address \r\n");
#endiftype = 0;OLD_allocated_ip[0] = DHCP_allocated_ip[0];OLD_allocated_ip[1] = DHCP_allocated_ip[1];OLD_allocated_ip[2] = DHCP_allocated_ip[2];OLD_allocated_ip[3] = DHCP_allocated_ip[3];DHCP_XID++;send_DHCP_REQUEST();reset_DHCP_timeout();dhcp_state = STATE_DHCP_REREQUEST;}break;case STATE_DHCP_REREQUEST :ret = DHCP_IP_LEASED;if (type == DHCP_ACK) {dhcp_retry_count = 0;if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] || OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||OLD_allocated_ip[3] != DHCP_allocated_ip[3]) {ret = DHCP_IP_CHANGED;dhcp_ip_update();#ifdef _DHCP_DEBUG_printf(">IP changed.\r\n");#endif}#ifdef _DHCP_DEBUG_else printf(">IP is continued.\r\n");#endif reset_DHCP_timeout();dhcp_state = STATE_DHCP_LEASED;} else if (type == DHCP_NAK) {#ifdef _DHCP_DEBUG_printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
#endifreset_DHCP_timeout();dhcp_state = STATE_DHCP_DISCOVER;} else ret = check_DHCP_timeout();break;default :break;}return ret;
}
如果我们想要使用 DHCP 功能,需要我们将 DHCP 的时基函数 DHCP_time_handler()
放入到 1s 的定时器中断。
定时器定时功能初始化函数:
TIM_HandleTypeDef g_timer6_handle;/*** @brief 定时器定时功能初始化函数* * @param htim 定时器句柄* @param TIMx 定时器寄存器基地址,可选值: TIMx, x可选范围: 1 ~ 14* @param prescaler 预分频系数,可选值: 0 ~ 65535* @param period 自动重装载值,可选值: 0 ~ 65535* * @note 默认为向上计数模式*/
void Timer_Base_Init(TIM_HandleTypeDef *htim, TIM_TypeDef *TIMx, uint16_t prescaler, uint16_t period)
{htim->Instance = TIMx; // 定时器寄存器基地址htim->Init.CounterMode = TIM_COUNTERMODE_UP; // 计数模式htim->Init.Prescaler = prescaler; // 预分频系数htim->Init.Period = period; // 自动重装载值HAL_TIM_Base_Init(htim);
}
基本定时器底层初始化函数:
/*** @brief 基本定时器底层初始化函数* * @param htim 定时器句柄*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM6){__HAL_RCC_TIM6_CLK_ENABLE(); // 使能定时器6的时钟HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn); // 使能定时器6中断HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 4, 0); // 设置中断优先级}
}
定时器 6 中断服务函数:
/*** @brief 定时器6中断服务函数* */
void TIM6_DAC_IRQHandler(void)
{HAL_TIM_IRQHandler(&g_timer6_handle); // 调用HAL库公共处理函数
}
定时器更新中断回调函数:
/*** @brief 定时器更新中断回调函数* * @param htim 定时器句柄*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM6){DHCP_time_handler();}
}
注册 DHCP 定时器中断主要为了 DHCP 超时处理。 在 dhcp.h 文件中,定义了 DHCP 超时时间、重试次数、端口号和主机名等内容。
然后,我们调用 DHCP_init()
函数初始化 DHCP,接着我们调用 reg_dhcp_cbfunc()
函数注册 DHCP 分配 IP 成功时,IP 更新时和 IP 冲突时的回调函数,如果参数为 NULL,则表示使用 W5500 官方提供的默认的函数注册。注册完后,我们使用 DHCP_run()
函数获取 DHCP 分配的结果,如果成功,DHCP_run()
返回将返回 DHCP_IP_LEASED
。最后调用 ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO)()
设置网络信息。
#define DATA_BUFFER_SIZE 2048
#define DHCP_MAX_RETRY_COUNT 30
uint8_t g_w5500_data_buffer[DATA_BUFFER_SIZE]; // 数据缓冲区/*** @brief DHCP分配IP地址* * @param socket_index 端口号*/
void DHCP_TryToGetIp(uint8_t socket_index)
{static uint8_t retry_count = 0;uint8_t state = 0;memset(g_w5500_data_buff, 0, DATA_BUFFER_SIZE);g_w5500_net_info.dhcp = NETINFO_DHCP; // 设置为DHCP模式setSHAR(g_w5500_net_info.mac); // 在DPCH开始之前需要手动设置MAC地址// 注册分配IP时,IP更新时,IP冲突时的回调函数,如果参数为NULL,则使用默认的注册函数reg_dhcp_cbfunc(NULL, NULL, NULL); DHCP_init(socket_index, g_w5500_data_buff); // 初始化DHCPprintf("DHCP开始分配IP\r\n");// 返回DHCP_IP_LEASED说明申请租赁IP地址成功,如果没成功,重新申请state = DHCP_run();while(state != DHCP_IP_LEASED && retry_count < DHCP_MAX_RETRY_COUNT){retry_count++;printf("DHCP第%d次分配IP失败\r\n", retry_count);state = DHCP_run();Delay_ms(1000);}if (state != DHCP_IP_LEASED && retry_count == DHCP_MAX_RETRY_COUNT){retry_count = 0;printf("DHCP分配IP失败,采用静态分配IP的方式\r\n");g_w5500_net_info.dhcp = NETINFO_STATIC;}getIPfromDHCP(g_w5500_net_info.ip);getGWfromDHCP(g_w5500_net_info.gw);getSNfromDHCP(g_w5500_net_info.sn);getDNSfromDHCP(g_w5500_net_info.dns);ctlnetwork(CN_SET_NETINFO, (void*)&g_w5500_net_info); // 设置网络信息close(socket_index); // 关闭端口
}
main() 函数:
int main(void)
{HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200, g_usart1_rx_buffer);SPI_Init(&g_spi1_handle, SPI1, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, SPI_BAUDRATEPRESCALER_4, SPI_FIRSTBIT_MSB);Timer_Base_Init(&g_timer6_handle, TIM6, 8399, 9999);W5500_Init(&g_spi1_handle);wizchip_init(NULL, NULL); // 缓冲区默认__HAL_TIM_CLEAR_IT(&g_timer6_handle, TIM_IT_UPDATE); HAL_TIM_Base_Start_IT(&g_timer6_handle);DHCP_TryToGetIp(0);PrintInfo(); // 打印网络信息while (1){}return 0;
}
使用 DHCP 动态获取 IP 时,必需将网络结构体配置中 dhcp 的值改为 NETINFO_DHCP,这样才能运行 DHCP 模式。
四、TCP通信
TCP (Transmission Control Protocol) 是互联网协议族中面向连接的、可靠的、基于字节流的传输层通信协议。在TCP/IP模型中,它位于传输层,为应用层提供服务。TCP 的主要功能包括:
- 连接建立:TCP 使用三次握手来建立连接。
- 第一次握手:客户端发送一个带有 SYN 标志的 TCP 段给服务器;
- 第二次握手:服务器回应一个带有 ACK 和 SYN 标志的 TCP 段;
- 第三次握手:客户端发送一个带有 ACK 标志的 TCP 段确认服务器的 SYN。
- 数据传输:一旦连接建立,客户端和服务器就可以通过 TCP 进行双向数据传输。TCP 将数据分割成多个数据包,并确保它们按正确的顺序到达目的地。
- 流量控制:TCP 使用滑动窗口机制来控制发送方的数据发送速率,防止接收方被过多的数据淹没。
- 拥塞控制:当网络拥塞时,TCP 会减少数据包的发送速率,以避免进一步加重网络负担。
- 错误检测与重传:每个 TCP 段都包含一个校验和,用于检测传输中的数据错误。如果数据包丢失或损坏,TCP 会要求重新发送。
- 连接终止:TCP 使用四次挥手来终止连接。
- 首先,一方发送一个带有 FIN 标志的 TCP 段,另一方收到后发送 ACK 确认,然后发送自己的 FIN 段,最后收到的 FIN 段的一方再发送 ACK 确认。
在 TCP 通信中,客户端通常发起连接请求,服务器端则监听特定端口等待客户端的连接。一旦连接建立,数据就可以在两端之间可靠地传输。TCP 适用于需要高可靠性和顺序保证的应用场景,如网页浏览、文件传输、电子邮件等。
4.1、TCP服务器的搭建
在 TCP 通讯时,客户端必须主动联系服务器,这样才能实现通讯。服务器与客户端之间的连接式一种长连接,一旦连上就不会断开的。在 STM32 上启动一个 TCP 服务端,在电脑上用 TCP 客户端去连接服务端,客户端给服务端发送数据后,服务端再原封不动的返回给客户端。
W5500 可以同时使用 8 个 SOCKET 做 TCP 服务器使用,且同时监听同一个端口;当 W5500 初始化完成后,程序进入主循环,此时可以用 for 循环读取多个 Socket 的状态值,并选择进入哪种模式。当 Socket 处于关闭状态时,在进行通信之前,我们先将 Socket 初始化。Socket 作为服务器端,端口号固定为要监听的端口。 当 socket 处于初始化完成状态即 SOCK_INIT
状态,此时,作为 TCP 服务器就要执行 listen()
函数来侦听端口。
由于 W5500 内嵌了 TCP/IP 协议,连接过程是不需要单片机干预的。如果连接过程中出错造成超时,该 Socket 将会被关闭, 重新进入 SOCK_CLOSE
状态。待 TCP 连接3次握手完成后,socket 的状态将会转变为连接建立状态,即代码中定义的 SOCK_ESTABLISHED
状态。在进入 SOCK_ESTABLISHED
状态后,便可进行数据收发。数据通信完毕之后即执行 disconnect()
函数。在收到对方 FIN
数据包之前,该 Socket 将进入 SOCK_CLOSE_WAIT
状态。
/*** @brief TCP服务器* * @param socket_index socket索引* @param monitor_port 监听的端口*/
void W5500_TCP_Server(uint8_t socket_index, uint16_t monitor_port)
{// 获取Socket状态,参数:要获取的socket索引,0 ~ 7switch (getSn_SR(socket_index)){case SOCK_CLOSED: // 表示Socket已经关闭了// 打开socket,// 第一个参数是要创建的socket索引,第二个参数是使用的协议,第三个参数是端口,第四个参数是使用SF_TCP_NODELAY表示无延迟响应// 如果成功,返回socket索引if (socket(socket_index, Sn_MR_TCP, monitor_port, SF_TCP_NODELAY) == socket_index){printf("socket %d 打开成功\r\n", socket_index);}else{printf("socket %d 打开失败\r\n", socket_index);}break;case SOCK_INIT: // 表示Socket已经打开了// 监听,参数是socket索引switch (listen(socket_index)){case SOCK_OK:printf("socket %d 监听成功\r\n", socket_index);break;case SOCKERR_SOCKINIT:printf("还未初始化 socket %d\r\n", socket_index);break;case SOCKERR_SOCKCLOSED:printf("socket %d 意外关闭\r\n", socket_index);break;default:printf("socket %d 监听失败\r\n", socket_index);break;}break;case SOCK_ESTABLISHED: // 表示Socket连接建立成功uint8_t cliendIP[4] = {0};uint16_t cliendPort = 0;uint16_t length = 0;// 获取目标的IP地址和端口号getSn_DIPR(socket_index, cliendIP);cliendPort = getSn_DPORT(socket_index);printf("客户端(%d.%d.%d.%d: %d)建立连接\r\n", cliendIP[0], cliendIP[1], cliendIP[2], cliendIP[3], cliendPort);// 在这里实现正常通信while (1){// 等待客户端发送信息while ((getSn_IR(socket_index) & Sn_IR_RECV) == 0) // 通过中断寄存器的REVE位来判断{// 在客户端发送信息时,客户端有可能断开,连接状态发生变化if (getSn_SR(socket_index) != SOCK_ESTABLISHED){printf("socket %d 连接发生变化\r\n", socket_index);close(socket_index); // 关闭Socketreturn;}}setSn_IR(socket_index, Sn_IR_RECV); // 接收到数据了,清除中断标志位,写1清除,写0无效length = getSn_RX_RSR(socket_index); // 获取接收到数据长度if (length > 0){memset(g_w5500_data_buff, 0, length + 1);recv(socket_index, g_w5500_data_buff, length); // 接收数据printf("客户端(%d.%d.%d.%d: %d)发来数据:%s\r\n", cliendIP[0], cliendIP[1], cliendIP[2], cliendIP[3], cliendPort, g_w5500_data_buff);send(socket_index, g_w5500_data_buff, length); // 发送回客户端数据}}break;case SOCK_CLOSE_WAIT:// 如果处于半不关闭状态,直接关闭socketprintf("服务端(%d.%d.%d.%d)异常关闭\r\n", g_w5500_net_info.ip[0], g_w5500_net_info.ip[1], g_w5500_net_info.ip[2], g_w5500_net_info.ip[3]);close(socket_index);default:break;}
}
int main(void)
{HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200, g_usart1_rx_buffer);SPI_Init(&g_spi1_handle, SPI1, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, SPI_BAUDRATEPRESCALER_4, SPI_FIRSTBIT_MSB);Timer_Base_Init(&g_timer6_handle, TIM6, 8399, 9999);W5500_Init(&g_spi1_handle);wizchip_init(NULL, NULL); // 缓冲区默认__HAL_TIM_CLEAR_IT(&g_timer6_handle, TIM_IT_UPDATE); HAL_TIM_Base_Start_IT(&g_timer6_handle);DHCP_TryToGetIp(0);PrintInfo(); // 打印网络信息while (1){W5500_TCP_Server(0, 8080);}return 0;
}
4.2、TCP客户端的搭建
当 W5500 初始化完成后,程序进入主循环,可以调用 getSn_SR()
来读取该 Socket 的状态值。这里通信协议这里我们将配置成 TCP,即 Sn_MR_TCP
。当程序成功执行 socket()
函数后,socket 将处于 SOCK_INIT
状态。此时,作为 TCP 客户端,就要调用 connect()
函数连接远程服务器。待 TCP 连接 3 次握手完成后,socket 的状态将会转变为 SOCK_ESTABLISHED
状态。在进入 SOCK_ESTABLISHED
状态后,便可进行数据收发。
/*** @brief TCP客户端* * @param socket_index socket索引 * @param port 自身的端口* @param server_ip 服务器ip地址* @param server_port 服务器的端口*/
void W5500_TCP_Client(uint8_t socket_index, uint16_t port, uint8_t *server_ip, uint16_t server_port)
{// 获取Socket状态,参数:要获取的socket索引,0 ~ 7switch (getSn_SR(socket_index)){case SOCK_CLOSED: // 表示Socket已经关闭了// 打开socket,// 第一个参数是要创建的socket索引,第二个参数是使用的协议,第三个参数是端口,第四个参数是使用SF_TCP_NODELAY表示无延迟响应// 如果成功,返回socket索引if (socket(socket_index, Sn_MR_TCP, port, SF_TCP_NODELAY) == socket_index){printf("socket %d 打开成功\r\n", socket_index);}else{printf("socket %d 打开失败\r\n", socket_index);}break;case SOCK_INIT: // 表示Socket已经打开了if (connect(socket_index, server_ip, server_port) == SOCK_OK) // 作为客户端主动连接服务器{printf("socket %d 连接服务器成功\r\n", socket_index);}else{close(socket_index); // 关闭socketprintf("socket %d 连接服务器失败\r\n", socket_index);}break;case SOCK_ESTABLISHED: // 表示Socket连接建立成功uint16_t length = 0;printf("连接服务端(%d.%d.%d.%d: %d)成功\r\n", server_ip[0], server_ip[1], server_ip[2], server_ip[3], server_port);// 客户端往服务端发送数据send(socket_index, (uint8_t *)"Hello World!", 12);// 在这里实现正常通信while (1){// 等待服务端返回信息while ((getSn_IR(socket_index) & Sn_IR_RECV) == 0) // 通过中断寄存器的REVE位来判断{// 在服务端返回信息时,服务端有可能断开,连接状态发生变化if (getSn_SR(socket_index) != SOCK_ESTABLISHED){printf("socket %d 连接发生变化\r\n", socket_index);close(socket_index); // 关闭Socketreturn;}}setSn_IR(socket_index, Sn_IR_RECV); // 接收到数据了,清除中断标志位,写1清除,写0无效length = getSn_RX_RSR(socket_index); // 获取接收到数据长度if (length > 0){memset(g_w5500_data_buff, 0, length + 1);recv(socket_index, g_w5500_data_buff, length); // 接收数据printf("服务端(%d.%d.%d.%d: %d)返回数据:%s\r\n", server_ip[0], server_ip[1], server_ip[2], server_ip[3], server_port, g_w5500_data_buff);send(socket_index, g_w5500_data_buff, length); // 发送回服务端数据}}break;case SOCK_CLOSE_WAIT:// 如果处于半不关闭状态,直接关闭socketprintf("客户端(%d.%d.%d.%d: %d)异常关闭\r\n", g_w5500_net_info.ip[0], g_w5500_net_info.ip[1], g_w5500_net_info.ip[2], g_w5500_net_info.ip[3], port);close(socket_index);default:break;}
}
int main(void)
{uint8_t seriveIP[4] = {192, 168, 3, 195};HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200, g_usart1_rx_buffer);SPI_Init(&g_spi1_handle, SPI1, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, SPI_BAUDRATEPRESCALER_4, SPI_FIRSTBIT_MSB);Timer_Base_Init(&g_timer6_handle, TIM6, 8399, 9999);W5500_Init(&g_spi1_handle);wizchip_init(NULL, NULL); // 缓冲区默认__HAL_TIM_CLEAR_IT(&g_timer6_handle, TIM_IT_UPDATE); HAL_TIM_Base_Start_IT(&g_timer6_handle);DHCP_TryToGetIp(0);PrintInfo(); // 打印网络信息while (1){W5500_TCP_Client(0, 8000, seriveIP, 8080);}return 0;
}
五、UDP通信
UDP(User Datagram Protocol)是一种无连接的、不可靠的传输层协议,它提供了轻量级的数据报服务。与 TCP 不同, UDP 不提供数据传输的可靠性保证,这意味着数据可能丢失、重复或乱序到达,也没有拥塞控制机制。然而,由于其简单性,UDP 提供了比 TCP 更低的延迟和更高的效率,这在对实时性要求较高的应用中非常有用,例如视频会议、在线游戏和实时音频传输。
/*** @brief UDP通信* * @param socket_index socket的索引* @param port 端口号*/
void W5500_UDP(uint8_t socket_index, uint16_t port)
{// 获取Socket状态,参数:要获取的socket索引,0 ~ 7switch (getSn_SR(socket_index)){case SOCK_CLOSED: // 表示Socket已经关闭了// 打开socket,// 第一个参数是要创建的socket索引,第二个参数是使用的协议,第三个参数是端口,第四个参数是使用SF_TCP_NODELAY表示无延迟响应// 如果成功,返回socket索引if (socket(socket_index, Sn_MR_UDP, port, 0) == socket_index){printf("socket %d 打开成功\r\n", socket_index);}else{printf("socket %d 打开失败\r\n", socket_index);}break;case SOCK_UDP: // 表示Socket已经打开了uint8_t length = 0;uint8_t targetIP[4];uint16_t targetPort = 0;while (1){while ((getSn_IR(socket_index) & Sn_IR_RECV) == 0) // 等待接收数据{if (getSn_SR(socket_index) != SOCK_UDP) // 如果socket状态不是SOCK_UDP,则跳出循环{printf("socket %d 意外关闭\r\n", socket_index);close(socket_index);}}setSn_IR(socket_index, Sn_IR_RECV); // 清除接收标志// 对于UDP而言,接收的数据长度比实际接收的数据要大,因为UDP协议在数据前面加了包头,包头长度为8字节,所以要减去8字节length = getSn_RX_RSR(socket_index); // 获取接收数据的长度if (length > 8){memset(g_w5500_data_buff, 0, length + 1);recvfrom(socket_index, g_w5500_data_buff, length - 8, targetIP, &targetPort); // 接收数据printf("%d.%d.%d.%d:%d 发来数据:%s\r\n", targetIP[0], targetIP[1], targetIP[2], targetIP[3], targetPort, g_w5500_data_buff);sendto(socket_index, g_w5500_data_buff, length - 8, targetIP, targetPort); // 发送回对方}}default:break;}
}
int main(void)
{HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200, g_usart1_rx_buffer);SPI_Init(&g_spi1_handle, SPI1, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, SPI_BAUDRATEPRESCALER_4, SPI_FIRSTBIT_MSB);Timer_Base_Init(&g_timer6_handle, TIM6, 8399, 9999);LED_Init();W5500_Init(&g_spi1_handle);wizchip_init(NULL, NULL); // 缓冲区默认__HAL_TIM_CLEAR_IT(&g_timer6_handle, TIM_IT_UPDATE); HAL_TIM_Base_Start_IT(&g_timer6_handle);DHCP_TryToGetIp(0);PrintInfo(); // 打印网络信息while (1){W5500_UDP(0, 8000);}return 0;
}
六、搭建简易WEB服务器
HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议, 基于 TCP/IP 通信协议来传递数据,是万维网(WWW)的数据通信的基础。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法,通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。
在 HTTP 协议中,GET
和 POST
是两种常用的请求方法, 用于客户端向服务器发送数据和获取资源。
GET 方法通常用于从服务器获取资源。它有以下特点:
- 参数传递:请求参数通过 URL 中的查询字符串传递,形如 ?key1=value1&key2=value2。
- 数据大小限制:由于参数附加在 URL 后,长度可能受 URL 长度限制(取决于浏览器和服务器设置)。
- 安全性:数据在 URL 中明文显示,不适合传递敏感信息。
POST 方法通常用于向服务器提交数据。它有以下特点:
- 参数传递:数据放在请求体中,而不是 URL 中。
- 数据大小限制:POST 请求的体积没有明显限制,可以传递大量数据。
- 安全性:数据在请求体中传输,相对来说更安全。
如果我们要搭建 WEB 服务器,需要将 W5500 的官方库下的【Internet】文件夹下的【httpServer】文件夹下文件拷贝到自己工程中。
通过我们浏览器访问 W5500 的 WEB 页面。首先需要编写网页内容,这里我们编写了一个简单的网页,如下所示:
char *g_web_server_content_name = "index.html";
char *g_web_server_content = "<!DOCTYPE html> \<html> \<head> \<title>W5500 Web Server</title> \<meta charset=\"utf-8\"></meta> \</head> \<body> \<a href=\"/index.html?action=1\"><button>开灯</button></a> \<a href=\"/index.html?action=2\"><button>关灯</button></a> \</body> \</html>";
然后,我们需要调用 httpServer_init()
函数 初始化 HTTP 服务器,接着我们需要调用 reg_httpServer_webContent()
函数 注册HTML页面,表示服务器要响应的内容(网页)。
/*** @brief W5500 Web服务器初始化* * @param socket_list socket 列表* @param socket_count socket 个数* @param content_name 响应的内容文件名* @param content 响应的内容*/
void W5500_WebServer_Init(uint8_t *socket_list, uint8_t socket_count, char *content_name, char *content)
{g_w5500_web_server_socket_list = socket_list;g_w5500_web_server_socket_count = socket_count;// 初始化HTTP服务器httpServer_init(g_w5500_web_server_tx_buff, g_w5500_web_server_rx_buff,socket_count, socket_list);// 注册HTML页面,表示服务器要响应的内容(网页)reg_httpServer_webContent((uint8_t *)content_name, (uint8_t *)content);
}
初始化完服务器之后,我们需要调用 httpServer_run()
函数 启动服务器。
/*** @brief 启动W5500 Web服务器* */
void W5500_WebServer_Start(void)
{// 处理HTTP服务器for (uint8_t i = 0; i < g_w5500_web_server_socket_count; i++){httpServer_run(g_w5500_web_server_socket_list[i]);}
}
httpServer_run()
函数的逻辑跟 TCP Server 基本一致,也是运行了一个状态机,根据 SOCKET 不同状态,执行相应的 HTTP Server 部分的处理。在 httpServer_run()
函数中,我们调用自己的处理函数。
void httpServer_run(uint8_t seqnum)
{uint8_t s; // socket numberuint16_t len;uint32_t gettime = 0;#ifdef _HTTPSERVER_DEBUG_uint8_t destip[4] = {0, };uint16_t destport = 0;
#endifhttp_request = (st_http_request *)pHTTP_RX; // Structure of HTTP Requestparsed_http_request = (st_http_request *)pHTTP_TX;// Get the H/W socket numbers = getHTTPSocketNum(seqnum);/* HTTP Service Start */switch(getSn_SR(s)){case SOCK_ESTABLISHED:// Interrupt clearif(getSn_IR(s) & Sn_IR_CON){setSn_IR(s, Sn_IR_CON);}// HTTP Process statesswitch(HTTPSock_Status[seqnum].sock_status){case STATE_HTTP_IDLE :if ((len = getSn_RX_RSR(s)) > 0){if (len > DATA_BUF_SIZE) len = DATA_BUF_SIZE;len = recv(s, (uint8_t *)http_request, len);*(((uint8_t *)http_request) + len) = '\0';parse_http_request(parsed_http_request, (uint8_t *)http_request);
#ifdef _HTTPSERVER_DEBUG_getSn_DIPR(s, destip);destport = getSn_DPORT(s);printf("\r\n");printf("> HTTPSocket[%d] : HTTP Request received ", s);printf("from %d.%d.%d.%d : %d\r\n", destip[0], destip[1], destip[2], destip[3], destport);
#endif
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : [State] STATE_HTTP_REQ_DONE\r\n", s);
#endif// HTTP 'response' handler; includes send_http_response_header / body functionhttp_process_handler(s, parsed_http_request);// 用户自定义处理函数extern handler_user_funcion(char *url);handler_user_funcion((char *)parsed_http_request->URI);gettime = get_httpServer_timecount();// Check the TX socket buffer for End of HTTP response sendswhile(getSn_TX_FSR(s) != (getSn_TxMAX(s))){if((get_httpServer_timecount() - gettime) > 3){
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : [State] STATE_HTTP_REQ_DONE: TX Buffer clear timeout\r\n", s);
#endifbreak;}}if(HTTPSock_Status[seqnum].file_len > 0) HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_INPROC;else HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_DONE; // Send the 'HTTP response' end}break;case STATE_HTTP_RES_INPROC :/* Repeat: Send the remain parts of HTTP responses */
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : [State] STATE_HTTP_RES_INPROC\r\n", s);
#endif// Repeatedly send remaining data to clientsend_http_response_body(s, 0, http_response, 0, 0);if(HTTPSock_Status[seqnum].file_len == 0) HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_DONE;break;case STATE_HTTP_RES_DONE :
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : [State] STATE_HTTP_RES_DONE\r\n", s);
#endif// Socket file info structure re-initializeHTTPSock_Status[seqnum].file_len = 0;HTTPSock_Status[seqnum].file_offset = 0;HTTPSock_Status[seqnum].file_start = 0;HTTPSock_Status[seqnum].sock_status = STATE_HTTP_IDLE;//#ifdef _USE_SDCARD_
// f_close(&fs);
//#endif
#ifdef _USE_WATCHDOG_HTTPServer_WDT_Reset();
#endifhttp_disconnect(s);break;default :break;}break;case SOCK_CLOSE_WAIT:
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : ClOSE_WAIT\r\n", s); // if a peer requests to close the current connection
#endifdisconnect(s);break;case SOCK_CLOSED:
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : CLOSED\r\n", s);
#endifif(socket(s, Sn_MR_TCP, HTTP_SERVER_PORT, 0x00) == s) /* Reinitialize the socket */{
#ifdef _HTTPSERVER_DEBUG_printf("> HTTPSocket[%d] : OPEN\r\n", s);
#endif}break;case SOCK_INIT:listen(s);break;case SOCK_LISTEN:break;default :break;} // end of switch#ifdef _USE_WATCHDOG_HTTPServer_WDT_Reset();
#endif
}
/*** @brief W55000 Web服务器用户自定义函数* * @param url 返回的URL*/
void handler_user_funcion(char *url)
{// 从URL里提取出参数char *pAction = strstr(url, "action=");if (pAction != NULL){// 获取具体的参数值int action = 0;if (sscanf(pAction, "action=%d", &action) == 1){switch (action){case 1:// 打开灯LED_SetStatus(GPIOF, GPIO_PIN_9, LED_ON);break;case 2:// 关闭灯LED_SetStatus(GPIOF, GPIO_PIN_9, LED_OFF);break;default:break;}}}
}
然后,我们修改定时器更新中断回调函数:
/*** @brief 定时器更新中断回调函数* * @param htim 定时器句柄*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM6){extern void DHCP_time_handler(void);DHCP_time_handler();extern void httpServer_time_handler(void);httpServer_time_handler();}
}
int main(void)
{uint8_t socket_list[3] = {0, 1, 3};HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200, g_usart1_rx_buffer);SPI_Init(&g_spi1_handle, SPI1, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, SPI_BAUDRATEPRESCALER_4, SPI_FIRSTBIT_MSB);Timer_Base_Init(&g_timer6_handle, TIM6, 8399, 9999);LED_Init();W5500_Init(&g_spi1_handle);wizchip_init(NULL, NULL); // 缓冲区默认__HAL_TIM_CLEAR_IT(&g_timer6_handle, TIM_IT_UPDATE); HAL_TIM_Base_Start_IT(&g_timer6_handle);DHCP_TryToGetIp(0);W5500_WebServer_Init(socket_list, sizeof(socket_list) / sizeof(socket_list[0]), g_web_server_content_name, g_web_server_content);PrintInfo(); // 打印网络信息while (1){W5500_WebServer_Start();}return 0;
}