libmodus源码解读

17 篇文章 0 订阅

libmodbus 源码分析:
1.  uint8_t 的头文件  #include "stdint.h" 
2.   两个最核心的结构体

typedef struct _modbus_backend {
    unsigned int backend_type;
    unsigned int header_length;
    unsigned int checksum_length;
    unsigned int max_adu_length;
    int (*set_slave) (modbus_t *ctx, int slave);
    int (*build_request_basis) (modbus_t *ctx, int function, int addr, int nb, uint8_t *req);                             
    int (*build_response_basis) (sft_t *sft, uint8_t *rsp);
    int (*prepare_response_tid) (const uint8_t *req, int *req_length);
    int (*send_msg_pre) (uint8_t *req, int req_length);
    ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length);
    int (*receive) (modbus_t *ctx, uint8_t *req);
    ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length);
    int (*check_integrity) (modbus_t *ctx, uint8_t *msg,
                            const int msg_length);
    int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req,
                                   const uint8_t *rsp, int rsp_length);
    int (*connect) (modbus_t *ctx);
    void (*close) (modbus_t *ctx);
    int (*flush) (modbus_t *ctx);
    int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
    void (*free) (modbus_t *ctx);
} modbus_backend_t;

struct _modbus {
    /* Slave address */
    int slave;
    /* Socket or file descriptor */
    int s;
    int debug;
    int error_recovery;
    struct timeval response_timeout;
    struct timeval byte_timeout;
    struct timeval indication_timeout;
    const modbus_backend_t *backend;   //把各种操作封装在一个结构体中,也可以不封装。
    void *backend_data;
};

typedef struct _modbus modbus_t;

这两个结构体定义在 modbus-private.h  从中可以借鉴到一点 在取名时不对外的结构体或函数命名可以在前面加 "_"

3.  应用:
----------------------------random-test-client.c-------------------------- 过程
modbus_t ctx = modbus_new_tcp("127.0.0.1", 1502);   // ctx表示容器  在这里既可以表示cli 也可以表示ser
modbus_connect(ctx);
modbus_read_registers()  位于modbus.c协议核心层
  ->read_registers       位于modbus.c协议核心层
     ->    req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req);  //构建requeset基础信息

           rc = send_msg(ctx, req, req_length);
           if (rc > 0) {

           rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
           rc = check_confirmation(ctx, req, rsp, rc);
           offset = ctx->backend->header_length;
            for (i = 0; i < rc; i++) {
            /* shift reg hi_byte to temp OR with lo_byte */
            dest[i] = (rsp[offset + 2 + (i << 1)] << 8) |
                rsp[offset + 3 + (i << 1)];
           }
           return dest;


           
4. modbus_new_tcp 函数过程:

modbus_t modbus_tcp_t的区别和联系:
modbus_t  定义了modbus rtu/tcp/asccii 的公共属性和接口。

modbus_tcp_t : 定义tcp 独有属性。
modbus_new_tcp 函数过程:

函数功能:给 modbus_t 变量分配内存并初始化

特别的,这里会用到一个 modbus_tcp_t  的结构体来初始化其私有数据变量ctx->backend_data
  
1. 分配内存
      ctx = (modbus_t *)malloc(sizeof(modbus_t)); 
      ctx->backend_data = (modbus_tcp_t *)malloc(sizeof(modbus_tcp_t));  //私有数据  
2. 函数初始化            
      ctx->backend = &_modbus_tcp_backend;        init函数指针 
3. 私有数据初始化      
      ctx_tcp = (modbus_tcp_t *)ctx->backend_data; // modbus_tcp_t ctx_tcp;
      填充数据
      ctx_rtu->confirmation_to_ignore = FALSE;
      ctx_rtu->baud = baud;  

     
5.      
     
// 具体的modbus_backend_t     
    modbus-tcp.c  _modbus_tcp_backend结构体变量。 全局
const modbus_backend_t _modbus_tcp_backend = {
    _MODBUS_BACKEND_TYPE_TCP,
    _MODBUS_TCP_HEADER_LENGTH,
    _MODBUS_TCP_CHECKSUM_LENGTH,
    MODBUS_TCP_MAX_ADU_LENGTH,
    _modbus_set_slave,
    _modbus_tcp_build_request_basis,
    _modbus_tcp_build_response_basis,
    _modbus_tcp_prepare_response_tid,
    _modbus_tcp_send_msg_pre,
    _modbus_tcp_send,
    _modbus_tcp_receive,
    _modbus_tcp_recv,
    _modbus_tcp_check_integrity,
    _modbus_tcp_pre_check_confirmation,
    _modbus_tcp_connect,
    _modbus_tcp_close,
    _modbus_tcp_flush,
    _modbus_tcp_select,
    _modbus_tcp_free
};     
     
6.     
     
//初始化modbus的公共部分
void _modbus_init_common(modbus_t *ctx)
{
    /* Slave and socket are initialized to -1 */
    ctx->slave = -1;
    ctx->s = -1;

    ctx->debug = FALSE;
    ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;

    ctx->response_timeout.tv_sec = 0;
    ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;

    ctx->byte_timeout.tv_sec = 0;
    ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT;

    ctx->indication_timeout.tv_sec = 0;
    ctx->indication_timeout.tv_usec = 0;
}

7. ----------------------------random-test-server.c--------------------------
modbus_mapping_t定义了modbus的四种寄存器,并进行了内存数据映射,以方便快速访问和读取寄存器的值。

其他跟client差不多,多了一个modbus_mapping_new

modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits,
                                     int nb_registers, int nb_input_registers)
{
    return modbus_mapping_new_start_address(
        0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers);
}


modbus_mapping_new_start_address :映射内存,实际是分配了4个内存。

实现如下:
    modbus_mapping_t *mb_mapping;

    mb_mapping = (modbus_mapping_t *)malloc(sizeof(modbus_mapping_t)); //申请内存
    /* 0X */
    mb_mapping->nb_bits = nb_bits;   nb:numbers 表示个数。
    mb_mapping->start_bits = start_bits;
    mb_mapping->tab_bits =(uint8_t *) malloc(nb_bits * sizeof(uint8_t)); 注意这里的长度
    memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t));


main函数实现:
    modbus_t *ctx;
    modbus_mapping_t *mb_mapping;
    ctx = modbus_new_tcp("127.0.0.1", 1502);
    mb_mapping = modbus_mapping_new(500, 500, 500, 500);
    s = modbus_tcp_listen(ctx, 1);
    modbus_tcp_accept(ctx, &s);
    rc = modbus_receive(ctx, query);
    //free malloc
    modbus_mapping_free(mb_mapping);
    modbus_close(ctx);
    modbus_free(ctx);

7.2  read函数API 调用过程

typedef struct _modbus modbus_t;

    rc = modbus_read_bits(ctx, UT_BITS_ADDRESS, UT_BITS_NB, tab_rp_bits);

-> rc = read_io_status(ctx, MODBUS_FC_READ_COILS, addr, nb, dest);

-> rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);

-> rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read); 最终调用到这里, 由上文  已经初始化好的结构体modbus_backend_t  函数指针指向的函数。


8. 超时处理 如果需要精确到ms us可以 也可以参考这里面的。

比如里面用到延时的例子:
static void _sleep_response_timeout(modbus_t *ctx)
{
    /* Response timeout is always positive */
#ifdef _WIN32
    /* usleep doesn't exist on Windows */
    Sleep((ctx->response_timeout.tv_sec * 1000) +
          (ctx->response_timeout.tv_usec / 1000));
#else
    /* usleep source code */
    struct timespec request, remaining;
    request.tv_sec = ctx->response_timeout.tv_sec;
    request.tv_nsec = ((long int)ctx->response_timeout.tv_usec) * 1000;
    while (nanosleep(&request, &remaining) == -1 && errno == EINTR) {
        request = remaining;
    }
#endif
}
    
    
9.困惑已久的问题:使用enum 或#define  场景:
如果所有的整型值是连续的建议使用enum,如果中间可能会有非连续的建议使用#define 
例如 libmodubs里面:
/* Protocol exceptions */
enum {
    MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01,
    MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
    MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
    MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE,
    MODBUS_EXCEPTION_ACKNOWLEDGE,
    MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY,
    MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE,
    MODBUS_EXCEPTION_MEMORY_PARITY,
    MODBUS_EXCEPTION_NOT_DEFINED,
    MODBUS_EXCEPTION_GATEWAY_PATH,
    MODBUS_EXCEPTION_GATEWAY_TARGET,
    MODBUS_EXCEPTION_MAX
};

/* Modbus function codes */
#define MODBUS_FC_READ_COILS                0x01
#define MODBUS_FC_READ_DISCRETE_INPUTS      0x02
#define MODBUS_FC_READ_HOLDING_REGISTERS    0x03
#define MODBUS_FC_READ_INPUT_REGISTERS      0x04
#define MODBUS_FC_WRITE_SINGLE_COIL         0x05
#define MODBUS_FC_WRITE_SINGLE_REGISTER     0x06
#define MODBUS_FC_READ_EXCEPTION_STATUS     0x07
#define MODBUS_FC_WRITE_MULTIPLE_COILS      0x0F
#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS  0x10
#define MODBUS_FC_REPORT_SLAVE_ID           0x11
#define MODBUS_FC_MASK_WRITE_REGISTER       0x16
#define MODBUS_FC_WRITE_AND_READ_REGISTERS  0x17

#define MODBUS_BROADCAST_ADDRESS    0

10 #define 宏定义
#define a b ,后接两个参数,表示用a代替b。
例如 :#define PI 3.14

       #define uint8_t  unsigned char 

#define 后只有一个参数
定义宏替换为空字符串,可以理解为后一个参数为空字符串

11. 宏函数与函数:
一般来说,应该用宏去替换小的、可重复的代码段,这样可以使程序运行速度更快;当任务比较复杂,需要多行代码才能实现时,或者要求程序越小越好时,就应该使用函数。

11.总结:libmodubs 构建了两个结构体 modbus_t 和 modbus_backend_t ,其中modbus_t用来表示modbus client或者server 通称为ctx(容器),modbus_backend_t 封装了各种modbus操作,作为指针放置在modbus_t 里面。

12 总结:


modbus_backend_t * 指向不同的具体类型设备 tcp/rtu。重点在于理解面向对象的设计方法

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值