Freemodbus port description ZZ, freemodbus port zz

Source: Internet
Author: User

Freemodbus port description ZZ, freemodbus port zz

1. Why port Freemodbus?

Why do we need to port Freemodbus? This question needs to be answered from two aspects. First, modbus is a very good application layer protocol, which is concise and relatively complete. For those who have not been familiar with modbus, we do not recommend porting freemodbus directly. We should patiently start with the modbus document and fully grasp all the resources around us, for example, the modbus part of the PLC. Second, in fact, the communication protocols of embedded systems can be developed by yourself, but through practice, we find that the customized protocols are full of loopholes, especially the expansion is extremely difficult. I always think that it is a good way to learn from others' experiences. By drawing on the mature code of others, you can reduce debugging time and implement more functions.

Personal opinion, for reference only.

Freemodbus tips

Freemodbus can only use the slave function. Freemodbus is more suitable for embedded systems. Although there are WIN32 examples in the example, if you want to do a PC program and implement the host function, we recommend that you use another modbus library-NMODBUS, and use C # for development. Similarly, WINFORM can implement the modbus function by writing serial code by itself, but this may take a long time, either a week or a month. If a ready-made code library is used, the development time may only be 10 minutes.
Self-Organized modbus protocol MODBUS protocol
The code references this post. Thank you for sharing it. Click here
FreeemodbusHow to send and receive data through the serial port

Freemodbus receives and sends data by means of serial interruption. In this way, I want to save the waiting time of the program and make full use of CPU resources. There is no doubt about the serial interrupt reception. In the interrupt service function, save the data in the array for later processing. But what is the form of serial port sending interruption? There are at least two methods to interrupt the sending of serial ports. First, the data register is interrupted in the air. If the data register is empty and the data register is blocked, the interrupt will occur. Second, the sending is interrupted. If the data in the data register is sent completely and the position of the interruption blocking is blocked, the interruption is also sent. I strongly recommend that you use the serial port to complete the interruption. In freemodbus communication, the slave machine receives or sends data. In most cases, the slave machine is in the receiving status and enters the sending status only when data is sent. When the message is sent, the data is sent out in one byte. When the last byte is sent out, the slave machine enters the receiving status again. If the sending register is air-disconnected, other methods are required to determine whether the last byte of data has been sent. If the data register is used for air disconnection, the last byte may be lost. (It is also recommended that you use sending interrupted articles to communicate with each other in the Teacher Ma Chao's AVR book, so there will be no references .)

FreemodbusHow to determine the end of a frame

It should be clear that the modbus protocol does not have an obvious start or end operator, but is determined by the interval between frames. If no new character data is received within the specified time, the new frame is considered to have been received. The next step is to process the data. The first thing to do is to determine the validity of the frame. Modbus determines whether the frame is accepted by time. It is natural that the timer in the single chip microcomputer is used together.

ThuOverall code

The following is a simple example of using freemodbus on the STM32 platform. The operation maintains the register. The operation commands can be and 16;

  • <FONT size = 3> # include "stm32f10x. h"
  • # Include <stdio. h>
  • # Include "mb. h"
  • # Include "mbutils. h"
  • // Keep the starting address of the Register
  • # Define REG_HOLDING_START 0x0000
  • // Number of reserved registers
  • # Define REG_HOLDING_NREGS 8
  • // Keep register content
  • Uint16_t usRegHoldingBuf [REG_HOLDING_NREGS]
  • = {0x147b, 0x3f8e, 0x147b, 0x400e, 0x1eb8, 0x4055, 0x147b, 0x408e };
  • Int main (void)
  • {
  • // Initialize the RTU mode. The slave address is 1 USART1 9600 and no verification is performed.
  • EMBInit (MB_RTU, 0x01, 0x01,960 0, MB_PAR_NONE );
  • // Start FreeModbus
  • EMBEnable ();
  • While (1)
  • {
  • // FreeMODBUS continuous Query
  • EMBPoll ();
  • }
  • }
  • /**
  • * @ Brief: maintains the register processing function to ensure that the Register is readable and writable.
  • * @ Param pucRegBuffer read operation -- Return Data Pointer, write operation -- Input Data Pointer
  • * UsAddress starting address of the Register
  • * UsNRegs Register Length
  • * EMode operation mode: read or write
  • * @ Retval eStatus register status
  • */
  • EMBErrorCode
  • EMBRegHoldingCB (UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
  • EMBRegisterMode eMode)
  • {
  • // Error status
  • EMBErrorCode eStatus = MB_ENOERR;
  • // Offset
  • Int16_t iRegIndex;
  • // Determine whether the register is in the range
  • If (int16_t) usAddress> = REG_HOLDING_START )\
  • & (UsAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ))
  • {
  • // Calculate the offset
  • IRegIndex = (int16_t) (usAddress-REG_HOLDING_START );
  • Switch (eMode)
  • {
  • // Read processing functions
  • Case MB_REG_READ:
  • While (usNRegs> 0)
  • {
  • * PucRegBuffer ++ = (uint8_t) (usRegHoldingBuf [iRegIndex]> 8 );
  • * PucRegBuffer ++ = (uint8_t) (usRegHoldingBuf [iRegIndex] & 0xFF );
  • IRegIndex ++;
  • UsNRegs --;
  • }
  • Break;
  • // Write processing functions
  • Case MB_REG_WRITE:
  • While (usNRegs> 0)
  • {
  • UsRegHoldingBuf [iRegIndex] = * pucRegBuffer ++ <8;
  • UsRegHoldingBuf [iRegIndex] | = * pucRegBuffer ++;
  • IRegIndex ++;
  • UsNRegs --;
  • }
  • Break;
  • }
  • }
  • Else
  • {
  • // Return Error status
  • EStatus = MB_ENOREG;
  • }
  • Return eStatus;
  • }
  • </FONT>


Copy code

First, let's give you an overall impression. First, we will use FREEMODBUS and describe the details in detail.

// Keep the starting address of the Register

# Define REG_HOLDING_START 0x0000

// Number of reserved registers

# Define REG_HOLDING_NREGS 8

These two macro definitions determine the starting address and total number of reserved registers. It should be emphasized that the modbus Register address has two sets of rules, one set is called the PLC address, which is a 5-digit decimal number, such as 40001. The other set is the Protocol address. PLC address 40001 means that the parameter type is to keep registers, and the Protocol address is 0x0000, which corresponds to each other,Remove the highest bit of the PLC address, and then subtract 1 from the rest.. This will cause a problem. The Protocol addresses of PLC address 30002 and PLC address 40002 are both 0x0001. Will the access conflict at this time. Dear friends, of course not. 30001 is the input register and needs to be accessed using the 04 command. 40001 can be accessed using the 03, 06, and 16 commands to keep the registers. Therefore, you must be familiar with the protocol before using modbus.

// Keep register content

Uint16_t usRegHoldingBuf [REG_HOLDING_NREGS]

= {0x147b, 0x3f8e, 0x147b, 0x400e, 0x1eb8, 0x4055, 0x147b, 0x408e };

The content of the keep register is defined next. Please note that the keep register is unsigned 16-bit data. During the test, I randomly found some data for testing. The nature of the data does not seem to be clear, but usRegHoldingBuf saves floating point numbers in hexadecimal notation.

  • Int main (void)
  • {
  • // Initialize the RTU mode. The slave address is 1 USART1 9600 and no verification is performed.
  • EMBInit (MB_RTU, 0x01, 0x01,960 0, MB_PAR_NONE );
  • // Start FreeModbus
  • EMBEnable ();
  • While (1)
  • {
  • // FreeMODBUS continuous Query
  • EMBPoll ();
  • }
  • }

Next, go to the main function section. There are three functions provided by FREEMODBUS, eMBInit, eMBEnable and eMBPoll. EMBInit is the modbus initialization function, eMBEnable is the modbus enable function, and eMBPoll is the modbus query function. eMBPoll is also a very simple function to query whether data frames arrive, if any data arrives, the processing is dependent. Observe these functions again. Only eMBInit has many parameters, which are related to the hardware at the underlying system level. This should attract more attention in the porting process. The following sections are further discussed.

  • <FONT size = 3> eMBErrorCode
  • EMBRegHoldingCB (UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
  • EMBRegisterMode eMode)
  • {
  • // Error status
  • EMBErrorCode eStatus = MB_ENOERR;
  • // Offset
  • Int16_t iRegIndex;
  • // Determine whether the register is in the range
  • If (int16_t) usAddress> = REG_HOLDING_START )\
  • & (UsAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ))
  • {
  • // Calculate the offset
  • IRegIndex = (int16_t) (usAddress-REG_HOLDING_START );
  • Switch (eMode)
  • {
  • // Read processing functions
  • Case MB_REG_READ:
  • While (usNRegs> 0)
  • {
  • * PucRegBuffer ++ = (uint8_t) (usRegHoldingBuf [iRegIndex]> 8 );
  • * PucRegBuffer ++ = (uint8_t) (usRegHoldingBuf [iRegIndex] & 0xFF );
  • IRegIndex ++;
  • UsNRegs --;
  • }
  • Break;
  • // Write processing functions
  • Case MB_REG_WRITE:
  • While (usNRegs> 0)
  • {
  • UsRegHoldingBuf [iRegIndex] = * pucRegBuffer ++ <8;
  • UsRegHoldingBuf [iRegIndex] | = * pucRegBuffer ++;
  • IRegIndex ++;
  • UsNRegs --;
  • }
  • Break;
  • }
  • }
  • Else
  • {
  • // Return Error status
  • EStatus = MB_ENOREG;
  • }
  • Return eStatus;
  • }
  • </FONT>

 

Finally, if you receive a valid data frame, you can start to process it.

The first step is to determine whether the Register address is within the valid range.

If (int16_t) usAddress> = REG_HOLDING_START )\

& (UsAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ))

Step 2: Determine the offset address of the register to be operated.

An example can be provided to quickly illustrate the problem. For example, if the starting address of the access register is 0x0002 and the starting address of the Register is 0x0000, the access offset is 2, the program starts to operate from the first (starting from 0) to keep the register array.

Step 3: Separate read/write operations

Case MB_REG_READ:

While (usNRegs> 0)

{

* PucRegBuffer ++ = (uint8_t) (usRegHoldingBuf [iRegIndex]> 8 );

* PucRegBuffer ++ = (uint8_t) (usRegHoldingBuf [iRegIndex] & 0xFF );

IRegIndex ++;

UsNRegs --;

}

Break;

Taking the read operation as an example, the code is not much said. Please pay attention to the operation sequence. The registers are kept in 16-bit format, but are measured in bytes during modbus communication. The high byte data goes before and the low byte data goes after.

Four serial port-related code writing

The code of the serial port is generally written. There are three main functions: Serial Port initialization, serial port data transmission, and serial port data receiving. In addition to the above three functions, there are also serial interrupt service functions.

  • /**
  • * @ Brief serial port Initialization
  • * @ Param ucPORT serial port number
  • * UlBaudRate baud rate
  • * UcDataBits data bit
  • * EParity check bit
  • * @ Retval None
  • */
  • BOOL
  • XMBPortSerialInit (UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
  • {
  • (Void) ucPORT; // do not modify the serial port
  • (Void) ucDataBits; // do not modify the data bit length
  • (Void) eParity; // do not modify the validation format
  • GPIO_InitTypeDef GPIO_InitStructure;
  • USART_InitTypeDef USART_InitStructure;
  • // Enable USART1 and GPIOA
  • RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA |
  • RCC_APB2Periph_USART1, ENABLE );
  • // GPIOA9 usart0000tx
  • GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  • GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  • GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // push the output
  • GPIO_Init (GPIOA, & GPIO_InitStructure );
  • // GPIOA.10 usartpolicrx
  • GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  • GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  • GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // floating Input
  • GPIO_Init (GPIOA, & GPIO_InitStructure );
  • USART_InitStructure.USART_BaudRate = ulBaudRate; // only modify the baud rate
  • USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  • USART_InitStructure.USART_StopBits = USART_StopBits_1;
  • USART_InitStructure.USART_Parity = USART_Parity_No;
  • USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  • USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  • // Serial port Initialization
  • USART_Init (USART1, & USART_InitStructure );
  • // Enable USART1
  • USART_Cmd (USART1, ENABLE );
  • NVIC_InitTypeDef NVIC_InitStructure;
  • NVIC_PriorityGroupConfig (NVIC_PriorityGroup_1 );
  • // Set the USART1 interrupt priority
  • NVIC_InitStructure.NVIC_IRQChannel = usart?irqn;
  • NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  • NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  • NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  • NVIC_Init (& NVIC_InitStructure );
  • // Configure the sending and receiving modes in 485.
  • RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOD, ENABLE );
  • // GPIOD.8
  • GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  • GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  • GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  • GPIO_Init (GPIOD, & GPIO_InitStructure );
  • Return TRUE;
  • }

The input parameters include the port number, baud rate, data bit, and check bit. You can modify the code according to the actual situation. I have not modified other parameters here. The input baud rate is valid. In addition to parameters related to the serial port, you also need to configure the interrupt priority of the serial port. Finally, because the 485 mode is used, a sending and receiving controller is also required. The IO is configured as the push-pull output mode.

  • <FONT size = 3> /**
  • * @ Brief controls the receiving and sending statuses
  • * @ Param xRxEnable receives the Enable,
  • * XTxEnable
  • * @ Retval None
  • */
  • Void
  • VMBPortSerialEnable (BOOL xRxEnable, BOOL xTxEnable)
  • {
  • If (xRxEnable)
  • {
  • // Enable receiving and receiving interruption
  • USART_ITConfig (USART1, USART_IT_RXNE, ENABLE );
  • // The maxcompute operation is in the receiving mode at a low level.
  • GPIO_ResetBits (GPIOD, GPIO_Pin_8 );
  • }
  • Else
  • {
  • USART_ITConfig (USART1, USART_IT_RXNE, DISABLE );
  • // The maxcompute operation is in the sending mode.
  • GPIO_SetBits (GPIOD, GPIO_Pin_8 );
  • }
  • If (xTxEnable)
  • {
  • // Enable sending to complete interruption
  • USART_ITConfig (USART1, USART_IT_TC, ENABLE );
  • }
  • Else
  • {
  • // Prohibit sending from being interrupted
  • USART_ITConfig (USART1, USART_IT_TC, DISABLE );
  • }
  • }
  • </FONT>

 

Because 485 is in half duplex mode, the slave machine is generally in the receiving status, and only when data is sent Will it enter the sending mode. There is a special function for controlling the receiving and sending status in FreeModbus. In this function, not only can the receiving and sending interruption be enabled or disabled, but also the sending and receiving ports of the 485 transceiver chip can be controlled. The code is very simple, but we recommend that you use sending to complete the interruption.

  • <FONT size = 3> BOOL
  • XMBPortSerialPutByte (CHAR ucByte)
  • {
  • // Send data
  • USART_SendData (USART1, ucByte );
  • Return TRUE;
  • }
  • BOOL
  • XMBPortSerialGetByte (CHAR * pucByte)
  • {
  • // Receive data
  • * PucByte = USART_ReceiveData (USART1 );
  • Return TRUE;
  • }
  • The xMBPortSerialPutByte and xMBPortSerialGetByte functions are used to send and receive data through the serial port. You only need to call the library function of STM32 here.
  • Static void prvvUARTTxReadyISR (void)
  • {
  • // Mb. c eMBInit Function
  • // PxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM
  • // Sending the state machine
  • PxMBFrameCBTransmitterEmpty ();
  • }
  • Static void prvvUARTRxISR (void)
  • {
  • // Mb. c eMBInit Function
  • // PxMBFrameCBByteReceived = xMBRTUReceiveFSM
  • // Receiving State Machine
  • PxMBFrameCBByteReceived ();
  • }
  • Void usartpolicirqhandler (void)
  • {
  • // Receive interrupted
  • If (USART_GetITStatus (USART1, USART_IT_RXNE) = SET)
  • {
  • PrvvUARTRxISR ();
  • // Clear the interrupt flag
  • USART_ClearITPendingBit (USART1, USART_IT_RXNE );
  • }
  • // Complete interruption
  • If (USART_GetITStatus (USART1, USART_IT_TC) = SET)
  • {
  • PrvvUARTTxReadyISR ();
  • // Clear the interrupt mark
  • USART_ClearITPendingBit (USART1, USART_IT_TC );
  • }
  • }
  • </FONT>

 

If you enter the serial port interrupt service function, you need to call the Response Function in FreeModbus. The serial port receives the interrupt service function corresponding to prvvUARTRxISR (). The Code is as follows:

  • <FONT size = 3> static void prvvUARTRxISR (void)
  • {
  • // Mb. c eMBInit Function
  • // PxMBFrameCBByteReceived = xMBRTUReceiveFSM
  • // Receiving State Machine
  • PxMBFrameCBByteReceived ();
  • }
  • </FONT>


Copy code

In prvvUARTRxISR, pxMBFrameCBByteReceived () is called again. In fact, pxMBFrameCBTransmitterEmpty () is not a function, but a function pointer. The definition is as follows. Note the differences between the declaration of function pointers and function declarations.

BOOL (* pxMBFrameCBTransmitterEmpty) (void );

Assign values to the eMBInit function in the mb. c file. Generally, the RTU mode is used, so pxMBFrameCBByteReceived is equivalent to xMBRTUReceiveFSM,

PxMBFrameCBByteReceived = xMBRTUReceiveFSM;

 

Similarly, if the serial port sending is interrupted, the interrupted service function corresponds to prvvUARTTxReadyISR. The Code is as follows:

  • <FONT size = 3> static void prvvUARTTxReadyISR (void)
  • {
  • // Mb. c eMBInit Function
  • // PxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM
  • // Sending the state machine
  • PxMBFrameCBTransmitterEmpty ();
  • }
  • </FONT>



In prvvUARTTxReadyISR, pxMBFrameCBTransmitterEmpty () is also called. pxMBFrameCBTransmitterEmpty is also a function pointer. It is equivalent to xMBRTUTransmitFSM when assigning values in eMBInit.

Note: Because I am using a serial port to complete the interruption, to enter the interrupt service function, I need to send a byte of data and start the serial port to send the interruption. The code needs to be modified a little. Make a slight modification in eMBRTUSend of mbRTU. c. The Code is as follows.

  • <P> the BIT may not be quite clear here. It is recommended to study the FreeModbus source code and make some modifications to make it easier to use.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.