Understand Modbus communication protocol

Catalog

Preface

1. Hardware introduction

1.2 introduction to hardware circuit

1.2 hardware communication platform

2. Software introduction

2.1 timer program design

2.1.1 configure clock function

2.1.2 timer interrupt service subroutine

2.2 serial port programming

2.2.1 configure the serial port function:

2.2.2 initialize interrupt service subroutine:

2.2.3 serial port interrupt response event:

2.3 modbus programming

2.2.1 crc16 comparison procedure

2.3.2 Modbus macro definition

2.3.3 Modbus function initialization

2.3.4 Modbus event function

2.3.5 Modbus read function code processing

2.3.6 Modbus write single register function code processing

2.3.7 main function

Preface

Let's first review the previous sectionIntroducedModbus The basic theory of communication protocol, first of all, introduces the master-slave communication mode characteristics of Modbus communication protocol, analyzes the transmission characteristics of Modbus communication; secondly, introduces two basic data formats of Modbus communication protocol: Modbus RTU protocol and Modbus ascll protocol. Modbus communication protocol is realized on the basis of RS-485 serial port experiment. In short, it is the first step to realize RS-485 serial port communication. The received and sent data strings are written according to MODBUS rules (compared to data encryption processing). Therefore, the programming is mainly divided into three steps: 1. Realizing timer of 1ms interrupt timing; 2. Realizing serial port of sending and receiving data; 3. Programming MODBUS . This section will implement the Modbus communication protocol code from the perspective of theory to practice.

1. Hardware introduction

1.2 introduction to hardware circuit

Microprocessor selection: STM32F103;

RS485 in and RS485 out are the receiving and sending pins: PB10 and PB11 pins of MCU are selected as the receiving and sending pins of RS-485;

RS485? De is the control pin of receiving and sending State: when RS485? De is high power level, the chip is in sending state; when RS485? De is low power level, the chip is in receiving state;

Impedance matching circuit: R44, R45 and R46 are impedance matching resistance

1.2 hardware communication platform

Modbus The communication experiment platform is built as follows: the USB serial port is quasi converted to TTL level, then TTL level is converted to RS-485 differential signal, and then connected to STM32 slave 1.

Modbus communication experiment debugging software is as follows:

Download link: https://pan.baidu.com/s/1ccJkBmZJQhuChypKy-qoug
Extraction code: ppe1

2. Software introduction

To implement the Modbus program, three things should be done first:

(1) Timer to realize 1ms interrupt timing;

(2) Realize the serial port of sending and receiving data;

(3) Modbus programming.

2.1 timer program design

TIM2 is used to realize 1ms timing interrupt function.

2.1.1 configure clock function

/******************************************************************
Function: configure clock function
******************************************************************/
//Universal timer 2 interrupt initialization
void Timer2_Init()  //1 update event in 1ms
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;//Initialization of structure type: including automatic reload value, frequency division coefficient and counting mode
    //① TIM2 clock enable
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); 
	TIM_DeInit(TIM2);

//Timer TIM2 initialization
	TIM_TimeBaseStructure.TIM_Period=1000-1;  // Automatic reload cycle 1ms
	TIM_TimeBaseStructure.TIM_Prescaler=72-1;  // Division coefficient 72m / 72 = 1MHz -- > 1US
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;  //Set the clock division factor to no division
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;  //Counting method is up counting
//② Initialize timer parameters
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); 
//③ Set TIM2 to allow update interrupt
    TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);//Enable TIM2 update interrupt
//④ Enable TIM2
	TIM_Cmd(TIM2,ENABLE);
}

2.1.2 timer interrupt service subroutine

/******************************************************************
Function: timer interrupt service subroutine
******************************************************************/
void TIM2_IRQHandler()    //Interrupt service subfunction of timer 2 1 ms once
{
  u8 st;
  st= TIM_GetFlagStatus(TIM2, TIM_FLAG_Update);	//Detect TIM2 interrupt update flag bit
	if(st==SET)  //If TIM2 meets the interrupt flag
	{
	  TIM_ClearFlag(TIM2, TIM_FLAG_Update);  //Clear TIM2 interrupt update flag bit
		//Tasks to be performed every millisecond
        if(modbus.timrun!=0)
		{
		  modbus.timout++; 
		  if(modbus.timout>=4)  //The interval time reaches 4 milliseconds
			{
				modbus.timrun=0;//Off timer -- stop timer
				modbus.reflag=1;  //Receive a frame of data
			}
		}  		
	}	
}

2.2 Serial programming

In order to achieve our basic communication function, we set the serial port from the library function operation level combined with the description of register. The general steps of serial port setting can be summarized as follows:

(1) Serial clock enable, GPIO clock enable;

(2) Serial port reset;

(3) GPIO port mode setting;

(4) Initialization of serial port parameters;

(5) Turn on interrupt and initialize NVIC;

(6) Enable serial port;

(7) Write interrupt handling functions.

2.2.1 configure the serial port function:

void RS485_Init()
{
GPIO_InitTypeDef  GPIO_InitStructure;
USART_InitTypeDef  USART_InitStructure;
//① Serial clock enable, GPIO clock enable, multiplexing clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE); //Turn on USART2 clock
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);  //Turn on USART2 clock
//② Serial port reset
USART_DeInit(USART2);  //Serial port 2 reset
//RS485DE pin initialization
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;  //Multiplexing push pull output
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //Initialize GPIOA.5
    RS485_RT_0;  //Make MAX485 chip in receiving state (transceiver control pin)
//③ GPIO port mode settings
    //Initialize 485 serial port pin and serial port configuration
    //USART1_TX   PB.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //Multiplexing push pull output
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //Initialize GPIOA.2
    //USART1_RX	PB.11
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //Floating input
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //Initialize GPIOA.3
//④ Initialization of serial port parameters, structure pointer member variables
    USART_InitStructure.USART_BaudRate = 9600;  //Set the baud rate to 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //The word length is 8-bit data format
    USART_InitStructure.USART_StopBits = USART_StopBits_1;  //One stop bit
    USART_InitStructure.USART_Parity = USART_Parity_No;  //No parity bit
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //No hardware data flow control
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  //Transceiver mode
    USART_Init(USART2, &USART_InitStructure);  //Initialization of serial port parameters
//⑤ Open interrupt
    USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); //Open serial port to respond to interrupt, USART it rxne receives data interrupt
//⑥ Enable serial port
    USART_Cmd(USART2, ENABLE);//Serial port enable
    USART_ClearFlag(USART2,USART_FLAG_TC ); //Clear the serial port TC sending completion interrupt flag

2.2.2 initialize interrupt service subroutine:

/******************************************************************
Function: initialize NVIC
******************************************************************/
//⑤ Initialize NVIC (timer interrupt + serial port interrupt)
void NVIC_Init()
{
    NVIC_InitTypeDef  NVIC_InitStructure;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //  Interrupt priority

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //Timer generates update event interrupt
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;  //Preempt priority 1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //Sub priority 2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  //IRQ channel enable
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;  //Preempt priority 1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //Child priority 0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  //IRQ channel enable
    NVIC_Init(&NVIC_InitStructure);
}

2.2.3 serial port interrupt response event:

/******************************************************************
Function: Modbus3 byte receive interrupt processing
******************************************************************/
void USART2_IRQHandler() //MODBUS byte receive interrupt
{
    u8 st,sbuf;
    st=USART_GetITStatus(USART2, USART_IT_RXNE);  //Judge whether the read register is not empty (RXNE)
    if(st==SET)  //The return value is SET, indicating that the serial port receives the data completion interrupt
   {   		 
	 sbuf=USART2->DR;
     if( modbus.reflag==1)  //There are packets in process
		 {
		   return ;
		 }			 
		  modbus.rcbuf[modbus.recount++]=sbuf;//Using array to store received data
      modbus.timout=0;  
      if(modbus.recount==1)  //Receive the first byte of a frame of data sent by the host
			{
			  modbus.timrun=1;  //Start timing
			}
   } 
}

2.3 modbus programming

2.2.1 crc16 comparison procedure

According to the high byte value table and low byte value table of crc16, write the verification program.

/******************************************************************
Function: CRC16 verification
******************************************************************/
uint crc16( uchar *puchMsg, uint usDataLen )
{
    uchar uchCRCHi = 0xFF ; // High CRC byte initialization
    uchar uchCRCLo = 0xFF ; // Low CRC byte initialization
    unsigned long uIndex ; 		// Index in CRC loop

    while ( usDataLen-- ) 	// Transmit message buffer
    {
        uIndex = uchCRCHi ^ *puchMsg++ ; 	// Calculate CRC
        uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
        uchCRCLo = auchCRCLo[uIndex] ;
    }

    return ( uchCRCHi << 8 | uchCRCLo ) ;
}

2.3.2 Modbus macro definition

#Define RS485? RT? 1 GPIO? Setbits (gpioa, GPIO? Pin? 5) / / 485 send status
#Define RS485? RT? 0 GPIO? Resetbits (gpioa, GPIO? Pin? 5) / / 485 set receiving status
typedef struct
{
    u8 myadd;  //Address of the device
    u8 rcbuf[64];  //Modbus receive buffer 64 bytes
    u16 timout;  //Data intermittent time of Modbus	
    u8 recount;  //Number of data received by Modbus port
    u8 timrun;  //Flag of whether the Modbus timer is timed
    u8 reflag;  //Flag to receive one frame of data
    u8 Sendbuf[64];  //Modbus send buffer	
}
MODBUS;

2.3.3 Modbus function initialization

/******************************************************************
Function: Modbus function initialization
******************************************************************/
void Modbus_Init()
{
	modbus.myadd=4;  //Address of the slave device
	modbus.timrun=0;  //Modbus timer stop timing
    RS485_Init();
}

2.3.4 Modbus event function

/******************************************************************
Function: Modbus event function
******************************************************************/
void Mosbus_Event()
{
	u16 crc;
	u16 rccrc;
  if(modbus.reflag==0)  //Modbus data not received
	{
	  return ;
	}
  crc= crc16(&modbus.rcbuf[0], modbus.recount-2);   //Calculate check code, - 2 remove two digit check code
  rccrc=modbus.rcbuf[modbus.recount-2]*256 + modbus.rcbuf[modbus.recount-1];  //Received check code
  if(crc == rccrc)  //CRC verification rules for packet symbols
	{ 
	  if(modbus.rcbuf[0] == modbus.myadd)  //Confirm whether the packet is sent to the device, and the address for receiving is the local address
		{
		  switch(modbus.rcbuf[1])  //Analysis function code
			{
			  case 0:     break;
			  case 1:     break;
		      case 2:     break;
		      case 3:     Modbud_fun3(); break;   //Function code 3 processing
		      case 4:     break;
		      case 5:     break;
		      case 6:     Modbud_fun6(); break;   //6 function code processing
	          case 7:     break;			
        //....				
			}
		}
		else if(modbus.rcbuf[0] == 0)   //Do not process if it is a broadcast address
		{
		}
	}
	modbus.recount=0;  
    modbus.reflag=0;	
}

2.3.5 Modbus read function code processing

/******************************************************************
Function: Modbus3 function code processing
******************************************************************/
void Modbud_fun3()  //Function code 3 processing - the host should read the registers of the slave
{
    u16 Regadd;  //Register start address
	u16 Reglen;  //Number of registers
	u16 byte;
	u16 i,j;
	u16 crc;
	Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];  //Get the first address of the register to read
	Reglen=modbus.rcbuf[4]*256+modbus.rcbuf[5];  //Get the number of registers to read
	i=0;
	modbus.Sendbuf[i++]=modbus.myadd;  //Address of the equipment
    modbus.Sendbuf[i++]=0x03;  //Function code      
    byte=Reglen*2;   //Number of data bytes to return
	modbus.Sendbuf[i++]=byte%256;
	for(j=0;j<Reglen;j++)
	{
	  modbus.Sendbuf[i++]=Reg[Regadd+j]/256;
		modbus.Sendbuf[i++]=Reg[Regadd+j]%256;		
	}
	crc=crc16(modbus.Sendbuf,i);
	modbus.Sendbuf[i++]=crc/256;  //
	modbus.Sendbuf[i++]=crc%256;
	RS485_RT_1; 
	for(j=0;j<i;j++)
	{
	 RS485_byte(modbus.Sendbuf[j]);
	}
	RS485_RT_0;
}

2.3.6 Modbus write single register function code processing

/******************************************************************
Function: Modbus6 write single register function code processing
******************************************************************/
void Modbud_fun6()  //6 function code processing
{
    u16 Regadd;
	u16 val;
	u16 i,crc,j;
	i=0;
    Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];  //Get the address to be modified 
	val=modbus.rcbuf[4]*256+modbus.rcbuf[5];     //Modified value
	Reg[Regadd]=val;  //Modify the corresponding registers of the device
	//Here is the response host
	modbus.Sendbuf[i++]=modbus.myadd;//Address of the equipment
    modbus.Sendbuf[i++]=0x06;        //Function code 
    modbus.Sendbuf[i++]=Regadd/256;
	modbus.Sendbuf[i++]=Regadd%256;
	modbus.Sendbuf[i++]=val/256;
	modbus.Sendbuf[i++]=val%256;
	crc=crc16(modbus.Sendbuf,i);
	modbus.Sendbuf[i++]=crc/256;  //
	modbus.Sendbuf[i++]=crc%256;
	RS485_RT_1;
	for(j=0;j<i;j++)
	{
	 RS485_byte(modbus.Sendbuf[j]);
	}
	RS485_RT_0;
}

2.3.7 main function

/******************************************************************
Functions: main functions
******************************************************************/
u16 Reg[]={0x0000,   //Value in the device register
           0x0001,
           0x0002,
           0x0003,
           0x0004,
           0x0005,
           0x0006,
           0x0007,
           0x0008,
           0x0009,
           0x000A,	
          };	
int main()
{
  Timer2_Init();  //Initialize timer Timer2
  Mosbus_Init();   //Initialization timer interrupt
  Isr_Init();
  while(1)
	{
		Mosbus_Event();  //Processing MODbus data
	}
}

Previous Blogs:

Understanding Modbus communication protocol

Hardware layer protocol and software layer protocol of communication

Introduction of RS-232, RS-485 and RS-422 communication interface standards

Tags: Programming

Posted on Mon, 04 May 2020 08:45:12 -0400 by jotgabbi