STM32
直播中

凤求凰

11年用户 425经验值
私信 关注
[问答]

如何利用stm32去移植w5500以太网模块呢

w5500是什么?
如何利用STM32去移植w5500以太网模块呢?有哪些步骤?

回帖(1)

赵鑫

2021-12-17 13:58:03
w5500就是一个以太网模块,这个模块本身集成了tcp/ip协议,mcu只需要通过spi协议读写他的寄存器就可以进行数据交互了,
下面大致说说stm32移植w5500的过程和步骤吧,

10个引脚中,真正使用的也就只有vcc gnd miso mosi sclk scs 这几个脚,RST和int引脚如果是引用官方的库的话,是不需要的(RST是复位引脚,INT是中断触发引脚)
移植W5500大致分为下面的几个步骤


  • 初始化SPI引脚
  • 初始化SCS引脚(片选)
  • 添加官方库到工程目录
  • 实现SPI接口和库文件的对接
  • 调用官方库提供的函数实现连接

1,初始化SPI
因为W5500模块内部自己实现了TCP/IP协议,所以我们在外部只需要通过他提供的接口把相关的参数传递进去就行了,比如物理地址,本机IP,本机端口,目标端口,目标IP等等,
而模块提供的是一个SPI的数据接口
下面是103的SPI初始化代码

//ÒÔÏÂÊÇSPIÄ£¿éµÄ³õʼ»¯´úÂ룬ÅäÖóÉÖ÷»úģʽ£¬·ÃÎÊSD Card/W25Q64/NRF24L01                          
//SPI¿Ú³õʼ»¯
//ÕâÀïÕëÊǶÔSPI1µÄ³õʼ»¯
void SPI1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef  SPI_InitStructure;


//  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );//PORTBʱÖÓʹÄÜ
//  RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1,  ENABLE );//SPI2ʱÖÓʹÄÜ   


    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1 | RCC_APB2Periph_AFIO, ENABLE);


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15¸´ÓÃÍÆÍìÊä³ö
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);//³õʼ»¯GPIOB


    GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);  //PB13/14/15ÉÏÀ­


    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //ÉèÖÃSPIµ¥Ïò»òÕßË«ÏòµÄÊý¾Ýģʽ:SPIÉèÖÃΪ˫ÏßË«ÏòÈ«Ë«¹¤
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       //ÉèÖÃSPI¹¤×÷ģʽ:ÉèÖÃΪÖ÷SPI
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   //ÉèÖÃSPIµÄÊý¾Ý´óС:SPI·¢ËͽÓÊÕ8λ֡½á¹¹
//  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;          //ʱÖÓÐü¿ÕµÍ
//  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;        //Êý¾Ý²¶»ñÓÚµÚ1¸öʱÖÓÑØ
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;     //´®ÐÐͬ²½Ê±ÖӵĿÕÏÐ״̬Ϊ¸ßµçƽ
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        //´®ÐÐͬ²½Ê±Öӵĵڶþ¸öÌø±äÑØ£¨ÉÏÉý»òϽµ£©Êý¾Ý±»²ÉÑù
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;           //NSSÐźÅÓÉÓ²¼þ£¨NSS¹Ü½Å£©»¹ÊÇÈí¼þ£¨Ê¹ÓÃSSI룩¹ÜÀí:ÄÚ²¿NSSÐźÅÓÐSSIλ¿ØÖÆ
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//²¨ÌØÂÊÔ¤·ÖƵֵ:²¨ÌØÂÊÔ¤·ÖƵֵΪ256 ³õʼ»¯Ê±Îª×îµÍËÙģʽ
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //Ö¸¶¨Êý¾Ý´«Êä´ÓMSBλ»¹ÊÇLSBλ¿ªÊ¼:Êý¾Ý´«Êä´ÓMSBλ¿ªÊ¼
    SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRCÖµ¼ÆËãµÄ¶àÏîʽ
    SPI_Init(SPI1, &SPI_InitStructure);


    SPI_Cmd(SPI1, ENABLE); //ʹÄÜSPIÍâÉè


//  SPI1_ReadWriteByte(0xff);//Æô¶¯´«Êä      




}   
//SPI ËÙ¶ÈÉèÖú¯Êý
//SpeedSet:
//SPI_BaudRatePrescaler_2   2·ÖƵ   
//SPI_BaudRatePrescaler_8   8·ÖƵ   
//SPI_BaudRatePrescaler_16  16·ÖƵ  
//SPI_BaudRatePrescaler_256 256·ÖƵ


void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
  assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
    SPI1->CR1&=0XFFC7;
    SPI1->CR1|=SPI_BaudRatePrescaler;   //ÉèÖÃSPIËÙ¶È
    SPI_Cmd(SPI1,ENABLE);


}


2,初始化GPIO
除去SPI口之外还有一个片选引脚SCS,这个引脚很重要,在官方协议实现的4个函数中,起到的很关键的作用,我的开发板上这个引脚是放到了PA0上,下面是初始化代码

void W5500_GPIO_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;   


  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );


    // ???????SPI-CS?? (PA0)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
3,下载官方库代码,
官方库代码分为三个部分


  • 基本的SOCKET连接和部分头文件
  • DHCP协议的文件(用来自动获取本地IP的协议)
  • DNS协议文件(用来解析域名的)

我上面提供的百度网盘中包括1,2,3和帮助文档CHM
如果只需要实现基本的tcp,udp协议只需要移植1就可以了,如果想要DHCP来自动获取本地IP,就需要把DHCP添加进去,如果要实现域名解析就需要DNS协议,也已实现部分也可以全部实现,下面会讲解具体的实现步骤和函数用法
socket.c 文件夹里面主要是socket的一些函数,比如close connect socket等等函数,实现了一些应用层的封装
w5500.c 文件主要实现的是对W5500模块寄存器的读写和操作,包括读写那些寄存器,地址是多少等等,
wizchip_conf.c 就是一个公共文件主要包括和SPI的连接以及连接W5500读写寄存器函数和socket函数。
DHCP.c 主要就是初始化DHCP,启动DHCP,获取ip,端口等函数
DNS.c 主要也就是一个初始化函数,一个获取函数,还有一个hande函数
以上的具体使用下面再说
4,添加文件到工程
这个比较简单,如果是使用的keil,直接把文件添加进去,然后c/c++ 下面的include path添加相应的目录就可以了
5,实现SPI和库文件对接
前面说了库文件wizchip_conf.c 里面就包括了和SPI口的对接实现函数,其实也就是一个函数指针,
在spi里实现一些函数,然后把函数名赋值给wizchip_conf.c里面的一些函数指针,这样W5500.c里面的函数实现读写的时候,就能够直接使用SPI接口了,
具体实现的函数有以下6个函数名可以自己随便改,这个不重要


void SPI1_WriteByte(u8 TxData)
u8 SPI1_ReadByte()
void SPI_CrisEnter(void)
void SPI_CrisExit(void)
void SPI_CS_Select(void)
void SPI_CS_Deselect(void)


实现了这几个函数之后,需要把他们注册到库函数里面(也就是把函数指针赋值给别人调用)
官方提供了一串代码

void register_wizchip()
{
    // First of all, Should register SPI callback functions implemented by user for accessing WIZCHIP
    /* Critical section callback */
    reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit);   
    /* Chip selection call back */
    reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);


    /* SPI Read & Write callback function */
    reg_wizchip_spi_cbfunc(SPI1_ReadByte, SPI1_WriteByte);  
}
这个函数是我自己包含的,官方提供的代码里面还包含一些宏定义,不过被我干掉了

在初始化函数调用这个函数之后,就把SPI接口的函数传递给库了,这样库在调用读写的时候,就能够正常读写了
下面是上面 几个函数的实现代码

//SPIx ¶Áдһ¸ö×Ö½Ú
//TxData:ҪдÈëµÄ×Ö½Ú
//·µ»ØÖµ:¶ÁÈ¡µ½µÄ×Ö½Ú
void SPI1_WriteByte(u8 TxData)
{      
    while((SPI1->SR&SPI_I2S_FLAG_TXE)==0);   //??????         
    SPI1->DR=TxData;                                         //????byte
    while((SPI1->SR&SPI_I2S_FLAG_RXNE)==0); //???????byte  
    SPI1->DR;     
}




u8 SPI1_ReadByte()
{


    while((SPI1->SR&SPI_I2S_FLAG_TXE)==0);   //??????              
    SPI1->DR=0xFF;                                               //????????????????
    while((SPI1->SR&SPI_I2S_FLAG_RXNE)==0); //???????byte  
    return SPI1->DR;      
}






/**
  * @brief  ?????
  * @retval None
  */
void SPI_CrisEnter(void)
{
    __set_PRIMASK(1);
}
/**
  * @brief  ?????
  * @retval None
  */
void SPI_CrisExit(void)
{
    __set_PRIMASK(0);
}


/**
  * @brief  ?????????
  * @retval None
  */
void SPI_CS_Select(void)
{
    GPIO_ResetBits(GPIOA,GPIO_Pin_0);
}
/**
  * @brief  ?????????
  * @retval None
  */
void SPI_CS_Deselect(void)
{
    GPIO_SetBits(GPIOA,GPIO_Pin_0);
}
PS,这里说一下,SPI的读写函数,有很多种写法,但是我测试过后发现,就这种是好的,具体原因不晓得,可能和库函数读写的时序有关,其他的的读写方式单独使用的时候都是好的,但是添加到库里面就不行了
PPS,上面的GPIO_0 就是上面说的SCS接口,按照自己开发板写就好了
6,调用函数接口实现TCP客户端的实现
官方提供了3个函数,分别是建立tcp,UDP,和tcp server,如果不移植DHCP协议,用户只需要填写本机IP地址和目标IP地址和目标端口以及,选择0~7的其中一条链路
三个具体的函数我就不放了,放一个github的连接把

以建立一个TCP客户端为例子说明一下函数调用的流程


  • wiz_NetInfo gWIZNETINFO; 初始化这个变量,其中包括IP,MAC等等数据
  • ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO); 调用这个函数和这个参数把数据写入到W5500的寄存器
  • int32_t loopback_tcpc(uint8_t sn, uint8_t* buf, uint8_t* destip, uint16_t destport) 调用这个函数,并且实现部分数据,比如端口,比如IP地址
  • 在swtich语句的下面找到SOCK_ESTABLISHED这个的下面,在这个选项的下面包括数据的读取和发送,函数名称分别是recv和send

使用了loopback_tcpc函数之后,tcp的连接完全不用我们管,因为他会读取W5500的寄存器自己再连接,
下面是DHCP的实现方向
1,添加DHCP文件到工程
这个貌似没啥好说的
2,对DHCP进行初始化代码如下

        uint8_t memsize[2][8] = { {2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};
        if(ctlwizchip(0,(void*)memsize) == -1){
             printf("WIZCHIP Initialized fail.rn");
             while(1);
        }


        setSHAR(gWIZNETINFO.mac);
        DHCP_init(0,buff);
        reg_dhcp_cbfunc(my_ip_assign, my_ip_assign, my_ip_conflict);


        uint8_t dhcp_ret = DHCP_run();
        while(dhcp_ret != DHCP_IP_LEASED)
        {
            delay_ms(500);
            dhcp_ret = DHCP_run();
        }
第一步是初始化ctlwizchip函数,
第二部是设置mac地址,可以放到别的地方或者改变顺序,
第三部是初始化DHCP,buff就是一个数组
第三部是初始化reg_dhcp_cbfunc函数,他实现的两个函数分别是

void my_ip_assign(void)
{
   getIPfromDHCP(gWIZNETINFO.ip);
   getGWfromDHCP(gWIZNETINFO.gw);
   getSNfromDHCP(gWIZNETINFO.sn);
   getDNSfromDHCP(gWIZNETINFO.dns);
   gWIZNETINFO.dhcp = NETINFO_DHCP;
   /* Network initialization */
   network_init();      // apply from dhcp //这是我自己实现的,内部的实现函数就是
//   ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO);   
   printf("DHCP LEASED TIME : %d Sec.rn", getDHCPLeasetime());
}
void my_ip_conflict(void)
{
    printf("CONFLICT IP from DHCPrn");
    //halt or reset or any...
    while(1); // this example is halt.
}


第四部就是运行run函数,并且等待ip地址获取成功,也就是返回码等待4,DHCP_IP_LEASED,在一个贴吧里面看到有个哥们给的代码是success,不知道为啥,我下载的库里面没有和这个枚举,测试了很久发现是等于4的时候是正常的之后就可以在mian里面进行读写处理了,
DNS其实很简单,就两个函数,一个是DNS_init初始化,一个是int8_t DNS_run(uint8_t * dns_ip, uint8_t * name, uint8_t * ip_from_dns);
这个函数的三个参数,文档上也有写

/*
* @brief DNS process
* @details Send DNS query and receive DNS response
* @param dns_ip : DNS server ip
* @param name : Domain name to be queryed
* @param ip_from_dns : IP address from DNS server
* @return -1 : failed. @ref MAX_DOMIN_NAME is too small n
* 0 : failed (Timeout or Parse error)n
* 1 : success
* @note This funtion blocks until success or fail. max time = @ref MAX_DNS_RETRY * @ref DNS_WAIT_TIME
*/
返回1是代表成功了,
dns_ip是dns服务器的ip地址,这个随便百度一个就可以了
name是我们需要解析的域名,比如www.baidu.com
ip_from_dns是返回的指针数据,也就是www.baidu.com的ip地址
DNS最典型的运用是PC的时候的ping





如果没有DNS的话是没有办法直接连接百度的服务器的,只有通过DNS服务器解析之后,获取到百度的IP之后,才能正常的使用
下面是我百度到的几个DNS服务器ip地址,
     
       OneDNS  (112.124.47.27)


  OpenerDNS(42.120.21.30)


  BaiduDNS (180.76.76.76)


  aliDNS (223.5.5.5, 223.6.6.6)


  114DNS (114.114.114.114, 114.114.115.115)


  114DNS安全版 (114.114.114.119, 114.114.115.119)


  114DNS家庭版 (114.114.114.110, 114.114.115.110)


  Dns派:电信/移动/铁通 (101.226.4.6, 218.30.118.6)


  Dns派:联通 (123.125.81.6, 140.2
07.198.6)



好久没写,算是做个开始把
举报

更多回帖

发帖
×
20
完善资料,
赚取积分