Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1333774
  • 博文数量: 124
  • 博客积分: 5772
  • 博客等级: 大校
  • 技术积分: 1647
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-27 10:39
文章分类

全部博文(124)

文章存档

2020年(1)

2019年(1)

2018年(5)

2017年(2)

2016年(17)

2015年(3)

2014年(7)

2013年(11)

2012年(13)

2011年(30)

2010年(34)

分类: LINUX

2016-08-31 13:30:12

在嵌入式LINUX开发板上,基于libmodbus第三方库编程实现土壤水分及温度的读取,传感器采用大连祺峰科技有限公司的土壤水分温度传感器(型号:SMTS-II-485)。在程序运行过程中,遇到了一些问题,记录如下:

查看土壤水分温度传感器手册,若要读取水分温度,则需要发送如下RTU帧:FE 03 00 00 00 02 D0 04,这里FE是传感器modbus站地址,这是传感器出厂默认地址。在libmodbus程序中,程序运行到modbus_set_slave(ctx,0xFE)时,程序报错并终止运行,为了查清原因,查看libmodbus源码中modbus_set_slave()函数的实现。

Modbus.c文件中,可以看到modbus_set_slave()函数的实现如下:

  1. /* Define the slave number */
  2. int modbus_set_slave(modbus_t *ctx, int slave)
  3. {
  4.     return ctx->backend->set_slave(ctx, slave);
  5. }
这个函数执行的是ctx->backend->set_slave(ctx, slave),ctx是一个modbus_t类型的结构体,定义在Modbus.h中

  1. typedef struct _modbus modbus_t;
在Modbus-Private.h中有_modbus的具体定义

  1. struct _modbus {
  2.     /* Slave address */
  3.     int slave;
  4.     /* Socket or file descriptor */
  5.     int s;
  6.     int debug;
  7.     int error_recovery;
  8.     struct timeval response_timeout;
  9.     struct timeval byte_timeout;
  10.     const modbus_backend_t *backend;
  11.     void *backend_data;
  12. };
可以看到成员backend是一个modbus_backend_t,继续追踪modbus_backend_t,定义在Modbus-Private.h中

  1. typedef struct _modbus_backend {
  2.     unsigned int backend_type;
  3.     unsigned int header_length;
  4.     unsigned int checksum_length;
  5.     unsigned int max_adu_length;
  6.     int (*set_slave) (modbus_t *ctx, int slave);
  7.     int (*build_request_basis) (modbus_t *ctx, int function, int addr,
  8.                                 int nb, uint8_t *req);
  9.     int (*build_response_basis) (sft_t *sft, uint8_t *rsp);
  10.     int (*prepare_response_tid) (const uint8_t *req, int *req_length);
  11.     int (*send_msg_pre) (uint8_t *req, int req_length);
  12.     ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length);
  13.     ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length);
  14.     int (*check_integrity) (modbus_t *ctx, uint8_t *msg,
  15.                             const int msg_length);
  16.     int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req,
  17.                                    const uint8_t *rsp, int rsp_length);
  18.     int (*connect) (modbus_t *ctx);
  19.     void (*close) (modbus_t *ctx);
  20.     int (*flush) (modbus_t *ctx);
  21.     int (*select) (modbus_t *ctx, fd_set *rfds, struct timeval *tv, int msg_length);
  22.     int (*filter_request) (modbus_t *ctx, int slave);
  23. } modbus_backend_t;
这个结构体中除了前面几个变量成员外,剩下的都是一些函数指针,这些函数指针用来对ctx进行操作。其中看到第一个函数指针int (*set_slave) (modbus_t *ctx, int slave);就是用来设置从站地址的。那么这个函数的具体实现在哪里?

在Modubs-rtu.c文件中,有函数modbus_new_rtu()的具体实现,这个函数用来创建一个modbus RTU通信的context(可以理解为一个标识符),这个函数的源码如下:

  1. modbus_t* modbus_new_rtu(const char *device,
  2.                          int baud, char parity, int data_bit,
  3.                          int stop_bit)
  4. {
  5.     modbus_t *ctx;
  6.     modbus_rtu_t *ctx_rtu;
  7.     size_t dest_size;
  8.     size_t ret_size;

  9.     ctx = (modbus_t *) malloc(sizeof(modbus_t));
  10.     _modbus_init_common(ctx);

  11.     ctx->backend = &_modbus_rtu_backend;
  12.     ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t));
  13.     ctx_rtu = (modbus_rtu_t *)ctx->backend_data;

  14.     dest_size = sizeof(ctx_rtu->device);
  15.     ret_size = strlcpy(ctx_rtu->device, device, dest_size);
  16.     if (ret_size == 0) {
  17.         fprintf(stderr, "The device string is empty\n");
  18.         modbus_free(ctx);
  19.         errno = EINVAL;
  20.         return NULL;
  21.     }

  22.     if (ret_size >= dest_size) {
  23.         fprintf(stderr, "The device string has been truncated\n");
  24.         modbus_free(ctx);
  25.         errno = EINVAL;
  26.         return NULL;
  27.     }

  28.     ctx_rtu->baud = baud;
  29.     if (parity == 'N' || parity == 'E' || parity == 'O') {
  30.         ctx_rtu->parity = parity;
  31.     } else {
  32.         modbus_free(ctx);
  33.         errno = EINVAL;
  34.         return NULL;
  35.     }
  36.     ctx_rtu->data_bit = data_bit;
  37.     ctx_rtu->stop_bit = stop_bit;

  38.     return ctx;
  39. }
这个函数中有很重要的一条语句:ctx->backend = &_modbus_rtu_backend;条语这条语句实际上就使用_modbus_rtu_backend对ctx中的backend成员进行初始化,因此当调用modbus_new_rtu()函数时,除了完成对串口的初始化之外,也完成了对ctx结构体中的成员进行初始化。那么_modbus_rtu_backend这个变量是在哪里定义的?追踪发下,在Modubs-rtu.c文件中有对_modbus_rtu_backend的定义:

  1. const modbus_backend_t _modbus_rtu_backend = {
  2.     _MODBUS_BACKEND_TYPE_RTU,
  3.     _MODBUS_RTU_HEADER_LENGTH,
  4.     _MODBUS_RTU_CHECKSUM_LENGTH,
  5.     MODBUS_RTU_MAX_ADU_LENGTH,
  6.     _modbus_set_slave,
  7.     _modbus_rtu_build_request_basis,
  8.     _modbus_rtu_build_response_basis,
  9.     _modbus_rtu_prepare_response_tid,
  10.     _modbus_rtu_send_msg_pre,
  11.     _modbus_rtu_send,
  12.     _modbus_rtu_recv,
  13.     _modbus_rtu_check_integrity,
  14.     NULL,
  15.     _modbus_rtu_connect,
  16.     _modbus_rtu_close,
  17.     _modbus_rtu_flush,
  18.     _modbus_rtu_select,
  19.     _modbus_rtu_filter_request
  20. }
这段代码对 modbus_backend_t类型的变量“_modbus_rtu_backend”进行了初始化。其中可以看到,modbus_set_slave()函数真正调用的是_modbus_set_slave这个函数。在Modbus-rtu.c文件中可以找到_modbus_set_slave()的函数定义

  1. /* Define the slave ID of the remote device to talk in master mode or set the
  2.  * internal slave ID in slave mode */
  3. static int _modbus_set_slave(modbus_t *ctx, int slave)
  4. {
  5.     /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
  6.     if (slave >= 0 && slave <= 247) {
  7.         ctx->slave = slave;
  8.     } else {
  9.         errno = EINVAL;
  10.         return -1;
  11.     }

  12.     return 0;
  13. }
阅读这个函数源码可以知道,modbus_set_slave(modbus_t *ctx, int slave)这个函数的作用是把ctx结构体中的slave成员设置为参数slave的值。该函数中规定,libmodbus中站地址的有效范围是0到247,其中0是广播地址。而所用传感器的默认出厂站地址是0xFE(254),超出了libmodbus中站地址的有效范围,因此导致对ctx的slave成员设置不成功。

实际上当使用modbus_new_rtu()函数创建ctx时,对ctx的各成员已经初始化了默认值。在modbus_new_rtu()函数中调用了函数_modbus_init_common(ctx);该函数用来对ctx成员进行初始化默认值。这个函数的实现在Modbus.c文件中:

  1. void _modbus_init_common(modbus_t *ctx)
  2. {
  3.     /* Slave and socket are initialized to -1 */
  4.     ctx->slave = -1;
  5.     ctx->s = -1;

  6.     ctx->debug = FALSE;
  7.     ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;

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

  10.     ctx->byte_timeout.tv_sec = 0;
  11.     ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT;
  12. }
这里可以看到ctx->slave = -1;也就是说ctx中slave默认值是-1。

那么执行modbus_set_slave(ctx,0xFE)函数产生的错误信息是哪里来的呢?在Modbus-rtu.c文件中有一个函数:

  1. /* Builds a RTU request header */
  2. static int _modbus_rtu_build_request_basis(modbus_t *ctx, int function,
  3.                                            int addr, int nb,
  4.                                            uint8_t *req)
  5. {
  6.     assert(ctx->slave != -1);
  7.     req[0] = ctx->slave;
  8.     req[1] = function;
  9.     req[2] = addr >> 8;
  10.     req[3] = addr & 0x00ff;
  11.     req[4] = nb >> 8;
  12.     req[5] = nb & 0x00ff;

  13.     return _MODBUS_RTU_PRESET_REQ_LENGTH;
  14. }
这个函数用来构造modbus-RTU帧头(RTU帧的前6个字节,包括地址、功能码、寄存器地址、个数等信息),这个函数中调用了assert(ctx->slave != -1);LINUX下assert函数的用法是assert( int expression );其作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行

执行modbus_set_slave(ctx,0xFE)函数时,由于0xFE不在libmodbus有效的站地址范围内(0~247),因此ctx结构体中的slave成员还是保持原来用_modbus_init_common(ctx)初始化的值,即-1,在构造modbus-RTU帧头的时候,运行到assert(ctx->slave != -1)就会报错,并终止程序运行。

解决方法就是把传感器的站地址修改为02(在0~247之内),再对传感器进行读取,成功。


附记:对连祺峰科技有限公司的土壤水分温度传感器(型号:SMTS-II-485)修改站地址流程如下:
该传感器有5根线,其中有一个为屏蔽线(或SET线),若要对传感器执行写操作,那么必须把SET线接高电平。当SET线
置高时,传感器站地址自动变为0xFF,查看传感器手册,站地址寄存器地址为0x200,因此需往传感器发送如下RTU指令:FF 06 02 00 00 02 1C 6D,传感器返回FF 06 02 00 00 02 1C 6D,说明地址修改成功,这里是把传感器地址修改为02。把SET重新接回到低电平,用02地址访问传感器,成功。

另外,在
SMTS-II-485手册中提到,当SET接高电平时,传感器内部通讯参数自动变为为9600,N,8,2(波特率9600,无校验,8个数据位,2个停止位),实验发现并非如此。SET接高电平时,通信参数还是和读取时一样,即9600,N,8,1(波特率9600,无校验,8个数据位,1个停止位







阅读(8414) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

心如止境2017-08-31 16:04:54

大神在吗,再请教一个问题,我读取的寄存器地址是0x0509,寄存器中的实际值是1048756,而我程序中读出来显示的是 tab_reg[1]=65527 和tab_reg[0]=15,这是怎么回事请大神指教一下,程序如下:
int regs = modbus_read_registers(mb,0x0509,0x0002,tab_reg);
程序中把16进制转换成10进制也是一样的结果