Modbus_TCP在Android与STM32F4上的应用

今天记录的是在学习了Modbus_RTU之后学习学习Modbus_TCP,并通过android系统的APP读写保持寄存器。

一、引言

为了学习为了学习为了学习。重要的事情说三遍。好了,that's over.

二、实验目的

为了实现移动端与终端的数据通信,应用MODBUS的通信协议,实现单片机与手机端的数据通信。

三、实验器材

硬件:1、原子哥的STM32F429IGTx开发板。2、安卓手机一部
软件:1、MDK5。2、STM32CUBEMX。3、Android Studio

四、实验原理

本实验的实验原理主要分为通信协议部分、硬件部分、软件部分。

1、modbus通信协议

modbus是很久以前的通信协议了。大概给介绍一下。
Modbus协议包括两个通信规程中使用的MODBUS应用层和服务规范
a.串行链路的RTU协议(这个在之前介绍过)
b.TCP/IP上的MODBUS。

Modbus_TCP在Android与STM32F4上的应用_第1张图片
上图是MODBUS的IOS层模型的两个通信规程。本次实验主要介绍的就是Modbus on TCP.modbus是OSI模型的第七层报文传输协议,它连接至不同类型总线或网络的设备之间提供客户机/服务器通信。MODBUS是一个请求/应答协议,并提供功能码规定的服务。本文将介绍MODBUS协议的各个功能码,并主要介绍本实验用到的读写寄存器的功能码。
下面是关于MODBUS协议允许在各种网络体系结构内进行简单通信。包括PLC、HMI以及一些IO口通信

Modbus_TCP在Android与STM32F4上的应用_第2张图片
MODBUS协议是通过报文传输数据,报文的结构是由地址域、功能码、数据和差错校验组成的。一般情况下Modbus是使用CRC校验的,但是在实际的使用中,Modbus_TCP是不用CRC校验的。因为TCP协议本身就存在校验+重传功能,所以MODBUS协议在TCP的应用上是不需要CRC校验的。
下图是modbus的数据帧结构
Modbus_TCP在Android与STM32F4上的应用_第3张图片
因为modbus是请求/应答的形式,所以在整个通信协议当中存在主站和从站,而且是一个主站+多个从站的组合形式。而且通信都是modbus主站发送查询指令,然后从站根据主站发送过来的功能码进行相应的操作。
当服务器对客户机响应时,它使用功能码来指示正常(无差错)响应或者出现某种差错(称为异常响应)。对于一个正常响应来说,服务器仅对原始功能码响应。
下图分别是对正常响应以及异常响应的解析图。Modbus_TCP在Android与STM32F4上的应用_第4张图片
Modbus_TCP在Android与STM32F4上的应用_第5张图片
关于modbus的一个逻辑流程图如下:
Modbus_TCP在Android与STM32F4上的应用_第6张图片
modbus的功能码有三个类型,分别是公共功能码、用户定义功能码、保留功能码。在这里主要使用的是公共功能码,便介绍公共功能码,至于其他功能码请自行翻阅modbus通信协议。
公共功能码一共有13个。其中有单个比特访问、16个比特访问、文件记录访问以及封装借口。
Modbus_TCP在Android与STM32F4上的应用_第7张图片
在这里主要应用的就是对保持寄存器的读写。因此主要使用的功能码就是03读保持寄存器功能码,以及06写单个寄存器功能码。
1.03 (0x03)读保持寄存器
在一个远程设备中,,使用该功能码读取保持寄存器连续块的内容。请求PDU说明了起始寄存器地址和寄存器数量。从零开始寻址寄存器。因此,寻址寄存器1-16为0-15.
将响应报文中的寄存器数据围城每个寄存器有两字节,在每个字节中直接地调整二进制内容。对于每个寄存器,第一个字节包括高位,并且第二个字节也包括低位比特。
Modbus_TCP在Android与STM32F4上的应用_第8张图片
下图是一个请求读寄存器108-110的实例:
Modbus_TCP在Android与STM32F4上的应用_第9张图片
将寄存器108的内容表示为两个十六进制字节值022B,或十进制555。将寄存器109-110的内容分别表示为十六进制0000和0064,或者十进制0和100。
Modbus_TCP在Android与STM32F4上的应用_第10张图片
2、06(0x06)
(偷点懒,直接上图。请大佬自行理解)
Modbus_TCP在Android与STM32F4上的应用_第11张图片
Modbus_TCP在Android与STM32F4上的应用_第12张图片
关于modbus大概就介绍到这里了,详细具体的细节还请自行了解modbus协议。如若有需要文档的,可以文末留言邮箱。

2、硬件

因本实验所用硬件载体为原子的阿波罗开发板STM32F429IGTx。是在STM32CUBEMX开发平台上配置相应的功能。由于是TCP/IP,而在开发板上只能板载轻型的TCP/IP协议即LWIP。下面是在CUBEMX上一些相应的配置。
使能ETH

Modbus_TCP在Android与STM32F4上的应用_第13张图片
由于要用到网络,需要用到操作系统,这里使能的是FREE RTOS
Modbus_TCP在Android与STM32F4上的应用_第14张图片
关于LWIP的使能,就是大概介绍一下
Modbus_TCP在Android与STM32F4上的应用_第15张图片
主要的都在这里了,毕竟是之前介绍过,就不具体详细讲了。如果不了解想知道的话可以看之前的博客。
https://mp.csdn.net/postedit/90084421
Modbus_TCP在Android与STM32F4上的应用_第16张图片
至于时钟树的配置都是不超过限定钟频即可。
关于硬件的配置就大体记录到这里了。

3、软件

软件这一部分包括开发板的程序以及安卓端的APP程序。
(1)开发板软件部分
在这里主要是移植了MODBUS的协议包。说实话,modbus_tcp比RTU简单超多,基本上把协议包移植过去就能直接使用了
Modbus_TCP在Android与STM32F4上的应用_第17张图片
协议包的内容我就不具体讲了,有需要的可以留言邮箱。都是根据Modbus协议转换成的代码。我也会把这个发到github上,有需要的可以自行下载。链接见文末。
在rtos的空闲任务队列中,加入两行代码

void TCPTask02(void const * argument)
{
  /* USER CODE BEGIN TCPTask02 */
	
	eMBTCPInit(0);	
	eMBEnable();
  /* Infinite loop */
  for(;;)
  {
		//socket_server_init();
		eMBPoll();
    osDelay(1);
  }
  /* USER CODE END TCPTask02 */
}

关于这两个函数的详细代码

#if MB_TCP_ENABLED > 0
eMBErrorCode
eMBTCPInit( USHORT ucTCPPort )
{
    eMBErrorCode    eStatus = MB_ENOERR;

    if( ( eStatus = eMBTCPDoInit( ucTCPPort ) ) != MB_ENOERR )
    {
        eMBState = STATE_DISABLED;
    }
    else if( !xMBPortEventInit(  ) )
    {
        /* Port dependent event module initalization failed. */
        eStatus = MB_EPORTERR;
    }
    else
    {
        pvMBFrameStartCur = eMBTCPStart;
        pvMBFrameStopCur = eMBTCPStop;
        peMBFrameReceiveCur = eMBTCPReceive;
        peMBFrameSendCur = eMBTCPSend;
        pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBTCPPortClose : NULL;
        ucMBAddress = MB_TCP_PSEUDO_ADDRESS;
        eMBCurrentMode = MB_TCP;
        eMBState = STATE_DISABLED;
    }
    return eStatus;
}
#endif

eMBEnable( void )
{
    eMBErrorCode    eStatus = MB_ENOERR;

    if( eMBState == STATE_DISABLED )
    {
        /* Activate the protocol stack. */
        pvMBFrameStartCur(  );
        eMBState = STATE_ENABLED;
    }
    else
    {
        eStatus = MB_EILLSTATE;
    }
    return eStatus;
}
eMBPoll( void )
{
    static UCHAR   *ucMBFrame;
    static UCHAR    ucRcvAddress;
    static UCHAR    ucFunctionCode;
    static USHORT   usLength;
    static eMBException eException;

    int             i;
    eMBErrorCode    eStatus = MB_ENOERR;
    eMBEventType    eEvent;

    /* Check if the protocol stack is ready. */
    if( eMBState != STATE_ENABLED )
    {
        return MB_EILLSTATE;
    }

    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    if( xMBPortEventGet( &eEvent ) == TRUE )
    {
        switch ( eEvent )
        {
        case EV_READY:
            break;

        case EV_FRAME_RECEIVED:
            eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
            if( eStatus == MB_ENOERR )
            {
                /* Check if the frame is for us. If not ignore the frame. */
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
                {
                    ( void )xMBPortEventPost( EV_EXECUTE );
                }
            }
            break;

        case EV_EXECUTE:
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
            eException = MB_EX_ILLEGAL_FUNCTION;
            for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
            {
                /* No more function handlers registered. Abort. */
                if( xFuncHandlers[i].ucFunctionCode == 0 )
                {
                    break;
                }
                else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
                {
                    eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
                    break;
                }
            }

            /* If the request was not sent to the broadcast address we
             * return a reply. */
            if( ucRcvAddress != MB_ADDRESS_BROADCAST )
            {
                if( eException != MB_EX_NONE )
                {
                    /* An exception occured. Build an error frame. */
                    usLength = 0;
                    ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
                    ucMBFrame[usLength++] = eException;
                }
                if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
                {
                    vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
                }                
                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
            }
            break;
 
        case EV_FRAME_SENT:
            break;
        }
    }
    return MB_ENOERR;
}

这些就是在此程序中应用到的主要代码了。就不逐句分析了。
[协议包的链接放这里吧]
(2)APP端程序
下面是关于app的GUI的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="64dp"
    android:orientation="horizontal"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingBottom="8dp"
    android:paddingTop="8dp"
    >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="name"
        android:layout_weight="1"
        android:layout_gravity="left"
        android:gravity="center"
        android:textSize="@dimen/textsize_edit"
        android:textColor="@color/black"
        android:background="@color/white"/>
    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:hint="请输入值"
        android:textColorHint="@color/text_hint"
        android:textColor="@color/text_hint"
        android:layout_weight="1"
        android:layout_gravity="right"
        android:gravity="center"
        android:textSize="@dimen/textsize_edit"
        android:background="@drawable/bg_edittext"
        />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:gravity="center"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingBottom="8dp"
    android:paddingTop="8dp"
    android:layout_height="64dp"
    >


    <TextView
        android:id="@+id/item_alise"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:layout_weight="1"
        android:text="地址"
        android:textSize="@dimen/textsize_edit"
        android:textColor="@color/black"
        android:background="@color/white"/>

    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:background="@color/gray" />

    <TextView
        android:id="@+id/item_value"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:layout_alignParentRight="true"
        android:layout_weight="1"
        android:text="值"
        android:textColor="@color/black"
        android:textSize="@dimen/textsize_edit"
        android:background="@color/white"/>

</LinearLayout>

这个是一部分,由于我使用是直接用GUI编写,所以对于代码没有深入的研究,就不做过多的讲解了,希望有大佬给讲解一下。

package com.example.administrator.modbustcp.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.example.administrator.modbustcp.Interface.ResultListener;
import com.example.administrator.modbustcp.R;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.msg.ModbusRequest;
import com.serotonin.modbus4j.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersResponse;
import com.serotonin.util.queue.ByteQueue;

import java.util.ArrayList;


public class TestActivity extends AppCompatActivity implements ResultListener {



    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final short[] shorts = new short[2];
        shorts[0]=1;
        shorts[1]=2;
        final boolean[] values = new boolean[2];
        values[0]=true;
        values[1]=true;
//        modbusWTCP("192.168.1.200",502,1,100,shorts);
//        modbusRTCP("192.168.1.158",502, 100,10);
        new Thread(new Runnable() {
            @Override
            public void run() {
//                Coil_Status.coilStatusWrite("192.168.1.158", 502, 1, 100, values);
//                Coil_Status.coilStatusRead("192.168.1.158",502,1,100,10,TestActivity.this);
//                Input_Status.inputStatusRead("192.168.1.158", 502, 1, 100, 10);
//                Holding_Register.holdingRegisterRead("192.168.1.158", 502, 1, 100, 10);
//                Holding_Register.holdingRegisterWrite("192.168.1.158", 502, 1, 100, shorts);
//                Input_Registers.inputRegisterRead("192.168.1.158", 502, 1, 100, 10);
//                Log.e("TAG","===========================");
            }
        }).start();
    }



    public void modbusWTCP(String ip, int port, int slaveId, int start, short[] values) {
        ModbusFactory modbusFactory = new ModbusFactory();
        // 设备ModbusTCP的Ip与端口,如果不设定端口则默认为502
        IpParameters params = new IpParameters();
        params.setHost(ip);
        if (502 != port) {
            params.setPort(port);
        }// 设置端口,默认502
        ModbusMaster tcpMaster = null;
        // 参数1:IP和端口信息 参数2:保持连接激活
        tcpMaster = modbusFactory.createTcpMaster(params, true);
        try {
            tcpMaster.init();
            System.out.println("===============" + 1111111);
        } catch (ModbusInitException e) {
             System.out.println("11111111111111==" + "此处出现问题了啊!");
//             如果出现了通信异常信息,则保存到数据库中
//            CommunityExceptionRecord cer = new CommunityExceptionRecord();
//            cer.setDate(new Date());
            //cer.setIp(ip);
            // cer.setRemark(bgName+"出现连接异常");
            // batteryGroupRecordService.saveCommunityException(cer);
        }
        try {
            WriteRegistersRequest request = new WriteRegistersRequest(slaveId, start, values);
            WriteRegistersResponse response = (WriteRegistersResponse) tcpMaster.send(request);
            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("Success");
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
        finally {
            tcpMaster.destroy();
        }
    }

    public ByteQueue modbusRTCP(String ip, int port, int start,int readLenth) {
        ModbusFactory modbusFactory = new ModbusFactory();
        // 设备ModbusTCP的Ip与端口,如果不设定端口则默认为502
        IpParameters params = new IpParameters();
        params.setHost(ip);
        if(502!=port){params.setPort(port);}//设置端口,默认502
        ModbusMaster tcpMaster = null;
        tcpMaster = modbusFactory.createTcpMaster(params, true);
        try {
            tcpMaster.init();
            System.out.println("==============="+1111111);
        } catch (ModbusInitException e) {
            Log.e("TAG",e.getMessage());
            return null;
        }
        ModbusRequest modbusRequest=null;
        try {
            modbusRequest = new ReadHoldingRegistersRequest(1, start, readLenth);//功能码03
        } catch (ModbusTransportException e) {
            Log.e("TAG","2");
            e.printStackTrace();
        }
        ModbusResponse modbusResponse=null;
        try {
            modbusResponse = tcpMaster.send(modbusRequest);
        } catch (ModbusTransportException e) {
            Log.e("TAG","3");
            e.printStackTrace();
        }
        ByteQueue byteQueue= new ByteQueue(12);
        modbusResponse.write(byteQueue);
        Log.e("TAG", "功能码:" + modbusRequest.getFunctionCode());
        Log.e("TAG","从站地址:"+modbusRequest.getSlaveId());
        Log.e("TAG","收到的响应信息大小:"+byteQueue.size());
        Log.e("TAG", "收到的响应信息值:" + byteQueue);
        Log.e("TAG", "收到的数值:" + modbusResponse.getExceptionCode());
        Log.e("TAG", "收到的数值:" + modbusResponse.getExceptionMessage());
        Log.e("TAG", "收到的数值:" + modbusResponse.getFunctionCode());
        Log.e("TAG", "收到的数值:" + byteQueue.pop());
        return byteQueue;
    }

    @Override
    public ArrayList<String> result(ArrayList<String> dataList, String ip, int port, int slaveId, int start, int type) {
        return null;
    }

    @Override
    public void onToast(String msg) {

    }
}

这个是关于在android上协议内容的一部分、

五、验证

基本上介绍到这里了吧。有需要要APP端的可以留个邮箱。
编译下载到开发板上,插上网线。
在PC端的, 按windows+r 输入ping 172.16.108.179(这个ip地址是在工程里面设定的,我在这里设的就是这个ip地址)
Modbus_TCP在Android与STM32F4上的应用_第18张图片
验证能用。然后就是手机app端
Modbus_TCP在Android与STM32F4上的应用_第19张图片
这个是app端的界面,连到同一个局域网,然后输入ip地址,点击读写
Modbus_TCP在Android与STM32F4上的应用_第20张图片
就看有读取到之前存在保持寄存器里面的数据了。
然后进行写数据
Modbus_TCP在Android与STM32F4上的应用_第21张图片
ok,fine.验证成功。好的,今天的实验记录在这里了。如有什么不对的地方请大佬们不吝赐教,留言或私聊,谢谢。

欢迎转载,注明出处即可!谢谢!

你可能感兴趣的:(初学)