学习 FreeModbus TCP服务器 在stm32f103上的实现
最近学习freemodbus 在stm32f103上的实现,有些心得,记录下来。
modbus rtu的实现在网上可以查到很多资料,很容易就成功了。而 modbus tcp的实现,费了一些周折,终于搞明白了。
测试用架构:stm32f103c8t6 + enc28j60 +EncEthernet + freemodbus TCP。
EncEthernet实现了arp + icmp + tcp协议。
让freemodbus支持tcp,需要修改在mbconfig.h
define MB_ASCII_ENABLED(0)
#define MB_RTU_ENABLED(0 )
#define MB_TCP_ENABLED(1 )
main函数里主要代码:
//定义modbus server 全局变量
#define REG_INPUT_START 1//初始地址为0会有问题
#define REG_INPUT_NREGS 8
static USHORTusRegInputBuf[REG_INPUT_NREGS] = {0x0102, 0x0304, 0x0506, 0x0708, 0x0910, 0x1112, 0x1314, 0x1516};
#define REG_HOLDING_START 1//初始地址为0会有问题
#define REG_HOLDING_NREGS 8
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x0102, 0x0304, 0x0506, 0x0708, 0x0910, 0x1112, 0x1314, 0x1516};
#define REG_COILS_START 1//初始地址为0会有问题
#define REG_COILS_SIZE 32
static UCHARucRegCoilsBuf[REG_COILS_SIZE / 8 + (REG_COILS_SIZE % 8 ? 1 : 0)] = {0};
#define REG_DISCRETE_START 1//初始地址为0会有问题
#define REG_DISCRETE_SIZE 32
static UCHARucRegDiscreteBuf[REG_DISCRETE_SIZE / 8 + (REG_DISCRETE_SIZE % 8 ? 1 : 0)] = {0};
#define BUFFER_SIZE 1518
unsigned char buf[BUFFER_SIZE + 1];
//定义enc28j60缓冲区,收发共用!
unsigned char mymac[6] = {0x54, 0x55, 0x58, 0x10, 0x00, 0x24};
//定义网卡mac地址
unsigned char myip[4] = {192, 168, 2, 8};
//定义网卡IP
unsigned char * info_addr ;
//保存tcp数据的起始地址,buf数据=以太网头+IP头+TCP头+TCP数据。
extern unsigned intinfo_data_len;
//tcp数据长度,这个定义在EncEthernet里定义了
unsigned int info_len;
//tcp数据长度
int main()
{
unsigned int plen;
//enc28j60接收数据长度
unsigned int dat_p;
//TCP数据在buf里的位置
eMBErrorCodeeStatus;
enc28j60_init(mymac);
//enc28j60初始化,包含了SPI初始化
init_ip_arp_udp_tcp(mymac, myip);
//EncEthernet初始化,设置mac和IP地址
eStatus = eMBTCPInit(502 );
//设置freemodbus tcp 接收端口
eStatus = eMBEnable();
//启用freemodbus
while(1) {
plen = enc28j60_packet_receive(buf, BUFFER_SIZE);
//从enc28j60读取数据
if(plen > 0) { //如果有数据
if(eth_type_is_arp_and_my_ip(buf, plen )) //如果是arp请求,则回应
make_arp_answer_from_request(buf);
else if(eth_type_is_ip_and_my_ip(buf, plen ) >0){ //如果包的目的ip是本机
if(buf[IP_PROTO_P] == IP_PROTO_ICMP_V && buf[ICMP_TYPE_P] == ICMP_TYPE_ECHOREQUEST_V)make_echo_reply_from_request(buf, plen );
//如果是ping包,则回应
else if (buf[IP_PROTO_P] == IP_PROTO_TCP_V && buf[TCP_DST_PORT_H_P] == (tcpport>>8) && buf[TCP_DST_PORT_L_P] == (tcpport &0xff)){ //如果是发给本机的tcp包
if (buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V) // 如果是握手sync,则tcp握手
make_tcp_synack_from_syn(buf);
else if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V){ //如果报里有ack标志
init_len_info(buf);
//计算数据包的长度,存储在info_data_len
dat_p = get_tcp_data_pointer();
//获得数据位置
if (dat_p == 0) { //没有数据
if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V) //如果包有Fin标志,对方请求结束连接,则回应
make_tcp_ack_from_any(buf,0);
}else{//如果包含有ack并携带数据
info_addr=&buf[dat_p];
//存储数据首地址
info_len=info_data_len;将数据长度转储到info_len里,因为info_data_len是freemodbus定义的
//info_len=buf[IP_TOTLEN_H_P]*8 + buf[IP_TOTLEN_L_P]-IP_HEADER_LEN-4*(buf[TCP_HEADER_LEN_P]>>4);
//或者这样计算info_len
xMBPortEventPost( EV_FRAME_RECEIVED );
//通知有数据到来,如果没有这一步,则eMBPoll不处理数据
( void)eMBPoll();
//数据处理
}
}
}
}
freemodbus TCP server 还需要实现几个函数
BOOLxMBTCPPortInit( USHORT usTCPPort )
{
if( usTCPPort == 0 ) return FALSE;
else return TRUE;
}
void vMBTCPPortDisable( )
{}
FreeModbusTCP 从xMBTCPPortGetRequest函数获得TCP数据,经过处理后,调用xMBTCPPortSendResponse函数,将处理结果数据作为参数传递,用户在该函数获得处理后的modbus报文,进行进一步处理。
BOOLxMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
{
*ppucMBTCPFrame = info_addr;
//TCP数据地址
*usTCPLength = info_len;
//TCP数据长度
return TRUE;
}
BOOL xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
{
【学习 FreeModbus TCP服务器 在stm32f103上的实现】info_len=usTCPLength;
info_data_len=info_len;
make_tcp_ack_with_data(buf,usTCPLength);
//发送数据
return TRUE;
}
此外eMBPoll函数还需小修改一下,确保一次调用eMBPoll能把数据处理完毕。
将if( xMBPortEventGet( &eEvent ) == TRUE )修改为while( xMBPortEventGet( &eEvent ) == TRUE )
实现Modbus协议,还需添加以下函数,modbus RTU和modbus TCP通用,这个是从网上抄的,感谢!
eMBErrorCodeeMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCodeeStatus = MB_ENOERR;
intiRegIndex;
if( ( usAddress >= REG_INPUT_START )
&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - REG_INPUT_START );
while( usNRegs > 0 )
{
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
//偏移量
int16_t iRegIndex;
//判断寄存器是不是在范围内
if( ( (int16_t)usAddress >= REG_HOLDING_START ) && ( (usAddress + usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS) ) )
{
//计算偏移量
iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);
switch ( eMode )
{
//读处理函数
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
//写处理函数
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
}
else
{
//返回错误状态
eStatus = MB_ENOREG;
}
return eStatus;
//return MB_ENOREG;
}
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
eMBRegisterMode eMode )
{
//错误状态
eMBErrorCode eStatus = MB_ENOERR;
//寄存器个数
int16_t iNCoils = ( int16_t )usNCoils;
//寄存器偏移量
int16_t usBitOffset;
//检查寄存器是否在指定范围内
if( ( (int16_t)usAddress >= REG_COILS_START ) &&
( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
{
//计算寄存器偏移量
usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
switch ( eMode )
{
//读操作
case MB_REG_READ:
while( iNCoils > 0 )
{
*pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
iNCoils -= 8;
usBitOffset += 8;
}
break;
//写操作
case MB_REG_WRITE:
while( iNCoils > 0 )
{
xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,
( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
*pucRegBuffer++ );
iNCoils -= 8;
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
//return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
//错误状态
eMBErrorCode eStatus = MB_ENOERR;
//操作寄存器个数
int16_t iNDiscrete = ( int16_t )usNDiscrete;
//偏移量
uint16_t usBitOffset;
//判断寄存器时候再制定范围内
if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
{
//获得偏移量
usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
while( iNDiscrete > 0 )
{
*pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
iNDiscrete -= 8;
usBitOffset += 8;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
//return MB_ENOREG;
}
后注: 网上EncEthernet代码需要进行一些修改才能用,比如TCP端口,如果是unsigned char类型,则要改为unsigned int类型,不然端口号不能超过255,所有处理端口号的语句都要检查一遍。make_tcp_ack_with_data也要修改,因为原版里这样的
// you must have called init_len_info at some time before calling this function
// dlen is the amount of tcp data (http data) we send in this packet
// You can use this function only immediately after make_tcp_ack_from_any
// This is because this function will NOT modify the eth/ip/tcp header except for
// length and checksum
void make_tcp_ack_with_data(unsigned char *buf, unsignedint dlen)
意思是调用make_tcp_ack_with_data之前必须先给init_len_info赋值,并且调用make_tcp_ack_from_any后才能调用make_tcp_ack_with_data,要修改make_tcp_ack_with_data,使之能独立重建一个tcp包。
*这个程序在结束TCP连接的时候只用了两次挥手,而不是四次挥手,但是用起来没有问题。
推荐阅读
- CVE-2020-16898|CVE-2020-16898 TCP/IP远程代码执行漏洞
- 由浅入深理解AOP
- 继续努力,自主学习家庭Day135(20181015)
- python学习之|python学习之 实现QQ自动发送消息
- 一起来学习C语言的字符串转换函数
- 定制一套英文学习方案
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)
- 《深度倾听》第5天──「RIA学习力」便签输出第16期
- 如何更好的去学习
- 【韩语学习】(韩语随堂笔记整理)