基于STM32F407与W5500的FreeModbus TCP移植及其实现
环境:STM32CUBEMX6.6.1 MDK532 freemodbus1.6
首先是创建能驱动W5500的工程
主要用到硬件为SPI UART
SPI速度建议不要太快,SPI1可以设置到40M,但是没必要这么快。
导出工程
添加fputc重定义
找到W5500官方例子
freemodbus tcp需要用到的是TCP server例子
将Ethernet文件夹拷入刚才建好的工程
添加C文件,添加头文件路径
我这里同时用到了HTTP SERVER,没用到可以不加
驱动主要修改是在w5500.conf
添加reset控制引脚,cs控制引脚
还有SPI发送函数
uint8_t SPI_SendByte(uint8_t byte)
{
uint8_t rxbyte=0;
uint8_t SPITimeout =200;
while(__HAL_SPI_GET_FLAG(&hspi1,SPI_FLAG_TXE)==RESET)
{
if((SPITimeout--)==0)
HAL_SPI_ErrorCallback(&hspi1);
return 0;
}
HAL_SPI_TransmitReceive(&hspi1,&byte,&rxbyte,1,100);
return rxbyte;
}
将例子中定时器初始化和中断屏蔽掉,没有用到动态IP分配就不需要定时器中断
然后主函数参考例子,添加函数do_tcp_server();
照葫芦画瓢就行
然后下载固件,连上自己的电脑或者路由器,这里要保证在同一个网段,就是IP地址前面三个数是一样才行,电脑IP可以用ipconfig查看
直连电脑就看上面的地址,我这里没连上,显示的断开
如果连的路由器,就看下面这个,设置W5500的IP为192.168.8.x
可以从串口1看到W5500初始化是否完成,初始化正确的话会正常显示MAC和IP为你设置的值
然后随便找一个TCP测试工具,我这里用的是sscom,输入IP和端口,测试一下官方的回环例子即可。
移植freemodbus
GitHub – cwalter-at/freemodbus: BSD licensed MODBUS RTU/ASCII and TCP slave
下载最新软件包,提取出这些文件,新建一个目录freemodbus
修改porttcp.c里的这几个函数,添加各类接口,添加poll处理流程
BOOL
xMBTCPPortInit( USHORT usTCPPort )
{
BOOL bOkay = FALSE;
switch(getSn_SR(SOCK_TCPS)) // 获取socket的状态
{
case SOCK_CLOSED: // socket处于关闭状态
socket(SOCK_TCPS ,Sn_MR_TCP,local_port,Sn_MR_ND); // 打开socket
break;
case SOCK_INIT: // socket已初始化状态
listen(SOCK_TCPS); // socket建立监听
break;
}
bOkay = TRUE;
return bOkay;
}
BOOL
xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
{
*ppucMBTCPFrame = &aucTCPBuf[0];
*usTCPLength = usTCPBufLen;
/* Reset the buffer. */
usTCPBufLen = 0;
return TRUE;
}
BOOL
xMBTCPPortSendResponse(const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
{
send(SOCK_TCPS,(UCHAR*)pucMBTCPFrame,usTCPLength); // 向Client发送数据
//send_tcp_socket_data(SOCK_TCP_PORT,(UCHAR*)pucMBTCPFrame,usTCPLength);
return TRUE;
}
BOOL
xMBPortTCPPool( void )
{
switch(getSn_SR(SOCK_TCPS)) // 获取socket的状态
{
case SOCK_CLOSED: // socket处于关闭状态
socket(SOCK_TCPS ,Sn_MR_TCP,local_port,Sn_MR_ND); // 打开socket
break;
case SOCK_INIT: // socket已初始化状态
listen(SOCK_TCPS); // socket建立监听
break;
case SOCK_ESTABLISHED: // socket处于连接建立状态
if(getSn_IR(SOCK_TCPS) & Sn_IR_CON)
{
setSn_IR(SOCK_TCPS, Sn_IR_CON); // 清除接收中断标志位
}
usTCPBufLen=getSn_RX_RSR(SOCK_TCPS); // 定义len为已接收数据的长度
if(usTCPBufLen>0)
{
recv(SOCK_TCPS,aucTCPBuf,usTCPBufLen); // 接收来自Client的数据
// aucTCPBuf[len]=0x00; // 添加字符串结束符
// printf("%s\r\n",aucTCPBuf);
// send(SOCK_TCPS,aucTCPBuf,len); // 向Client发送数据
( void )xMBPortEventPost( EV_FRAME_RECEIVED ); //发送已接收到新数据到Modbus-TCP状态机
}
break;
case SOCK_CLOSE_WAIT: // socket处于等待关闭状态
disconnect(SOCK_TCPS); // 断开当前TCP连接
close(SOCK_TCPS); // 关闭当前所使用socket
break;
}
return TRUE;
}
然后新建一个modbus_tcp.c文件,加入一些协议实现,就是那些什么线圈,什么保持寄存器,什么输入寄存器之类的, 02 04 06 01这些个东西。
#include "modbus_tcp.h"
#include "mb.h"
//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x0};
//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x0};
//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE] = {0x00};
//离散寄存器内容
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE]; //= {0x01,0x01,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x01};
/****************************************************************************
* 名 称:eMBRegInputCB
* 功 能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
* usAddress: 寄存器地址
* usNRegs: 要读取的寄存器个数
* 出口参数:
* 注 意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
* +StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
* +LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
* +CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
* 3 区
****************************************************************************/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( ( usAddress >= REG_INPUT_START )
&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - REG_INPUT_START );
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/****************************************************************************
* 名 称:eMBRegHoldingCB
* 功 能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister
* 16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
* 03 读保持寄存器 eMBFuncReadHoldingRegister
* 23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
* usAddress: 寄存器地址
* usNRegs: 要读写的寄存器个数
* eMode: 功能码
* 出口参数:
* 注 意:4 区
****************************************************************************/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if((usAddress >= REG_HOLDING_START)&&\
((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
{
iRegIndex = (int)(usAddress - REG_HOLDING_START);
switch(eMode)
{
case MB_REG_READ://读 MB_REG_READ = 0
while(usNRegs > 0)
{
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE://写 MB_REG_WRITE = 0
while(usNRegs > 0)
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
}
else//错误
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/****************************************************************************
* 名 称:eMBRegCoilsCB
* 功 能:对应功能码有:01 读线圈 eMBFuncReadCoils
* 05 写线圈 eMBFuncWriteCoil
* 15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
* usAddress: 线圈地址
* usNCoils: 要读写的线圈个数
* eMode: 功能码
* 出口参数:
* 注 意:如继电器
* 0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
u8 i;
USHORT readNumber=usNCoils;
USHORT coilValue=0x0000;
if((usAddress >= REG_COILS_START)&&\
((usAddress+usNCoils) <= (REG_COILS_START + REG_COILS_SIZE)))
{
iRegIndex = (int)(usAddress + usNCoils-REG_COILS_START);
switch(eMode)
{
case MB_REG_READ://读 MB_REG_READ = 0
for(i=0;i<usNCoils;i++)
{
readNumber--;
iRegIndex--;
coilValue|=ucRegCoilsBuf[iRegIndex]<<readNumber;
}
if(usNCoils<=8)
{
* pucRegBuffer=coilValue;
}
else
{
* pucRegBuffer++ = (coilValue)&0x00ff;
* pucRegBuffer++ = (coilValue>>8)&0x00ff;
}
break;
case MB_REG_WRITE://写 MB_REG_WRITE = 1
while(usNCoils > 0)
{
// usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
// usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNCoils--;
}
break;
}
}
else//错误
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/****************************************************************************
* 名 称:eMBRegDiscreteCB
* 功 能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
* usAddress: 寄存器地址
* usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注 意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
u8 i;
USHORT readNumber=usNDiscrete;
USHORT coilValue=0x0000;
iRegIndex = (int)(usAddress + usNDiscrete-REG_DISCRETE_START);
if((usAddress >= REG_DISCRETE_START)&&\
((usAddress+usNDiscrete) <= (REG_DISCRETE_START + REG_DISCRETE_SIZE)))
{
for(i=0;i<usNDiscrete;i++)
{
readNumber--;
iRegIndex--;
coilValue|=ucRegDiscreteBuf[iRegIndex]<<readNumber;
}
if(usNDiscrete<=8)
{
* pucRegBuffer=coilValue;
}
else
{
* pucRegBuffer++ = (coilValue)&0x00ff;
* pucRegBuffer++ = (coilValue>>8)&0x00ff;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
随便塞点数据
主函数里添加
eMBPoll();
替换原来的
do_tcp_server();
然后烧写,找一个MODBUS tcp测试软件,我这里用的是Qmodbus
设置好IP PORT function 点send
结果如下