嵌入式学习交流Q群 679912988

介绍

w5500是一款集成了TCP/IP协议栈的以太网控制器,具有高速、低功耗、低成本、易用、可靠、安全等特点。

基于STM32F103C8T6上搭载的w5500, 实现了udp、tcp Server、tcp Client、mqtt、http、dhcp、dns 的完整项目地址

https://github.com/hub-yu/ModbusDevice

硬件功能验证

在移植库之前,排查硬件异常,确保w5500的硬件连接正确。

以下代码示例对w5500的寄存器直接操作,调整合适的ip后,正常情况下应该是网口灯亮,且可以ping通的

static uint16_t spi_rw(uint16_t data)
{
    uint32_t count = 10000;
    while ((count) && (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET))
        count--;
    if (count == 0)
        return 0xffff;

    SPI_I2S_SendData(SPI2, data);
    count = 10000;
    while ((count) && (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET))
        count--;
    if (count == 0)
        return 0xffff;
    uint16_t recv = SPI_I2S_ReceiveData(SPI2);
    return recv;
}

uint16_t wiz_write_buf(uint32_t addrbsb, uint8_t *buf, uint16_t len)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    spi_rw((addrbsb & 0x00FF0000) >> 16);
    spi_rw((addrbsb & 0x0000FF00) >> 8);
    spi_rw((addrbsb & 0x000000F8) + 4);
    for (uint16_t i = 0; i < len; i++)
        spi_rw(buf[i]);
    GPIO_SetBits(GPIOB, GPIO_Pin_12);
    return len;
}

uint16_t wiz_read_buf(uint32_t addrbsb, uint8_t *buf, uint16_t len)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    spi_rw((addrbsb & 0x00FF0000) >> 16);
    spi_rw((addrbsb & 0x0000FF00) >> 8);
    spi_rw((addrbsb & 0x000000F8));
    for (uint16_t i = 0; i < len; i++)
        buf[i] = spi_rw(0x00);
    GPIO_SetBits(GPIOB, GPIO_Pin_12);
    return len;
}

void reset_net(void)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_11); // min 500us
    vTaskDelay(pdMS_TO_TICKS(10));
    GPIO_SetBits(GPIOB, GPIO_Pin_11); // max 1ms
    vTaskDelay(pdMS_TO_TICKS(10));
}

void set_mac()
{
    uint8_t mac[6] = {0x00, 0x22, 0x33, 0x44, 0x55, 0x66};
    wiz_write_buf(0x000900, mac, 6);
    wiz_read_buf(0x000900, mac, 6);
    LOG_INFO("W5500 mac: %02x.%02x.%02x.%02x.%02x.%02x\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

void set_ip()
{
    uint8_t gateway[4] = {192, 168, 1, 1};
    uint8_t mask[4] = {255, 255, 255, 0};
    uint8_t ip[4] = {192, 168, 1, 50};

    wiz_write_buf(0x000100, gateway, 4);
    wiz_write_buf(0x000500, mask, 4);
    wiz_write_buf(0x000F00, ip, 4);

    wiz_read_buf(0x000F00, ip, 4);
    wiz_read_buf(0x000500, mask, 4);
    wiz_read_buf(0x000100, gateway, 4);
    LOG_INFO("W5500 ip: %d.%d.%d.%d\r\n", ip[0], ip[1], ip[2], ip[3]);
    LOG_INFO("W5500 mask: %d.%d.%d.%d\r\n", mask[0], mask[1], mask[2], mask[3]);
    LOG_INFO("W5500 gateway: %d.%d.%d.%d\r\n", gateway[0], gateway[1], gateway[2], gateway[3]);
}

void main() {
    ...
    reset_net();
    set_mac();
    set_ip();
    ...

    while(1) {
        ...
    }
}

官方库移植

在使用w5500时,官方提供的库是一个很好的起点。它包含了对w5500的基本操作和配置的支持。

下载地址:https://github.com/Wiznet/ioLibrary_Driver

上不去github的小伙伴可以从鄙人网盘,直接下载即可

名称: ioLibrary_Driver-master.zip
大小: 6461214 字节 : 6309 KiB
SHA256: 33943e196776e24469210117a22c91628fb843689bcdcbfc0d46b0dfc1cc0307

这里分享 tcp/udp 测试工具 sokit

下载地址:https://github.com/sinpolib/sokit/releases

也同时放到了网盘上,直接下载即可

名称: sokit-1.3-win32-chs.zip
大小: 3983986 字节 : 3890 KiB
SHA256: 2a871139fe4122378a31c257842186e6e9f0f621e371293ad248f7dcaf7545ab

需要注意和修改的地方如下

在 wizchip_conf.h 中修改 网卡信号及通讯方式



#ifndef _WIZCHIP_
   // NOTE_LIHAN: Some sections of this code are not yet fully defined.
   #define _WIZCHIP_                      W5500   // W5100, W5100S, W5200, W5300, W5500, 6300
#endif

...

#ifndef _WIZCHIP_IO_MODE_
   //#define _WIZCHIP_IO_MODE_           _WIZCHIP_IO_MODE_SPI_FDM_
   #define _WIZCHIP_IO_MODE_           _WIZCHIP_IO_MODE_SPI_
#endif

在 wizchip_conf.c 中修改回调函数类型

/**
 * @\ref _WIZCHIP instance
 */
//
//M20150401 : For a compiler didnot support a member of structure
//            Replace the assignment of struct members with the assingment of array
//
/*
_WIZCHIP  WIZCHIP =
      {
      .id                  = _WIZCHIP_ID_,
      .if_mode             = _WIZCHIP_IO_MODE_,
      .CRIS._enter         = wizchip_cris_enter,
      .CRIS._exit          = wizchip_cris_exit,
      .CS._select          = wizchip_cs_select,
      .CS._deselect        = wizchip_cs_deselect,
      // .IF.BUS._read_byte   = wizchip_bus_readbyte,
      // .IF.BUS._write_byte  = wizchip_bus_writebyte
      .IF.SPI._read_byte   = wizchip_spi_readbyte,
      .IF.SPI._write_byte  = wizchip_spi_writebyte
      };
*/      
_WIZCHIP  WIZCHIP =
{
    _WIZCHIP_IO_MODE_,
    _WIZCHIP_ID_ ,
    {
        wizchip_cris_enter,
        wizchip_cris_exit
    },
    {
        wizchip_cs_select,
        wizchip_cs_deselect
    },
    {
        {
            //M20150601 : Rename the function 
            //wizchip_bus_readbyte,
            //wizchip_bus_writebyte
            wizchip_bus_readdata,
            wizchip_bus_writedata
        },

    }
};

tcp client 示例

以下内容是STM32F103C8T6的标准库代码

  • 配置w5500 ip:192.168.1.49端口自增的TCP客户端
  • 连接到192.168.1.4的TCP服务器5000端口。
W5500 STM32F103C8T6
MOSI PB15 SPI2_MOSI
MISO SPB14 SPI2_MISO
SCK PB13 SPI2_SCK
NSS PB12 CS
RESET PB11
INT 未使用
#include "net.h"
#include "log.h"
#include "stm32f10x.h"

#include "FreeRTOS.h"
#include "task.h"
#include "stream_buffer.h"

#include "w5500.h"
#include "socket.h"
#include "wizchip_conf.h"

#include <string.h>


static uint16_t spi_rw(uint16_t data)
{
    uint32_t count = 10000;
    while ((count) && (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET))
        count--;
    if (count == 0)
        return 0xffff;

    SPI_I2S_SendData(SPI2, data);
    count = 10000;
    while ((count) && (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET))
        count--;
    if (count == 0)
        return 0xffff;
    uint16_t recv = SPI_I2S_ReceiveData(SPI2);
    return recv;
}

static void cris_en()
{
}

static void cris_ex()
{
}

static void cs_sel()
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}

static void cs_desel()
{
    GPIO_SetBits(GPIOB, GPIO_Pin_12);
}

static uint8_t spi_read()
{
    return spi_rw(0x00);
}

static void spi_write(uint8_t data)
{
    spi_rw(data);
}

void reset_net(void)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_11); // min 500us
    vTaskDelay(pdMS_TO_TICKS(10));
    GPIO_SetBits(GPIOB, GPIO_Pin_11); // max 1ms
    vTaskDelay(pdMS_TO_TICKS(10));

    setPHYCFGR(0xb8); // reset PHY
    vTaskDelay(pdMS_TO_TICKS(10));
}

void get_common_regs()
{
    LOG_INFO("Mode: 0x%02x\r\n", getMR()); // 配置模式寄存器

    uint8_t mac[6] = {0};
    getSHAR(mac);
    LOG_INFO("mac: %02x.%02x.%02x.%02x.%02x.%02x\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // 网卡MAC地址

    uint8_t ip[4] = {0};
    getSIPR(ip);
    LOG_INFO("ip: %d.%d.%d.%d\r\n", ip[0], ip[1], ip[2], ip[3]); // 网卡IP地址

    uint8_t mask[4] = {0};
    getSUBR(mask);
    LOG_INFO("mask: %d.%d.%d.%d\r\n", mask[0], mask[1], mask[2], mask[3]); // 网卡子网掩码

    uint8_t gateway[4] = {0};
    getGAR(gateway);
    LOG_INFO("gateway: %d.%d.%d.%d\r\n", gateway[0], gateway[1], gateway[2], gateway[3]); // 网卡网关地址

    // vTaskDelay(pdMS_TO_TICKS(1000));
    LOG_INFO("INTLEVEL: 0x%02x\r\n", getINTLEVEL()); // 中断触发延时 𝐼𝐴𝑊𝑇 = (𝐼𝑁𝑇𝐿𝐸𝑉𝐸𝐿 + 1) ×(1/𝑃𝐿𝐿𝑐𝑙𝑘3 × 4) (when INTLEVEL > 0)
    LOG_INFO("IR: 0x%02x\r\n", getIR());             // 状态中断标志
    LOG_INFO("IMR: 0x%02x\r\n", getIMR());           // 状态中断掩码
    LOG_INFO("SIR: 0x%02x\r\n", getSIR());           // socket中断标志
    LOG_INFO("SIMR: 0x%02x\r\n", getSIMR());         // socket中断掩码
    LOG_INFO("RTR: 0x%04x\r\n", getRTR());           // 重发延时 100us × RTR
    LOG_INFO("RCR: 0x%02x\r\n", getRCR());           // 重发次数 RCR + 1

    vTaskDelay(pdMS_TO_TICKS(200));
    LOG_INFO("PTIMER: 0x%02x\r\n", getPTIMER()); // ppp链路控制协议请求定时器  25ms × PTIMER
    LOG_INFO("PMAGIC: 0x%02x\r\n", getPMAGIC()); // PMAGIC configures the 4bytes magic number to be used in LCP echo request
    uint8_t pppoe_mac[6] = {0x00, 0x22, 0x33, 0x44, 0x55, 0x66};
    getPHAR(pppoe_mac); // PHAR should be written to the PPPoE server hardware address acquired in PPPoE connection process
    LOG_INFO("PHAR: %02x.%02x.%02x.%02x.%02x.%02x\r\n", pppoe_mac[0], pppoe_mac[1], pppoe_mac[2], pppoe_mac[3], pppoe_mac[4], pppoe_mac[5]);
    LOG_INFO("PSID: 0x%04x\r\n", getPSID()); // PSID should be written to the PPPoE sever session ID acquired in PPPoE connectionprocess.
    LOG_INFO("PMRU: 0x%02x\r\n", getPMRU()); // PMRU configures the maximum receive unit of PPPoE

    uint8_t unreachable_ip[4] = {0, 0, 0, 0};
    getUIPR(unreachable_ip);
    LOG_INFO("UIPR: %d.%d.%d.%d\r\n", unreachable_ip[0], unreachable_ip[1], unreachable_ip[2], unreachable_ip[3]); // 不可达 IP 地址寄存器
    LOG_INFO("UPORTR: %d\r\n", getUPORTR());                                                                       // 不可达端口寄存器

    LOG_INFO("PHYCFGR: 0x%02x\r\n", getPHYCFGR()); // configures PHY operation
    LOG_INFO("version: %d\r\n", getVERSIONR());    // W5500 版本号 always 0x04
}

void get_socket_regs(uint8_t ch)
{
    LOG_INFO("ch[%d] ##############\r\n", ch);
    LOG_INFO("ch[%d] MR: 0x%02x\r\n", ch, getSn_MR(ch));   // Socket n Mode Register
    LOG_INFO("ch[%d] CR: 0x%02x\r\n", ch, getSn_CR(ch));   // Socket n Command Register
    LOG_INFO("ch[%d] IR: 0x%02x\r\n", ch, getSn_IR(ch));   // Socket n Interrupt Register
    LOG_INFO("ch[%d] SR: 0x%02x\r\n", ch, getSn_SR(ch));   // Socket n Status Register
    LOG_INFO("ch[%d] IMR: 0x%02x\r\n", ch, getSn_IMR(ch)); // Socket n Interrupt Mask
    LOG_INFO("ch[%d] PORT: %d\r\n", ch, getSn_PORT(ch));   // Socket n Source Port Register

    uint8_t dhar[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    getSn_DHAR(ch, dhar); // Socket n Destination Hardware Address Register
    LOG_INFO("ch[%d] DHAR: %02x.%02x.%02x.%02x.%02x.%02x\r\n", ch, dhar[0], dhar[1], dhar[2], dhar[3], dhar[4], dhar[5]);

    uint8_t dipr[4] = {0x00, 0x00, 0x00, 0x00};
    getSn_DIPR(ch, dipr); // Socket n Destination IP Address Register
    LOG_INFO("ch[%d] DIPR: %d.%d.%d.%d\r\n", ch, dipr[0], dipr[1], dipr[2], dipr[3]);

    LOG_INFO("ch[%d] DPORT: %d\r\n", ch, getSn_DPORT(ch)); // Socket n Destination Port Register

    LOG_INFO("ch[%d] MSSR: 0x%04x\r\n", ch, getSn_MSSR(ch));         // Socket n Maximum Segment Size Register
    LOG_INFO("ch[%d] TOS: %d\r\n", ch, getSn_TOS(ch));               // Socket n IP Type of Service Register
    LOG_INFO("ch[%d] TTL: %d\r\n", ch, getSn_TTL(ch));               // Socket n TTL Register
    LOG_INFO("ch[%d] RXBUF_SIZE: %d\r\n", ch, getSn_RXBUF_SIZE(ch)); // Socket n RX Buffer Size Register
    LOG_INFO("ch[%d] TXBUF_SIZE: %d\r\n", ch, getSn_TXBUF_SIZE(ch)); // Socket n TX Buffer Size Register

    LOG_INFO("ch[%d] TX_FSR: 0X%04x\r\n", ch, getSn_TX_FSR(ch)); // Socket n TX Free Size Register

    LOG_INFO("ch[%d] TX_RD: 0X%04x\r\n", ch, getSn_TX_RD(ch)); // Socket n TX Read Pointer Register
    LOG_INFO("ch[%d] TX_WR: 0X%04x\r\n", ch, getSn_TX_WR(ch)); // Socket n TX Write Pointer Register

    LOG_INFO("ch[%d] RX_RSR: 0X%04x\r\n", ch, getSn_RX_RSR(ch)); // Socket n Received Size Register
    LOG_INFO("ch[%d] RX_RD: 0X%04x\r\n", ch, getSn_RX_RD(ch));   // Socket n RX Read Data Pointer Register

    LOG_INFO("ch[%d] RX_RX_WR: 0X%04x\r\n", ch, getSn_RX_WR(ch));     // Socket n RX Write Pointer Register
    LOG_INFO("ch[%d] RX_FRAG: 0X%04x\r\n", ch, getSn_FRAG(ch));       // Socket n Fragment Register
    LOG_INFO("ch[%d] RX_KPALVTR: 0X%02x\r\n", ch, getSn_KPALVTR(ch)); // Socket n Keep Alive Time Register

    LOG_INFO("ch[%d] RxMAX: 0X%02x\r\n", ch, getSn_RxMAX(ch)); //
    LOG_INFO("ch[%d] TxMAX: 0X%02x\r\n", ch, getSn_TxMAX(ch)); //
}

void net_task(void *arg)
{

    reg_wizchip_cris_cbfunc(cris_en, cris_ex);   // 注册用于进入和退出临界区的回调函数
    reg_wizchip_cs_cbfunc(cs_sel, cs_desel);     // 注册用于选择和取消选择SPI时钟的回调函数
    reg_wizchip_spi_cbfunc(spi_read, spi_write); // 注册用于通过SPI接口读写字节的回调函数

    reset_net();

    if(getVERSIONR() != 0x04) {
        LOG_ERROR("W5500 version error: 0x%02x\r\n", getVERSIONR());
        return;
    }

    uint8_t mac[6] = {0x00, 0x22, 0x33, 0x44, 0x55, 0x66};
    uint8_t gateway[4] = {192, 168, 1, 1};
    uint8_t mask[4] = {255, 255, 255, 0};
    uint8_t ip[4] = {192, 168, 1, 49};
    setGAR(gateway);
    setSUBR(mask);
    setSHAR(mac);
    setSIPR(ip);
    get_common_regs();
    for(uint8_t i = 0; i < 1; i++) {
        vTaskDelay(pdMS_TO_TICKS(1000));
        get_socket_regs(i);
    }

    uint8_t remote_ip[4] = {192, 168, 1, 4};

    LOG_INFO("socket %d\r\n", socket(1, Sn_MR_TCP, 0, Sn_MR_ND));
    LOG_INFO("connect %d\r\n", connect(1, remote_ip, 5000));


    uint8_t buff[100];

    while (1)
    {

        vTaskDelay(pdMS_TO_TICKS(100));

        if (getSn_IR(1) & Sn_IR_CON)
            setSn_IR(1, Sn_IR_CON);

        uint16_t len = getSn_RX_RSR(1);
        if(len == 0)
            continue;
        memset(buff, 0, sizeof(buff));
        recv(1, buff, len);
        LOG_INFO("%s\r\n", buff);
        send(1, buff, len);
    }
}

void net_init()
{

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure = {
        .GPIO_Pin = GPIO_Pin_15 | GPIO_Pin_14 | GPIO_Pin_13, // SCK, MISO, MOSI
        .GPIO_Mode = GPIO_Mode_AF_PP,                        // Alternate function push-pull
        .GPIO_Speed = GPIO_Speed_50MHz,                      // 50 MHz
    };
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    SPI_InitTypeDef SPI_InitStructure = {
        .SPI_Direction = SPI_Direction_2Lines_FullDuplex, // Bidirectional mode
        .SPI_Mode = SPI_Mode_Master,                      // Master mode
        .SPI_DataSize = SPI_DataSize_8b,                  // 8 bits per transfer
        .SPI_CPOL = SPI_CPOL_Low,                         // Clock polarity low
        .SPI_CPHA = SPI_CPHA_1Edge,                       // Clock phase first edge
        .SPI_NSS = SPI_NSS_Soft,                          // Software NSS management
        .SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2, // 36 MHz clock (72 MHz / 2)
        .SPI_FirstBit = SPI_FirstBit_MSB,                 // MSB first
        .SPI_CRCPolynomial = 7                            // CRC polynomial
    };

    SPI_Init(SPI2, &SPI_InitStructure);
    SPI_Cmd(SPI2, ENABLE);

    // 定义RES CS引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_11; // 选择要控制的GPIO引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        // 设置引脚速率为50MHz
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;         // 设置引脚模式为通用推挽输出
    GPIO_Init(GPIOB, &GPIO_InitStructure);                   // 调用库函数,初始化GPIO
    GPIO_SetBits(GPIOB, GPIO_Pin_12 | GPIO_Pin_11);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_ResetBits(GPIOB, GPIO_Pin_9);



    xTaskCreate(net_task, NET_TASK_NAME, NET_TASK_STACK_SIZE, NULL, NET_TASK_PRIORITY, NULL);
}

常见问题

网口灯不亮

复位引脚拉低再拉高之后,网口在插入网线连接交换机后就应该亮起,且芯片温度会升高,如果灯不亮,可能是以下原因:

  • 检查芯片温度, 没有温度说明压根就没有工作,比如晶振没有起振,复位引脚未正常配置,或其他硬件问题
  • 检查电源供电(仅用用仿真器供电可能达不到整体功耗要求)
  • 检查SPI是否正常,若SPI通讯正常,可能是网卡的外围部分电容焊接时连在一起导致电容损耗,导致灯不亮。

灯亮ping不通

复位成功后,配置完ip地址后,ping不通,可能是以下原因:

  • 检查网卡是否配置正确
  • 检查网口是否接到路由器
  • 检查SPI是否正常,是否有异常波形导致SPI通信失败,测试获取w5500版本是否为4
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐