今天记录的是在学习了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的IOS层模型的两个通信规程。本次实验主要介绍的就是Modbus on TCP.modbus是OSI模型的第七层报文传输协议,它连接至不同类型总线或网络的设备之间提供客户机/服务器通信。MODBUS是一个请求/应答协议,并提供功能码规定的服务。本文将介绍MODBUS协议的各个功能码,并主要介绍本实验用到的读写寄存器的功能码。
下面是关于MODBUS协议允许在各种网络体系结构内进行简单通信。包括PLC、HMI以及一些IO口通信
MODBUS协议是通过报文传输数据,报文的结构是由地址域、功能码、数据和差错校验组成的。一般情况下Modbus是使用CRC校验的,但是在实际的使用中,Modbus_TCP是不用CRC校验的。因为TCP协议本身就存在校验+重传功能,所以MODBUS协议在TCP的应用上是不需要CRC校验的。
下图是modbus的数据帧结构
因为modbus是请求/应答的形式,所以在整个通信协议当中存在主站和从站,而且是一个主站+多个从站的组合形式。而且通信都是modbus主站发送查询指令,然后从站根据主站发送过来的功能码进行相应的操作。
当服务器对客户机响应时,它使用功能码来指示正常(无差错)响应或者出现某种差错(称为异常响应)。对于一个正常响应来说,服务器仅对原始功能码响应。
下图分别是对正常响应以及异常响应的解析图。
关于modbus的一个逻辑流程图如下:
modbus的功能码有三个类型,分别是公共功能码、用户定义功能码、保留功能码。在这里主要使用的是公共功能码,便介绍公共功能码,至于其他功能码请自行翻阅modbus通信协议。
公共功能码一共有13个。其中有单个比特访问、16个比特访问、文件记录访问以及封装借口。
在这里主要应用的就是对保持寄存器的读写。因此主要使用的功能码就是03读保持寄存器功能码,以及06写单个寄存器功能码。
1.03 (0x03)读保持寄存器
在一个远程设备中,,使用该功能码读取保持寄存器连续块的内容。请求PDU说明了起始寄存器地址和寄存器数量。从零开始寻址寄存器。因此,寻址寄存器1-16为0-15.
将响应报文中的寄存器数据围城每个寄存器有两字节,在每个字节中直接地调整二进制内容。对于每个寄存器,第一个字节包括高位,并且第二个字节也包括低位比特。
下图是一个请求读寄存器108-110的实例:
将寄存器108的内容表示为两个十六进制字节值022B,或十进制555。将寄存器109-110的内容分别表示为十六进制0000和0064,或者十进制0和100。
2、06(0x06)
(偷点懒,直接上图。请大佬自行理解)
关于modbus大概就介绍到这里了,详细具体的细节还请自行了解modbus协议。如若有需要文档的,可以文末留言邮箱。
2、硬件
因本实验所用硬件载体为原子的阿波罗开发板STM32F429IGTx。是在STM32CUBEMX开发平台上配置相应的功能。由于是TCP/IP,而在开发板上只能板载轻型的TCP/IP协议即LWIP。下面是在CUBEMX上一些相应的配置。
使能ETH
由于要用到网络,需要用到操作系统,这里使能的是FREE RTOS
关于LWIP的使能,就是大概介绍一下
主要的都在这里了,毕竟是之前介绍过,就不具体详细讲了。如果不了解想知道的话可以看之前的博客。
https://mp.youkuaiyun.com/postedit/90084421
至于时钟树的配置都是不超过限定钟频即可。
关于硬件的配置就大体记录到这里了。
3、软件
软件这一部分包括开发板的程序以及安卓端的APP程序。
(1)开发板软件部分
在这里主要是移植了MODBUS的协议包。说实话,modbus_tcp比RTU简单超多,基本上把协议包移植过去就能直接使用了
协议包的内容我就不具体讲了,有需要的可以留言邮箱。都是根据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 )
{