基于STM32和W5500的Modbus TCP通讯
在最近的一个项目中需要实现Modbus TCP通讯,而选用的硬件平台则是STM32F103和W5500,软件平台则选用IAR EWAR6.4来实现。
1、移植千的准备工作
为了实现Modbus TCP通讯首先需要下载W5500的驱动源码,可以到WIZnet的官网下载:
http://wizwiki.net/wiki/doku.php?id=products:w5500:driver
下载下来的压缩包,解压后如下图:
需要将ethernet文件夹拷贝到我们的项目目录中:
并在IAR的项目下添加相关的文件和路径,主要是socket.c、w5500.c、wizchip_.conf.c三个文件。这三个文件分别实现socket、硬件驱动及相关通讯配置功能,具体可以查看相应的源码级手册。
并在如下图所示的项目选项设置中添加Ethernet和Ethernet\W5500目录。
2、移植过程和代码编写
在完成以上工作后就可以开始真正地移植工作了。具体步骤如下:
- 硬件配置及初始化。
- 以太网通讯配置的初始化。
- 实现具体的通讯过程。
2.1、硬件的配置及初始化
由于W5500通过SPI接口与STM32通讯,所以硬件配置和初始化是非常简单的,与W5500实际上没有关系,使一些通用的操作。事实上就是STM32F103的SPI接口初始化的过程,需要实现RCC、GPIO以及SPI的初始化就可以了。关于这部分可以查看ST的例程。
2.2、以太网通讯配置的初始化
以太网通讯配置的初始化主要有三个方面的内容:
- 注册TCP通讯相关的回调函数 RegisterFunction();
- 初始化芯片参数 ChipParametersConfiguration();
- 初始化网络通讯参数 NetworkParameterConfiguration()
三个函数的具体实现内容如下:
//函数注册,首先,应由用户实现SPI注册回调函数来访问WIZCHIP void RegisterFunction(void) { //临界区回调函数 reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit); //注册临界区函数 //片选回调函数 #if _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_VDM_ reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);//注册SPI片选信号函数 #elif _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_FDM_ reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect); // CS必须为低电平. #else #if (_WIZCHIP_IO_MODE_ & _WIZCHIP_IO_MODE_SIP_) != _WIZCHIP_IO_MODE_SIP_ #error "Unknown _WIZCHIP_IO_MODE_" #else reg_wizchip_cs_cbfunc(wizchip_select, wizchip_deselect); #endif #endif //SPI的读写回调函数 reg_wizchip_spi_cbfunc(SPI_ReadByte, SPI_WriteByte); //注册读写函数 }
注册函数实际上就是函数指针的调用,可参考C语言函数指针部分内容。对于以上注册的函数,SPI_WriteByte需要说明一下,无论是用可函数还是直接操作寄存器,在写完之后都需要再读一下(红色部分),否则就会在客户端出现连接TCPServer超时的报警,没明白什么原因。
//写1字节数据到SPI总线 void SPI_WriteByte(uint8_t TxData) { // while((SPI2->SR&SPI_I2S_FLAG_TXE)==0); //等待发送区空 // SPI2->DR=TxData; //发送一个byte // while((SPI2->SR&SPI_I2S_FLAG_RXNE)==0); //等待接收完一个byte // SPI2->DR; while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET); //等待发送区空 SPI_I2S_SendData(SPI2,TxData); //发送一个byte while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET); //等待接收完一个byte SPI_I2S_ReceiveData(SPI2); //返回接收的数据 }
初始化芯片参数:
//初始化芯片参数 void ChipParametersConfiguration(void) { uint8_t tmp; uint8_t memsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}}; //WIZCHIP SOCKET缓存区初始化 if(ctlwizchip(CW_INIT_WIZCHIP,(void*)memsize) == -1){ //printf("WIZCHIP Initialized fail.\r\n"); while(1); } //PHY物理层连接状态检查 do{ if(ctlwizchip(CW_GET_PHYLINK, (void*)&tmp) == -1){ //printf("Unknown PHY Link stauts.\r\n"); } }while(tmp == PHY_LINK_OFF); }
以上实现网络物理层的配置。
初始化WIZCHIP中的网络参数信息:
//初始化WIZCHIP中的网络参数信息 void NetworkParameterConfiguration(void) { uint8_t tmpstr[6]; ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO); ctlnetwork(CN_GET_NETINFO, (void*)&gWIZNETINFO); ctlwizchip(CW_GET_ID,(void*)tmpstr); }
其中gWIZNETINFO是一个wiz_NetInfo类型的结构体变量,该结构体在wizchip_conf.h中定义,用于设置mac地址、IP地址等网络参数,具体如下:
typedef struct wiz_NetInfo_t { uint8_t mac[6]; ///< Source Mac Address uint8_t ip[4]; ///< Source IP Address uint8_t sn[4]; ///< Subnet Mask uint8_t gw[4]; ///< Gateway IP Address uint8_t dns[4]; ///< DNS server IP Address dhcp_mode dhcp; ///< 1 - Static, 2 - DHCP }wiz_NetInfo;
至此网络部分的初始化就已完成。
2.3、具体通讯过程的实现
经过前面的配置网络已经可以ping通了,下面可以实现具体的应用。对于我这个项目就是可是实现Modbus TCP的编写了。
编写TCP Server,这部分有很多资料,直接附代码:
//TCP服务器数据通讯 int32_t TCPServer(uint8_t sn, uint16_t port) { int32_t ret; uint8_t socketStatus=getSn_SR(sn); switch(socketStatus) { case SOCK_ESTABLISHED : { if(getSn_IR(sn) & Sn_IR_CON) { setSn_IR(sn,Sn_IR_CON); } uint16_t size=0; if((size = getSn_RX_RSR(sn)) > 0) { if(size > DATA_BUFFER_SIZE) { size = DATA_BUFFER_SIZE; } uint8_t rxBuffer[DATA_BUFFER_SIZE]; ret = recv(sn,rxBuffer,size); if(ret <= 0) { return ret; } //添加数据解析及响应的函数 uint8_t txBuffer[DATA_BUFFER_SIZE]; uint16_t length=ReceivedDataParsing(rxBuffer,txBuffer); uint16_t sentsize=0; while(length != sentsize) { ret = send(sn,txBuffer+sentsize,length-sentsize); if(ret < 0) { close(sn); return ret; } sentsize += ret; // 不用管SOCKERR_BUSY, 因为它是零. } } break; } case SOCK_CLOSE_WAIT : if((ret=disconnect(sn)) != SOCK_OK) { return ret; } break; case SOCK_INIT : if( (ret = listen(sn)) != SOCK_OK) { return ret; } break; case SOCK_CLOSED: if((ret=socket(sn,Sn_MR_TCP,port,0x00)) != sn) { return ret; } break; default: break; } return 1; }
其中ReceivedDataParsing(rxBuffer,txBuffer)实现具体的Modbus协议,根据具体的需求而定。
通过Modscan连接测试,结果正确。
欢迎关注:
如果阅读这篇文章让您略有所得,还请点击下方的【好文要顶】按钮。
当然,如果您想及时了解我的博客更新,不妨点击下方的【关注我】按钮。
如果您希望更方便且及时的阅读相关文章,也可以扫描上方二维码关注我的微信公众号【木南创智】