STM32+W5500:解锁以太网开发的潜力


参考资料

实验-Ping

硬件电路

image-20241214160129237

image-20241214160135462

(1)W5500-RST:重置硬件,重置(Reset)低电平有效;该引脚需要保持低电平至少 500 us,才能重置 W5500;(正常使用应该高电平,需要重置芯片的时候置为低电平不少500us)。连接的是PG7。

(2)W5500-INT:中断输出(Interrupt output)低电平有效;低电平:W5500的中断生效。高电平:无中断;连接的是PG6。

(3)W5500-CS片选引脚。连接的是PD3

(4)连接的是STM32的SPI2外设。

W5500官方驱动移植

https://github.com/Wiznet/ioLibrary_Driver

image-20241214160231952

移植我们需要的

image-20241214160305162

修改宏定义

修改wizchip_conf.h

找到宏定义_WIZCHIP_,如果不是W5500,就改成W5500。

#ifndef _WIZCHIP_
#define _WIZCHIP_ W5500 // W5100, W5100S, W5200, W5300, W5500
#endif

修改工作模式为可变数据长度模式(大致在155行,_WIZCHIP_IO_MODE_SPI_FDM_

#elif (_WIZCHIP_ == W5500)
#define _WIZCHIP_ID_ "W5500\0"

/**
 * @brief Define interface mode. \n
 * @todo Should select interface mode as chip.
 *        - @ref \_WIZCHIP_IO_MODE_SPI_ \n
 *          -@ref \_WIZCHIP_IO_MODE_SPI_VDM_ : Valid only in @ref \_WIZCHIP_ == W5500 \n
 *          -@ref \_WIZCHIP_IO_MODE_SPI_FDM_ : Valid only in @ref \_WIZCHIP_ == W5500 \n
 *        - @ref \_WIZCHIP_IO_MODE_BUS_ \n
 *          - @ref \_WIZCHIP_IO_MODE_BUS_DIR_ \n
 *          - @ref \_WIZCHIP_IO_MODE_BUS_INDIR_ \n
 *        - Others will be defined in future. \n\n
 *        ex> <code> #define \_WIZCHIP_IO_MODE_ \_WIZCHIP_IO_MODE_SPI_VDM_ </code>
 *
 */
#ifndef _WIZCHIP_IO_MODE_
// #define _WIZCHIP_IO_MODE_           _WIZCHIP_IO_MODE_SPI_FDM_
#define _WIZCHIP_IO_MODE_ _WIZCHIP_IO_MODE_SPI_VDM_

钩子函数 & 注册

wizchip_conf.c文件中,官方提供了一些接口,待用户补充。

void    wizchip_cris_enter(void)  {} // 进入临界区(没有上系统,默认即可)
void    wizchip_cris_exit(void) {} // 退出临界区(没有上系统,默认即可)
void    wizchip_cs_select(void)  {} // 片选使能
void    wizchip_cs_deselect(void) {} // 片选失能
iodata_t wizchip_bus_readdata(uint32_t AddrSel) { return * ((volatile iodata_t *)((ptrdiff_t) AddrSel)); } // 总线读函数
void    wizchip_bus_writedata(uint32_t AddrSel, iodata_t wb)  { *((volatile iodata_t*)((ptrdiff_t)AddrSel)) = wb; } // 总线写函数
uint8_t wizchip_spi_readbyte(void)        {return 0;} // 读一个字节
void    wizchip_spi_writebyte(uint8_t wb) {} // 写一个字节
void    wizchip_spi_readburst(uint8_t* pBuf, uint16_t len)  {} //按长度读
void    wizchip_spi_writeburst(uint8_t* pBuf, uint16_t len) {} // 按长度写

实现上述函数后需要通过如下函数进行注册

//注册进入/退出临界区函数
void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void))
//注册SPI片选(CS)使能/失能函数
void reg_wizchip_cs_cbfunc(void(*cs_sel)(void), void(*cs_desel)(void))
//注册总线 读/写 函数
void reg_wizchip_bus_cbfunc(iodata_t(*bus_rb)(uint32_t addr), void (*bus_wb)(uint32_t addr, iodata_t wb))
//注册 SPI 按字节 读/写 函数
void reg_wizchip_spi_cbfunc(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb))
//注册 SPI 按长度 读/写函数
void reg_wizchip_spiburst_cbfunc(void (*spi_rb)(uint8_t* pBuf, uint16_t len), void (*spi_wb)(uint8_t* pBuf, uint16_t len))

对接SPI

spi.h

#ifndef __SPI_H__
#define __SPI_H__

#include "stm32f10x.h"
#include "delay.h"

// CS
#define CS_HIGH (GPIOD->ODR |= GPIO_ODR_ODR3)
#define CS_LOW (GPIOD->ODR &= ~GPIO_ODR_ODR3)

void SPI_Init(void);

void SPI_Start(void);
void SPI_Stop(void);

uint8_t SPI_SwapByte(uint8_t dataByte);

#endif /* __SPI_H__ */

wizchip_conf.c

实现钩子函数

void 	wizchip_cs_select(void)            {
    CS_LOW;
}

void 	wizchip_cs_deselect(void)          {
    CS_HIGH;
}

uint8_t wizchip_spi_readbyte(void)        {
    return SPI_SwapByte(0);
}

void 	wizchip_spi_writebyte(uint8_t wb) {
    SPI_SwapByte(wb);
}

新增注册接口

void wizchip_register_callbacks(void) {
    reg_wizchip_cris_cbfunc(wizchip_cris_enter, wizchip_cris_exit);
    reg_wizchip_cs_cbfunc(wizchip_cs_select, wizchip_cs_deselect);
    reg_wizchip_spi_cbfunc(wizchip_spi_readbyte, wizchip_spi_writebyte);
}

SPI(STM32F103外设SPI2)

spi.h

#ifndef __SPI_H__
#define __SPI_H__

#include "stm32f10x.h"
#include "delay.h"

// CS
#define CS_HIGH (GPIOD->ODR |= GPIO_ODR_ODR3)
#define CS_LOW (GPIOD->ODR &= ~GPIO_ODR_ODR3)

void SPI_Init(void);

void SPI_Start(void);
void SPI_Stop(void);

uint8_t SPI_SwapByte(uint8_t dataByte);

#endif /* __SPI_H__ */

spi.c

#include "spi.h"

void SPI_Init(void) {
    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;

    // CS-PD3 通用推挽 MODE=11 CNF=00
    GPIOD->CRL |= GPIO_CRL_MODE3;
    GPIOD->CRL &= ~GPIO_CRL_CNF3;

    // SCK-PB13,MOSI-PB15 复用推挽 MODE=11 CNF=10
    GPIOB->CRH |= GPIO_CRH_MODE13;
    GPIOB->CRH |= GPIO_CRH_CNF13_1;
    GPIOB->CRH &= ~GPIO_CRH_CNF13_0;

    GPIOB->CRH |= GPIO_CRH_MODE15;
    GPIOB->CRH |= GPIO_CRH_CNF15_1;
    GPIOB->CRH &= ~GPIO_CRH_CNF15_0;

    // MISO-PB14 浮空输入 MODE=00, CNF=01
    GPIOB->CRH &= ~GPIO_CRH_MODE14;
    GPIOB->CRH |= GPIO_CRH_CNF14_0;
    GPIOB->CRH &= ~GPIO_CRH_CNF14_1;

    // 波特率配置为PCLK/4 即 72M/4=18M => 001
    SPI2->CR1 &= ~SPI_CR1_BR;
    SPI2->CR1 |= SPI_CR1_BR_0;
    // SPI模式0(时钟极性=0,相位=0)
    SPI2->CR1 &= ~SPI_CR1_CPOL;
    SPI2->CR1 &= ~SPI_CR1_CPHA;
    // 数据帧格式 8bit
    SPI2->CR1 &= ~SPI_CR1_DFF;
    // 高位优先
    SPI2->CR1 &= ~SPI_CR1_LSBFIRST;
    // NSS为当前MCU作为从设备时的片选信号输入,主模式下,要么硬件上将该引脚拉高,要么通过SSM和SSI将其强制置1
    SPI2->CR1 |= SPI_CR1_SSM; // 将NSS输入配置为软件控制
    SPI2->CR1 |= SPI_CR1_SSI; // 通过SSI=1将NSS强制拉高
    // 设置主模式
    SPI2->CR1 |= SPI_CR1_MSTR;
    // 使能SPI外设
    SPI2->CR1 |= SPI_CR1_SPE;

    // SPI模式0(极性=0,相位=0) 空闲状态 CS拉高(低电平使能),SCK拉低
    CS_HIGH;
}

void SPI_Start(void) { CS_LOW; }
void SPI_Stop(void) { CS_HIGH; }

uint8_t SPI_SwapByte(uint8_t dataByte) {
    // 等待发送缓冲区为空
    while ((SPI2->SR & SPI_SR_TXE) == 0) {
    }
    // 发送数据
    SPI2->DR = dataByte;
    // 等待接收缓冲区非空
    while ((SPI2->SR & SPI_SR_RXNE) == 0) {
    }
    // 接收数据
    return (uint8_t)SPI2->DR;
}

以太驱动封装

logger.h

[!NOTE]

增加打印IP信息的工具接口 LOG_ARR

#ifndef LOGGER_H
#define LOGGER_H

#ifdef __cplusplus
extern "C" {
#endif

#include "stdio.h"
#include "stm32f10x.h"
#include <string.h>

#ifndef USE_LOG
#define USE_LOG 1
#endif

#ifndef USE_DUMP
#define USE_DUMP 1
#endif

#ifndef USE_BLOCK
#define USE_BLOCK 1
#endif

#define ONLY_FILENAME(x) (strrchr(x, '\\') ? strrchr(x, '\\') + 1 : x)

void log_arr(const uint8_t *data, uint16_t len);

#if USE_BLOCK > 0
#define BLOCK(s, ...)                                                          \
    do {                                                                       \
        printf("\r\n[BLOCK %s:%d] ", ONLY_FILENAME(__FILE__), __LINE__);       \
        printf(s, ##__VA_ARGS__);                                              \
        printf(",按任意键继续");                                               \
        block_flag = 0;                                                        \
        while (block_flag == 0)                                                \
            ;                                                                  \
        printf("\r\n");                                                        \
    } while (0)

#else
#define BLOCK(x)
#endif

#if USE_LOG > 0
#define LOG_ARR(info, data, len, ...)                                          \
    do {                                                                       \
        printf("\033[1;36m\r\n[DEBUG %s:%d] ", ONLY_FILENAME(__FILE__),        \
               __LINE__);                                                      \
        printf(info, ##__VA_ARGS__);                                           \
        printf(" => ");                                                        \
        log_arr(data, len);                                                    \
        printf("\33[0m\r\n");                                                  \
    } while (0);
#define LOG_DEBUG(s, ...)                                                      \
    do {                                                                       \
        printf("\033[1;36m\r\n[DEBUG %s:%d] ", ONLY_FILENAME(__FILE__),        \
               __LINE__);                                                      \
        printf(s, ##__VA_ARGS__);                                              \
        printf("\33[0m\r\n");                                                  \
    } while (0);
#define LOG_INFO(s, ...)                                                       \
    do {                                                                       \
        printf("\033[0;32m\r\n[INFO] ");                                       \
        printf(s, ##__VA_ARGS__);                                              \
        printf("\33[0m\r\n");                                                  \
    } while (0);
#define LOG_ERROR(s, ...)                                                      \
    do {                                                                       \
        printf("\033[0;31m\r\n[ERROR %s:%d] ", ONLY_FILENAME(__FILE__),        \
               __LINE__);                                                      \
        printf(s, ##__VA_ARGS__);                                              \
        printf("\33[0m\r\n");                                                  \
    } while (0);
#define LOG_ASSERT(cond)                                                       \
    do {                                                                       \
        if (!(cond)) {                                                         \
            printf("\r\n[ASSERT] File=[%s],Line=[%ld] Failed to vertify thc "  \
                   "condition [\"%s\"]\r\n",                                   \
                   __FILE__, __LINE__, #cond);                                 \
        }                                                                      \
    } while (0);
#else
#define LOG_DEBUG(s, ...)
#define LOG_INFO(s, ...)
#define LOG_ERROR(s, ...)
#define LOG_ERROR2()
#define LOG_ERROR3(cond)
#define LOG_ERROR4(s, ...)
#define LOG_ASSERT(cond)
#endif

#if USE_DUMP > 0
#define LOG_DUMP(info, data, len, ...)                                         \
    do {                                                                       \
        printf("\033[1;36m\r\n[DUMP] ");                                       \
        printf(info, ##__VA_ARGS__);                                           \
        printf("\r\n\r\n");                                                    \
        log_dump(data, len);                                                   \
        printf("\33[0m\r\n");                                                  \
    } while (0);

void log_dump(const uint8_t *data, uint16_t len);
#else
#define LOG_DUMP(info, data, len)
#endif

#ifdef __cplusplus
}
#endif

#endif /* LOGGER_H */

logger.c

#include "logger.h"

void log_dump(const uint8_t *data, uint16_t len) {
    uint8_t ch, cl;
    for (int i = 0; i < len; i++) {
        ch = data[i] >> 4;
        cl = data[i] & 0x0F;
        if (ch < 10) ch += '0';
        else
            ch += 'A' - 10;
        if (cl < 10) cl += '0';
        else
            cl += 'A' - 10;
        putchar(ch);
        putchar(cl);
        printf(" ");
        if ((i + 1) % 16 == 0) {
            printf("\r\n");
        } else if ((i + 1) % 8 == 0) {
            ch = ' ';
            putchar(ch);
            putchar(ch);
            putchar(ch);
            putchar(ch);
        } else {
            ch = ' ';
            putchar(ch);
        }
    }
}

void log_arr(const uint8_t *data, uint16_t len) {
    for (int i = 0; i < len; i++) {
        printf("%d ", data[i]);
    }
}

ethernet.h

#ifndef __ETHERNET_H__
#define __ETHERNET_H__

#include "w5500.h"
#include "logger.h"
#include "delay.h"

#define RST_HIGH() GPIOG->ODR |= GPIO_ODR_ODR7
#define RST_LOW() GPIOG->ODR &= ~GPIO_ODR_ODR7

void Ethernet_Init(void);

void Ethernet_SetMac(void);

void Ethernet_SetIP(void);

void Ethernet_Test(void);

#endif /* __ETHERNET_H__ */

ethernet.c

#include "ethernet.h"

uint8_t mac[6]        = {110, 120, 130, 140, 150, 160};
uint8_t ip[4]         = {192, 168, 39, 222};
uint8_t gateway[4]    = {192, 168, 39, 1};
uint8_t subnetMask[4] = {255, 255, 255, 0};

static void Ethernet_GPIOConfig(void) {
    RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;
    // RST-PG7 推挽输出 MODE=11 CNF=00
    GPIOG->CRL |= GPIO_CRL_MODE7;
    GPIOG->CRL &= ~GPIO_CRL_CNF7;
    // INT-PG6 上拉输入 MODE=00 CNF=10 ODR=1
    GPIOG->CRL &= ~GPIO_CRL_MODE6;
    GPIOG->CRL |= GPIO_CRL_CNF6_1;
    GPIOG->CRL &= ~GPIO_CRL_CNF6_0;
    GPIOG->ODR |= GPIO_ODR_ODR6;
}

void Ethernet_Reset(void) {
    RST_LOW();
    delay_ms(1);
    RST_HIGH();
    delay_ms(1);
}

void Ethernet_Init(void) {
    SPI_Init();
    wizchip_register_callbacks();

    Ethernet_GPIOConfig();
    Ethernet_Reset();
    Ethernet_SetMac();
    Ethernet_SetIP();

    uint8_t macBuf[6] = {0};
    getSHAR(macBuf);
    LOG_ARR("mac", macBuf, 6);

    uint8_t ipBuf[4]      = {0};
    uint8_t gatewayBuf[4] = {0};
    uint8_t subBuf[4]     = {0};

    getSHAR(macBuf);
    getSIPR(ipBuf);
    getGAR(gatewayBuf);
    getSUBR(subBuf);

    LOG_ARR("ip", ipBuf, 4);
    LOG_ARR("gateway", gatewayBuf, 4);
    LOG_ARR("sub", subBuf, 4);

    uint8_t mr = getMR();
    LOG_ARR("mr", &mr, 1);
}

void Ethernet_SetMac(void) { setSHAR(mac); }

void Ethernet_SetIP(void) {
    setSIPR(ip);
    setGAR(gateway);
    setSUBR(subnetMask);
}

IP配置注意事项

[!IMPORTANT]

  • 要和电脑在同一局域网下,STM32可以通过水晶头网线接入交换机
  • MAC地址要保持唯一性
  • 静态IP地址:网段、网关要和电脑保持一一致,同时确保IP地址没有被占用(可以换一些IP多试下)

image-20241214162538777

测试Ping功能

#include "logger.h"
#include "stm32f10x.h"
#include "uart.h"
#include "ethernet.h"

void uart1_received_callback(uint8_t buf[], uint8_t size) {}

int main() {
    uart_init();
    Ethernet_Init();
    LOG_DEBUG("main start")

    while (1) {
    }
}

image-20241214205658007

image-20241214162450520

时序——注意事项

[!IMPORTANT]

  • 复位低电平至少保持500us
  • 复位拉高后,W5500需要最多1ms来稳定自身内部的PLL时钟,因此我们需要在复位拉高后至少1ms之后才开始与其通信

image-20241214162046415

SPI-W5500逻辑分析插件

image-20241214164451376

实验-TCP建立连接

示例代码

w5500_tcpserver.h

#ifndef __W5500_TCPSERVER_H__
#define __W5500_TCPSERVER_H__

#include "socket.h"

void W5500_TCPServer_Handle(void);

#endif /* __W5500_TCPSERVER_H__ */

w5500_tcpserver.c

#include "w5500_tcpserver.h"
#include "logger.h"

#define SOCK_SN 0
#define TCP_PORT 8080

void W5500_TCPServer_Handle(void) {
    uint8_t sockStatus = getSn_SR(SOCK_SN);
    int8_t result     = 0;
    uint8_t clientIP[4] = {0};
    uint16_t clientPort = 0;

    switch (sockStatus) {
        case SOCK_CLOSED:
            /*
            1. 要打开的socket序列号sn
            2. socket协议模式:TCP
            3. TCP Nagle算法配置:不延迟ack的发送
            */
            result = socket(SOCK_SN, Sn_MR_TCP, TCP_PORT, SF_TCP_NODELAY);
            if (result != SOCK_SN) {
                LOG_ERROR("socket open failed, result = %d", result)
            } else {
                LOG_DEBUG("socket open success")
            }
            break;

        case SOCK_INIT:
            result = listen(SOCK_SN);
            if (result != SOCK_OK) {
                LOG_ERROR("socket listen failed, result = %d", result)
            } else {
                LOG_DEBUG("socket listen success")
            }
            break;

        case SOCK_ESTABLISHED:
            getSn_DIPR(SOCK_SN, &clientIP);
            clientPort = getSn_DPORT(SOCK_SN);
            LOG_ARR("client port = %d, ip", clientIP, 4, clientPort)
            while (1)
            {
                // handle client socket
            }
            
            break;
        
        default:
            break;
    }
}

main.c

#include "stm32f10x.h"
#include "uart.h"
#include "logger.h"
#include "ethernet.h"
#include "w5500_tcpserver.h"

void uart1_received_callback(uint8_t buf[], uint8_t size) {
    
}

int main() {
    uart_init();
    LOG_DEBUG("usart test")
    Ethernet_Init();
    LOG_DEBUG("main start")

    while (1) {
        W5500_TCPServer_Handle();
    }
}

测试

image-20241214205734147

客户端请求连接

image-20241214214202495

服务端socket连接建立

image-20241214205922046

实验-TCPServer收发

驱动依赖

  • socket.h

示例代码

w5500_tcpserver.c

[!NOTE]

socket的入参 flag可以配置 socket为非阻塞模式

#include "w5500_tcpserver.h"
#include "logger.h"

#define SOCK_SN 0
#define TCP_PORT 8080
// W5500每个socket有2kb的接收缓冲区、2kb的发送缓冲区
#define SOCK_RX_BUFFER_MAX_SIZE 2048

uint8_t rxbuf[SOCK_RX_BUFFER_MAX_SIZE] = {0};

void W5500_TCPServer_Handle(void) {
    uint8_t sockStatus    = getSn_SR(SOCK_SN);
    int8_t result         = 0;
    uint8_t clientIP[4]   = {0};
    uint16_t clientPort   = 0;
    uint16_t receivedSize = 0;

    switch (sockStatus) {
        case SOCK_CLOSED:
            /*
            1. 要打开的socket序列号sn
            2. socket协议模式:TCP
            3. socket参数配置:不延迟ack的发送
            */
            result = socket(SOCK_SN, Sn_MR_TCP, TCP_PORT, SF_TCP_NODELAY);
            if (result != SOCK_SN) {
                LOG_ERROR("socket open failed, result = %d", result)
            } else {
                LOG_DEBUG("socket open success")
            }
            break;

        case SOCK_INIT:
            result = listen(SOCK_SN);
            if (result != SOCK_OK) {
                LOG_ERROR("socket listen failed, result = %d", result)
            } else {
                LOG_DEBUG("socket listen success")
            }
            break;

        case SOCK_ESTABLISHED:
            getSn_DIPR(SOCK_SN, clientIP);
            clientPort = getSn_DPORT(SOCK_SN);
            LOG_ARR("client port = %d, ip", clientIP, 4, clientPort)
            while (1) {
                // 轮询方式处理接收事件中断标志位,当有数据可读时进行处理
                while ((getSn_IR(SOCK_SN) & Sn_IR_RECV) == 0) {
                    // 轮询过程中,如果客户端断开连接,则关闭socket,走重新监听的流程
                    if (getSn_SR(SOCK_SN) != SOCK_ESTABLISHED) {
                        LOG_DEBUG("socket state changed, close socket.");
                        close(SOCK_SN);
                        return;
                    }
                }
                // 轮询结束,清除中断标志
                setSn_IR(SOCK_SN, Sn_IR_RECV);
                // 获取socket接收缓冲区中的数据大小
                receivedSize = getSn_RX_RSR(SOCK_SN); // received size
                recv(SOCK_SN, rxbuf, receivedSize);
                LOG_ARR("received msg = %.*s, port = %d, ip", clientIP, 4,
                        receivedSize, rxbuf, clientPort);
                // 回送消息
                send(SOCK_SN, rxbuf, receivedSize);
            }

        default:
            break;
    }
}

测试

image-20241214214514823

image-20241214214755653

实验-TCPClient收发

驱动依赖

  • socket.h

示例代码

w5500_tcpclient.h

#ifndef __W5500_TCPCLIENT_H__
#define __W5500_TCPCLIENT_H__

#include "socket.h"

void W5500_TCPClient_Handle(void);

#endif /* __W5500_TCPCLIENT_H__ */

w5500_tcpclient.c

#include "w5500_tcpclient.h"
#include "logger.h"

#define SOCK_SN 0
#define TCP_PORT 8080
// W5500每个socket有2kb的接收缓冲区、2kb的发送缓冲区
#define SOCK_RX_BUFFER_MAX_SIZE 2048

uint8_t serverIP[4] = {192, 168, 39, 57};
uint16_t serverPort = 8080;
uint8_t rxbuf[SOCK_RX_BUFFER_MAX_SIZE] = {0};

void W5500_TCPClient_Handle(void) {
    uint8_t sockStatus    = getSn_SR(SOCK_SN);
    int8_t result         = 0;
    uint16_t receivedSize = 0;

    switch (sockStatus) {
        case SOCK_CLOSED:
            /*
            1. 要打开的socket序列号sn
            2. socket协议模式:TCP
            3. socket参数配置:不延迟ack的发送
            */
            result = socket(SOCK_SN, Sn_MR_TCP, TCP_PORT, SF_TCP_NODELAY);
            if (result != SOCK_SN) {
                LOG_ERROR("socket open failed, result = %d", result)
            } else {
                LOG_DEBUG("socket open success")
            }
            break;

        case SOCK_INIT:
            result = connect(SOCK_SN, serverIP, serverPort);
            if (result != SOCK_OK) {
                LOG_ERROR("socket connect failed, result = %d", result)
                close(SOCK_SN);
            } else {
                LOG_DEBUG("socket connect success")
            }
            break;

        case SOCK_ESTABLISHED:
            // 发送数据
            send(SOCK_SN, "hello server", 12);

            // 轮询接收数据
            while (1) {
                // 轮询方式处理接收事件中断标志位,当有数据可读时进行处理
                while ((getSn_IR(SOCK_SN) & Sn_IR_RECV) == 0) {
                    // 轮询过程中,如果socket状态变化,则关闭socket,走重新连接的流程
                    if (getSn_SR(SOCK_SN) != SOCK_ESTABLISHED) {
                        LOG_DEBUG("socket state changed, close socket.");
                        close(SOCK_SN);
                        return;
                    }
                }
                // 轮询结束,清除中断标志
                setSn_IR(SOCK_SN, Sn_IR_RECV);
                // 获取socket接收缓冲区中的数据大小
                receivedSize = getSn_RX_RSR(SOCK_SN); // received size
                recv(SOCK_SN, rxbuf, receivedSize);
                LOG_ARR("received msg = %.*s, port = %d, ip", serverIP, 4,
                        receivedSize, rxbuf, serverPort);
                // 回送消息
                send(SOCK_SN, rxbuf, receivedSize);
            }

        default:
            LOG_DEBUG("unexpected socket status = %d", sockStatus)
            break;
    }
}

测试

串口助手TCPServer模式

image-20241215092719982

MCU日志

image-20241215092801097

实验-UDP收发

示例代码

w5500_udp.h

#ifndef __W5500_UDP_H__
#define __W5500_UDP_H__

#include "socket.h"

void W5500_UDP_Handle(void);

#endif /* __W5500_UDP_H__ */

w5500_udp.c

[!NOTE]

通过recvfrom获取报文及其来源IP端口

#include "w5500_udp.h"
#include "logger.h"

#define SOCK_SN 0
#define UDP_PORT 8080
// W5500每个socket有2kb的接收缓冲区、2kb的发送缓冲区
#define SOCK_RX_BUFFER_MAX_SIZE 2048

uint8_t rxbuf[SOCK_RX_BUFFER_MAX_SIZE] = {0};

void W5500_UDP_Handle(void) {
    uint8_t sockStatus    = getSn_SR(SOCK_SN);
    int8_t result         = 0;
    uint16_t receivedSize = 0;
    uint8_t sourceIP[4]   = {0};
    uint16_t sourcePort   = 0;

    switch (sockStatus) {
        case SOCK_CLOSED:
            /*
            1. 要打开的socket序列号sn
            2. socket协议模式:UDP
            3. socket参数配置:不配置任何参数
            */
            result = socket(SOCK_SN, Sn_MR_UDP, UDP_PORT, 0);
            if (result != SOCK_SN) {
                LOG_ERROR("socket open failed, result = %d", result)
            } else {
                LOG_DEBUG("socket open success")
            }
            break;

        case SOCK_UDP:
            // 轮询接收数据
            while (1) {
                // 轮询方式处理接收事件中断标志位,当有数据可读时进行处理
                while ((getSn_IR(SOCK_SN) & Sn_IR_RECV) == 0) {
                    // 轮询过程中,如果socket状态变化,则关闭socket
                    if (getSn_SR(SOCK_SN) != SOCK_UDP) {
                        LOG_DEBUG("socket state changed, close socket.");
                        close(SOCK_SN);
                        return;
                    }
                }
                // 轮询结束,清除中断标志
                setSn_IR(SOCK_SN, Sn_IR_RECV);
                // 获取报文以及来源IP端口
                receivedSize = recvfrom(SOCK_SN, rxbuf, SOCK_RX_BUFFER_MAX_SIZE,
                                        sourceIP, &sourcePort);
                LOG_ARR("received msg = %.*s, size = %d, port = %d, ip",
                        sourceIP, 4, receivedSize, rxbuf, receivedSize,
                        sourcePort);

                // 回送消息
                sendto(SOCK_SN, rxbuf, receivedSize, sourceIP, sourcePort);
            }

        default:
            LOG_DEBUG("unexpected socket status = %d", sockStatus)
            break;
    }
}

测试

串口助手模拟发送方

image-20241215101226563

MCU日志

image-20241215101414638

实验-搭建HTTPServer & WEB控制LED

部署WEB资源

w5500_httpserver.h

#ifndef __W5500_HTTPSERVER_H__
#define __W5500_HTTPSERVER_H__

void W5500_HTTPServer_Init(void);

void W5500_HTTPServer_Start(void);

#endif /* __W5500_HTTPSERVER_H__ */

w5500_httpserver.c

#include "w5500_httpserver.h"
#include "httpServer.h"
#include "logger.h"
#include "socket.h"
#include "led.h"
#include "string.h"

#define SOCKET_MAX_BUFFER_SIZE_TX 2048
#define SOCKET_MAX_BUFFER_SIZE_RX 2048

uint8_t txbuf[SOCKET_MAX_BUFFER_SIZE_TX] = {0};
uint8_t rxbuf[SOCKET_MAX_BUFFER_SIZE_RX] = {0};

// socket序号列表
uint8_t socketList[] = {0, 1, 2, 3, 4, 5, 6, 7};
uint8_t socketSize   = sizeof(socketList) / sizeof(socketList[0]);

const uint8_t *contentName = "index.html";

/* 响应的网页的内容 */
const uint8_t content[2048] = "<!doctype html>\n"
                        "<html lang=\"en\">\n"
                        "<head>\n"
                        "    <meta charset=\"GBK\">\n"
                        "    <meta name=\"viewport\"\n"
                        "          content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n"
                        "    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n"
                        "    <title>尚硅谷嵌入式课程</title>\n"
                        "\n"
                        "    <style type=\"text/css\">\n"
                        "        #open_red{\n"
                        "            color: red;\n"
                        "            width: 100px;\n"
                        "            height: 40px;\n"
                        "\n"
                        "\n"
                        "        }\n"
                        "        #close_red{\n"
                        "            color: black;\n"
                        "            width: 100px;\n"
                        "            height: 40px;\n"
                        "        }\n"
                        "    </style>\n"
                        "</head>\n"
                        "<body>\n"
                        "<a href=\"/index.html?action=0\"><button id=\"open_red\" >开灯</button></a>\n"
                        "<a href=\"/index.html?action=1\"><button id=\"close_red\" >关灯</button></a>\n"
                        "<a href=\"/index.html?action=2\"><button id=\"close_red\" >翻转</button></a>\n"
                        "</body>\n"
                        "</html>";


void W5500_HTTPServer_Init(void) {
    // 初始化httpserver, 使用多少个socket来支撑http服务
    httpServer_init(txbuf, rxbuf, socketSize, socketList);
    // 注册web资源
    reg_httpServer_webContent(contentName, content);
}

void W5500_HTTPServer_Start(void) {
    while (1) {
        // 启动http,监听并处理一次http请求
        httpServer_run(0);
    }
}

main.c

#include "ethernet.h"
#include "logger.h"
#include "stm32f10x.h"
#include "uart.h"
#include "w5500_httpserver.h"
#include "led.h"

void uart1_received_callback(uint8_t buf[], uint8_t size) {}

int main() {
    uart_init();
    LOG_DEBUG("usart test")
    Ethernet_Init();
    LED_Init();
    LOG_DEBUG("main start")

    W5500_HTTPServer_Init();
    W5500_HTTPServer_Start();
    while (1) {
    }
}

浏览器访问WEB

image-20241215120557766

按钮发出HTTP请求

image-20241215120646017

image-20241215120708524

HTTP请求参数解析

官方驱动httpServer.c

image-20241215120814482

parse_http_request(parsed_http_request, (uint8_t *)http_request);
//LOG_DEBUG("request uri = %s", parsed_http_request->URI)
parse_http_request_post_process(parsed_http_request->URI);
__weak void parse_http_request_post_process(uint8_t *uri) {

}

httpServer.h

void parse_http_request_post_process(uint8_t *uri);

自定义uri处理逻辑

w5500_httpserver.h

static int8_t parse_uri(uint8_t *uri) {
    size_t len = strlen(uri);
    if (len > 0) {
        char *token = strstr(uri, "action=");
        if (token != NULL) {
            return *(token + 7) - '0';
        }
    }
    return -1;
}

void parse_http_request_post_process(uint8_t *uri) {
    LOG_DEBUG("uri = %s", uri);
    int8_t actionType = parse_uri(uri);
    LOG_DEBUG("actionType = %d", actionType);
    switch (actionType) {
        case 0:
            LED_On(LED1);
            break;
        case 1:
            LED_Off(LED1);
            break;

        case 2:
            LED_Toggle(LED1);
            break;
        default:
            break;
    }
}

测试

image-20241215121112679

THE END


文章作者: 安文
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 安文 !
  目录