Agile Modbus is a lightweight modbus protocol stack that meets the needs of users in any scenario.
-
Online documentation: API Manual
-
The
examplesfolder provides examples on PC -
See examples on MCU mcu_demos
-
Bootloader based on RT-Thread on AT32F437 that supports Modbus firmware upgrade: AT32F437_Boot
-
Bootloader based on RT-Thread on HPM6750 that supports Modbus firmware upgrade: HPM6750_Boot
- Supports rtu and tcp protocols, is developed using pure C, does not involve any hardware interface, and can be used directly on any form of hardware.
- Since it is developed using pure C and does not involve hardware, it can run the tcp protocol on the serial port and the rtu protocol on the network.
- Support custom protocols compliant with modbus format.
- Supports multiple masters and multiple slaves at the same time.
- It is easy to use. You only need to initialize the rtu or tcp handle and call the corresponding API to package and unpack.
| Name | Description |
|---|---|
| doc | documentation |
| examples | examples |
| figures | materials |
| inc | header file |
| src | source code |
| util | Provides simple and practical components |
Agile Modbus complies with the Apache-2.0 license, see the LICENSE file for details.
Please view the help document doc/doxygen/Agile_Modbus.chm
-
Users need to implement the
send data,wait for data reception to endandclear the receive bufferfunctions of the hardware interfaceRegarding
waiting for data reception to end, the following ideas are provided:-
General method
Every 20 /50 ms (this time can be set according to the baud rate and hardware, here is just a reference value) reads data from the hardware interface and stores it in the buffer and updates the offset until it cannot be read or the buffer is full. , exit reading.
This applies to both bare metal and operating systems, which can accomplish blocking via
selectorsemaphore. -
Serial port
DMA + IDLEinterrupt modeConfigure the
DMA + IDLEinterrupt, enable the flag in the interrupt, and determine whether the flag is set in the application program.However, this solution is prone to problems. If the data bytes are slightly staggered, it will not be a frame. The first option is recommended.
-
-
Host:
agile_modbus_rtu_init/agile_modbus_tcp_initinitializesRTU/TCPenvironmentagile_modbus_set_slavesets the slave addressClear the receive cacheagile_modbus_serialize_xxxpackage request dataSend dataWaiting for data reception to endagile_modbus_deserialize_xxxParse response data- Data processed by users
-
Slave machine:
- Implement the
agile_modbus_slave_callback_ttype callback function agile_modbus_rtu_init/agile_modbus_tcp_initinitializesRTU/TCPenvironmentagile_modbus_set_slavesets the slave addressWaiting for data reception to endagile_modbus_slave_handleprocesses request dataClear the receive buffer(optional)Send data
- Implement the
-
Special function code
You need to call the
agile_modbus_set_compute_meta_length_after_function_cbandagile_modbus_set_compute_data_length_after_meta_cbAPIs to set the callbacks for special function codes to be processed in master-slave mode.-
agile_modbus_set_compute_meta_length_after_function_cbmsg_type == AGILE_MODBUS_MSG_INDICATION: Returns the data element length of the host request message (uint8_t type). If it is not a special function code, 0 must be returned.msg_type == MSG_CONFIRMATION: Returns the data element length (uint8_t type) of the slave response message. If it is not a special function code, 1 must be returned. -
agile_modbus_set_compute_data_length_after_meta_cbmsg_type == AGILE_MODBUS_MSG_INDICATION: Returns the data length after the data element of the host request message. If it is not a special function code, 0 must be returned.msg_type == MSG_CONFIRMATION: Returns the data length after the data element of the slave response message. If it is not a special function code, it must return 0.
-
-
agile_modbus_rtu_init/agile_modbus_tcp_initWhen initializing the
RTU/TCPenvironment, the user needs to pass in thesend bufferandreceive buffer. It is recommended that the size of both buffers isAGILE_MODBUS_MAX_ADU_LENGTH(260) bytes.Special function codeis determined by the user according to the agreement.But for small memory MCUs, these two buffers can also be set small, and all APIs will judge the buffer size:
Send buffer setting: If
expected request data lengthorexpected response data lengthis greater thanset send buffer size, an exception is returned.Receive buffer setting: If the
message length requested by the hostis greater than theset receive buffer size, an exception will be returned. This is reasonable. When a small memory MCU is used as a slave, certain function codes must be restricted.
See 2.1. Transplantation.
-
Introduction to
agile_modbus_slave_handleint agile_modbus_slave_handle(agile_modbus_t *ctx, int msg_length, uint8_t slave_strict, agile_modbus_slave_callback_t slave_cb, const void *slave_data, int *frame_length)
msg_length: The length of data received after
waiting for the end of data reception.slave_strict: slave address strictness check (0: Do not judge whether the address is consistent, it will be processed by user callback; 1: The address must be consistent, otherwise the callback will not be called and the response data will not be packaged).
slave_cb:
agile_modbus_slave_callback_ttype callback function, implemented and passed in by the user. If it is NULL, all function codes can respond and are successful, but the register data is still 0.slave_data: slave callback function private data.
frame_length: Get the length of the parsed modbus data frame. The meaning of this parameter is:
- There is dirty data at the end: it can still be parsed successfully and tells the user the real modbus frame length, which the user can process.
- Data sticky packet: The data consists of a complete frame of modbus data + a partial modbus data frame. After the user obtains the real modbus frame length, he can remove the processed modbus data frame and read the hardware interface data and the current one again. Part of the modbus data frame forms a new frame
- This parameter is often used when modbus broadcast transmits big data (such as custom function code broadcast to upgrade firmware). Ordinary slave responses are one question and one answer, and only the complete data frame is processed. It is recommended to Execute
clear receive cache
-
Introduction to
agile_modbus_slave_callback_t/** * @brief slave callback function * @param ctx modbus handle * @param slave_info slave information body * @param data private data * @return =0: normal; * <0: Abnormal * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) * (Other negative exception codes: package exception response data from the opportunity) */ typedef int (*agile_modbus_slave_callback_t)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data);
agile_modbus_slave_info:sft: Contains slave address and function code attributes, which can be used in callbacks
rsp_length: response data length pointer, its value needs to be updated when processing
special function codein the callback, otherwise not allowed to changeaddress: register address (not used by all function codes)
nb: number (not used by all function codes)
buf: data field required by different function codes (not used by all function codes)
send_index: the current index of the send buffer (not used by all function codes)
-
agile_modbus_slave_infoused by different function codes-
AGILE_MODBUS_FC_READ_COILS、AGILE_MODBUS_FC_READ_DISCRETE_INPUTS
The
address,nb, andsend_indexattributes need to be used, and theagile_modbus_slave_io_setAPI needs to be called to store the IO data in the data area starting fromctx->send_buf + send_index. -
AGILE_MODBUS_FC_READ_HOLDING_REGISTERS、AGILE_MODBUS_FC_READ_INPUT_REGISTERS
The
address,nb, andsend_indexattributes need to be used, and theagile_modbus_slave_register_setAPI needs to be called to store the register data in the data area starting fromctx->send_buf + send_index. -
AGILE_MODBUS_FC_WRITE_SINGLE_COIL、AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER
You need to use the
addressandbufattributes, forcebufto theint *type, and get the value and store it in a register. -
AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS
The
address,nb,bufattributes need to be used, and theagile_modbus_slave_io_getAPI needs to be called to obtain the IO data to be written. -
AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS
The
address,nb, andbufattributes need to be used, and theagile_modbus_slave_register_getAPI needs to be called to obtain the register data to be written. -
AGILE_MODBUS_FC_MASK_WRITE_REGISTER
You need to use the
addressandbufattributes, pass(buf[0] << 8) + buf[1]to get theandvalue, pass(buf[2] << 8) + buf[3 ]Gets theorvalue. Get the register valuedata, perform thedata = (data & and) | (or & (~and))operation to update thedatavalue, and write it to the register. -
AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS
You need to use the
address,buf,send_indexattributes, pass(buf[0] << 8) + buf[1]to get the number of registers to be read, pass(buf[2] << 8) + buf[3]Get the register address to be written, and use(buf[4] << 8) + buf[5]to get the number of registers to be written. You need to call theagile_modbus_slave_register_getAPI to obtain the register data to be written, and call theagile_modbus_slave_register_setAPI to store the register data in the data area starting fromctx->send_buf + send_index. -
Custom function code
You need to use the
send_index,nb, andbufattributes, and the user processes the data in the callback.send_index: current index of send buffer
nb: PUD - 1, which is the modbus data field length
buf: starting position of modbus data field
Note: After the user fills data into the send buffer in the callback, the
rsp_lengthvalue ofagile_modbus_slave_infoneeds to be updated.
-
Agile Modbus provides an implementation of agile_modbus_slave_callback_t, allowing users to access it simply and conveniently.
See examples/slave for examples of usage.
How to use:
#include "agile_modbus.h"
#include "agile_modbus_slave_util.h"
const agile_modbus_slave_util_t slave_util = {
/* User implementation */
};
agile_modbus_slave_handle(ctx, read_len, 0, agile_modbus_slave_util_callback, &slave_util, NULL);-
Introduction to
agile_modbus_slave_util_callback-
An implementation of
agile_modbus_slave_callback_tprovided by Agile Modbus, which requiresagile_modbus_slave_util_ttype variable pointer as private data. -
The private data is NULL, all function codes can respond and are successful, but the register data is still 0.
-
-
Introduction to
agile_modbus_slave_util_ttypedef struct agile_modbus_slave_util { const agile_modbus_slave_util_map_t *tab_bits; /**< Coil register definition array */ int nb_bits; /**< Number of coil register definition arrays */ const agile_modbus_slave_util_map_t *tab_input_bits; /**< Discrete input register definition array */ int nb_input_bits; /**< Number of discrete input register definition arrays */ const agile_modbus_slave_util_map_t *tab_registers; /**< Holding register definition array */ int nb_registers; /**< Number of holding register definition arrays */ const agile_modbus_slave_util_map_t *tab_input_registers; /**< Input register definition array */ int nb_input_registers; /**< Number of input register definition arrays */ int (*addr_check)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info); /**< Address check interface */ int (*special_function)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info); /**< Special function code processing interface */ int (*done)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, int ret); /**< Processing end interface */ } agile_modbus_slave_util_t;
-
Introduction to
agile_modbus_slave_util_maptypedef struct agile_modbus_slave_util_map { int start_addr; /**< starting address */ int end_addr; /**< end address */ int (*get)(void *buf, int bufsz); /**< Get register data interface */ int (*set)(int index, int len, void *buf, int bufsz); /**< Set register data interface */ } agile_modbus_slave_util_map_t;
-
Precautions:
-
The number of registers determined by the start address and end address is limited. Changing the size of the
map_bufarray inside the function can make it larger.-
bit register < 250
-
register register < 125
-
-
The interface function is NULL, and the function code corresponding to the register can respond and is successful.
-
-
getinterfaceCopy all data in the address field to
buf. -
setinterface-
index: offset within the address field -
len: length
Modify data based on
indexandlen. -
-
-
Examples on PC are provided in the examples folder, which can be compiled and run under
WSLorLinux.-
Examples of RTU/TCP master and slave
-
Examples of special function codes
RTU point-to-point transmission of files: Demonstrates the use of special function codes
RTU broadcast transmission file: Demonstrates the use of
frame_lengthinagile_modbus_slave_handle
-
-
mcu_demos provides examples on MCU.
-
AT32F437_Boot A Bootloader based on RT-Thread implemented on AT32F437 that supports Modbus firmware upgrade.
-
HPM6750_Boot A Bootloader based on RT-Thread implemented on HPM6750 that supports Modbus firmware upgrade.
- Use
Doxywizardto open Doxyfile and run it. The generated file will be under doxygen/output. Graphvizpath needs to be changed.HTMLis generated without usingchmformat. If it is enabled, you need to change thehhc.exepath.
If Agile Modbus solves your problem, you might as well scan the QR code above and invite me for a cup of coffee ~
- Maintenance: Ma Longwei
- Home page: https://github.com/loogg/agile_modbus
- Email: [email protected]