打印
[STM32F4]

【Nucleo设计分享】基于411RET6板卡的W5500网络UDP温度传输_申酷

[复制链接]
3582|21
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
湛只为无双|  楼主 | 2015-2-23 11:06 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 湛只为无双 于 2015-2-23 11:33 编辑



W5500_UDP_DS18B20调试笔记


本次设计中主要涉及了对W5500的底层操作,以及怎样进行UDP连接,TCPService连接和TCPClient连接三种常用的传输层协议。根据以前所写的程序,由于TCP连接是可靠的连接,需要三次握手释放的时候需要再次进行释放,底层的不可靠,导致了TCP进行连接后无法进行第二次连接,这是一个BUG,而且查阅了很多资料都没有一个良好的解决方法,通过这次查阅资料和查看别人的程序,无意中成功了,可以随时进行连接和断开,并且不会出现死机的情况。


具体的更改方法如下:


一、源码的移植:


首先是进行底层驱动的移植,这里参考了EmbedNet论坛飞鸿踏雪的移植教程,并把其中不需要的部分给去除了(原网页—— http://www.embed-net.com/thread-55-1-1.html)。接着是下载http://wizwiki.net/wiki/doku.php?id=products:w5500:driver网站下的驱动源码,添加进入工程后需要更改所下载源码的四个地方,分别是片选确定,片选取消,SPI读函数以及SPI写函数,具体如下:
wizchip_config.c中的_WIZCHIP结构体由:
_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 =
{
        .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
};

也就是注释掉IF.BUS,使用IF.SPI接口,然后更改void  wizchip_cs_select(void)函数中的内容和void wizchip_cs_deselect(void)函数中的内容如下:

void wizchip_cs_select(void)
{
        GPIO_ResetBits(GPIOB,GPIO_Pin_6);
};

void wizchip_cs_deselect(void)
{
        GPIO_SetBits(GPIOB,GPIO_Pin_6);
};

     这里使用你打算使用的片选信号的拉高和拉低;然后是SPI接口的读写
SPI读函数:
uint8_t wizchip_spi_readbyte(void)
{
        return SPI1_ReadWriteByte(0xff);
}

    和SPI写函数:
void wizchip_spi_writebyte(uint8_t wb)
{
        SPI1_ReadWriteByte(wb);
}




当你把这些都完成了以后,就可以使用W5500的移植了。


二、接口的初始化操作:


接口的初始化主要包括了对SPI1的模式设置,以及开启SPI1,还有就是GPIO接口模式的初始化,以及片选的设置。
SPI的初始化:
void SPI1_Init(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        SPI_InitTypeDef  SPI_InitStructure;
        static int SPI1_InitFlag=0;
        
        if(SPI1_InitFlag == 0)
        {
                RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE);
               
                GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
                GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
                GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
                GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
                GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
                GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
                GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
                GPIO_Init(GPIOA, &GPIO_InitStructure);
               
                SPI_I2S_DeInit(SPI1);
                SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工
                SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//8位数据模式
                SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//空闲模式下SCK为1
                SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//数据采样从第2个时间边沿开始
                SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS软件管理
                SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//波特率
                SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//大端模式
                SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC多项式
                SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主机模式
                SPI_Init(SPI1, &SPI_InitStructure);
                SPI_Cmd(SPI1, ENABLE);
                SPI1_InitFlag=1;
        }
}

片选的初始化:
void SPI1_CS2Pin_Init(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB , ENABLE);
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//NET_CS:PB6
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
        GPIO_Init(GPIOB, &GPIO_InitStructure);
        GPIO_SetBits(GPIOB, GPIO_Pin_6);//不选中
}






沙发
湛只为无双|  楼主 | 2015-2-23 11:06 | 只看该作者
本帖最后由 湛只为无双 于 2015-2-23 11:29 编辑



三、W5500的相关配置:
W5500的配置主要包括了对SOCKETFIFOSIZE设置,在这里采用了EmbedNet论坛中所给出的默认配置,即八个SOCKET的收发缓冲全为2KB。具体设置如下:
uint8_t memsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};
/* SOCKET缓冲的初始化——使用了memsize中的数据进行发送接收缓冲的初始化 */
if(ctlwizchip(CW_INIT_WIZCHIP,(void*)memsize) == -1)
{
        printf("WIZCHIP Initialized fail.\r\n");
        while(1);
}




第二步是对PHY连接状态的初始化,等待PHY连接成功:
/* PHY的链接状态初始化 */
do
{
        if(ctlwizchip(CW_GET_PHYLINK, (void*)&tmp) == -1)
        {
                printf("Unknown PHY Link stauts.\r\n");
        }
}
while(tmp == PHY_LINK_OFF);


第三步是对本机的设置,包括了本机的MAC,本机的IP、网关、子网掩码、DNS服务器和DHCP状态:
wiz_NetInfo gNETINFO = { .mac = {0x01, 0x23, 0x45,0x67, 0x89, 0xab},
                         .ip = {192, 168, 2, 123},
                         .sn = {255,255,255,0},
                         .gw = {192, 168, 2, 1},
                         .dns = {0,0,0,0},
                         .dhcp = NETINFO_STATIC
                        };
/* 网口的初始化 包括了MAC 本机IP 网关 子网掩码 DNS服务器 和 DHCP状态  */
do
{
        if(network_init() == 0)
        {
                printf("Net is OK\r\n");
                break;
        }
        else
        {
                printf("Net is Error\r\n");
        }
        delay_ms(500);
}while(1);
//当初始换这个后,如果没有错误,实际上就已经可以PING通了



在这个里面的network_init函数如下:
在此可以判断W5500是否已经连接上了:


u8 network_init(void)
{
        wiz_NetInfo lNETINFO={0};
        uint8_t *plNETINFO,*pgNETINFO,i;
        
        ctlnetwork(CN_SET_NETINFO, (void*)&gNETINFO);
        ctlnetwork(CN_GET_NETINFO, (void*)&lNETINFO);
        
        plNETINFO=(uint8_t*)&lNETINFO;
        pgNETINFO=(uint8_t*)&gNETINFO;
        
        for(i=0;i<sizeof(gNETINFO);i++)
        {
                if(*plNETINFO != *pgNETINFO)
                {
                        return 1;
                }
                plNETINFO++;
                pgNETINFO++;
        }
}
//当有W5500正常将会返回0,否则返回1。
第四步是可选设置,设置溢出时间和最大重发次数:

setRTR(2000);//设置溢出时间值
setRCR(3);//设置最大重新发送的次数


到了这里W5500的相关配置已经配置完成了,可以通过PING来查看配置是否成功,如图一所示。



如果能够到了这里,说明网络的连接已经完成了,总体的任务完成了一半。


四、W5500UDP设置:


UDP的设置如下,是使用了状态机的形式:
while(1)
{
        if( (ret = udp_ds18b20(SOCK_UDPS, gDATABUF, 3000)) < 0)
        {
                printf("SOCKET ERROR : %ld\r\n", ret);
        }
}

int32_t udp_ds18b20(uint8_t sn, uint8_t* buf, uint16_t port)
{
        int32_t  ret;
        uint16_t size;
        uint8_t  destip[4];
        uint16_t destport;
        
        switch(getSn_SR(sn))
        {
        case SOCK_UDP :
                if((size = getSn_RX_RSR(sn)) > 0)
                {
                        if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE;
                        ret = recvfrom(sn,buf,size,destip,(uint16_t*)&destport);
                        if(ret <= 0)
                        {
                                printf("%d: recvfrom error. %ld\r\n",sn,ret);
                                return ret;
                        }
                        size = (uint16_t) ret;
                        sentsize = 0;
                        //sprintf((char*)buf,"DS18B20:%.1f℃\r\n",DS18B20_Get_Temp());
                        sprintf((char*)buf,"%.1f\r\n",DS18B20_Get_Temp());
                        ret = sendto(sn,buf,strlen((char*)buf),destip,destport);
                        if(ret < 0)
                        {
                                printf("%d: sendto error. %ld\r\n",sn,ret);
                                return ret;
                        }
                }
                break;
        case SOCK_CLOSED:
                printf("W5500 UDP%d:Start\r\n",sn);
                if((ret=socket(sn,Sn_MR_UDP,port,0x00)) != sn)
                        return ret;
                printf("W5500 UDP%d:Opened, port [%d]\r\n",sn, port);
                break;
        default :
                break;
        }
        return 1;
}




在这个里面,在最开始的时候是出于SOCK_CLOSED状态,进行了SOCKET模式的设置,和W5500SOCKET端口号设置,通过串口会打印出相关的信息,包括了配置成功和失败的原因。
通过串口调试助手上电后可以看到如下信息,如图二所示。



通过图二可以看出对W5500所设置的MAC地址、本机IP地址SIP、网关GAR、子网掩码SUBDNS;然后通过读取设置,并与原设置进行比较来判断出来网络是否配置成功。


接着就是开始了W5500UDP,并显示使用的是端口1,如果UDP开启成功后会打印出相关信息和端口号,由图二可以得出端口号为3000


五、实验现象和结论:
最后就是当通过电脑的UDP向端口发送了任何信息,就向相对应的IP端口返回当前的温度值,这是为了使任何电脑都能够与此相连,并获得温度值所设计的,具体的现象如图三所示。



通过图三可以看出循环发送数据后可以返回相对应的温度值,在此可以查看温度传感器DS18B20的灵敏度,通过将数据复制到MATLAB中进行绘图,得到了如下结果,如图四。
通过图四可以看出第一次用手稍微碰到DS18B20后温度迅速上升,当手指离开后温度缓慢下降,接着再次用手指一直捏着传感器,温度开始迅速的上升,一直到了顶峰22度后停止,中间手指稍微松了一下后温度稍有下降,手指离开后温度显示迅速下降一段时间后缓慢下降,这是与周围的环境温度有关的;在最后温度下降太慢了,向传感器吹气使其温度下降也是能够查看出这个细节。总之,通过本设计,可以实现温度的远程监测,并对外界微小的温度变化进行捕捉,并且具有成本控制较好,软件编程简单等特点。


六、附录——DS18B20程序:

在本次设计中,使用了ALIENTEK正点原子所出的探索者STM32F407开发板中的DS18B20程序源码,再次提出感谢。在使用过程中,处于接口的设计,更改了所使用的端口和端口的配置,以及获取温度的部分源码。

①:其中端口由原来的PG9更改为了PB9,端口的更改如下:
将ds18b20.h中的IO端口操作由原来的:
//IO方向设置
#define DS18B20_IO_IN()  {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=0<<9*2;}        //PG9输入模式
#define DS18B20_IO_OUT() {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=1<<9*2;}         //PG9输出模式

////IO操作函数                                                                                          
#define        DS18B20_DQ_OUT PGout(9) //数据端口        PG9
#define        DS18B20_DQ_IN  PGin(9)  //数据端口        PG9
改为了:
//IO方向设置
#define DS18B20_IO_IN()  {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}        //PB9输入模式
#define DS18B20_IO_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}         //PB9输出模式

////IO操作函数
#define        DS18B20_DQ_OUT PBout(9) //数据端口        PB9
#define        DS18B20_DQ_IN  PBin(9)  //数据端口        PB9
②:将ds18b20.c中的端口初始化由原来的:
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
u8 DS18B20_Init(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;

        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//使能GPIOG时钟

        //GPIOG9
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
        GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化

        DS18B20_Rst();
        return DS18B20_Check();
}

更改为了:
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
u8 DS18B20_Init(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;

        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟

        //GPIOB9
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
        GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化

        DS18B20_Rst();
        return DS18B20_Check();
}


③最后是获取温度的操作,为了能够很好的还原所得到的原始温度,将原来的获取温度函数返回值由整形更改为了浮点型:
具体的操作是由原来的:
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
short DS18B20_Get_Temp(void)
{
        u8 temp;
        u8 TL,TH;
        short tem;
        DS18B20_Start ();                    // ds1820 start convert
        DS18B20_Rst();
        DS18B20_Check();
        DS18B20_Write_Byte(0xcc);// skip rom
        DS18B20_Write_Byte(0xbe);// convert
        TL=DS18B20_Read_Byte(); // LSB
        TH=DS18B20_Read_Byte(); // MSB
        if(TH>7)
        {
                TH=~TH;
                TL=~TL;
                temp=0;//温度为负
        }
        else temp=1; //温度为正
        tem=TH; //获得高八位
        tem<<=8;
        tem+=TL;//获得底八位
        tem=(double)tem*0.625;//转换
        if(temp)return tem; //返回温度值
        else return -tem;
}

更改为了:

//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
float DS18B20_Get_Temp(void)
{
        u8 temp;
        u8 TL,TH;
        short tem;
        DS18B20_Start ();                    // ds1820 start convert
        DS18B20_Rst();
        DS18B20_Check();
        DS18B20_Write_Byte(0xcc);// skip rom
        DS18B20_Write_Byte(0xbe);// convert
        TL=DS18B20_Read_Byte(); // LSB
        TH=DS18B20_Read_Byte(); // MSB
        if(TH>7)
        {
                TH=~TH;
                TL=~TL;
                temp=0;//温度为负
        }
        else temp=1; //温度为正
        tem=TH; //获得高八位
        tem<<=8;
        tem+=TL;//获得低八位
        if(temp)return (double)tem*0.0625; //返回温度值
        else return -(double)tem*0.0625;
}

并记得更改相对应的ds18b20.h中的函数声明。
short DS18B20_Get_Temp(void);        //获取温度

改为:
float DS18B20_Get_Temp(void);        //获取温度


七、感谢:


在本次设计中,一个人的力量是远远不够的,非常感谢网络上论坛背后那些默默付出的人们,本次设计中参考了EmbedNet论坛飞鸿踏雪所提供的移植教程和相关W5500底层驱动的网络链接,在DS18B20的程序中,使用了ALIENTEK正点原子所出的探索者STM32F407开发板中相关源码,再次对以上两位提出由衷的感谢,特此提出。
最后,感谢21ic网提供的STM32F411RET6的NUCLEO板卡,同样也感谢在寄板卡过程中付出的每一位工作人员,能够有这次机会和广大网友就行分享相关的知识。




















PING截图.png (38.48 KB )

图一 W5500连接后PING通图示

图一 W5500连接后PING通图示

DS18B20串口数据.png (33.09 KB )

图二 串口返回的调试图示

图二 串口返回的调试图示

DS18B20网口UDP数据.png (33.59 KB )

图三 网口UDP返回的温度信息

图三 网口UDP返回的温度信息

DS18B20注释图.png (7.8 KB )

图四 数据导入至MATLAB后得到的图形

图四 数据导入至MATLAB后得到的图形

使用特权

评论回复
板凳
湛只为无双|  楼主 | 2015-2-23 11:06 | 只看该作者
本帖最后由 湛只为无双 于 2015-2-23 11:32 编辑

老规矩 最后上传相关源码和数据

Nucleo411_W5500_UDP_DS18B20.zip

499.59 KB

源代码以及内部包含了测得的数据的保存

使用特权

评论回复
地板
湛只为无双|  楼主 | 2015-2-23 11:18 | 只看该作者
本帖最后由 湛只为无双 于 2015-2-23 11:35 编辑

最后 是下一步的W5500上传数据至Yeelink,是下一篇帖子的内容,也是最后一步,做完了就算是完成了当初申请板子所填的申请要求。
另外,天冷了,恳求版主给个裤子穿穿:lol

使用特权

评论回复
5
人民币的幻想| | 2015-2-23 12:33 | 只看该作者
俺感觉还可以,顶一个。咋不加个phy芯片试试?

使用特权

评论回复
6
湛只为无双|  楼主 | 2015-2-23 13:21 | 只看该作者
*币的幻想 发表于 2015-2-23 12:33
俺感觉还可以,顶一个。咋不加个phy芯片试试?

对于W5500已经有phy了,不用再加了,可以直接用。

使用特权

评论回复
7
zhjerry| | 2015-2-23 21:05 | 只看该作者
407 or 411?

使用特权

评论回复
8
dong_abc| | 2015-2-24 10:37 | 只看该作者
407,411没有集成以太网?

使用特权

评论回复
9
湛只为无双|  楼主 | 2015-2-24 13:53 | 只看该作者
dong_abc 发表于 2015-2-24 10:37
407,411没有集成以太网?

这个是外部的以太网,用spi接口来实现网络通信,就像enc28j60一样

使用特权

评论回复
10
沉默胜过白金| | 2015-2-24 14:49 | 只看该作者
keil5 一直用不惯,keil4 没有这个型号。。。

使用特权

评论回复
11
湛只为无双|  楼主 | 2015-2-24 20:45 | 只看该作者
沉默胜过白金 发表于 2015-2-24 14:49
keil5 一直用不惯,keil4 没有这个型号。。。

那你是怎么给你收到的板子写程序的?  直接用网页的形式么?

使用特权

评论回复
12
tianhaolan| | 2015-2-25 14:25 | 只看该作者
不错不错

使用特权

评论回复
13
第三世界| | 2015-2-26 17:34 | 只看该作者
很详细啊,  就是不是很懂, 硬件怎么显示

使用特权

评论回复
14
powerful1| | 2015-2-26 21:39 | 只看该作者
很详细啊,谢谢分享

使用特权

评论回复
15
energy1| | 2015-2-26 22:59 | 只看该作者
不错的资料,很详细,谢谢分享

使用特权

评论回复
16
搞IT的| | 2015-2-28 18:30 | 只看该作者
由于TCP连接是可靠的连接,需要三次握手释放的时候需要再次进行释放。

使用特权

评论回复
17
ljl342301| | 2015-6-29 17:08 | 只看该作者
好东西,很详细,最近正在学

使用特权

评论回复
18
xiaopohaixx| | 2015-12-17 15:03 | 只看该作者
请教一下,UDP部分的内容,为何要做一个先接收再发送温度传感器的值呢,接收这部分的代码的作用是什么呢

使用特权

评论回复
19
sourceInsight| | 2015-12-17 15:37 | 只看该作者
这么多代码,看上去真有点头疼啊!!!!

使用特权

评论回复
20
CallReceiver| | 2015-12-17 15:55 | 只看该作者
不是很清楚源码的移植过程,学习学习了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

15

主题

171

帖子

9

粉丝