MODBUS协议是一种主从式协议,即主站发起通讯,从站响应。主设备可以与一个或多个从设备通信。

当MODBUS主设备想要从一台从设备请求数据的时候,这个主设备会发送一条包含该从设备地址、请求数据以及一个用于检测错误的校验码的数据帧。网络上的所有其它设备都可以看到这一条信息,但是只有指定地址的设备才会做出反应。数据帧结构如下:

AliOS Things的MODBUS主协议是针对物联网RTOS系统特点而专门设计的。协议在实现时分为四层,整体架构如下:

API层主要用于提供用户接口。PDU层用于封装数据包PDU部分,ADU层用于封装数据包ADU部分,PHY用于通信链路初始化及数据发送与接收。

API列表

mbmaster_rtu_init() 初始化基于RTU的MODBUS主协议通信
mbmaster_rtu_uninit() 释放基于RTU的modbus主协议通信
mbmaster_read_coils() 读线圈,功能码0x01
mbmaster_read_discrete_inputs() 读离散输入,功能码0x02
mbmaster_read_holding_registers() 读保持寄存器,功能码0x03
mbmaster_read_input_registers() 读输入寄存器,功能码0x04
mbmaster_write_single_coil() 写单个线圈,功能码0x05
mbmaster_write_single_register() 写单个寄存器,功能码0x06
mbmaster_write_multiple_coils() 写多个线圈,功能码0x0F
mbmaster_write_multiple_registers() 写多个寄存器,功能码0x10

使用

添加该组件

在调用该组件的其它组件aos.mk中增加

$(NAME)_COMPONENTS += mbmaster
或者在应用层代码中包含头文件
#include "mbmaster.h"

包含头文件

#include "mbmaster.h"

使用示例

#include "modbus/mbmaster.h"

/* 定义串口通讯参数 */
#define SERIAL_PORT         2             /* 用于MODBUS通讯的uart端口号 */
#define SERIAL_BAUD_RATE    9600 /* 波特率 */

/* 定义从设备参数 */
#define DEVICE1_SLAVE_ADDR  0x1  /* 从设备地址 */

/* 定义请求参数 */
#define DEVICE1_REG1_ADDR   0x0   /* 待读寄存器地址 */
#define RECV_LEN_MAX            20     /* 数据缓存长度, 必须大于等于  (REQ_REGISTER_NUMBER * 2) */
#define REQ_REGISTER_NUMBER 2    /* 读寄存器的个数 */

void mb_main(void)
{
    uint8_t     buf[RECV_LEN_MAX];
    uint8_t     len;
    mb_status_t status;
    uint16_t    simulator1 = 0, simulator2 = 0;
    uint16_t    data_write = 0, data_resp = 0;
    uint16_t   *register_buf;

    /* 定义一个指针,调用mbmaster_rtu_init函数进行初始化 */
    mb_handler_t *mb_handler;

    /**
     * 初始化通信端口。用户需根据实际使用的串口参数进行配置
     */
    status = mbmaster_rtu_init(&mb_handler, SERIAL_PORT, SERIAL_BAUD_RATE, MB_PAR_NONE);
    if (status != MB_SUCCESS) {
        LOGE(MODBUSM_APP, "mbmaster init error");
        return;
    }
    /* 该用例每隔2秒循环一次,首先发起一个写请求,然后再发起一个读请求。*/
    while (1) {
        /**
         * 发起写寄存器请求。
         * data_resp是从设备返回的写结果,如果写成功,则该值等于写入值data_write。
         */
        status = mbmaster_write_single_register(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_REG1_ADDR,
                                                data_write, NULL, &data_resp, NULL, AOS_WAIT_FOREVER);
        if (status == MB_SUCCESS) {
            if (data_write != data_resp) {
                LOGE(MODBUSM_APP, "write single register error");
            } else {
                LOGI(MODBUSM_APP, "write single register ok");
            }
        } else {
            LOGE(MODBUSM_APP, "write single register error");
        }

        data_write++;  /* 生成一个新的待写值 */

        aos_msleep(1000);

        memset(buf, 0, RECV_LEN_MAX);

        /**
         * 发起一个读寄存器请求。
         * 缓存长度必须大于等于 (REQ_REGISTER_NUMBER * 2)
         */
        status = mbmaster_read_holding_registers(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_REG1_ADDR,
                                                 REQ_REGISTER_NUMBER, buf, &len, AOS_WAIT_FOREVER);
        if (status == MB_SUCCESS) {
            /*寄存器长度为16位  */
            register_buf = buf;
            simulator1 = register_buf[0];
            simulator2 = register_buf[1];
            LOGI(MODBUSM_APP, "read holding register simulator1: %d,simulator2: %d", simulator1, simulator2);
        } else {
            LOGE(MODBUSM_APP, "read holding register error");
        }

        aos_msleep(1000);
    }
}

API详情

modbus的应用层API说明请参考include/bus/modbus/mbmaster.h

aos_mbmaster_rtu_init()

初始化基于RTU的MODBUS主协议通信。

函数原型

mb_status_t mbmaster_rtu_init(mb_handler_t **handler, uint8_t port, uint32_t baud_rate, mb_parity_t parity);

输入参数

handler modbus句柄,用户定义一个mb_handler_t类型指针,将该指针的地址传入,调用成功后该指针指向新分配的句柄结构体
port 用于通信的串口端口号 2
baud_rate 串口波特率 9600
parity 串行通信校验方式,MB_PAR_NONE不校验,MB_PAR_ODD奇校验,MB_PAR_EVEN偶校验 MB_PAR_NONE

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

mb_status_t status;
mb_handler_t *mb_handler;

status = mbmaster_rtu_init(&mb_handler, SERIAL_PORT, SERIAL_BAUD_RATE, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

aos_mbmaster_rtu_uninit()

释放基于RTU的modbus主协议通信。

函数原型

mb_status_t mbmaster_rtu_uninit(mb_handler_t *req_handler);

输入参数

req_handler modbus句柄

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

mb_status_t status;
mb_handler_t *mb_handler;

status = mbmaster_rtu_init(&mb_handler, SERIAL_PORT, SERIAL_BAUD_RATE, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}
……
mbmaster_rtu_uninit(&mb_handler);

mbmaster_read_coils()

读线圈,功能码0x01。线圈是位码形式,每个线圈长度为1 bit。

函数原型

mb_status_t mbmaster_read_coils(mb_handler_t *req_handler, uint8_t slave_addr, uint16_t start_addr,
                                                     uint16_t quantity, uint8_t *respond_buf, uint8_t *respond_count,
                                                     uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
start_addr 待读线圈起始地址 0x0
quantity 待读线圈数量 10
respond_buf 用于接收从设备响应数据的缓存,该缓存长度必须大于等于quantity/8字节(若不能整除需再增加1字节
respond_count 从设备实际返回的数据长度,单位为字节)
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_COILS_ADDR  0x0
#define RECV_LEN_MAX        20
#define REQ_BIT_NUMBER      13

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

status = mbmaster_read_coils(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_COILS_ADDR,
                                     REQ_BIT_NUMBER, buf, &len, AOS_WAIT_FOREVER);

mbmaster_read_discrete_inputs()

读离散输入,功能码0x02。离散输入是位码形式,每个离散输入长度为1 bit。

函数原型

mb_status_t mbmaster_read_discrete_inputs(mb_handler_t *req_handler, uint8_t slave_addr, uint16_t start_addr,
                                          uint16_t quantity, uint8_t *respond_buf, uint8_t *respond_count,
                                          uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
start_addr 待读离散输入的起始地址 0x0
quantity 待读离散输入的数量 10
respond_buf 用于接收从设备响应数据的缓存,该缓存长度必须大于等于quantity/8 字节(若不能整除需再增加1字节)
respond_count 从设备实际返回的数据长度,单位为字节
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_COILS_ADDR  0x0
#define RECV_LEN_MAX        20
#define REQ_BIT_NUMBER      13

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {     
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

status = mbmaster_read_discrete_inputs(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_COILS_ADDR,
                                               REQ_BIT_NUMBER, buf, &len, AOS_WAIT_FOREVER);

mbmaster_read_holding_registers()

读保持寄存器,功能码0x03。每个寄存器长度为16位。

函数原型

mb_status_t mbmaster_read_holding_registers(mb_handler_t *req_handler, uint8_t slave_addr,
                                           uint16_t start_addr, uint16_t quantity,
                                           uint8_t *respond_buf, uint8_t *respond_count, uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
start_addr 待读寄存器的起始地址 0x0
quantity 待读寄存器的数量 2
respond_buf 用于接收从设备响应数据的缓存,该缓存长度必须大于等于quantity *2 字节
respondcount 从设备实际返回的数据长度,单位为字节
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_REG1_ADDR   0x0
#define RECV_LEN_MAX        20
#define REQ_REGISTER_NUMBER 2

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

status = mbmaster_read_holding_registers(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_REG1_ADDR,
                                                 REQ_REGISTER_NUMBER, buf, &len, AOS_WAIT_FOREVER);

mbmaster_read_input_registers()

读输入寄存器,功能码0x04。每个寄存器长度为16位。

函数原型

mb_status_t mbmaster_read_input_registers(mb_handler_t *req_handler, uint8_t slave_addr,
                                         uint16_t start_addr, uint16_t quantity,
                                         uint8_t *respond_buf, uint8_t *respond_count, uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
start_addr 待读寄存器的起始地址 0x0
quantity 待读寄存器的数量 2
respond_buf 用于接收从设备响应数据的缓存,该缓存长度必须大于等于quantity *2 字节
respondcount 从设备实际返回的数据长度,单位为字节
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_REG1_ADDR   0x0
#define RECV_LEN_MAX        20
#define REQ_REGISTER_NUMBER 2

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

status = mbmaster_read_input_registers(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_REG1_ADDR,
                                               REQ_REGISTER_NUMBER, buf, &len, AOS_WAIT_FOREVER);

mbmaster_write_single_coil()

写单个线圈,功能码0x05。线圈是位码形式,每个线圈长度为1 bit。

函数原型

mb_status_t mbmaster_write_single_coil(mb_handler_t *req_handler, uint8_t slave_addr, uint16_t coil_addr,
                                       uint16_t coil_value, uint16_t *resp_addr, uint16_t *resp_value,
                                       uint8_t *exception_code, uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
coil_addr 待写线圈地址 0x0
coil_value 待写的值,0x0000表示off即写0,0xFF00表示ON即写1 0xFF00
resp_addr 从设备返回的实际写的地址,写成功时等于coil_addr
resp_value 从设备返回的实际写的值,写成功时等于coil_value
exception_code 异常码,当写异常时有效
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_COILS_ADDR  0x0
#define RECV_LEN_MAX        20
#define REQ_BIT_NUMBER      13

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;
uint16_t    data_resp = 0;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

status = mbmaster_write_single_coil(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_COILS_ADDR ,
                                            0xFF00, NULL, &data_resp, NULL, AOS_WAIT_FOREVER);

mbmaster_write_single_register()

写单个寄存器,功能码0x06。寄存器长度16位。

函数原型

mb_status_t mbmaster_write_single_register(mb_handler_t *req_handler, uint8_t slave_addr, uint16_t register_addr,
                                           uint16_t register_value, uint16_t *resp_addr, uint16_t *resp_value,
                                           uint8_t *exception_code, uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
register_addr 待写寄存器地址 0x0
register_value 待写的值 0x03
resp_addr 从设备返回的实际写的地址,写成功时等于register_addr
resp_value 从设备返回的实际写的值,写成功时等于register_value
exception_code 异常码,当写异常时有效
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_REG1_ADDR   0x0
#define RECV_LEN_MAX        20
#define REQ_REGISTER_NUMBER 2

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;
uint16_t   data_resp = 0;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

status = mbmaster_write_single_register(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_REG1_ADDR,
                                                0x03, NULL, &data_resp, NULL, AOS_WAIT_FOREVER);

mbmaster_write_multiple_coils()

写多个线圈,功能码0x0F。线圈是位码形式,每个线圈长度为1 bit。

函数原型

mb_status_t mbmaster_write_multiple_coils(mb_handler_t *req_handler, uint8_t slave_addr, uint16_t start_addr,
                                          uint16_t quantity, uint8_t *outputs_buf, uint16_t *resp_addr,
                                          uint16_t *resp_quantity, uint8_t *exception_code, uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
start_addr 待写线圈起始地址 0x0
quantity 写的数量 10
outputs_buf 输出缓存,里面保存了待写的值,位码形式1位对应1个线圈
resp_addr 从设备返回的实际写的地址,写成功时等于start_addr
resp_quantity 从设备返回的实际写的数量,写成功时等于quantity
exception_code 异常码,当写异常时有效
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_COILS_ADDR  0x0
#define RECV_LEN_MAX        20
#define REQ_BIT_NUMBER      13

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     buf[RECV_LEN_MAX];
uint8_t     len;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

buf[0] = 0x55;
buf[1] = 0x3c;

status = mbmaster_write_multiple_coils(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_COILS_ADDR, REQ_BIT_NUMBER,
                                               buf, NULL, NULL, NULL, AOS_WAIT_FOREVER);

mbmaster_write_multiple_registers()

写多个寄存器,功能码0x10。寄存器长度为16位。

函数原型

mb_status_t mbmaster_write_multiple_registers(mb_handler_t *req_handler, uint8_t slave_addr, uint16_t start_addr,
                                              uint16_t quantity, uint8_t *outputs_buf, uint16_t *resp_addr,
                                              uint16_t *resp_quantity, uint8_t *exception_code, uint32_t timeout);

输入参数

req_handler modbus句柄
slave_addr 从设备地址 0x01
start_addr 待写寄存器的起始地址 0x0
quantity 写的数量 10
outputs_buf 输出缓存,里面保存了待写的值
resp_addr 从设备返回的实际写的地址,写成功时等于start_addr
resp_quantity 从设备返回的实际写的数量,写成功时等于quantity
exception_code 异常码,当写异常时有效
timeout 请求超时时间,单位ms 100

返回参数

返回参数宏定义见include/bus/modbus/mbmaster.h文件

调用示例

#define DEVICE1_SLAVE_ADDR  0x1
#define DEVICE1_REG1_ADDR   0x0
#define RECV_LEN_MAX        20
#define REQ_REGISTER_NUMBER 2

mb_status_t status;
mb_handler_t *mb_handler;
uint8_t     register_buf[RECV_LEN_MAX];
uint8_t     len;

status = mbmaster_rtu_init(&mb_handler, 2, 9600, MB_PAR_NONE);
if (status != MB_SUCCESS) {
    LOGE(MODBUSM_APP, "mbmaster init error");
    return;
}

register_buf[0] = 0x03;
register_buf[1] = 0x04;

status = mbmaster_write_multiple_registers(mb_handler, DEVICE1_SLAVE_ADDR, DEVICE1_REG1_ADDR, REQ_REGISTER_NUMBER,
										   register_buf, NULL, NULL, NULL, AOS_WAIT_FOREVER);

配置说明

在AliOS Things 源码里面输入aos make menuconfig命令即可进入到menuconfig配置界面中,依次选择Middleware -> Modbus Master Support即可进入到MODBUS主协议组件的配置界面:

配置项说明

--- Key-value Storage
--- Modbus Master Support
[*]   Enable the rtu transmission mode (NEW)          #使能RTU传输,默认使能
(1)   The max number of handler resources (NEW)  #modbus句柄结构体的数量,默认为1

移植说明

MODBUS主协议RTU方式基于串口HAL层接口,完成串口适配后即可使用。

其他

返回参数定义

返回参数定义在include/bus/modbus/mbmaster.h文件中。

typedef enum mb_status {
    MB_SUCCESS = 0u,
    MB_MUTEX_ERROR,              /* 请求互斥信号量失败,MODBUS协议栈内部通过互斥信号量保护临界资源 */
    MB_INVALID_SLAVE_ADDR,  /* 无效的从设备地址 */
    MB_INVALID_PARAM,           /* 无效的参数 */
    MB_RESPOND_EXCEPTION,    /* 远程设备响应异常 */
    MB_RESPOND_LENGTH_ERR, /* 远程设备响应的数据帧长度错误 */
    MB_RESPOND_FRAME_ERR,   /* 远程设备响应的帧错误,比如校验和错误 */
    MB_RESPOND_TIMEOUT,       /* 请求超时 */
    MB_CANNOT_GET_HANDLER, /*分配句柄失败,可能是配置的资源不足引起的 */
    MB_SLAVE_NO_RESPOND,      /* 从设备无响应 */
    MB_FRAME_SEND_ERR,          /* 帧发送错误 */
    MB_SERIAL_INIT_FAILED,        /* 串行通信链路初始化失败 */
    MB_SERIAL_UNINIT_FAILED,   /* 串行通信链路逆初始化失败 */
    MB_FUNCTION_CODE_NOT_SUPPORT, /* 不支持的功能码 */
} mb_status_t;

使用注意事项

在调用mbmaster_read_coils()、mbmaster_read_discrete_inputs()、mbmaster_read_holding_registers()、mbmaster_read_input_registers函数时,需确保传入的用户缓存足够大,若为读寄存器类型则至少为2*quantity字节,若为读位码类型,则至少为quantity/8字节(若无法整除还需再增加1)。