File syn_modbus_master.c¶
File List > proto > syn_modbus_master.c
Go to the documentation of this file
#include "syn_modbus_master.h"
#include "../util/syn_assert.h"
#include "../util/syn_crc.h"
#include "../util/syn_pack.h"
#include <string.h>
static inline void write_u16_be(uint8_t *p, uint16_t val)
{
size_t pos = 0;
syn_pack_u16(p, &pos, val);
}
static inline uint16_t read_u16_be(const uint8_t *p)
{
return syn_peek_u16(p, 0);
}
void syn_modbus_master_init(SYN_ModbusMaster *m, uint32_t timeout_ms)
{
SYN_ASSERT(m != NULL);
memset(m, 0, sizeof(*m));
m->timeout_ms = (timeout_ms > 0) ? timeout_ms : 500;
m->state = SYN_MB_MASTER_STATE_IDLE;
}
static SYN_Status send_request(SYN_ModbusMaster *m, uint8_t slave_addr, uint8_t fc, uint16_t addr,
uint16_t count, const uint16_t *write_vals)
{
if (m->state == SYN_MB_MASTER_STATE_WAITING_RESPONSE) {
return SYN_BUSY;
}
m->slave_addr = slave_addr;
m->func_code = fc;
m->start_addr = addr;
m->count = count;
m->rx_len = 0;
m->read_count = 0;
m->exception_code = 0;
m->buf[0] = slave_addr;
m->buf[1] = fc;
write_u16_be(&m->buf[2], addr);
if (fc == SYN_MB_FC_READ_HOLDING || fc == SYN_MB_FC_READ_INPUT) {
write_u16_be(&m->buf[4], count);
syn_poke_u16_le(syn_crc16_modbus(m->buf, 6), m->buf, 6);
m->tx_len = 8;
} else if (fc == SYN_MB_FC_WRITE_SINGLE) {
write_u16_be(&m->buf[4], count); /* count parameter holds write value */
syn_poke_u16_le(syn_crc16_modbus(m->buf, 6), m->buf, 6);
m->tx_len = 8;
} else if (fc == SYN_MB_FC_WRITE_MULTIPLE) {
write_u16_be(&m->buf[4], count);
m->buf[6] = (uint8_t)(count * 2);
for (uint16_t i = 0; i < count; i++) {
write_u16_be(&m->buf[7 + i * 2], write_vals[i]);
}
uint16_t len = 7 + count * 2;
syn_poke_u16_le(syn_crc16_modbus(m->buf, len), m->buf, len);
m->tx_len = len + 2;
}
m->state = SYN_MB_MASTER_STATE_WAITING_RESPONSE;
return SYN_OK;
}
SYN_Status syn_modbus_master_read_holding(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t start_addr, uint16_t count)
{
if (m == NULL || slave_addr == 0 || count == 0 || count > 125)
return SYN_INVALID_PARAM;
return send_request(m, slave_addr, SYN_MB_FC_READ_HOLDING, start_addr, count, NULL);
}
SYN_Status syn_modbus_master_read_input(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t start_addr, uint16_t count)
{
if (m == NULL || slave_addr == 0 || count == 0 || count > 125)
return SYN_INVALID_PARAM;
return send_request(m, slave_addr, SYN_MB_FC_READ_INPUT, start_addr, count, NULL);
}
SYN_Status syn_modbus_master_write_single(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t reg_addr, uint16_t value)
{
if (m == NULL || slave_addr == 0)
return SYN_INVALID_PARAM;
return send_request(m, slave_addr, SYN_MB_FC_WRITE_SINGLE, reg_addr, value, NULL);
}
SYN_Status syn_modbus_master_write_multiple(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t start_addr, uint16_t count,
const uint16_t *values)
{
if (m == NULL || slave_addr == 0 || count == 0 || count > 123 || values == NULL)
return SYN_INVALID_PARAM;
return send_request(m, slave_addr, SYN_MB_FC_WRITE_MULTIPLE, start_addr, count, values);
}
SYN_Status syn_modbus_master_read_coils(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t start_addr, uint16_t count)
{
if (m == NULL || slave_addr == 0 || count == 0 || count > 2000)
return SYN_INVALID_PARAM;
return send_request(m, slave_addr, SYN_MB_FC_READ_COILS, start_addr, count, NULL);
}
SYN_Status syn_modbus_master_read_discrete_inputs(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t start_addr, uint16_t count)
{
if (m == NULL || slave_addr == 0 || count == 0 || count > 2000)
return SYN_INVALID_PARAM;
return send_request(m, slave_addr, SYN_MB_FC_READ_DISCRETE_INPUTS, start_addr, count, NULL);
}
SYN_Status syn_modbus_master_write_single_coil(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t coil_addr, bool state)
{
if (m == NULL || slave_addr == 0)
return SYN_INVALID_PARAM;
uint16_t val = state ? 0xFF00U : 0x0000U;
return send_request(m, slave_addr, SYN_MB_FC_WRITE_SINGLE_COIL, coil_addr, val, NULL);
}
SYN_Status syn_modbus_master_write_multiple_coils(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t start_addr, uint16_t count,
const uint8_t *coil_bytes)
{
if (m == NULL || slave_addr == 0 || count == 0 || count > 1968 || coil_bytes == NULL)
return SYN_INVALID_PARAM;
if (m->state == SYN_MB_MASTER_STATE_WAITING_RESPONSE)
return SYN_BUSY;
m->slave_addr = slave_addr;
m->func_code = SYN_MB_FC_WRITE_MULTIPLE_COILS;
m->start_addr = start_addr;
m->count = count;
m->rx_len = 0;
m->read_count = 0;
m->exception_code = 0;
m->buf[0] = slave_addr;
m->buf[1] = SYN_MB_FC_WRITE_MULTIPLE_COILS;
write_u16_be(&m->buf[2], start_addr);
write_u16_be(&m->buf[4], count);
uint8_t num_bytes = (uint8_t)((count + 7U) / 8U);
m->buf[6] = num_bytes;
memcpy(&m->buf[7], coil_bytes, num_bytes);
uint16_t len = (uint16_t)(7 + num_bytes);
syn_poke_u16_le(syn_crc16_modbus(m->buf, len), m->buf, len);
m->tx_len = (uint16_t)(len + 2);
m->state = SYN_MB_MASTER_STATE_WAITING_RESPONSE;
return SYN_OK;
}
SYN_Status syn_modbus_master_mask_write_register(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t reg_addr, uint16_t and_mask,
uint16_t or_mask)
{
if (m == NULL || slave_addr == 0)
return SYN_INVALID_PARAM;
if (m->state == SYN_MB_MASTER_STATE_WAITING_RESPONSE)
return SYN_BUSY;
m->slave_addr = slave_addr;
m->func_code = SYN_MB_FC_MASK_WRITE_REGISTER;
m->start_addr = reg_addr;
m->count = 1;
m->rx_len = 0;
m->read_count = 0;
m->exception_code = 0;
m->buf[0] = slave_addr;
m->buf[1] = SYN_MB_FC_MASK_WRITE_REGISTER;
write_u16_be(&m->buf[2], reg_addr);
write_u16_be(&m->buf[4], and_mask);
write_u16_be(&m->buf[6], or_mask);
syn_poke_u16_le(syn_crc16_modbus(m->buf, 8), m->buf, 8);
m->tx_len = 10;
m->state = SYN_MB_MASTER_STATE_WAITING_RESPONSE;
return SYN_OK;
}
SYN_Status syn_modbus_master_read_fifo_queue(SYN_ModbusMaster *m, uint8_t slave_addr,
uint16_t fifo_addr)
{
if (m == NULL || slave_addr == 0)
return SYN_INVALID_PARAM;
if (m->state == SYN_MB_MASTER_STATE_WAITING_RESPONSE)
return SYN_BUSY;
m->slave_addr = slave_addr;
m->func_code = SYN_MB_FC_READ_FIFO_QUEUE;
m->start_addr = fifo_addr;
m->count = 0;
m->rx_len = 0;
m->read_count = 0;
m->exception_code = 0;
m->buf[0] = slave_addr;
m->buf[1] = SYN_MB_FC_READ_FIFO_QUEUE;
write_u16_be(&m->buf[2], fifo_addr);
syn_poke_u16_le(syn_crc16_modbus(m->buf, 4), m->buf, 4);
m->tx_len = 6;
m->state = SYN_MB_MASTER_STATE_WAITING_RESPONSE;
return SYN_OK;
}
SYN_Status syn_modbus_master_report_server_id(SYN_ModbusMaster *m, uint8_t slave_addr)
{
if (m == NULL || slave_addr == 0)
return SYN_INVALID_PARAM;
if (m->state == SYN_MB_MASTER_STATE_WAITING_RESPONSE)
return SYN_BUSY;
m->slave_addr = slave_addr;
m->func_code = SYN_MB_FC_REPORT_SERVER_ID;
m->start_addr = 0;
m->count = 0;
m->rx_len = 0;
m->read_count = 0;
m->exception_code = 0;
m->buf[0] = slave_addr;
m->buf[1] = SYN_MB_FC_REPORT_SERVER_ID;
syn_poke_u16_le(syn_crc16_modbus(m->buf, 2), m->buf, 2);
m->tx_len = 4;
m->state = SYN_MB_MASTER_STATE_WAITING_RESPONSE;
return SYN_OK;
}
void syn_modbus_master_feed(SYN_ModbusMaster *m, uint8_t byte)
{
SYN_ASSERT(m != NULL);
if (m->state == SYN_MB_MASTER_STATE_WAITING_RESPONSE) {
if (m->rx_len < sizeof(m->buf)) {
m->buf[m->rx_len++] = byte;
}
}
}
SYN_ModbusMaster_State syn_modbus_master_process(SYN_ModbusMaster *m, uint32_t current_tick_ms)
{
SYN_ASSERT(m != NULL);
if (m->state != SYN_MB_MASTER_STATE_WAITING_RESPONSE) {
return m->state;
}
if (m->request_tick_ms == 0) {
m->request_tick_ms = current_tick_ms;
}
/* Check response timeout */
if ((current_tick_ms - m->request_tick_ms) >= m->timeout_ms) {
m->state = SYN_MB_MASTER_STATE_TIMEOUT;
return m->state;
}
/* Minimum frame length is 5 bytes (for exception responses) */
if (m->rx_len < 5) {
return m->state;
}
/* Verify response CRC */
uint16_t crc_calc = syn_crc16_modbus(m->buf, m->rx_len - 2);
uint16_t crc_rx = syn_peek_u16_le(m->buf, m->rx_len - 2);
if (crc_calc != crc_rx) {
return m->state; /* Incomplete or corrupted frame — keep waiting or timeout */
}
if (m->buf[0] != m->slave_addr) {
m->state = SYN_MB_MASTER_STATE_ERROR;
return m->state;
}
uint8_t fc = m->buf[1];
m->response_fc = fc;
/* Exception response check */
if (fc == (m->func_code | 0x80)) {
m->exception_code = m->buf[2];
m->state = SYN_MB_MASTER_STATE_ERROR;
return m->state;
}
if (fc != m->func_code) {
m->state = SYN_MB_MASTER_STATE_ERROR;
return m->state;
}
/* Process successful response */
if (fc == SYN_MB_FC_READ_HOLDING || fc == SYN_MB_FC_READ_INPUT) {
uint8_t byte_count = m->buf[2];
uint16_t words = byte_count / 2;
if (words > 125)
words = 125;
for (uint16_t i = 0; i < words; i++) {
m->read_data[i] = read_u16_be(&m->buf[3 + i * 2]);
}
m->read_count = words;
m->state = SYN_MB_MASTER_STATE_COMPLETE;
} else if (fc == SYN_MB_FC_READ_COILS || fc == SYN_MB_FC_READ_DISCRETE_INPUTS ||
fc == SYN_MB_FC_REPORT_SERVER_ID) {
uint8_t byte_count = m->buf[2];
if (byte_count > 250)
byte_count = 250;
for (uint16_t i = 0; i < byte_count; i++) {
m->read_data[i] = m->buf[3 + i];
}
m->read_count = byte_count;
m->state = SYN_MB_MASTER_STATE_COMPLETE;
} else if (fc == SYN_MB_FC_READ_FIFO_QUEUE) {
uint16_t fifo_count = read_u16_be(&m->buf[4]);
if (fifo_count > 31)
fifo_count = 31;
for (uint16_t i = 0; i < fifo_count; i++) {
m->read_data[i] = read_u16_be(&m->buf[6 + i * 2]);
}
m->read_count = fifo_count;
m->state = SYN_MB_MASTER_STATE_COMPLETE;
} else if (fc == SYN_MB_FC_WRITE_SINGLE || fc == SYN_MB_FC_WRITE_MULTIPLE ||
fc == SYN_MB_FC_WRITE_SINGLE_COIL || fc == SYN_MB_FC_WRITE_MULTIPLE_COILS ||
fc == SYN_MB_FC_MASK_WRITE_REGISTER) {
m->state = SYN_MB_MASTER_STATE_COMPLETE;
}
return m->state;
}
void syn_modbus_master_queue_init(SYN_ModbusMasterQueue *q, uint8_t max_retries)
{
if (q == NULL) {
return;
}
(void)memset(q, 0, sizeof(*q));
q->max_retries = max_retries;
}
SYN_Status syn_modbus_master_queue_push(SYN_ModbusMasterQueue *q,
const SYN_ModbusMasterQuery *query)
{
if (q == NULL || query == NULL) {
return SYN_INVALID_PARAM;
}
if (q->count >= SYN_MODBUS_QUEUE_SIZE) {
return SYN_ERROR; /* Queue full */
}
q->queries[q->tail] = *query;
q->tail = (uint8_t)((q->tail + 1U) % SYN_MODBUS_QUEUE_SIZE);
q->count++;
return SYN_OK;
}
SYN_Status syn_modbus_master_queue_step(SYN_ModbusMaster *m, SYN_ModbusMasterQueue *q,
uint32_t now_ms)
{
if (m == NULL || q == NULL) {
return SYN_INVALID_PARAM;
}
SYN_ModbusMaster_State state = syn_modbus_master_process(m, now_ms);
if (state == SYN_MB_MASTER_STATE_IDLE && q->count > 0) {
SYN_ModbusMasterQuery *qry = &q->queries[q->head];
SYN_Status st = SYN_ERROR;
switch (qry->func_code) {
case SYN_MB_FC_READ_HOLDING:
st = syn_modbus_master_read_holding(m, qry->slave_addr, qry->start_addr, qry->count);
break;
case SYN_MB_FC_READ_INPUT:
st = syn_modbus_master_read_input(m, qry->slave_addr, qry->start_addr, qry->count);
break;
case SYN_MB_FC_WRITE_SINGLE:
st = syn_modbus_master_write_single(m, qry->slave_addr, qry->start_addr,
qry->write_value);
break;
case SYN_MB_FC_READ_COILS:
st = syn_modbus_master_read_coils(m, qry->slave_addr, qry->start_addr, qry->count);
break;
case SYN_MB_FC_READ_DISCRETE_INPUTS:
st = syn_modbus_master_read_discrete_inputs(m, qry->slave_addr, qry->start_addr,
qry->count);
break;
default:
break;
}
if (st == SYN_OK) {
syn_modbus_master_process(m, now_ms); /* transition state to WAITING_RESPONSE */
}
} else if (state == SYN_MB_MASTER_STATE_COMPLETE && q->count > 0) {
SYN_ModbusMasterQuery *qry = &q->queries[q->head];
if (qry->callback != NULL) {
qry->callback(qry->slave_addr, qry->func_code, m->read_data, m->read_count, SYN_OK,
qry->user_ctx);
}
q->head = (uint8_t)((q->head + 1U) % SYN_MODBUS_QUEUE_SIZE);
q->count--;
q->retry_count = 0;
m->state = SYN_MB_MASTER_STATE_IDLE;
} else if ((state == SYN_MB_MASTER_STATE_TIMEOUT || state == SYN_MB_MASTER_STATE_ERROR) &&
q->count > 0) {
SYN_ModbusMasterQuery *qry = &q->queries[q->head];
if (q->retry_count < q->max_retries) {
q->retry_count++;
m->state = SYN_MB_MASTER_STATE_IDLE; /* Retry query */
} else {
if (qry->callback != NULL) {
qry->callback(qry->slave_addr, qry->func_code, NULL, 0, SYN_ERROR, qry->user_ctx);
}
q->head = (uint8_t)((q->head + 1U) % SYN_MODBUS_QUEUE_SIZE);
q->count--;
q->retry_count = 0;
m->state = SYN_MB_MASTER_STATE_IDLE;
}
}
return SYN_OK;
}