Hopi-pico/modbus_rtu.c

145 lines
4.5 KiB
C

#include "modbus_rtu.h"
#include <string.h>
// Standard Modbus RTU CRC16 (poly 0xA001)
uint16_t modbus_rtu_crc16(const uint8_t *data, size_t len) {
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 1) {
crc = (uint16_t)((crc >> 1) ^ 0xA001);
} else {
crc >>= 1;
}
}
}
return crc;
}
uint16_t modbus_rtu_get_u16_be(const uint8_t *p) {
return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]);
}
void modbus_rtu_master_init(modbus_rtu_master_t *m, modbus_rtu_master_config_t cfg) {
memset(m, 0, sizeof(*m));
m->cfg = cfg;
}
size_t modbus_rtu_build_read_holding(uint8_t *out, size_t out_cap,
uint8_t slave_id, uint16_t start_addr, uint16_t quantity,
bool disable_crc) {
// Request: [id][0x03][start hi][start lo][qty hi][qty lo][crc lo][crc hi]
const size_t base_len = 6;
const size_t crc_len = disable_crc ? 0 : 2;
if (out_cap < base_len + crc_len) return 0;
out[0] = slave_id;
out[1] = 0x03;
out[2] = (uint8_t)(start_addr >> 8);
out[3] = (uint8_t)(start_addr & 0xFF);
out[4] = (uint8_t)(quantity >> 8);
out[5] = (uint8_t)(quantity & 0xFF);
if (!disable_crc) {
uint16_t crc = modbus_rtu_crc16(out, base_len);
out[6] = (uint8_t)(crc & 0xFF);
out[7] = (uint8_t)(crc >> 8);
}
return base_len + crc_len;
}
void modbus_rtu_feed(modbus_rtu_master_t *m, const uint8_t *data, size_t len) {
// Simple append with truncation on overflow.
size_t space = sizeof(m->rx_buf) - m->rx_len;
if (len > space) len = space;
if (len == 0) return;
memcpy(&m->rx_buf[m->rx_len], data, len);
m->rx_len += len;
}
static void drop_prefix(modbus_rtu_master_t *m, size_t n) {
if (n == 0 || n > m->rx_len) return;
memmove(m->rx_buf, m->rx_buf + n, m->rx_len - n);
m->rx_len -= n;
}
bool modbus_rtu_try_parse_read_holding_response(modbus_rtu_master_t *m,
uint8_t expected_slave_id,
uint16_t expected_quantity,
size_t *out_data_offset,
uint16_t *out_word_count) {
// Response: [id][0x03][byte_count][data...][crc lo][crc hi]
// Some devices always include CRC but may compute it incorrectly.
// If m->cfg.disable_crc==true, we do NOT validate CRC, but we still accept frames that include CRC bytes.
// We also accept CRC-less frames only when m->cfg.disable_crc==true.
const size_t min_header = 3;
// Try to resync if buffer has junk: look for [id][0x03].
while (m->rx_len >= 2) {
if (m->rx_buf[0] == expected_slave_id && m->rx_buf[1] == 0x03) break;
drop_prefix(m, 1);
}
if (m->rx_len < min_header) return false;
uint8_t byte_count = m->rx_buf[2];
if ((byte_count % 2) != 0) {
// invalid, resync
drop_prefix(m, 1);
return false;
}
const uint16_t word_count = (uint16_t)(byte_count / 2);
if (word_count != expected_quantity) {
// Could be a different request/response; resync minimally.
// (You can relax this if you later add more Modbus queries.)
drop_prefix(m, 1);
return false;
}
const size_t data_len = (size_t)3 + (size_t)byte_count;
if (m->rx_len < data_len) return false;
// Prefer CRC-framed response when present.
const bool has_crc = (m->rx_len >= (data_len + 2));
const size_t total_len = has_crc ? (data_len + 2) : data_len;
if (has_crc && !m->cfg.disable_crc) {
uint16_t got_crc = (uint16_t)((uint16_t)m->rx_buf[total_len - 2] | ((uint16_t)m->rx_buf[total_len - 1] << 8));
uint16_t calc_crc = modbus_rtu_crc16(m->rx_buf, total_len - 2);
if (got_crc != calc_crc) {
drop_prefix(m, 1);
return false;
}
}
if (!has_crc && !m->cfg.disable_crc) {
// In strict mode we require CRC bytes to be present.
return false;
}
*out_data_offset = 3;
*out_word_count = word_count;
// Leave data in buffer for caller to consume; caller should drop_prefix(total_len) afterwards.
return true;
}
float modbus_float_dcba(uint16_t w1, uint16_t w2) {
// Matches your ESPHome lambdas:
// b0=low(w2), b1=high(w2), b2=low(w1), b3=high(w1)
// u = b0<<24 | b1<<16 | b2<<8 | b3
uint8_t b0 = (uint8_t)(w2 & 0xFF);
uint8_t b1 = (uint8_t)((w2 >> 8) & 0xFF);
uint8_t b2 = (uint8_t)(w1 & 0xFF);
uint8_t b3 = (uint8_t)((w1 >> 8) & 0xFF);
uint32_t u = ((uint32_t)b0 << 24) | ((uint32_t)b1 << 16) | ((uint32_t)b2 << 8) | (uint32_t)b3;
float f;
memcpy(&f, &u, sizeof(f));
return f;
}