mod bus _ build _ request 는 Mod bus 요청 프레 임을 수 동 으로 구성 하는 lib mod bus 라이 브 러 리의 기본 함 수 입니다 .대부분의 경우 개발 자는 mod bus _ read _ register s 및 mod bus _ write _ bit 와 같은 상 위 레벨 함 수를 사용하여 Mod bus 통신 을 수행 할 수 있지만 특정 특수 시 나 리오 (사 용 자 정의 기능 코드 , 프로토 콜 디 버 그 , 비 표 준 기능 구현 과 같은) 에서는 요청 프레 임을 수 동 으로 빌 드 해야합니다 .이 문서 에서는 mod bus _ build _ request 함 수의 프로토 타입 , 매 개 변 수의 의미 , 사용 방법 및 전체 예 제 코드를 자세히 설명 합니다 .
1) 함 수 프로토 타 입
int mod bus _ build _ request (
mod bus _ t * c t x , Mod bus 컨 텍 스트 객 체
int 함 수 , Mod bus 기능 코드
int ad dr , / / 시작 주소
int nb , / / 수 량 / 숫자
u int 8 _ t * re q / / 출 력 요청 프레 임 버 퍼
);
1. 1 매 개 변 수 상세 설명
| 매개변수 | ||
|---|---|---|
| ctx | ||
| function | ||
| addr | ||
| nb | ||
| req |
1. 2 반환 값
- 成功
- 失败
기능 코드 와 매 개 변 수의 대응 관계
다른 함 수 코드 에서 , add r 및 nb 매 개 변 수의 의미는 다릅니다 :
| 기능 코드 | |||
|---|---|---|---|
| 0x01 | |||
| 0x02 | |||
| 0x03 | |||
| 0x04 | |||
| 0x05 | |||
| 0x06 | |||
| 0x0F | |||
| 0x10 |
프레 임 구조 요청
mod bus _ build _ request 에서 빌 드 하는 요청 프레 임 형 식은 사용 중 인 프로토 콜 모 드에 따라 달라 집니다 .
3. 1 Mod bus TCP 모 드 (M BA P 헤 더 + PD U)
TCP 요청 프레 임 구조 (읽 기 및 유지 레 지 스터 의 경우 총 12 바 이트)
// +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
// |트 랜 잭 션 ID 높 음| 트 랜 잭 션 ID 가 낮 습니다 .| 프로토 콜 ID 높 음| 프로토 콜 ID 가 낮 음| 길이 높 음| 길이 낮 음| 슬 레이 브 주소|기능 코드|주소 높 음| 주소 낮 음| 수 량이 높 다 .| 수 량이 낮 다 .|
// +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
// |0 x 00 | 0 x 01 | 0 x 00 | 0 x 00 | 0 x 00 | 0 x 06 | 0 x 01 | 0 x 03 | 0 x 00 | 0 x 00 | 0 x 00 | 0 x 0 a |
// +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
/ / MBA P 헤 더 (7 바 이트):
트 랜 잭 션 식별 자 (2 바 이트): 요청 과 응답 을 일치 시키는 데 사용됩니다 .
- 프로토 콜 식별 자 (2 바 이트): Mod bus 프로토 콜 은 0 x 00 00 입니다 .
// - 길이 (2 바 이트): 후 속 바 이트 수 (스 레이 브 주소 + PD U)
// - 슬 레이 브 주소 (1 바 이트): Unit ID
PD U (Pr oto col 데이터 Unit):
// - 기능 코드 (1 바 이트)
// - 데이터 (N 바 이트)
Mod bus R TU 모 드 (Add ress + PD U + CR C)
/ / R TU 요청 프레 임 구조 (읽 기 및 유지 레 지 스터 를 예 로 들어 총 8 바 이트)
// +--------+--------+--------+--------+--------+--------+--------+--------+
// |슬 레이 브 주소|기능 코드|주소 높 음| 주소 낮 음| 수 량이 높 다 .| 수 량이 낮 다 .| 낮은 CR C |CR C 높 음 |
// +--------+--------+--------+--------+--------+--------+--------+--------+
// |0 x 01 | 0 x 03 | 0 x 00 | 0 x 00 | 0 x 00 | 0 x 0 a | 0 x C 5 | 0 x CD |
// +--------+--------+--------+--------+--------+--------+--------+--------+
/ / R TU 프레 임 = 슬 레이 브주소(1바이트) + PDU + CRC16(2바이트)
// CRC低바이트在前,高바이트在后
四、基础使用예제
4.1 构建读유지 레지스터请求
#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
int main() {
modbus_t *ctx;
uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
int req_len;
int i;
// 创建TCP上下文
ctx = modbus_new_tcp("192.168.1.100", 502);
if (ctx == NULL) {
fprintf(stderr, "创建上下文失败\n");
return -1;
}
// 设置슬레이브 주소
modbus_set_slave(ctx, 1);
// 构建读유지 레지스터请求:주소0,읽기10个
req_len = modbus_build_request(ctx, 0x03, 0, 10, req);
if (req_len == -1) {
fprintf(stderr, "构建请求失败:%s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
// 打印请求帧(16진수)
printf("请求帧길이:%d바이트\n", req_len);
printf("请求帧콘텐츠:");
for (i = 0; i < req_len; i++) {
printf("%02X ", req[i]);
}
printf("\n");
// 출력예제(TCP모드):
// 请求帧길이:12바이트
// 请求帧콘텐츠:00 01 00 00 00 06 01 03 00 00 00 0A
modbus_free(ctx);
return 0;
}
4.2 构建写单个코일请求
// 构建写单个코일请求:주소0,吸合(0xFF00)
req_len = modbus_build_request(ctx, 0x05, 0, 0xFF00, req);
printf("写코일请求:");
for (i = 0; i < req_len; i++) printf("%02X ", req[i]);
printf("\n");
// 출력:00 01 00 00 00 06 01 05 00 00 FF 00
// 构建写单个코일请求:주소0,断开(0x0000)
req_len = modbus_build_request(ctx, 0x05, 0, 0x0000, req);
// 출력:00 01 00 00 00 06 01 05 00 00 00 00
4.3 构建写单个레지스터请求
// 构建写单个레지스터请求:주소0,쓰기1234
req_len = modbus_build_request(ctx, 0x06, 0, 1234, req);
printf("写레지스터请求:");
for (i = 0; i < req_len; i++) printf("%02X ", req[i]);
printf("\n");
// 출력:00 01 00 00 00 06 01 06 00 00 04 D2
// 1234 = 0x04D2
五、手动보내기请求与수신响应
构建请求帧后,可以使用modbus_send和modbus_receive手动보내기和수신:
#include <stdio.h>
#include <modbus.h>
int main() {
modbus_t *ctx;
uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
int req_len, rsp_len;
ctx = modbus_new_tcp("192.168.1.100", 502);
modbus_set_slave(ctx, 1);
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "연결失败:%s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
// 1. 构建读유지 레지스터请求
req_len = modbus_build_request(ctx, 0x03, 0, 10, req);
printf("请求길이:%d\n", req_len);
// 2. 보내기请求
int sent = modbus_send(ctx, req, req_len);
if (sent != req_len) {
fprintf(stderr, "보내기失败\n");
modbus_close(ctx);
modbus_free(ctx);
return -1;
}
// 3. 수신响应
rsp_len = modbus_receive(ctx, rsp);
if (rsp_len == -1) {
fprintf(stderr, "수신失败:%s\n", modbus_strerror(errno));
modbus_close(ctx);
modbus_free(ctx);
return -1;
}
// 4. 분석响应(TCP모드,跳过MBAP头7바이트)
printf("响应길이:%d\n", rsp_len);
printf("기능 코드:0x%02X\n", rsp[7]);
if (rsp[7] == 0x03) {
// 读유지 레지스터响应
// rsp[7] = 기능 코드
// rsp[8] = 바이트数
// rsp[9...] = 레지스터데이터(每个레지스터2바이트,高바이트在前)
int byte_count = rsp[8];
int reg_count = byte_count / 2;
printf("레지스터 수량:%d\n", reg_count);
for (int i = 0; i < reg_count; i++) {
uint16_t value = (rsp[9 + i*2] << 8) | rsp[9 + i*2 + 1];
printf("레지스터%d:%d\n", i, value);
}
} else if (rsp[7] & 0x80) {
// 异常响应(기능 코드最高位为1)
printf("异常响应,异常码:0x%02X\n", rsp[8]);
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
六、自定义기능 코드实现
modbus_build_request的一个重要用途是实现设备厂商自定义的기능 코드。以下예제实现一个自定义기능 코드0x43(읽기设备시퀀스号):
// 自定义기능 코드:읽기设备시퀀스号
// 请求:기능 코드0x43,子기능 코드0x01
// 响应:기능 코드0x43,子기능 코드0x01,시퀀스号길이,시퀀스号데이터
int read_device_serial(modbus_t *ctx, uint8_t *serial, int max_len) {
uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
uint8_t rsp[MODBUS_TCP_MAX_ADU_LENGTH];
int req_len, rsp_len;
// 注意:modbus_build_request只能构建표준기능 코드
// 对于自定义기능 코드,需要手动构建PDU部分
// 手动构建TCP请求帧
// MBAP头
req[0] = 0x00; req[1] = 0x01; // 事务ID
req[2] = 0x00; req[3] = 0x00; // 프로토콜ID
req[4] = 0x00; req[5] = 0x04; // 길이(슬레이브 주소+기능 코드+子기능 코드)
req[6] = 0x01; // 슬레이브 주소(需与modbus_set_slave设置一致,libmodbus无公开getter)
// PDU
req[7] = 0x43; // 自定义기능 코드
req[8] = 0x01; // 子기능 코드:读시퀀스号
req_len = 9;
// 보내기请求
modbus_send(ctx, req, req_len);
// 수신响应
rsp_len = modbus_receive(ctx, rsp);
if (rsp_len == -1) return -1;
// 분석响应(跳过MBAP头7바이트)
if (rsp[7] == 0x43 && rsp[8] == 0x01) {
int serial_len = rsp[9];
if (serial_len > max_len) serial_len = max_len;
memcpy(serial, &rsp[10], serial_len);
return serial_len;
}
return -1;
}
// 使用예제
int main() {
modbus_t *ctx = modbus_new_tcp("192.168.1.100", 502);
modbus_connect(ctx);
uint8_t serial[32];
int len = read_device_serial(ctx, serial, sizeof(serial));
if (len > 0) {
printf("设备시퀀스号:");
for (int i = 0; i < len; i++) printf("%02X ", serial[i]);
printf("\n");
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
七、RTU모드下的使用
#include <modbus.h>
#include <modbus-rtu.h>
int main() {
modbus_t *ctx;
uint8_t req[MODBUS_RTU_MAX_ADU_LENGTH];
uint8_t rsp[MODBUS_RTU_MAX_ADU_LENGTH];
int req_len, rsp_len;
// 创建RTU上下文
ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
modbus_set_slave(ctx, 1);
modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
modbus_connect(ctx);
// 构建读코일请求(RTU모드会自动추가CRC)
req_len = modbus_build_request(ctx, 0x01, 0, 8, req);
printf("RTU请求길이:%d\n", req_len);
printf("RTU请求帧:");
for (int i = 0; i < req_len; i++) printf("%02X ", req[i]);
printf("\n");
// 출력(8바이트):01 01 00 00 00 08 3D C9
// 最后2바이트3D C9是CRC16체크섬
// 보내기并수신
modbus_send(ctx, req, req_len);
rsp_len = modbus_receive(ctx, rsp);
// RTU响应帧:슬레이브 주소 + 기능 코드 + 데이터 + CRC
if (rsp[1] == 0x01) {
int byte_count = rsp[2];
printf("코일 상태바이트数:%d\n", byte_count);
for (int i = 0; i < byte_count; i++) {
printf("바이트%d:0x%02X\n", i, rsp[3 + i]);
}
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
八、디버깅与抓包分析
modbus_build_request在프로토콜디버깅中非常有用,可以打印出完整的请求帧用于分析:
// 디버깅函数:打印请求帧详情
void print_request_detail(modbus_t *ctx, int function, int addr, int nb) {
uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
int len = modbus_build_request(ctx, function, addr, nb, req);
printf("=== 请求帧详情 ===\n");
printf("기능 코드:0x%02X\n", function);
printf("주소:%d (0x%04X)\n", addr, addr);
printf("数量/值:%d (0x%04X)\n", nb, nb);
printf("帧길이:%d바이트\n", len);
// TCP모드분석MBAP头
if (len >= 7) {
uint16_t trans_id = (req[0] << 8) | req[1];
uint16_t proto_id = (req[2] << 8) | req[3];
uint16_t length = (req[4] << 8) | req[5];
uint8_t slave_id = req[6];
printf("事务ID:%d\n", trans_id);
printf("프로토콜ID:%d\n", proto_id);
printf("길이필드:%d\n", length);
printf("슬레이브 주소:%d\n", slave_id);
}
printf("原始데이터:");
for (int i = 0; i < len; i++) printf("%02X ", req[i]);
printf("\n\n");
}
// 使用
print_request_detail(ctx, 0x03, 0, 10); // 读유지 레지스터
print_request_detail(ctx, 0x05, 0, 0xFF00); // 写코일
print_request_detail(ctx, 0x06, 5, 100); // 写레지스터
九、相关函数
| 函数 | 功能 |
|---|---|
| modbus_send() | 보내기请求帧 |
| modbus_receive() | 수신响应帧 |
| modbus_reply() | 슬레이브모드下回复请求 |
| modbus_read_registers() | 高层函数:读유지 레지스터(内部调用build_request) |
| modbus_write_bit() | 高层函数:写单个코일 |
| modbus_write_register() | 高层函数:写单个레지스터 |
| modbus_set_slave() | 设置슬레이브 주소 |
| modbus_strerror() | 获取错误설명 |
十、常见문제
- 返回-1:检查ctx是否有效,기능 코드是否受支持,缓冲区是否足够大
- 请求帧길이不对:确认프로토콜모드(TCP/RTU),TCP有7바이트MBAP头,RTU有2바이트CRC
- 设备无响应:检查슬레이브 주소是否正确,기능 코드和주소是否在设备支持范围内
- CRC错误:RTU모드下modbus_build_request会自动계산CRC,不要手动추가
- 事务ID不일치:TCP모드下每次调用会自动递增事务ID,无需手动管理
- 自定义기능 코드:modbus_build_request不支持自定义기능 코드,需手动构建PDU
- 0x0F/0x10기능 코드:modbus_build_request仅支持01-06기능 코드,写多个코일/레지스터需手动构建전체 프레임(含데이터바이트)
- modbus_get_slave不存在:libmodbus无公开的슬레이브 주소getter函数,需自行记录设置的值
modbus_build_request是libmodbus库中理解Modbus프로토콜帧结构的关键函数。通过它,개발者可以深入了解Modbus请求的构建过程,实现自定义기능 코드、프로토콜디버깅和底层优化。在实际개발中,建议优先使用高层函数(如modbus_read_registers),只有在特殊需求下才使用modbus_build_request手动构建请求帧。
답글 작성