Alicat BASIS-2 Driver 1.0.0
API reference
Loading...
Searching...
No Matches
alicat_basis2_modbus.hpp
Go to the documentation of this file.
1
18#pragma once
19
20#include <array>
21#include <cstdint>
22#include <cstddef>
23
26
27namespace alicat_basis2 {
28namespace modbus {
29
36inline uint16_t Crc16(const uint8_t* data, std::size_t length) noexcept {
37 uint16_t crc = 0xFFFF;
38 for (std::size_t i = 0; i < length; ++i) {
39 crc ^= static_cast<uint16_t>(data[i]);
40 for (uint8_t b = 0; b < 8; ++b) {
41 if (crc & 0x0001U) {
42 crc = static_cast<uint16_t>((crc >> 1) ^ 0xA001U);
43 } else {
44 crc >>= 1;
45 }
46 }
47 }
48 return crc;
49}
50
52inline std::size_t AppendCrc(uint8_t* frame, std::size_t length) noexcept {
53 const uint16_t crc = Crc16(frame, length);
54 frame[length] = static_cast<uint8_t>(crc & 0x00FFU);
55 frame[length + 1] = static_cast<uint8_t>((crc >> 8) & 0x00FFU);
56 return length + 2;
57}
58
60inline bool CheckCrc(const uint8_t* frame, std::size_t length) noexcept {
61 if (length < 4) return false;
62 const uint16_t want = Crc16(frame, length - 2);
63 const uint16_t got = static_cast<uint16_t>(frame[length - 2]) |
64 (static_cast<uint16_t>(frame[length - 1]) << 8);
65 return want == got;
66}
67
68//==============================================================================
69// REQUEST BUILDERS — format the bytes a master sends on the wire.
70//==============================================================================
71
74inline std::size_t BuildReadHolding(uint8_t unit_addr,
75 uint16_t reg_addr,
76 uint16_t reg_count,
77 uint8_t* out, std::size_t out_capacity) noexcept {
78 if (out_capacity < 8) return 0;
79 out[0] = unit_addr;
80 out[1] = reg::fn::ReadHolding;
81 out[2] = static_cast<uint8_t>((reg_addr >> 8) & 0xFFU);
82 out[3] = static_cast<uint8_t>(reg_addr & 0xFFU);
83 out[4] = static_cast<uint8_t>((reg_count >> 8) & 0xFFU);
84 out[5] = static_cast<uint8_t>(reg_count & 0xFFU);
85 return AppendCrc(out, 6);
86}
87
89inline std::size_t BuildWriteSingle(uint8_t unit_addr,
90 uint16_t reg_addr,
91 uint16_t value,
92 uint8_t* out, std::size_t out_capacity) noexcept {
93 if (out_capacity < 8) return 0;
94 out[0] = unit_addr;
95 out[1] = reg::fn::WriteSingle;
96 out[2] = static_cast<uint8_t>((reg_addr >> 8) & 0xFFU);
97 out[3] = static_cast<uint8_t>(reg_addr & 0xFFU);
98 out[4] = static_cast<uint8_t>((value >> 8) & 0xFFU);
99 out[5] = static_cast<uint8_t>(value & 0xFFU);
100 return AppendCrc(out, 6);
101}
102
105inline std::size_t BuildWriteMultiple(uint8_t unit_addr,
106 uint16_t reg_addr,
107 const uint16_t* regs, uint8_t count,
108 uint8_t* out, std::size_t out_capacity) noexcept {
109 const std::size_t needed = 9 + count * 2;
110 if (count == 0 || out_capacity < needed) return 0;
111 out[0] = unit_addr;
112 out[1] = reg::fn::WriteMultiple;
113 out[2] = static_cast<uint8_t>((reg_addr >> 8) & 0xFFU);
114 out[3] = static_cast<uint8_t>(reg_addr & 0xFFU);
115 out[4] = static_cast<uint8_t>((count >> 8) & 0xFFU);
116 out[5] = static_cast<uint8_t>(count & 0xFFU);
117 out[6] = static_cast<uint8_t>(count * 2);
118 for (uint8_t i = 0; i < count; ++i) {
119 out[7 + i * 2] = static_cast<uint8_t>((regs[i] >> 8) & 0xFFU);
120 out[7 + i * 2 + 1] = static_cast<uint8_t>(regs[i] & 0xFFU);
121 }
122 return AppendCrc(out, 7 + count * 2);
123}
124
125//==============================================================================
126// TIMING — Modbus over Serial Line V1.02, section 2.5.1.1
127//==============================================================================
128
130inline constexpr uint32_t kRtuBitsPerChar = 11;
131
133inline constexpr uint32_t kFixedGapAboveBps = 19200;
134
136inline constexpr uint32_t kFixedGapUs = 1750;
137
145constexpr uint32_t InterFrameGapUs(uint32_t bps) noexcept {
146 if (bps == 0)
147 bps = 4800;
148 if (bps > kFixedGapAboveBps)
149 return kFixedGapUs;
150 // 3.5 * 11 bits * 1e6 us / bps, rounded up.
151 constexpr uint32_t kNumerator = 7U * kRtuBitsPerChar * 1000000U / 2U; // 38'500'000
152 return (kNumerator + bps - 1U) / bps;
153}
154
156constexpr uint32_t InterFrameGapMs(uint32_t bps) noexcept {
157 return (InterFrameGapUs(bps) + 999U) / 1000U;
158}
159
161inline constexpr std::size_t kExceptionFrameLength = 5;
162
165inline constexpr std::size_t kReplyHeaderLength = 3;
166
168inline bool IsExceptionReply(const uint8_t* frame, std::size_t length) noexcept {
169 return length >= 2 && (frame[1] & reg::fn::ExceptionMask) != 0;
170}
171
172//==============================================================================
173// RESPONSE PARSERS — return the number of registers extracted, or 0 on error.
174//==============================================================================
175
183inline DriverError ParseException(const uint8_t* frame, std::size_t length, uint8_t expected_addr,
184 uint8_t expected_fn, uint8_t* code) noexcept {
185 if (length != kExceptionFrameLength)
187 if (!CheckCrc(frame, length))
188 return DriverError::BadCrc;
189 if (frame[0] != expected_addr)
191 if (frame[1] != (expected_fn | reg::fn::ExceptionMask))
193 if (code)
194 *code = frame[2];
196}
197
200inline DriverError ParseReadHolding(const uint8_t* frame, std::size_t length,
201 uint8_t expected_addr, uint8_t expected_count,
202 uint16_t* regs) noexcept {
203 if (length < 5) return DriverError::InvalidFrame;
204 if (!CheckCrc(frame, length)) return DriverError::BadCrc;
205 if (frame[0] != expected_addr) return DriverError::ResponseMismatch;
208 const std::size_t byte_count = frame[2];
209 if (byte_count != expected_count * 2U) return DriverError::InvalidFrame;
210 if (length < 3 + byte_count + 2) return DriverError::InvalidFrame;
211 for (uint8_t i = 0; i < expected_count; ++i) {
212 regs[i] = (static_cast<uint16_t>(frame[3 + i * 2]) << 8) |
213 static_cast<uint16_t>(frame[3 + i * 2 + 1]);
214 }
215 return DriverError::None;
216}
217
220inline DriverError ParseWriteSingleEcho(const uint8_t* frame, std::size_t length,
221 const uint8_t* request, std::size_t req_len) noexcept {
222 if (length < req_len) return DriverError::InvalidFrame;
223 if (!CheckCrc(frame, length)) return DriverError::BadCrc;
224 if (frame[0] != request[0]) return DriverError::ResponseMismatch;
226 for (std::size_t i = 0; i < req_len; ++i) {
227 if (frame[i] != request[i]) return DriverError::ResponseMismatch;
228 }
229 return DriverError::None;
230}
231
233inline DriverError ParseWriteMultipleEcho(const uint8_t* frame, std::size_t length,
234 uint8_t expected_addr,
235 uint16_t expected_reg_addr,
236 uint16_t expected_count) noexcept {
237 if (length < 8) return DriverError::InvalidFrame;
238 if (!CheckCrc(frame, length)) return DriverError::BadCrc;
239 if (frame[0] != expected_addr) return DriverError::ResponseMismatch;
242 const uint16_t got_addr = (static_cast<uint16_t>(frame[2]) << 8) | frame[3];
243 const uint16_t got_cnt = (static_cast<uint16_t>(frame[4]) << 8) | frame[5];
244 if (got_addr != expected_reg_addr) return DriverError::ResponseMismatch;
245 if (got_cnt != expected_count) return DriverError::ResponseMismatch;
246 return DriverError::None;
247}
248
249} // namespace modbus
250} // namespace alicat_basis2
Modbus-RTU holding-register addresses for Alicat BASIS-2.
Public type definitions for the Alicat BASIS-2 driver.
bool CheckCrc(const uint8_t *frame, std::size_t length) noexcept
Verify the CRC at frame[length-2..length-1] matches the body.
Definition alicat_basis2_modbus.hpp:60
constexpr uint32_t InterFrameGapUs(uint32_t bps) noexcept
Minimum idle time t3.5 between two Modbus-RTU frames, in microseconds.
Definition alicat_basis2_modbus.hpp:145
constexpr uint32_t kFixedGapUs
The fixed t3.5 used above 19200 baud, in microseconds.
Definition alicat_basis2_modbus.hpp:136
bool IsExceptionReply(const uint8_t *frame, std::size_t length) noexcept
Definition alicat_basis2_modbus.hpp:168
std::size_t BuildWriteMultiple(uint8_t unit_addr, uint16_t reg_addr, const uint16_t *regs, uint8_t count, uint8_t *out, std::size_t out_capacity) noexcept
Build a Function-16 (Write Multiple Registers) request.
Definition alicat_basis2_modbus.hpp:105
DriverError ParseReadHolding(const uint8_t *frame, std::size_t length, uint8_t expected_addr, uint8_t expected_count, uint16_t *regs) noexcept
Parse a Function-03 response into regs[].
Definition alicat_basis2_modbus.hpp:200
uint16_t Crc16(const uint8_t *data, std::size_t length) noexcept
Compute the Modbus-RTU CRC-16 over a byte buffer.
Definition alicat_basis2_modbus.hpp:36
DriverError ParseWriteSingleEcho(const uint8_t *frame, std::size_t length, const uint8_t *request, std::size_t req_len) noexcept
Validate the echo of a Function-06 (Write Single) request against the just-sent request.
Definition alicat_basis2_modbus.hpp:220
std::size_t BuildReadHolding(uint8_t unit_addr, uint16_t reg_addr, uint16_t reg_count, uint8_t *out, std::size_t out_capacity) noexcept
Build a Function-03 (Read Holding Registers) request.
Definition alicat_basis2_modbus.hpp:74
std::size_t BuildWriteSingle(uint8_t unit_addr, uint16_t reg_addr, uint16_t value, uint8_t *out, std::size_t out_capacity) noexcept
Build a Function-06 (Write Single Register) request.
Definition alicat_basis2_modbus.hpp:89
constexpr uint32_t InterFrameGapMs(uint32_t bps) noexcept
InterFrameGapUs(bps) rounded up to whole milliseconds (for UartInterface::delay_ms()).
Definition alicat_basis2_modbus.hpp:156
constexpr uint32_t kFixedGapAboveBps
Above this baud rate the spec fixes t3.5 at 1750 us instead of 3.5 character times.
Definition alicat_basis2_modbus.hpp:133
DriverError ParseException(const uint8_t *frame, std::size_t length, uint8_t expected_addr, uint8_t expected_fn, uint8_t *code) noexcept
Parse a 5-byte exception reply (Modbus Application Protocol V1.1b3, section 7).
Definition alicat_basis2_modbus.hpp:183
constexpr uint32_t kRtuBitsPerChar
Bits per RTU character used for the timing: start + 8 data + parity/stop + stop (section 2....
Definition alicat_basis2_modbus.hpp:130
constexpr std::size_t kReplyHeaderLength
Number of reply bytes a receiver reads before it can tell an exception reply (bit 7 of the function c...
Definition alicat_basis2_modbus.hpp:165
DriverError ParseWriteMultipleEcho(const uint8_t *frame, std::size_t length, uint8_t expected_addr, uint16_t expected_reg_addr, uint16_t expected_count) noexcept
Validate the response of a Function-16 (Write Multiple) request.
Definition alicat_basis2_modbus.hpp:233
constexpr std::size_t kExceptionFrameLength
Length of a Modbus-RTU exception reply: address, function | 0x80, exception code, CRC-16.
Definition alicat_basis2_modbus.hpp:161
std::size_t AppendCrc(uint8_t *frame, std::size_t length) noexcept
Append the CRC-16 to frame[length..length+1] and return new length.
Definition alicat_basis2_modbus.hpp:52
constexpr uint8_t ReadHolding
Definition alicat_basis2_registers.hpp:99
constexpr uint8_t WriteMultiple
Definition alicat_basis2_registers.hpp:101
constexpr uint8_t ExceptionMask
Definition alicat_basis2_registers.hpp:102
constexpr uint8_t WriteSingle
Definition alicat_basis2_registers.hpp:100
Definition alicat_basis2.hpp:52
DriverError
Driver-level error codes.
Definition alicat_basis2_types.hpp:39
@ InvalidFrame
Frame too short, wrong function code, etc.
@ BadCrc
CRC-16 mismatch on received Modbus frame.
@ None
Operation succeeded.
@ ResponseMismatch
Echo doesn't match request (function 6/16).
@ ModbusException
Slave returned a Modbus exception PDU.