当前位置:网站首页>libmodus源码解读

libmodus源码解读

2022-05-14 13:51:43一帘忧梦

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_new_tcp-》
    modbus_t *ctx;
    modbus_tcp_t *ctx_tcp;
      ctx = (modbus_t *)malloc(sizeof(modbus_t));}
      _modbus_init_common(ctx);  //给ctx 赋初始值  不管是modbus_tcp 还是modbus_rtu都是调用这个函数。
      
    ctx->slave = MODBUS_TCP_SLAVE;       //tcp 

    ctx->backend = &_modbus_tcp_backend; //tcp  _modbus_tcp_backend 这个是一个已经定义并初始化了的结构体。 位于modbus-tcp.c

    ctx->backend_data = (modbus_tcp_t *)malloc(sizeof(modbus_tcp_t));  //tcp  分配内存
    
    ctx_tcp = (modbus_tcp_t *)ctx->backend_data;
    ->根据modbus_new_tcp 传入的参数填充ctx_tcp 也就是ctx->backend_data

    >end

     
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);


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 里面。


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

原网站

版权声明
本文为[一帘忧梦]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_29898767/article/details/124755995

随机推荐